Merge pull request #85 from ChainSafe/dapplion/multi-threading
Enable multi-threading for all implementations
This commit is contained in:
commit
0f895b4795
|
@ -86,29 +86,25 @@ import {aggCount, runs} from "./params";
|
|||
|
||||
// Verify multiple signatures
|
||||
|
||||
await runBenchmark<{pks: PublicKey[]; msgs: Uint8Array[]; sigs: Signature[]}, boolean>({
|
||||
await runBenchmark({
|
||||
id: `${implementation} verifyMultipleSignatures (${aggCount})`,
|
||||
|
||||
prepareTest: () => {
|
||||
const dataArr = range(aggCount).map(() => {
|
||||
const sets = range(aggCount).map(() => {
|
||||
const sk = bls.SecretKey.fromKeygen();
|
||||
const pk = sk.toPublicKey();
|
||||
const msg = randomMessage();
|
||||
const sig = sk.sign(msg);
|
||||
return {pk, msg, sig};
|
||||
return {publicKey: pk, message: msg, signature: sig};
|
||||
});
|
||||
|
||||
const pks = dataArr.map((data) => data.pk);
|
||||
const msgs = dataArr.map((data) => data.msg);
|
||||
const sigs = dataArr.map((data) => data.sig);
|
||||
|
||||
return {
|
||||
input: {pks, msgs, sigs},
|
||||
input: sets,
|
||||
resultCheck: (valid) => valid === true,
|
||||
};
|
||||
},
|
||||
testRunner: ({pks, msgs, sigs}) => {
|
||||
return bls.Signature.verifyMultipleSignatures(pks, msgs, sigs);
|
||||
testRunner: (sets) => {
|
||||
return bls.Signature.verifyMultipleSignatures(sets);
|
||||
},
|
||||
runs,
|
||||
});
|
||||
|
|
|
@ -12,21 +12,17 @@ import {range, randomMessage} from "../test/util";
|
|||
const pk = sk.toPublicKey();
|
||||
const msg = randomMessage();
|
||||
const sig = sk.sign(msg);
|
||||
return {pk, msg, sig};
|
||||
return {publicKey: pk, message: msg, signature: sig};
|
||||
});
|
||||
|
||||
const pks = dataArr.map((data) => data.pk);
|
||||
const msgs = dataArr.map((data) => data.msg);
|
||||
const sigs = dataArr.map((data) => data.sig);
|
||||
|
||||
const startMulti = process.hrtime.bigint();
|
||||
bls.Signature.verifyMultipleSignatures(pks, msgs, sigs);
|
||||
bls.Signature.verifyMultipleSignatures(dataArr);
|
||||
const endMulti = process.hrtime.bigint();
|
||||
const diffMulti = endMulti - startMulti;
|
||||
|
||||
const startSingle = process.hrtime.bigint();
|
||||
for (const {pk, msg, sig} of dataArr) {
|
||||
sig.verify(pk, msg);
|
||||
for (const {publicKey, message, signature} of dataArr) {
|
||||
signature.verify(publicKey, message);
|
||||
}
|
||||
const endSingle = process.hrtime.bigint();
|
||||
const diffSingle = endSingle - startSingle;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# yarn lockfile v1
|
||||
|
||||
|
||||
noble-bls12-381@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/noble-bls12-381/-/noble-bls12-381-0.6.1.tgz#b44bb5443b4b5c409723f19a8288155f0b3ad126"
|
||||
integrity sha512-Dt0lq24ez75HqOqNIsxxbzfY7YOuwArtE3H6Clp1XbwnY4Ga1OjFbTaXq5aDBE3+ab1wLK11s0b3yR3+RiWWqw==
|
||||
noble-bls12-381@^0.7.2:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/noble-bls12-381/-/noble-bls12-381-0.7.2.tgz#9a9384891569ba32785d6e4ff8588b783487eae4"
|
||||
integrity sha512-Z5isbU6opuWPL3dxsGqO5BdOE8WP1XUM7HFIn/xeE5pATTnml/PEIy4MFQQrktHiitkuJdsCDtzEOnS9eIpC3Q==
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
"randombytes": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chainsafe/blst": "^0.1.6",
|
||||
"@chainsafe/blst": "^0.2.0",
|
||||
"@chainsafe/lodestar-spec-test-util": "^0.18.0",
|
||||
"@types/chai": "^4.2.9",
|
||||
"@types/mocha": "^8.0.4",
|
||||
|
@ -67,6 +67,7 @@
|
|||
"mocha": "^8.2.1",
|
||||
"nyc": "^15.0.0",
|
||||
"prettier": "^2.1.2",
|
||||
"threads": "^1.6.3",
|
||||
"ts-loader": "^6.2.1",
|
||||
"ts-node": "^8.6.2",
|
||||
"typescript": "^3.7.5",
|
||||
|
@ -78,6 +79,6 @@
|
|||
"v8-profiler-next": "1.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@chainsafe/blst": "^0.1.6"
|
||||
"@chainsafe/blst": "^0.2.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ export function destroy(): void {
|
|||
}
|
||||
|
||||
export const bls: IBls = {
|
||||
implementation: "blst-native",
|
||||
SecretKey,
|
||||
PublicKey,
|
||||
Signature,
|
||||
|
|
|
@ -1,25 +1,21 @@
|
|||
import * as blst from "@chainsafe/blst";
|
||||
import {EmptyAggregateError, ZeroPublicKeyError} from "../errors";
|
||||
import {bytesToHex, hexToBytes} from "../helpers";
|
||||
import {PublicKey as IPublicKey} from "../interface";
|
||||
import {PointFormat, PublicKey as IPublicKey} from "../interface";
|
||||
|
||||
export class PublicKey implements IPublicKey {
|
||||
readonly affine: blst.PublicKey;
|
||||
readonly jacobian: blst.AggregatePublicKey;
|
||||
|
||||
constructor(affine: blst.PublicKey, jacobian: blst.AggregatePublicKey) {
|
||||
this.affine = affine;
|
||||
this.jacobian = jacobian;
|
||||
export class PublicKey extends blst.PublicKey implements IPublicKey {
|
||||
constructor(value: ConstructorParameters<typeof blst.PublicKey>[0]) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
static fromBytes(bytes: Uint8Array): PublicKey {
|
||||
const affine = blst.PublicKey.fromBytes(bytes);
|
||||
if (affine.value.is_inf()) {
|
||||
/** @param type Defaults to `CoordType.jacobian` */
|
||||
static fromBytes(bytes: Uint8Array, type?: blst.CoordType): PublicKey {
|
||||
const pk = blst.PublicKey.fromBytes(bytes, type);
|
||||
if (pk.value.is_inf()) {
|
||||
throw new ZeroPublicKeyError();
|
||||
}
|
||||
|
||||
const jacobian = blst.AggregatePublicKey.fromPublicKey(affine);
|
||||
return new PublicKey(affine, jacobian);
|
||||
return new PublicKey(pk.value);
|
||||
}
|
||||
|
||||
static fromHex(hex: string): PublicKey {
|
||||
|
@ -31,16 +27,19 @@ export class PublicKey implements IPublicKey {
|
|||
throw new EmptyAggregateError();
|
||||
}
|
||||
|
||||
const jacobian = blst.aggregatePubkeys(publicKeys.map((pk) => pk.jacobian));
|
||||
const affine = jacobian.toPublicKey();
|
||||
return new PublicKey(affine, jacobian);
|
||||
const pk = blst.aggregatePubkeys(publicKeys);
|
||||
return new PublicKey(pk.value);
|
||||
}
|
||||
|
||||
toBytes(): Uint8Array {
|
||||
return this.affine.toBytes();
|
||||
toBytes(format?: PointFormat): Uint8Array {
|
||||
if (format === PointFormat.uncompressed) {
|
||||
return this.value.serialize();
|
||||
} else {
|
||||
return this.value.compress();
|
||||
}
|
||||
}
|
||||
|
||||
toHex(): string {
|
||||
return bytesToHex(this.toBytes());
|
||||
toHex(format?: PointFormat): string {
|
||||
return bytesToHex(this.toBytes(format));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import {ZeroSecretKeyError} from "../errors";
|
|||
|
||||
export class SecretKey implements ISecretKey {
|
||||
readonly value: blst.SecretKey;
|
||||
|
||||
constructor(value: blst.SecretKey) {
|
||||
this.value = value;
|
||||
}
|
||||
|
@ -33,13 +32,12 @@ export class SecretKey implements ISecretKey {
|
|||
}
|
||||
|
||||
sign(message: Uint8Array): Signature {
|
||||
return new Signature(this.value.sign(message));
|
||||
return new Signature(this.value.sign(message).value);
|
||||
}
|
||||
|
||||
toPublicKey(): PublicKey {
|
||||
const jacobian = this.value.toAggregatePublicKey();
|
||||
const affine = jacobian.toPublicKey();
|
||||
return new PublicKey(affine, jacobian);
|
||||
const pk = this.value.toPublicKey();
|
||||
return new PublicKey(pk.value);
|
||||
}
|
||||
|
||||
toBytes(): Uint8Array {
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
import * as blst from "@chainsafe/blst";
|
||||
import {bytesToHex, hexToBytes} from "../helpers";
|
||||
import {Signature as ISignature} from "../interface";
|
||||
import {PointFormat, Signature as ISignature} from "../interface";
|
||||
import {PublicKey} from "./publicKey";
|
||||
import {EmptyAggregateError, ZeroSignatureError} from "../errors";
|
||||
|
||||
export class Signature implements ISignature {
|
||||
readonly affine: blst.Signature;
|
||||
|
||||
constructor(value: blst.Signature) {
|
||||
this.affine = value;
|
||||
export class Signature extends blst.Signature implements ISignature {
|
||||
constructor(value: ConstructorParameters<typeof blst.Signature>[0]) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
static fromBytes(bytes: Uint8Array): Signature {
|
||||
return new Signature(blst.Signature.fromBytes(bytes));
|
||||
/** @param type Defaults to `CoordType.affine` */
|
||||
static fromBytes(bytes: Uint8Array, type?: blst.CoordType, validate?: boolean): Signature {
|
||||
const sig = blst.Signature.fromBytes(bytes, type);
|
||||
if (validate) sig.sigValidate();
|
||||
return new Signature(sig.value);
|
||||
}
|
||||
|
||||
static fromHex(hex: string): Signature {
|
||||
|
@ -24,55 +25,53 @@ export class Signature implements ISignature {
|
|||
throw new EmptyAggregateError();
|
||||
}
|
||||
|
||||
const agg = blst.AggregateSignature.fromSignatures(signatures.map((sig) => sig.affine));
|
||||
return new Signature(agg.toSignature());
|
||||
const agg = blst.aggregateSignatures(signatures);
|
||||
return new Signature(agg.value);
|
||||
}
|
||||
|
||||
static verifyMultipleSignatures(publicKeys: PublicKey[], messages: Uint8Array[], signatures: Signature[]): boolean {
|
||||
static verifyMultipleSignatures(sets: {publicKey: PublicKey; message: Uint8Array; signature: Signature}[]): boolean {
|
||||
return blst.verifyMultipleAggregateSignatures(
|
||||
messages,
|
||||
publicKeys.map((publicKey) => publicKey.affine),
|
||||
signatures.map((signature) => signature.affine)
|
||||
sets.map((s) => ({msg: s.message, pk: s.publicKey, sig: s.signature}))
|
||||
);
|
||||
}
|
||||
|
||||
verify(publicKey: PublicKey, message: Uint8Array): boolean {
|
||||
// Individual infinity signatures are NOT okay. Aggregated signatures MAY be infinity
|
||||
if (this.affine.value.is_inf()) {
|
||||
if (this.value.is_inf()) {
|
||||
throw new ZeroSignatureError();
|
||||
}
|
||||
|
||||
return this.aggregateVerify([message], [publicKey.affine]);
|
||||
return blst.verify(message, publicKey, this);
|
||||
}
|
||||
|
||||
verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean {
|
||||
const agg = PublicKey.aggregate(publicKeys);
|
||||
return this.aggregateVerify([message], [agg.affine]);
|
||||
return blst.fastAggregateVerify(message, publicKeys, this);
|
||||
}
|
||||
|
||||
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean {
|
||||
return this.aggregateVerify(
|
||||
messages,
|
||||
publicKeys.map((pk) => pk.affine)
|
||||
);
|
||||
return blst.aggregateVerify(messages, publicKeys, this);
|
||||
}
|
||||
|
||||
toBytes(): Uint8Array {
|
||||
return this.affine.toBytes();
|
||||
toBytes(format?: PointFormat): Uint8Array {
|
||||
if (format === PointFormat.uncompressed) {
|
||||
return this.value.serialize();
|
||||
} else {
|
||||
return this.value.compress();
|
||||
}
|
||||
}
|
||||
|
||||
toHex(): string {
|
||||
return bytesToHex(this.toBytes());
|
||||
toHex(format?: PointFormat): string {
|
||||
return bytesToHex(this.toBytes(format));
|
||||
}
|
||||
|
||||
private aggregateVerify(msgs: Uint8Array[], pks: blst.PublicKey[]): boolean {
|
||||
// If this set is simply an infinity signature and infinity publicKey then skip verification.
|
||||
// This has the effect of always declaring that this sig/publicKey combination is valid.
|
||||
// for Eth2.0 specs tests
|
||||
if (this.affine.value.is_inf() && pks.length === 1 && pks[0].value.is_inf()) {
|
||||
if (this.value.is_inf() && pks.length === 1 && pks[0].value.is_inf()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return blst.aggregateVerify(msgs, pks, this.affine);
|
||||
return blst.aggregateVerify(msgs, pks, this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export const SECRET_KEY_LENGTH = 32;
|
||||
export const SIGNATURE_LENGTH = 96;
|
||||
export const FP_POINT_LENGTH = 48;
|
||||
export const PUBLIC_KEY_LENGTH = FP_POINT_LENGTH;
|
||||
export const G2_HASH_PADDING = 16;
|
||||
export const PUBLIC_KEY_LENGTH_COMPRESSED = 48;
|
||||
export const PUBLIC_KEY_LENGTH_UNCOMPRESSED = 48 * 2;
|
||||
export const SIGNATURE_LENGTH_COMPRESSED = 96;
|
||||
export const SIGNATURE_LENGTH_UNCOMPRESSED = 96 * 2;
|
||||
|
|
|
@ -40,6 +40,6 @@ export class InvalidOrderError extends Error {
|
|||
|
||||
export class InvalidLengthError extends Error {
|
||||
constructor(arg: string, length: number) {
|
||||
super(`INVALID_LENGTH: ${arg} must have ${length} bytes`);
|
||||
super(`INVALID_LENGTH: ${arg} - ${length} bytes`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,22 +117,17 @@ export function functionalInterfaceFactory({
|
|||
* https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
|
||||
*/
|
||||
function verifyMultipleSignatures(
|
||||
publicKeys: Uint8Array[],
|
||||
messages: Uint8Array[],
|
||||
signatures: Uint8Array[]
|
||||
sets: {publicKey: Uint8Array; message: Uint8Array; signature: Uint8Array}[]
|
||||
): boolean {
|
||||
validateBytes(publicKeys, "publicKey");
|
||||
validateBytes(messages, "message");
|
||||
validateBytes(signatures, "signatures");
|
||||
if (!sets) throw Error("sets is null or undefined");
|
||||
|
||||
if (publicKeys.length === 0 || publicKeys.length !== messages.length || publicKeys.length !== signatures.length) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return Signature.verifyMultipleSignatures(
|
||||
publicKeys.map((publicKey) => PublicKey.fromBytes(publicKey)),
|
||||
messages.map((msg) => msg),
|
||||
signatures.map((signature) => Signature.fromBytes(signature))
|
||||
sets.map((s) => ({
|
||||
publicKey: PublicKey.fromBytes(s.publicKey),
|
||||
message: s.message,
|
||||
signature: Signature.fromBytes(s.signature),
|
||||
}))
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof NotInitializedError) throw e;
|
||||
|
|
|
@ -9,6 +9,7 @@ export * from "../constants";
|
|||
export {SecretKey, PublicKey, Signature, init, destroy};
|
||||
|
||||
export const bls: IBls = {
|
||||
implementation: "herumi",
|
||||
SecretKey,
|
||||
PublicKey,
|
||||
Signature,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {PublicKeyType} from "bls-eth-wasm";
|
||||
import {getContext} from "./context";
|
||||
import {PUBLIC_KEY_LENGTH} from "../constants";
|
||||
import {bytesToHex, hexToBytes, isZeroUint8Array} from "../helpers";
|
||||
import {PublicKey as IPublicKey} from "../interface";
|
||||
import {PointFormat, PublicKey as IPublicKey} from "../interface";
|
||||
import {EmptyAggregateError, InvalidLengthError, ZeroPublicKeyError} from "../errors";
|
||||
import {PUBLIC_KEY_LENGTH_COMPRESSED, PUBLIC_KEY_LENGTH_UNCOMPRESSED} from "../constants";
|
||||
|
||||
export class PublicKey implements IPublicKey {
|
||||
readonly value: PublicKeyType;
|
||||
|
@ -17,14 +17,16 @@ export class PublicKey implements IPublicKey {
|
|||
}
|
||||
|
||||
static fromBytes(bytes: Uint8Array): PublicKey {
|
||||
if (bytes.length !== PUBLIC_KEY_LENGTH) {
|
||||
throw new InvalidLengthError("PublicKey", PUBLIC_KEY_LENGTH);
|
||||
}
|
||||
|
||||
const context = getContext();
|
||||
const publicKey = new context.PublicKey();
|
||||
if (!isZeroUint8Array(bytes)) {
|
||||
publicKey.deserialize(bytes);
|
||||
if (bytes.length === PUBLIC_KEY_LENGTH_COMPRESSED) {
|
||||
publicKey.deserialize(bytes);
|
||||
} else if (bytes.length === PUBLIC_KEY_LENGTH_UNCOMPRESSED) {
|
||||
publicKey.deserializeUncompressed(bytes);
|
||||
} else {
|
||||
throw new InvalidLengthError("PublicKey", bytes.length);
|
||||
}
|
||||
}
|
||||
return new PublicKey(publicKey);
|
||||
}
|
||||
|
@ -45,11 +47,15 @@ export class PublicKey implements IPublicKey {
|
|||
return agg;
|
||||
}
|
||||
|
||||
toBytes(): Uint8Array {
|
||||
return this.value.serialize();
|
||||
toBytes(format?: PointFormat): Uint8Array {
|
||||
if (format === PointFormat.uncompressed) {
|
||||
return this.value.serializeUncompressed();
|
||||
} else {
|
||||
return this.value.serialize();
|
||||
}
|
||||
}
|
||||
|
||||
toHex(): string {
|
||||
return bytesToHex(this.toBytes());
|
||||
toHex(format?: PointFormat): string {
|
||||
return bytesToHex(this.toBytes(format));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import {SIGNATURE_LENGTH} from "../constants";
|
||||
import {SignatureType, multiVerify} from "bls-eth-wasm";
|
||||
import {getContext} from "./context";
|
||||
import {PublicKey} from "./publicKey";
|
||||
import {bytesToHex, concatUint8Arrays, hexToBytes, isZeroUint8Array} from "../helpers";
|
||||
import {Signature as ISignature} from "../interface";
|
||||
import {PointFormat, Signature as ISignature} from "../interface";
|
||||
import {EmptyAggregateError, InvalidLengthError, InvalidOrderError} from "../errors";
|
||||
import {SIGNATURE_LENGTH_COMPRESSED, SIGNATURE_LENGTH_UNCOMPRESSED} from "../constants";
|
||||
|
||||
export class Signature implements ISignature {
|
||||
readonly value: SignatureType;
|
||||
|
@ -18,13 +18,16 @@ export class Signature implements ISignature {
|
|||
}
|
||||
|
||||
static fromBytes(bytes: Uint8Array): Signature {
|
||||
if (bytes.length !== SIGNATURE_LENGTH) {
|
||||
throw new InvalidLengthError("Signature", SIGNATURE_LENGTH);
|
||||
}
|
||||
|
||||
const context = getContext();
|
||||
const signature = new context.Signature();
|
||||
if (!isZeroUint8Array(bytes)) {
|
||||
if (bytes.length === SIGNATURE_LENGTH_COMPRESSED) {
|
||||
signature.deserialize(bytes);
|
||||
} else if (bytes.length === SIGNATURE_LENGTH_UNCOMPRESSED) {
|
||||
signature.deserializeUncompressed(bytes);
|
||||
} else {
|
||||
throw new InvalidLengthError("Signature", bytes.length);
|
||||
}
|
||||
signature.deserialize(bytes);
|
||||
}
|
||||
return new Signature(signature);
|
||||
|
@ -45,11 +48,11 @@ export class Signature implements ISignature {
|
|||
return new Signature(signature);
|
||||
}
|
||||
|
||||
static verifyMultipleSignatures(publicKeys: PublicKey[], messages: Uint8Array[], signatures: Signature[]): boolean {
|
||||
static verifyMultipleSignatures(sets: {publicKey: PublicKey; message: Uint8Array; signature: Signature}[]): boolean {
|
||||
return multiVerify(
|
||||
publicKeys.map((publicKey) => publicKey.value),
|
||||
signatures.map((signature) => signature.value),
|
||||
messages
|
||||
sets.map((s) => s.publicKey.value),
|
||||
sets.map((s) => s.signature.value),
|
||||
sets.map((s) => s.message)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -71,11 +74,15 @@ export class Signature implements ISignature {
|
|||
);
|
||||
}
|
||||
|
||||
toBytes(): Uint8Array {
|
||||
return this.value.serialize();
|
||||
toBytes(format?: PointFormat): Uint8Array {
|
||||
if (format === PointFormat.uncompressed) {
|
||||
return this.value.serializeUncompressed();
|
||||
} else {
|
||||
return this.value.serialize();
|
||||
}
|
||||
}
|
||||
|
||||
toHex(): string {
|
||||
return bytesToHex(this.toBytes());
|
||||
toHex(format?: PointFormat): string {
|
||||
return bytesToHex(this.toBytes(format));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,8 @@
|
|||
export interface IBls {
|
||||
SecretKey: {
|
||||
fromBytes(bytes: Uint8Array): SecretKey;
|
||||
fromHex(hex: string): SecretKey;
|
||||
fromKeygen(ikm?: Uint8Array): SecretKey;
|
||||
};
|
||||
PublicKey: {
|
||||
fromBytes(bytes: Uint8Array): PublicKey;
|
||||
fromHex(hex: string): PublicKey;
|
||||
aggregate(publicKeys: PublicKey[]): PublicKey;
|
||||
};
|
||||
Signature: {
|
||||
fromBytes(bytes: Uint8Array): Signature;
|
||||
fromHex(hex: string): Signature;
|
||||
aggregate(signatures: Signature[]): Signature;
|
||||
verifyMultipleSignatures(publicKeys: PublicKey[], messages: Uint8Array[], signatures: Signature[]): boolean;
|
||||
};
|
||||
implementation: Implementation;
|
||||
SecretKey: Omit<typeof SecretKey, "prototype">;
|
||||
PublicKey: Omit<typeof PublicKey, "prototype">;
|
||||
Signature: Omit<typeof Signature, "prototype">;
|
||||
|
||||
sign(secretKey: Uint8Array, message: Uint8Array): Uint8Array;
|
||||
aggregatePublicKeys(publicKeys: Uint8Array[]): Uint8Array;
|
||||
|
@ -22,7 +10,7 @@ export interface IBls {
|
|||
verify(publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array): boolean;
|
||||
verifyAggregate(publicKeys: Uint8Array[], message: Uint8Array, signature: Uint8Array): boolean;
|
||||
verifyMultiple(publicKeys: Uint8Array[], messages: Uint8Array[], signature: Uint8Array): boolean;
|
||||
verifyMultipleSignatures(publicKeys: Uint8Array[], messages: Uint8Array[], signatures: Uint8Array[]): boolean;
|
||||
verifyMultipleSignatures(sets: {publicKey: Uint8Array; message: Uint8Array; signature: Uint8Array}[]): boolean;
|
||||
secretKeyToPublicKey(secretKey: Uint8Array): Uint8Array;
|
||||
|
||||
init(): Promise<void>;
|
||||
|
@ -40,21 +28,37 @@ export declare class SecretKey {
|
|||
}
|
||||
|
||||
export declare class PublicKey {
|
||||
static fromBytes(bytes: Uint8Array): PublicKey;
|
||||
/** @param type Only for impl `blst-native`. Defaults to `CoordType.jacobian` */
|
||||
static fromBytes(bytes: Uint8Array, type?: CoordType): PublicKey;
|
||||
static fromHex(hex: string): PublicKey;
|
||||
static aggregate(publicKeys: PublicKey[]): PublicKey;
|
||||
toBytes(): Uint8Array;
|
||||
toHex(): string;
|
||||
/** @param format Defaults to `PointFormat.compressed` */
|
||||
toBytes(format?: PointFormat): Uint8Array;
|
||||
toHex(format?: PointFormat): string;
|
||||
}
|
||||
|
||||
export declare class Signature {
|
||||
static fromBytes(bytes: Uint8Array): Signature;
|
||||
/** @param type Only for impl `blst-native`. Defaults to `CoordType.affine` */
|
||||
static fromBytes(bytes: Uint8Array, type?: CoordType, validate?: boolean): Signature;
|
||||
static fromHex(hex: string): Signature;
|
||||
static aggregate(signatures: Signature[]): Signature;
|
||||
static verifyMultipleSignatures(publicKeys: PublicKey[], messages: Uint8Array[], signatures: Signature[]): boolean;
|
||||
static verifyMultipleSignatures(sets: {publicKey: PublicKey; message: Uint8Array; signature: Signature}[]): boolean;
|
||||
verify(publicKey: PublicKey, message: Uint8Array): boolean;
|
||||
verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean;
|
||||
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean;
|
||||
toBytes(): Uint8Array;
|
||||
toHex(): string;
|
||||
/** @param format Defaults to `PointFormat.compressed` */
|
||||
toBytes(format?: PointFormat): Uint8Array;
|
||||
toHex(format?: PointFormat): string;
|
||||
}
|
||||
|
||||
export type Implementation = "herumi" | "blst-native";
|
||||
|
||||
export enum PointFormat {
|
||||
compressed = "compressed",
|
||||
uncompressed = "uncompressed",
|
||||
}
|
||||
|
||||
export enum CoordType {
|
||||
affine,
|
||||
jacobian,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {expect} from "chai";
|
||||
import {IBls} from "../../src/interface";
|
||||
import {getN, randomMessage} from "../util";
|
||||
import {IBls, PointFormat} from "../../src/interface";
|
||||
import {getN, randomMessage, hexToBytesNode} from "../util";
|
||||
import {hexToBytes} from "../../src/helpers";
|
||||
import {maliciousVerifyMultipleSignaturesData} from "../data/malicious-signature-test-data";
|
||||
|
||||
|
@ -96,24 +96,23 @@ export function runIndexTests(bls: IBls): void {
|
|||
describe("verifyMultipleSignatures", () => {
|
||||
it("Should verify multiple signatures", () => {
|
||||
const n = 4;
|
||||
const dataArr = getN(n, () => {
|
||||
const sets = getN(n, () => {
|
||||
const sk = bls.SecretKey.fromKeygen();
|
||||
const pk = sk.toPublicKey();
|
||||
const msg = randomMessage();
|
||||
const sig = sk.sign(msg);
|
||||
return {pk, msg, sig};
|
||||
const publicKey = sk.toPublicKey();
|
||||
const message = randomMessage();
|
||||
const signature = sk.sign(message);
|
||||
return {publicKey, message, signature};
|
||||
});
|
||||
const pks = dataArr.map((data) => data.pk);
|
||||
const msgs = dataArr.map((data) => data.msg);
|
||||
const sigs = dataArr.map((data) => data.sig);
|
||||
|
||||
expect(bls.Signature.verifyMultipleSignatures(pks, msgs, sigs)).to.equal(true, "class interface failed");
|
||||
expect(bls.Signature.verifyMultipleSignatures(sets)).to.equal(true, "class interface failed");
|
||||
|
||||
expect(
|
||||
bls.verifyMultipleSignatures(
|
||||
pks.map((pk) => pk.toBytes()),
|
||||
msgs,
|
||||
sigs.map((sig) => sig.toBytes())
|
||||
sets.map((s) => ({
|
||||
publicKey: s.publicKey.toBytes(),
|
||||
message: s.message,
|
||||
signature: s.signature.toBytes(),
|
||||
}))
|
||||
)
|
||||
).to.equal(true, "functional (bytes serialized) interface failed");
|
||||
});
|
||||
|
@ -138,11 +137,47 @@ export function runIndexTests(bls: IBls): void {
|
|||
"Malicious signature should be validated with bls.verifyMultiple"
|
||||
);
|
||||
|
||||
const maliciousSets = pks.map((_, i) => ({
|
||||
publicKey: pks[i],
|
||||
message: msgs[i],
|
||||
signature: sigs[i],
|
||||
}));
|
||||
|
||||
// This method is expected to catch the malicious signature and not verify
|
||||
expect(bls.Signature.verifyMultipleSignatures(pks, msgs, sigs)).to.equal(
|
||||
expect(bls.Signature.verifyMultipleSignatures(maliciousSets)).to.equal(
|
||||
false,
|
||||
"Malicous signature should not validate with bls.verifyMultipleSignatures"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("serialize deserialize", () => {
|
||||
/* eslint-disable max-len */
|
||||
|
||||
const skHex = "0x0101010101010101010101010101010101010101010101010101010101010101";
|
||||
const pkHexCompExpected =
|
||||
"0xaa1a1c26055a329817a5759d877a2795f9499b97d6056edde0eea39512f24e8bc874b4471f0501127abb1ea0d9f68ac1";
|
||||
const pkHexUncompExpected =
|
||||
"0x0a1a1c26055a329817a5759d877a2795f9499b97d6056edde0eea39512f24e8bc874b4471f0501127abb1ea0d9f68ac111392125a1c3750363c2c97d9650fb78696e6428db8ff9efaf0471cbfd20324916ab545746db83756d335e92f9e8c8b8";
|
||||
|
||||
it("Should serialize comp pubkey", () => {
|
||||
const sk = bls.SecretKey.fromBytes(hexToBytesNode(skHex));
|
||||
const pkHexComp = sk.toPublicKey().toHex(PointFormat.compressed);
|
||||
expect(pkHexComp).to.equal(pkHexCompExpected, "Wrong pkHexComp");
|
||||
});
|
||||
|
||||
it("Should serialize uncomp pubkey", () => {
|
||||
const sk = bls.SecretKey.fromBytes(hexToBytesNode(skHex));
|
||||
const pkHexUncomp = sk.toPublicKey().toHex(PointFormat.uncompressed);
|
||||
expect(pkHexUncomp).to.equal(pkHexUncompExpected, "Wrong pkHexUncomp");
|
||||
});
|
||||
|
||||
it("Should deserialize comp pubkey", () => {
|
||||
bls.PublicKey.fromHex(pkHexCompExpected);
|
||||
});
|
||||
|
||||
it("Should deserialize uncomp pubkey", () => {
|
||||
bls.PublicKey.fromHex(pkHexUncompExpected);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import {expect} from "chai";
|
||||
import {chunkify} from "./utils";
|
||||
|
||||
describe("chunkify", () => {
|
||||
const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15];
|
||||
|
||||
const results = {
|
||||
0: [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15]],
|
||||
1: [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15]],
|
||||
2: [
|
||||
[0, 1, 2, 3, 4, 5, 6, 7],
|
||||
[8, 9, 10, 12, 13, 14, 15],
|
||||
],
|
||||
3: [
|
||||
[0, 1, 2, 3, 4],
|
||||
[5, 6, 7, 8, 9],
|
||||
[10, 12, 13, 14, 15],
|
||||
],
|
||||
4: [
|
||||
[0, 1, 2, 3],
|
||||
[4, 5, 6, 7],
|
||||
[8, 9, 10, 12],
|
||||
[13, 14, 15],
|
||||
],
|
||||
5: [
|
||||
[0, 1, 2],
|
||||
[3, 4, 5],
|
||||
[6, 7, 8],
|
||||
[9, 10, 12],
|
||||
[13, 14, 15],
|
||||
],
|
||||
6: [
|
||||
[0, 1, 2],
|
||||
[3, 4, 5],
|
||||
[6, 7, 8],
|
||||
[9, 10, 12],
|
||||
[13, 14, 15],
|
||||
],
|
||||
7: [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10, 12], [13, 14], [15]],
|
||||
8: [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10, 12], [13, 14], [15]],
|
||||
};
|
||||
|
||||
const testCases: {
|
||||
id: string;
|
||||
n: number;
|
||||
arr: number[];
|
||||
expectArr: number[][];
|
||||
}[] = Object.entries(results).map(([i, expectArr]) => ({
|
||||
id: i,
|
||||
n: parseInt(i),
|
||||
arr,
|
||||
expectArr,
|
||||
}));
|
||||
|
||||
for (const {id, arr, n, expectArr} of testCases) {
|
||||
it(id, () => {
|
||||
expect(chunkify(arr, n)).to.deep.equal(expectArr);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
import {spawn, Pool, Worker, Thread} from "threads";
|
||||
import {Implementation, PointFormat, PublicKey, Signature} from "../../../../src";
|
||||
import {WorkerApi} from "./worker";
|
||||
|
||||
type ThreadType = {
|
||||
[K in keyof WorkerApi]: (...args: Parameters<WorkerApi[K]>) => Promise<ReturnType<WorkerApi[K]>>;
|
||||
};
|
||||
|
||||
export class BlsMultiThreadNaive {
|
||||
impl: Implementation;
|
||||
pool: Pool<Thread & ThreadType>;
|
||||
format: PointFormat;
|
||||
|
||||
constructor(impl: Implementation, workerCount?: number) {
|
||||
this.impl = impl;
|
||||
// Use compressed for herumi for now.
|
||||
// THe worker is not able to deserialize from uncompressed
|
||||
// `Error: err _wrapDeserialize`
|
||||
this.format = impl === "blst-native" ? PointFormat.uncompressed : PointFormat.compressed;
|
||||
this.pool = Pool(() => (spawn(new Worker("./worker")) as any) as Promise<Thread & ThreadType>, workerCount);
|
||||
}
|
||||
|
||||
async destroy(): Promise<void> {
|
||||
await this.pool.terminate(true);
|
||||
}
|
||||
|
||||
async verify(pk: PublicKey, msg: Uint8Array, sig: Signature): Promise<boolean> {
|
||||
return this.pool.queue((worker) =>
|
||||
worker.verify(this.impl, pk.toBytes(PointFormat.uncompressed), msg, sig.toBytes(PointFormat.uncompressed))
|
||||
);
|
||||
}
|
||||
|
||||
async verifyMultipleAggregateSignatures(
|
||||
sets: {publicKey: PublicKey; message: Uint8Array; signature: Signature}[]
|
||||
): Promise<boolean> {
|
||||
return this.pool.queue((worker) =>
|
||||
worker.verifyMultipleAggregateSignatures(
|
||||
this.impl,
|
||||
sets.map((s) => ({
|
||||
publicKey: s.publicKey.toBytes(PointFormat.uncompressed),
|
||||
message: s.message,
|
||||
signature: s.signature.toBytes(PointFormat.uncompressed),
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import {expect} from "chai";
|
||||
import {IBls, PublicKey, Signature} from "../../../../src";
|
||||
import {BlsMultiThreadNaive} from "./index";
|
||||
|
||||
export function runMultithreadTests(bls: IBls): void {
|
||||
describe("bls pool naive", function () {
|
||||
// Starting all threads may take a while due to ts-node compilation
|
||||
this.timeout(20 * 1000);
|
||||
|
||||
const n = 16;
|
||||
let pool: BlsMultiThreadNaive;
|
||||
|
||||
before("Create pool and warm-up wallets", async function () {
|
||||
pool = new BlsMultiThreadNaive(bls.implementation);
|
||||
});
|
||||
|
||||
after("Destroy pool", async function () {
|
||||
await pool.destroy();
|
||||
});
|
||||
|
||||
describe("1 msg, 1 pk", function () {
|
||||
const msg = Buffer.from("sample-msg");
|
||||
const sk = bls.SecretKey.fromKeygen(Buffer.alloc(32, 1));
|
||||
const pk = sk.toPublicKey();
|
||||
const sig = sk.sign(msg);
|
||||
|
||||
it("verify", async function () {
|
||||
if (bls.implementation === "herumi") this.skip();
|
||||
|
||||
const validArr = await Promise.all(
|
||||
Array.from({length: 32}, (i) => i).map(async () => pool.verify(pk, msg, sig))
|
||||
);
|
||||
for (const [i, valid] of validArr.entries()) {
|
||||
expect(valid).to.equal(true, `Invalid ${i}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("N msgs, N pks", function () {
|
||||
const sets: {publicKey: PublicKey; message: Uint8Array; signature: Signature}[] = [];
|
||||
for (let i = 0; i < n; i++) {
|
||||
const message = Buffer.alloc(32, i);
|
||||
const sk = bls.SecretKey.fromKeygen(Buffer.alloc(32, i));
|
||||
sets.push({message, publicKey: sk.toPublicKey(), signature: sk.sign(message)});
|
||||
}
|
||||
|
||||
it("verify", async function () {
|
||||
if (bls.implementation === "herumi") this.skip();
|
||||
|
||||
const validArr = await Promise.all(sets.map((s) => pool.verify(s.publicKey, s.message, s.signature)));
|
||||
for (const [i, valid] of validArr.entries()) {
|
||||
expect(valid).to.equal(true, `Invalid ${i}`);
|
||||
}
|
||||
});
|
||||
|
||||
it("verifyMultipleAggregateSignatures", async function () {
|
||||
if (bls.implementation === "herumi") this.skip();
|
||||
|
||||
const valid = await pool.verifyMultipleAggregateSignatures(sets);
|
||||
expect(valid).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
export function chunkify<T>(arr: T[], chunkCount: number): T[][] {
|
||||
const chunkSize = Math.round(arr.length / chunkCount);
|
||||
const arrArr: T[][] = [];
|
||||
|
||||
for (let i = 0, j = arr.length; i < j; i += chunkSize) {
|
||||
arrArr.push(arr.slice(i, i + chunkSize));
|
||||
}
|
||||
|
||||
return arrArr;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import {expose} from "threads/worker";
|
||||
import {bls, init, CoordType, Implementation} from "../../../../src";
|
||||
|
||||
export type WorkerApi = typeof workerApi;
|
||||
|
||||
const workerApi = {
|
||||
async verify(impl: Implementation, publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array) {
|
||||
await init(impl);
|
||||
const pk = bls.PublicKey.fromBytes(publicKey, CoordType.affine);
|
||||
const sig = bls.Signature.fromBytes(signature, CoordType.affine, true);
|
||||
return sig.verify(pk, message);
|
||||
},
|
||||
async verifyMultipleAggregateSignatures(
|
||||
impl: Implementation,
|
||||
sets: {publicKey: Uint8Array; message: Uint8Array; signature: Uint8Array}[]
|
||||
) {
|
||||
await init(impl);
|
||||
return bls.Signature.verifyMultipleSignatures(
|
||||
sets.map((s) => ({
|
||||
publicKey: bls.PublicKey.fromBytes(s.publicKey, CoordType.affine),
|
||||
message: s.message,
|
||||
signature: bls.Signature.fromBytes(s.signature, CoordType.affine, true),
|
||||
}))
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
expose(workerApi);
|
|
@ -1,6 +1,7 @@
|
|||
import {runSecretKeyTests} from "./secretKey.test";
|
||||
import {runPublicKeyTests} from "./publicKey.test";
|
||||
import {runIndexTests} from "./index.test";
|
||||
import {runMultithreadTests} from "./multithread/naive/naive.test";
|
||||
import {describeForAllImplementations} from "../switch";
|
||||
|
||||
// Import test's bls lib lazily to prevent breaking test with Karma
|
||||
|
@ -8,4 +9,5 @@ describeForAllImplementations((bls) => {
|
|||
runSecretKeyTests(bls);
|
||||
runPublicKeyTests(bls);
|
||||
runIndexTests(bls);
|
||||
runMultithreadTests(bls);
|
||||
});
|
||||
|
|
51
yarn.lock
51
yarn.lock
|
@ -161,10 +161,10 @@
|
|||
bls-eth-wasm "^0.4.4"
|
||||
randombytes "^2.1.0"
|
||||
|
||||
"@chainsafe/blst@^0.1.6":
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@chainsafe/blst/-/blst-0.1.6.tgz#d933a1568d9e781cd13673d80ff1eaf40c955107"
|
||||
integrity sha512-iv1CASFce9T1QQB+pVznMXadEYZFw3x/YOgstjw14OoKGZSYvjupA7h7247u/ZHQguw7aO3jmVTEzHAwO8yQqw==
|
||||
"@chainsafe/blst@^0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@chainsafe/blst/-/blst-0.2.0.tgz#5e2d2707c2c0d56ff077a00179a5255eaca14099"
|
||||
integrity sha512-eyyLm4C+Zhl18YwFa93J+xRSHj0NrBZodBO+z+aaREf71RnA7/EvOcAPVLpEW2CI7PsInhVne/ufb+A7gfHQrg==
|
||||
dependencies:
|
||||
node-fetch "^2.6.1"
|
||||
node-gyp "^7.1.2"
|
||||
|
@ -1171,7 +1171,7 @@ callsite@1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
|
||||
integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA=
|
||||
|
||||
callsites@^3.0.0:
|
||||
callsites@^3.0.0, callsites@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||
|
@ -2133,6 +2133,11 @@ eslint@^6.8.0:
|
|||
text-table "^0.2.0"
|
||||
v8-compile-cache "^2.0.3"
|
||||
|
||||
esm@^3.2.25:
|
||||
version "3.2.25"
|
||||
resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
|
||||
integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==
|
||||
|
||||
espree@^6.1.2:
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.2.tgz#6c272650932b4f91c3714e5e7b5f5e2ecf47262d"
|
||||
|
@ -3152,6 +3157,13 @@ is-number@^7.0.0:
|
|||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||
|
||||
is-observable@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-1.1.0.tgz#b3e986c8f44de950867cab5403f5a3465005975e"
|
||||
integrity sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==
|
||||
dependencies:
|
||||
symbol-observable "^1.1.0"
|
||||
|
||||
is-plain-obj@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
|
||||
|
@ -4216,6 +4228,11 @@ object.values@^1.1.0:
|
|||
function-bind "^1.1.1"
|
||||
has "^1.0.3"
|
||||
|
||||
observable-fns@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/observable-fns/-/observable-fns-0.5.1.tgz#9b56478690dd0fa8603e3a7e7d2975d88bca0904"
|
||||
integrity sha512-wf7g4Jpo1Wt2KIqZKLGeiuLOEMqpaOZ5gJn7DmSdqXgTdxRwSdBhWegQQpPteQ2gZvzCKqNNpwb853wcpA0j7A==
|
||||
|
||||
on-finished@~2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||
|
@ -5518,6 +5535,11 @@ supports-color@^7.1.0:
|
|||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
symbol-observable@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
|
||||
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
|
||||
|
||||
table@^5.2.3:
|
||||
version "5.4.6"
|
||||
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
|
||||
|
@ -5588,6 +5610,18 @@ text-table@^0.2.0:
|
|||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
|
||||
|
||||
threads@^1.6.3:
|
||||
version "1.6.3"
|
||||
resolved "https://registry.yarnpkg.com/threads/-/threads-1.6.3.tgz#89324a93509403c90a169344023151ae1fe4986b"
|
||||
integrity sha512-tKwFIWRgfAT85KGkrpDt2jWPO8IVH0sLNfB/pXad/VW9eUIY2Zlz+QyeizypXhPHv9IHfqRzvk2t3mPw+imhWw==
|
||||
dependencies:
|
||||
callsites "^3.1.0"
|
||||
debug "^4.1.1"
|
||||
is-observable "^1.1.0"
|
||||
observable-fns "^0.5.1"
|
||||
optionalDependencies:
|
||||
tiny-worker ">= 2"
|
||||
|
||||
through2@^2.0.0:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
|
||||
|
@ -5608,6 +5642,13 @@ timers-browserify@^2.0.4:
|
|||
dependencies:
|
||||
setimmediate "^1.0.4"
|
||||
|
||||
"tiny-worker@>= 2":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/tiny-worker/-/tiny-worker-2.3.0.tgz#715ae34304c757a9af573ae9a8e3967177e6011e"
|
||||
integrity sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==
|
||||
dependencies:
|
||||
esm "^3.2.25"
|
||||
|
||||
tmp@0.0.33, tmp@0.0.x, tmp@^0.0.33:
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||
|
|
Reference in New Issue