From 7a3cffcb701e98359ccad7ca69a0d4d835085a14 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Fri, 19 Apr 2024 21:19:51 +0700 Subject: [PATCH 01/16] feat: move all async to respective Signature class --- src/blst-native/signature.ts | 55 +++++++++++++++++++++++++++++++++++- src/functional.ts | 40 +++++--------------------- src/herumi/signature.ts | 37 ++++++++++++++++++++++-- src/types.ts | 6 +++- 4 files changed, 101 insertions(+), 37 deletions(-) diff --git a/src/blst-native/signature.ts b/src/blst-native/signature.ts index b6f9d63..a1a8f47 100644 --- a/src/blst-native/signature.ts +++ b/src/blst-native/signature.ts @@ -1,6 +1,6 @@ import blst from "@chainsafe/blst"; import {bytesToHex, hexToBytes} from "../helpers/index.js"; -import {CoordType, PointFormat, Signature as ISignature} from "../types.js"; +import {SignatureSet, CoordType, PointFormat, Signature as ISignature, PublicKeyArg, SignatureArg} from "../types.js"; import {PublicKey} from "./publicKey.js"; import {EmptyAggregateError, ZeroSignatureError} from "../errors.js"; @@ -35,6 +35,20 @@ export class Signature implements ISignature { ); } + static async asyncVerifyMultipleSignatures(sets: SignatureSet[]): Promise { + try { + return blst.asyncVerifyMultipleAggregateSignatures( + sets.map((set) => ({ + message: set.message, + publicKey: Signature.convertToBlstPublicKeyArg(set.publicKey), + signature: Signature.convertToBlstSignatureArg(set.signature), + })) + ); + } catch { + return false; + } + } + /** * Implemented for SecretKey to be able to call .sign() */ @@ -42,6 +56,16 @@ export class Signature implements ISignature { return new Signature(sig); } + private static convertToBlstPublicKeyArg(publicKey: PublicKeyArg): blst.PublicKeyArg { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return publicKey instanceof Uint8Array ? publicKey : publicKey.value; + } + + private static convertToBlstSignatureArg(signature: SignatureArg): blst.SignatureArg { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return signature instanceof Uint8Array ? signature : signature.value; + } + verify(publicKey: PublicKey, message: Uint8Array): boolean { // Individual infinity signatures are NOT okay. Aggregated signatures MAY be infinity if (this.value.isInfinity()) { @@ -69,6 +93,35 @@ export class Signature implements ISignature { ); } + async asyncVerify(publicKey: PublicKeyArg, message: Uint8Array): Promise { + // Individual infinity signatures are NOT okay. Aggregated signatures MAY be infinity + if (this.value.isInfinity()) { + throw new ZeroSignatureError(); + } + // @ts-expect-error Need to hack type to get access to the private `value` + return blst.asyncVerify(message, publicKey.value, this.value); + } + + async asyncVerifyAggregate(publicKeys: PublicKeyArg[], message: Uint8Array): Promise { + return blst.asyncFastAggregateVerify( + message, + // @ts-expect-error Need to hack type to get access to the private `value` + publicKeys.map((pk) => pk.value), + this.value + ); + } + + async asyncVerifyMultiple(publicKeys: PublicKeyArg[], messages: Uint8Array[]): Promise { + // 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 + const pks = publicKeys.map(Signature.convertToBlstPublicKeyArg); + if (this.value.isInfinity() && publicKeys.length === 1 && publicKeys[0].isInfinity()) { + return true; + } + return blst.asyncAggregateVerify(messages, , this.value); + } + toBytes(format?: PointFormat): Uint8Array { if (format === PointFormat.uncompressed) { return this.value.serialize(false); diff --git a/src/functional.ts b/src/functional.ts index 65e12c9..ec79913 100644 --- a/src/functional.ts +++ b/src/functional.ts @@ -1,4 +1,3 @@ -import blst from "@chainsafe/blst"; import {IBls, PublicKey, PublicKeyArg, Signature, SignatureArg, SignatureSet} from "./types.js"; import {validateBytes} from "./helpers/index.js"; import {NotInitializedError} from "./errors.js"; @@ -171,7 +170,8 @@ export function functionalInterfaceFactory({ async function asyncVerify(publicKey: PublicKeyArg, message: Uint8Array, signature: SignatureArg): Promise { if (implementation === "herumi") return verify(publicKey, message, signature); try { - return blst.asyncVerify(message, convertToBlstPublicKeyArg(publicKey), convertToBlstSignatureArg(signature)); + const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature); + return sig.asyncVerify(publicKey, message); } catch { return false; } @@ -187,11 +187,8 @@ export function functionalInterfaceFactory({ ): Promise { if (implementation === "herumi") return verifyAggregate(publicKeys, message, signature); try { - return blst.asyncFastAggregateVerify( - message, - publicKeys.map((key) => convertToBlstPublicKeyArg(key)), - convertToBlstSignatureArg(signature) - ); + const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature); + return sig.asyncVerifyAggregate(publicKeys, message); } catch { return false; } @@ -207,11 +204,8 @@ export function functionalInterfaceFactory({ ): Promise { if (implementation === "herumi") return verifyMultiple(publicKeys, messages, signature); try { - return blst.asyncAggregateVerify( - messages, - publicKeys.map((key) => convertToBlstPublicKeyArg(key)), - convertToBlstSignatureArg(signature) - ); + const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature); + return sig.asyncVerifyMultiple(publicKeys, messages); } catch { return false; } @@ -229,17 +223,7 @@ export function functionalInterfaceFactory({ */ async function asyncVerifyMultipleSignatures(sets: SignatureSet[]): Promise { if (implementation === "herumi") return verifyMultipleSignatures(sets); - try { - return blst.asyncVerifyMultipleAggregateSignatures( - sets.map((set) => ({ - message: set.message, - publicKey: convertToBlstPublicKeyArg(set.publicKey), - signature: convertToBlstSignatureArg(set.signature), - })) - ); - } catch { - return false; - } + return Signature.asyncVerifyMultipleSignatures(sets); } /** @@ -250,16 +234,6 @@ export function functionalInterfaceFactory({ return SecretKey.fromBytes(secretKey).toPublicKey().toBytes(); } - function convertToBlstPublicKeyArg(publicKey: PublicKeyArg): blst.PublicKeyArg { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return publicKey instanceof PublicKey ? ((publicKey as any).value as blst.PublicKey) : publicKey; - } - - function convertToBlstSignatureArg(signature: SignatureArg): blst.SignatureArg { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return signature instanceof Signature ? ((signature as any).value as blst.Signature) : signature; - } - return { sign, aggregateSignatures, diff --git a/src/herumi/signature.ts b/src/herumi/signature.ts index 48a0a33..1d24927 100644 --- a/src/herumi/signature.ts +++ b/src/herumi/signature.ts @@ -2,7 +2,7 @@ import type {SignatureType} from "bls-eth-wasm"; import {getContext} from "./context.js"; import {PublicKey} from "./publicKey.js"; import {bytesToHex, concatUint8Arrays, hexToBytes, isZeroUint8Array} from "../helpers/index.js"; -import {PointFormat, Signature as ISignature, CoordType} from "../types.js"; +import {SignatureSet, PointFormat, Signature as ISignature, CoordType} from "../types.js"; import {EmptyAggregateError, InvalidLengthError, InvalidOrderError} from "../errors.js"; import {SIGNATURE_LENGTH_COMPRESSED, SIGNATURE_LENGTH_UNCOMPRESSED} from "../constants.js"; @@ -52,7 +52,7 @@ export class Signature implements ISignature { return new Signature(signature); } - static verifyMultipleSignatures(sets: {publicKey: PublicKey; message: Uint8Array; signature: Signature}[]): boolean { + static verifyMultipleSignatures(sets: SignatureSet[]): boolean { const context = getContext(); return context.multiVerify( sets.map((s) => s.publicKey.value), @@ -61,6 +61,15 @@ export class Signature implements ISignature { ); } + static async asyncVerifyMultipleSignatures(sets: SignatureSet[]): Promise { + // eslint-disable-next-line no-console + console.log( + "asyncVerifyMultipleSignatures is not implemented by bls-eth-wasm.\n" + + "Please use the synchronous Signature.verifyMultipleSignatures instead" + ); + return Signature.verifyMultipleSignatures(sets); + } + verify(publicKey: PublicKey, message: Uint8Array): boolean { return publicKey.value.verify(this.value, message); } @@ -79,6 +88,30 @@ export class Signature implements ISignature { ); } + async asyncVerify(publicKey: PublicKey, message: Uint8Array): Promise { + // eslint-disable-next-line no-console + console.log("asyncVerify is not implemented by bls-eth-wasm.\nPlease use the synchronous Signature.verify instead"); + return this.verify(publicKey, message); + } + + async asyncVerifyAggregate(publicKeys: PublicKey[], message: Uint8Array): Promise { + // eslint-disable-next-line no-console + console.log( + "asyncVerifyAggregate is not implemented by bls-eth-wasm.\n" + + "Please use the synchronous Signature.verifyAggregate instead" + ); + return this.verifyAggregate(publicKeys, message); + } + + async asyncVerifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): Promise { + // eslint-disable-next-line no-console + console.log( + "asyncVerifyMultiple is not implemented by bls-eth-wasm.\n" + + "Please use the synchronous Signature.verifyMultiple instead" + ); + return this.verifyMultiple(publicKeys, messages); + } + toBytes(format?: PointFormat): Uint8Array { if (format === PointFormat.uncompressed) { return this.value.serializeUncompressed(); diff --git a/src/types.ts b/src/types.ts index 7bac3a0..a665a97 100644 --- a/src/types.ts +++ b/src/types.ts @@ -82,10 +82,14 @@ export declare class Signature { static fromBytes(bytes: Uint8Array, type?: CoordType, validate?: boolean): Signature; static fromHex(hex: string): Signature; static aggregate(signatures: Signature[]): Signature; - static verifyMultipleSignatures(sets: {publicKey: PublicKey; message: Uint8Array; signature: Signature}[]): boolean; + static verifyMultipleSignatures(sets: SignatureSet[]): boolean; + static asyncVerifyMultipleSignatures(sets: SignatureSet[]): Promise; verify(publicKey: PublicKey, message: Uint8Array): boolean; verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean; verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean; + asyncVerify(publicKey: PublicKeyArg, message: Uint8Array): Promise; + asyncVerifyAggregate(publicKeys: PublicKeyArg[], message: Uint8Array): Promise; + asyncVerifyMultiple(publicKeys: PublicKeyArg[], messages: Uint8Array[]): Promise; /** @param format Defaults to `PointFormat.compressed` */ toBytes(format?: PointFormat): Uint8Array; toHex(format?: PointFormat): string; From 05293705b164ba3a952ed259258e28bcc69f379c Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Sat, 20 Apr 2024 22:50:40 +0700 Subject: [PATCH 02/16] feat: update blst-native classes for async implementation --- src/blst-native/publicKey.ts | 10 ++- src/blst-native/signature.ts | 123 +++++++++++++++++------------------ 2 files changed, 67 insertions(+), 66 deletions(-) diff --git a/src/blst-native/publicKey.ts b/src/blst-native/publicKey.ts index 9ba6923..d95c031 100644 --- a/src/blst-native/publicKey.ts +++ b/src/blst-native/publicKey.ts @@ -1,7 +1,7 @@ import blst from "@chainsafe/blst"; import {EmptyAggregateError} from "../errors.js"; import {bytesToHex, hexToBytes} from "../helpers/index.js"; -import {CoordType, PointFormat, PublicKey as IPublicKey} from "../types.js"; +import {CoordType, PointFormat, PublicKey as IPublicKey, PublicKeyArg} from "../types.js"; export class PublicKey implements IPublicKey { private constructor(private readonly value: blst.PublicKey) {} @@ -18,15 +18,19 @@ export class PublicKey implements IPublicKey { return this.fromBytes(hexToBytes(hex)); } - static aggregate(publicKeys: PublicKey[]): PublicKey { + static aggregate(publicKeys: PublicKeyArg[]): PublicKey { if (publicKeys.length === 0) { throw new EmptyAggregateError(); } - const pk = blst.aggregatePublicKeys(publicKeys.map(({value}) => value)); + const pk = blst.aggregatePublicKeys(publicKeys.map(PublicKey.convertToBlstPublicKeyArg)); return new PublicKey(pk); } + static convertToBlstPublicKeyArg(publicKey: PublicKeyArg): blst.PublicKeyArg { + return publicKey instanceof PublicKey ? publicKey.value : (publicKey as Uint8Array); + } + /** * Implemented for SecretKey to be able to call .toPublicKey() */ diff --git a/src/blst-native/signature.ts b/src/blst-native/signature.ts index a1a8f47..b4aa2b7 100644 --- a/src/blst-native/signature.ts +++ b/src/blst-native/signature.ts @@ -19,34 +19,37 @@ export class Signature implements ISignature { return this.fromBytes(hexToBytes(hex)); } - static aggregate(signatures: Signature[]): Signature { + static aggregate(signatures: SignatureArg[]): Signature { if (signatures.length === 0) { throw new EmptyAggregateError(); } - const agg = blst.aggregateSignatures(signatures.map(({value}) => value)); + const agg = blst.aggregateSignatures(signatures.map(Signature.convertToBlstSignatureArg)); return new Signature(agg); } - static verifyMultipleSignatures(sets: {publicKey: PublicKey; message: Uint8Array; signature: Signature}[]): boolean { + static verifyMultipleSignatures(sets: SignatureSet[]): boolean { return blst.verifyMultipleAggregateSignatures( - // @ts-expect-error Need to hack type to get access to the private `value` - sets.map((s) => ({message: s.message, publicKey: s.publicKey.value, signature: s.signature.value})) + sets.map((set) => ({ + message: set.message, + publicKey: PublicKey.convertToBlstPublicKeyArg(set.publicKey), + signature: Signature.convertToBlstSignatureArg(set.signature), + })) ); } - static async asyncVerifyMultipleSignatures(sets: SignatureSet[]): Promise { - try { - return blst.asyncVerifyMultipleAggregateSignatures( - sets.map((set) => ({ - message: set.message, - publicKey: Signature.convertToBlstPublicKeyArg(set.publicKey), - signature: Signature.convertToBlstSignatureArg(set.signature), - })) - ); - } catch { - return false; - } + static asyncVerifyMultipleSignatures(sets: SignatureSet[]): Promise { + return blst.asyncVerifyMultipleAggregateSignatures( + sets.map((set) => ({ + message: set.message, + publicKey: PublicKey.convertToBlstPublicKeyArg(set.publicKey), + signature: Signature.convertToBlstSignatureArg(set.signature), + })) + ); + } + + static convertToBlstSignatureArg(signature: SignatureArg): blst.SignatureArg { + return signature instanceof Signature ? signature.value : (signature as Uint8Array); } /** @@ -56,70 +59,42 @@ export class Signature implements ISignature { return new Signature(sig); } - private static convertToBlstPublicKeyArg(publicKey: PublicKeyArg): blst.PublicKeyArg { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return publicKey instanceof Uint8Array ? publicKey : publicKey.value; - } + verify(publicKey: PublicKeyArg, message: Uint8Array): boolean { + // TODO (@matthewkeil) The note in aggregateVerify and the checks in this method + // do not seem to go together. Need to check the spec further. - private static convertToBlstSignatureArg(signature: SignatureArg): blst.SignatureArg { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return signature instanceof Uint8Array ? signature : signature.value; - } - - verify(publicKey: PublicKey, message: Uint8Array): boolean { // Individual infinity signatures are NOT okay. Aggregated signatures MAY be infinity if (this.value.isInfinity()) { throw new ZeroSignatureError(); } - - // @ts-expect-error Need to hack type to get access to the private `value` - return blst.verify(message, publicKey.value, this.value); + return blst.verify(message, PublicKey.convertToBlstPublicKeyArg(publicKey), this.value); } - verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean { - return blst.fastAggregateVerify( - message, - // @ts-expect-error Need to hack type to get access to the private `value` - publicKeys.map((pk) => pk.value), - this.value - ); + verifyAggregate(publicKeys: PublicKeyArg[], message: Uint8Array): boolean { + return blst.fastAggregateVerify(message, publicKeys.map(PublicKey.convertToBlstPublicKeyArg), this.value); } - verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean { - return this.aggregateVerify( - messages, - // @ts-expect-error Need to hack type to get access to the private `value` - publicKeys.map((pk) => pk.value) - ); + verifyMultiple(publicKeys: PublicKeyArg[], messages: Uint8Array[]): boolean { + return this.aggregateVerify(publicKeys, messages, false); } async asyncVerify(publicKey: PublicKeyArg, message: Uint8Array): Promise { + // TODO (@matthewkeil) The note in aggregateVerify and the checks in this method + // do not seem to go together. Need to check the spec further. + // Individual infinity signatures are NOT okay. Aggregated signatures MAY be infinity if (this.value.isInfinity()) { throw new ZeroSignatureError(); } - // @ts-expect-error Need to hack type to get access to the private `value` - return blst.asyncVerify(message, publicKey.value, this.value); + return blst.asyncVerify(message, PublicKey.convertToBlstPublicKeyArg(publicKey), this.value); } async asyncVerifyAggregate(publicKeys: PublicKeyArg[], message: Uint8Array): Promise { - return blst.asyncFastAggregateVerify( - message, - // @ts-expect-error Need to hack type to get access to the private `value` - publicKeys.map((pk) => pk.value), - this.value - ); + return blst.asyncFastAggregateVerify(message, publicKeys.map(PublicKey.convertToBlstPublicKeyArg), this.value); } async asyncVerifyMultiple(publicKeys: PublicKeyArg[], messages: Uint8Array[]): Promise { - // 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 - const pks = publicKeys.map(Signature.convertToBlstPublicKeyArg); - if (this.value.isInfinity() && publicKeys.length === 1 && publicKeys[0].isInfinity()) { - return true; - } - return blst.asyncAggregateVerify(messages, , this.value); + return this.aggregateVerify(publicKeys, messages, true); } toBytes(format?: PointFormat): Uint8Array { @@ -138,14 +113,36 @@ export class Signature implements ISignature { return new Signature(this.value.multiplyBy(bytes)); } - private aggregateVerify(msgs: Uint8Array[], pks: blst.PublicKey[]): boolean { + private aggregateVerify(publicKeys: PublicKeyArg[], messages: Uint8Array[], runAsync: T): boolean; + private aggregateVerify( + publicKeys: PublicKeyArg[], + messages: Uint8Array[], + runAsync: T + ): Promise; + private aggregateVerify( + publicKeys: PublicKeyArg[], + messages: Uint8Array[], + runAsync: T + ): Promise | boolean { + // TODO (@matthewkeil) The note in verify and the checks in this method + // do not seem to go together. Need to check the spec further. + // 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.value.isInfinity() && pks.length === 1 && pks[0].isInfinity()) { - return true; + if (publicKeys.length === 1) { + // eslint-disable-next-line prettier/prettier + const pk: PublicKey = publicKeys[0] instanceof Uint8Array + ? PublicKey.fromBytes(publicKeys[0]) + : (publicKeys[0] as PublicKey); // need to cast to blst-native key instead of IPublicKey + // @ts-expect-error Need to hack type to get access to the private `value` + if (this.value.isInfinity() && pk.value.isInfinity()) { + return runAsync ? Promise.resolve(true) : true; + } } - return blst.aggregateVerify(msgs, pks, this.value); + return runAsync + ? blst.asyncAggregateVerify(messages, publicKeys.map(PublicKey.convertToBlstPublicKeyArg), this.value) + : blst.aggregateVerify(messages, publicKeys.map(PublicKey.convertToBlstPublicKeyArg), this.value); } } From 7afcd19853c248d840b0dc03734bf771b5727e96 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Sat, 20 Apr 2024 22:51:22 +0700 Subject: [PATCH 03/16] feat: update herumi classes to move herumi specific code into class methods --- src/herumi/publicKey.ts | 28 +++++++++++++++------ src/herumi/signature.ts | 56 ++++++++++++++++++++++++++++------------- 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/src/herumi/publicKey.ts b/src/herumi/publicKey.ts index 847d479..8a33efd 100644 --- a/src/herumi/publicKey.ts +++ b/src/herumi/publicKey.ts @@ -1,7 +1,7 @@ -import type {PublicKeyType} from "bls-eth-wasm"; +import {PublicKeyType} from "bls-eth-wasm"; import {getContext} from "./context.js"; -import {bytesToHex, hexToBytes, isZeroUint8Array} from "../helpers/index.js"; -import {PointFormat, PublicKey as IPublicKey} from "../types.js"; +import {bytesToHex, hexToBytes, isZeroUint8Array, validateBytes} from "../helpers/index.js"; +import {PointFormat, PublicKey as IPublicKey, PublicKeyArg} from "../types.js"; import {EmptyAggregateError, InvalidLengthError, ZeroPublicKeyError} from "../errors.js"; import {PUBLIC_KEY_LENGTH_COMPRESSED, PUBLIC_KEY_LENGTH_UNCOMPRESSED} from "../constants.js"; @@ -35,16 +35,28 @@ export class PublicKey implements IPublicKey { return this.fromBytes(hexToBytes(hex)); } - static aggregate(publicKeys: PublicKey[]): PublicKey { + static aggregate(publicKeys: PublicKeyArg[]): PublicKey { if (publicKeys.length === 0) { throw new EmptyAggregateError(); } + const context = getContext(); + const agg = new context.PublicKey(); + for (const publicKey of publicKeys) { + agg.add(PublicKey.convertToPublicKeyType(publicKey)); + } + return new PublicKey(agg); + } - const agg = new PublicKey(publicKeys[0].value.clone()); - for (const pk of publicKeys.slice(1)) { - agg.value.add(pk.value); + static convertToPublicKeyType(publicKey: PublicKeyArg): PublicKeyType { + let pk: PublicKey; + if (publicKey instanceof Uint8Array) { + validateBytes(publicKey, "publicKey"); + pk = PublicKey.fromBytes(publicKey); + } else { + // need to cast to herumi key instead of IPublicKey + pk = publicKey as PublicKey; } - return agg; + return pk.value; } toBytes(format?: PointFormat): Uint8Array { diff --git a/src/herumi/signature.ts b/src/herumi/signature.ts index 1d24927..a1a1bb2 100644 --- a/src/herumi/signature.ts +++ b/src/herumi/signature.ts @@ -1,8 +1,8 @@ import type {SignatureType} from "bls-eth-wasm"; import {getContext} from "./context.js"; import {PublicKey} from "./publicKey.js"; -import {bytesToHex, concatUint8Arrays, hexToBytes, isZeroUint8Array} from "../helpers/index.js"; -import {SignatureSet, PointFormat, Signature as ISignature, CoordType} from "../types.js"; +import {bytesToHex, concatUint8Arrays, hexToBytes, isZeroUint8Array, validateBytes} from "../helpers/index.js"; +import {SignatureSet, PointFormat, Signature as ISignature, CoordType, PublicKeyArg, SignatureArg} from "../types.js"; import {EmptyAggregateError, InvalidLengthError, InvalidOrderError} from "../errors.js"; import {SIGNATURE_LENGTH_COMPRESSED, SIGNATURE_LENGTH_UNCOMPRESSED} from "../constants.js"; @@ -41,22 +41,24 @@ export class Signature implements ISignature { return this.fromBytes(hexToBytes(hex)); } - static aggregate(signatures: Signature[]): Signature { + static aggregate(signatures: SignatureArg[]): Signature { if (signatures.length === 0) { throw new EmptyAggregateError(); } const context = getContext(); - const signature = new context.Signature(); - signature.aggregate(signatures.map((sig) => sig.value)); - return new Signature(signature); + const agg = new context.Signature(); + agg.aggregate(signatures.map(Signature.convertToSignatureType)); + return new Signature(agg); } static verifyMultipleSignatures(sets: SignatureSet[]): boolean { + if (!sets) throw Error("sets is null or undefined"); + const context = getContext(); return context.multiVerify( - sets.map((s) => s.publicKey.value), - sets.map((s) => s.signature.value), + sets.map((s) => PublicKey.convertToPublicKeyType(s.publicKey)), + sets.map((s) => Signature.convertToSignatureType(s.signature)), sets.map((s) => s.message) ); } @@ -70,20 +72,40 @@ export class Signature implements ISignature { return Signature.verifyMultipleSignatures(sets); } - verify(publicKey: PublicKey, message: Uint8Array): boolean { - return publicKey.value.verify(this.value, message); + static convertToSignatureType(signature: SignatureArg): SignatureType { + let sig: Signature; + if (signature instanceof Uint8Array) { + validateBytes(signature, "signature"); + sig = Signature.fromBytes(signature); + } else { + // need to cast to herumi key instead of ISignature + sig = signature as Signature; + } + return sig.value; } - verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean { - return this.value.fastAggregateVerify( - publicKeys.map((key) => key.value), - message - ); + verify(publicKey: PublicKeyArg, message: Uint8Array): boolean { + validateBytes(message, "message"); + return PublicKey.convertToPublicKeyType(publicKey).verify(this.value, message); } - verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean { + verifyAggregate(publicKeys: PublicKeyArg[], message: Uint8Array): boolean { + validateBytes(message, "message"); + return this.value.fastAggregateVerify(publicKeys.map(PublicKey.convertToPublicKeyType), message); + } + + verifyMultiple(publicKeys: PublicKeyArg[], messages: Uint8Array[]): boolean { + // TODO: (@matthewkeil) this was in the verifyMultiple free function but was moved here for herumi. blst-native + // does this check but throws error instead of returning false. Need to double check spec on which is + // correct handling + if (publicKeys.length === 0 || publicKeys.length != messages.length) { + return false; + } + + validateBytes(messages, "message"); + return this.value.aggregateVerifyNoCheck( - publicKeys.map((key) => key.value), + publicKeys.map(PublicKey.convertToPublicKeyType), concatUint8Arrays(messages) ); } From f228072e1b701c7e16fa5a95c64aca98896dcba5 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Sat, 20 Apr 2024 22:52:44 +0700 Subject: [PATCH 04/16] feat: fix functional.ts to make isomorphic again --- src/functional.ts | 109 ++++++++++++++-------------------------------- 1 file changed, 32 insertions(+), 77 deletions(-) diff --git a/src/functional.ts b/src/functional.ts index ec79913..b9a62a7 100644 --- a/src/functional.ts +++ b/src/functional.ts @@ -1,4 +1,4 @@ -import {IBls, PublicKey, PublicKeyArg, Signature, SignatureArg, SignatureSet} from "./types.js"; +import {IBls, PublicKeyArg, SignatureArg, SignatureSet} from "./types.js"; import {validateBytes} from "./helpers/index.js"; import {NotInitializedError} from "./errors.js"; @@ -25,18 +25,14 @@ export function functionalInterfaceFactory({ * Compines all given signature into one. */ function aggregateSignatures(signatures: SignatureArg[]): Uint8Array { - const agg = Signature.aggregate( - signatures.map((sig) => (sig instanceof Uint8Array ? Signature.fromBytes(sig) : sig)) - ); - return agg.toBytes(); + return Signature.aggregate(signatures).toBytes(); } /** * Combines all given public keys into single one */ function aggregatePublicKeys(publicKeys: PublicKeyArg[]): Uint8Array { - const agg = PublicKey.aggregate(publicKeys.map((pk) => (pk instanceof Uint8Array ? PublicKey.fromBytes(pk) : pk))); - return agg.toBytes(); + return PublicKey.aggregate(publicKeys).toBytes(); } /** @@ -44,25 +40,12 @@ export function functionalInterfaceFactory({ */ function verify(publicKey: PublicKeyArg, message: Uint8Array, signature: SignatureArg): boolean { try { - validateBytes(message, "message"); - - let pk: PublicKey; - if (publicKey instanceof Uint8Array) { - validateBytes(publicKey, "publicKey"); - pk = PublicKey.fromBytes(publicKey); - } else { - pk = publicKey; - } - - let sig: Signature; - if (signature instanceof Uint8Array) { + if (implementation === "herumi" && signature instanceof Uint8Array) { validateBytes(signature, "signature"); - sig = Signature.fromBytes(signature); - } else { - sig = signature; } - return sig.verify(pk, message); + const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature); + return sig.verify(publicKey, message); } catch (e) { if (e instanceof NotInitializedError) throw e; return false; @@ -74,27 +57,12 @@ export function functionalInterfaceFactory({ */ function verifyAggregate(publicKeys: PublicKeyArg[], message: Uint8Array, signature: SignatureArg): boolean { try { - validateBytes(message, "message"); - - const pks: PublicKey[] = []; - for (const pk of publicKeys) { - if (pk instanceof Uint8Array) { - validateBytes(pk, "publicKey"); - pks.push(PublicKey.fromBytes(pk)); - } else { - pks.push(pk); - } - } - - let sig: Signature; - if (signature instanceof Uint8Array) { + if (implementation === "herumi" && signature instanceof Uint8Array) { validateBytes(signature, "signature"); - sig = Signature.fromBytes(signature); - } else { - sig = signature; } - return sig.verifyAggregate(pks, message); + const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature); + return sig.verifyAggregate(publicKeys, message); } catch (e) { if (e instanceof NotInitializedError) throw e; return false; @@ -105,32 +73,18 @@ export function functionalInterfaceFactory({ * Verifies if signature is list of message signed with corresponding public key. */ function verifyMultiple(publicKeys: PublicKeyArg[], messages: Uint8Array[], signature: SignatureArg): boolean { - if (publicKeys.length === 0 || publicKeys.length != messages.length) { - return false; - } - + // TODO: (@matthewkeil) blst-ts has this check but throws an error instead of returning false. Moving to + // the herumi and commenting here for now. Will double check spec on what is appropriate. + // if (publicKeys.length === 0 || publicKeys.length != messages.length) { + // return false; + // } try { - validateBytes(messages, "message"); - - const pks: PublicKey[] = []; - for (const pk of publicKeys) { - if (pk instanceof Uint8Array) { - validateBytes(pk, "publicKey"); - pks.push(PublicKey.fromBytes(pk)); - } else { - pks.push(pk); - } - } - - let sig: Signature; - if (signature instanceof Uint8Array) { + if (implementation === "herumi" && signature instanceof Uint8Array) { validateBytes(signature, "signature"); - sig = Signature.fromBytes(signature); - } else { - sig = signature; } - return sig.verifyMultiple(pks, messages); + const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature); + return sig.verifyMultiple(publicKeys, messages); } catch (e) { if (e instanceof NotInitializedError) throw e; return false; @@ -148,16 +102,8 @@ export function functionalInterfaceFactory({ * https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407 */ function verifyMultipleSignatures(sets: SignatureSet[]): boolean { - if (!sets) throw Error("sets is null or undefined"); - try { - return Signature.verifyMultipleSignatures( - sets.map((s) => ({ - publicKey: s.publicKey instanceof Uint8Array ? PublicKey.fromBytes(s.publicKey) : s.publicKey, - message: s.message, - signature: s.signature instanceof Uint8Array ? Signature.fromBytes(s.signature) : s.signature, - })) - ); + return Signature.verifyMultipleSignatures(sets); } catch (e) { if (e instanceof NotInitializedError) throw e; return false; @@ -170,9 +116,11 @@ export function functionalInterfaceFactory({ async function asyncVerify(publicKey: PublicKeyArg, message: Uint8Array, signature: SignatureArg): Promise { if (implementation === "herumi") return verify(publicKey, message, signature); try { + // must be in try/catch in case sig is invalid const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature); return sig.asyncVerify(publicKey, message); - } catch { + } catch (e) { + if (e instanceof NotInitializedError) throw e; return false; } } @@ -189,7 +137,8 @@ export function functionalInterfaceFactory({ try { const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature); return sig.asyncVerifyAggregate(publicKeys, message); - } catch { + } catch (e) { + if (e instanceof NotInitializedError) throw e; return false; } } @@ -206,7 +155,8 @@ export function functionalInterfaceFactory({ try { const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature); return sig.asyncVerifyMultiple(publicKeys, messages); - } catch { + } catch (e) { + if (e instanceof NotInitializedError) throw e; return false; } } @@ -222,8 +172,13 @@ export function functionalInterfaceFactory({ * https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407 */ async function asyncVerifyMultipleSignatures(sets: SignatureSet[]): Promise { - if (implementation === "herumi") return verifyMultipleSignatures(sets); - return Signature.asyncVerifyMultipleSignatures(sets); + try { + if (implementation === "herumi") return Signature.verifyMultipleSignatures(sets); + return Signature.asyncVerifyMultipleSignatures(sets); + } catch (e) { + if (e instanceof NotInitializedError) throw e; + return false; + } } /** From 55681f0aed533e2aefa16f1d63f14325e3e384f2 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Sat, 20 Apr 2024 22:53:40 +0700 Subject: [PATCH 05/16] fix: remove webpack config fixes triggered by unseen blst import --- webpack.config.cjs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/webpack.config.cjs b/webpack.config.cjs index c1fdaf8..6e549a2 100644 --- a/webpack.config.cjs +++ b/webpack.config.cjs @@ -1,4 +1,3 @@ -const path = require('path'); const ResolveTypeScriptPlugin = require("resolve-typescript-plugin"); module.exports = { @@ -10,10 +9,6 @@ module.exports = { module: { rules: [ {test: /\.(ts)$/, use: {loader: "ts-loader", options: {transpileOnly: true}}}, - { - test: /@chainsafe\/blst/, - use: 'null-loader', - }, ], }, optimization: { @@ -28,12 +23,12 @@ module.exports = { ], alias: { "crypto": "crypto-browserify", - './blst-native/index.js': path.resolve(__dirname, './src/herumi/index.js') }, fallback: { fs: false, path: false, stream: false, + child_process: false, }, }, experiments: { From 321100d671d0fac39d1567127b5c051eb768ddcf Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Sat, 20 Apr 2024 23:08:34 +0700 Subject: [PATCH 06/16] feat: widen types and add comments for methods to highlight throwing behavior --- src/types.ts | 100 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 7 deletions(-) diff --git a/src/types.ts b/src/types.ts index a665a97..d0a499b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -35,17 +35,59 @@ export interface IBls { PublicKey: typeof PublicKey; Signature: typeof Signature; + secretKeyToPublicKey(secretKey: Uint8Array): Uint8Array; sign(secretKey: Uint8Array, message: Uint8Array): Uint8Array; aggregatePublicKeys(publicKeys: PublicKeyArg[]): Uint8Array; aggregateSignatures(signatures: SignatureArg[]): Uint8Array; + /** + * Will synchronously verify a signature. This function catches invalid input and return false for + * bad keys or signatures. Use the `Signature.verify` method if throwing is desired. + */ verify(publicKey: PublicKeyArg, message: Uint8Array, signature: SignatureArg): boolean; + /** + * Will synchronously verify a signature over a message by multiple aggregated keys. This function + * catches invalid input and return false for bad keys or signatures. Use the + * `Signature.verifyAggregate` if throwing is desired. + */ verifyAggregate(publicKeys: PublicKeyArg[], message: Uint8Array, signature: SignatureArg): boolean; + /** + * Will synchronously verify an aggregated signature over a number of messages each signed by a + * different key. This function catches invalid input and return false for bad keys or signatures. + * Use the `Signature.verifyAggregate` if throwing is desired. + * + * Note: the number of keys and messages must match. + */ verifyMultiple(publicKeys: PublicKeyArg[], messages: Uint8Array[], signature: SignatureArg): boolean; + /** + * Will synchronously verify a group of SignatureSets where each contains a signature signed for + * a message by a public key. This function catches invalid input and return false for bad keys or + * signatures. Use the `Signature.verifyMultipleSignatures` if throwing is desired. + */ verifyMultipleSignatures(sets: SignatureSet[]): boolean; - secretKeyToPublicKey(secretKey: Uint8Array): Uint8Array; + /** + * Will asynchronously verify a signature. This function catches invalid input and return false for + * bad keys or signatures. Use the `Signature.asyncVerify` method if throwing is desired. + */ asyncVerify(publicKey: PublicKeyArg, message: Uint8Array, signature: SignatureArg): Promise; + /** + * Will asynchronously verify a signature over a message by multiple aggregated keys. This function + * catches invalid input and return false for bad keys or signatures. Use the + * `Signature.asyncVerifyAggregate` if throwing is desired. + */ asyncVerifyAggregate(publicKeys: PublicKeyArg[], message: Uint8Array, signature: SignatureArg): Promise; + /** + * Will asynchronously verify an aggregated signature over a number of messages each signed by a + * different key. This function catches invalid input and return false for bad keys or signatures. + * Use the `Signature.asyncVerifyAggregate` if throwing is desired. + * + * Note: the number of keys and messages must match. + */ asyncVerifyMultiple(publicKeys: PublicKeyArg[], messages: Uint8Array[], signature: SignatureArg): Promise; + /** + * Will asynchronously verify a group of SignatureSets where each contains a signature signed for + * a message by a public key. This function catches invalid input and return false for bad keys or + * signatures. Use the Signature.asyncVerifyMultipleSignatures if throwing is desired. + */ asyncVerifyMultipleSignatures(sets: SignatureSet[]): Promise; } @@ -67,7 +109,7 @@ export declare class PublicKey { /** @param type Only for impl `blst-native`. Defaults to `CoordType.jacobian` */ static fromBytes(bytes: Uint8Array, type?: CoordType, validate?: boolean): PublicKey; static fromHex(hex: string): PublicKey; - static aggregate(publicKeys: PublicKey[]): PublicKey; + static aggregate(publicKeys: PublicKeyArg[]): PublicKey; /** @param format Defaults to `PointFormat.compressed` */ toBytes(format?: PointFormat): Uint8Array; toHex(format?: PointFormat): string; @@ -81,16 +123,60 @@ export declare class Signature { * @param validate When using `herumi` implementation, signature validation is always on regardless of this flag. */ static fromBytes(bytes: Uint8Array, type?: CoordType, validate?: boolean): Signature; static fromHex(hex: string): Signature; - static aggregate(signatures: Signature[]): Signature; + static aggregate(signatures: SignatureArg[]): Signature; + /** + * Will synchronously verify a group of SignatureSets where each contains a signature signed for + * a message by a public key. This version of the function will potentially throw errors for + * invalid input. Use the free function `verifyMultipleSignatures` if throwing is not desired. + */ static verifyMultipleSignatures(sets: SignatureSet[]): boolean; + /** + * Will asynchronously verify a group of SignatureSets where each contains a signature signed for + * a message by a public key. This version of the function will potentially throw errors for + * invalid input. Use the free function `verifyMultipleSignatures` if throwing is not desired. + */ static asyncVerifyMultipleSignatures(sets: SignatureSet[]): Promise; - verify(publicKey: PublicKey, message: Uint8Array): boolean; - verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean; - verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean; + /** + * Will synchronously verify a signature. This version of the function will potentially throw + * errors for invalid input. Use the free function `verify` if throwing is not desired. + */ + verify(publicKey: PublicKeyArg, message: Uint8Array): boolean; + /** + * Will synchronously verify a signature over a message by multiple aggregated keys. This + * version of the function will potentially throw errors for invalid input. Use the free function + * `verifyAggregate` if throwing is not desired. + */ + verifyAggregate(publicKeys: PublicKeyArg[], message: Uint8Array): boolean; + /** + * Will synchronously verify an aggregated signature over a number of messages each signed by a + * different key. This version of the function will potentially throw errors for invalid input. + * Use the free function `verifyMultiple` if throwing is not desired. + * + * Note: the number of keys and messages must match. + */ + verifyMultiple(publicKeys: PublicKeyArg[], messages: Uint8Array[]): boolean; + /** + * Will asynchronously verify a signature. This version of the function will potentially throw + * errors for invalid input. Use the free function `asyncVerify` if throwing is not desired. + */ asyncVerify(publicKey: PublicKeyArg, message: Uint8Array): Promise; + /** + * Will asynchronously verify a signature over a message by multiple aggregated keys. This + * version of the function will potentially throw errors for invalid input. Use the free function + * `asyncVerifyAggregate` if throwing is not desired. + */ asyncVerifyAggregate(publicKeys: PublicKeyArg[], message: Uint8Array): Promise; + /** + * Will asynchronously verify an aggregated signature over a number of messages each signed by a + * different key. This version of the function will potentially throw errors for invalid input. + * Use the free function `asyncVerifyMultiple` if throwing is not desired. + * + * Note: the number of keys and messages must match. + */ asyncVerifyMultiple(publicKeys: PublicKeyArg[], messages: Uint8Array[]): Promise; - /** @param format Defaults to `PointFormat.compressed` */ + /** + * @default format - `PointFormat.compressed` + */ toBytes(format?: PointFormat): Uint8Array; toHex(format?: PointFormat): string; multiplyBy(bytes: Uint8Array): Signature; From 7f336f48ba645445cf717b1b3a56cd27857f385b Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Sat, 20 Apr 2024 23:26:13 +0700 Subject: [PATCH 07/16] fix: typo --- src/herumi/signature.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/herumi/signature.ts b/src/herumi/signature.ts index a1a1bb2..e6143cf 100644 --- a/src/herumi/signature.ts +++ b/src/herumi/signature.ts @@ -78,7 +78,7 @@ export class Signature implements ISignature { validateBytes(signature, "signature"); sig = Signature.fromBytes(signature); } else { - // need to cast to herumi key instead of ISignature + // need to cast to herumi sig instead of ISignature sig = signature as Signature; } return sig.value; From 977f708575a3aa98fb0aecabd9b95a61f31965c8 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Sat, 20 Apr 2024 23:31:50 +0700 Subject: [PATCH 08/16] chore: bump minor version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a087457..a3371ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@chainsafe/bls", - "version": "8.0.0", + "version": "8.1.0", "description": "Implementation of bls signature verification for ethereum 2.0", "engines": { "node": ">=18" From c7b268babd9f14e434fd99a686aa4f3f9ac15132 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Mon, 22 Apr 2024 14:10:51 +0700 Subject: [PATCH 09/16] docs: refactor ternary and add comment about casting --- src/blst-native/publicKey.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/blst-native/publicKey.ts b/src/blst-native/publicKey.ts index d95c031..b47545a 100644 --- a/src/blst-native/publicKey.ts +++ b/src/blst-native/publicKey.ts @@ -28,7 +28,8 @@ export class PublicKey implements IPublicKey { } static convertToBlstPublicKeyArg(publicKey: PublicKeyArg): blst.PublicKeyArg { - return publicKey instanceof PublicKey ? publicKey.value : (publicKey as Uint8Array); + // need to cast to blst-native key instead of IPublicKey + return publicKey instanceof Uint8Array ? publicKey : (publicKey as PublicKey).value; } /** From 6dd7acb44870f7a8ae8c0dcbb0bac319301b53e2 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Mon, 22 Apr 2024 14:11:27 +0700 Subject: [PATCH 10/16] feat: refactor to get array obj once --- src/blst-native/signature.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/blst-native/signature.ts b/src/blst-native/signature.ts index b4aa2b7..899843b 100644 --- a/src/blst-native/signature.ts +++ b/src/blst-native/signature.ts @@ -131,10 +131,11 @@ export class Signature implements ISignature { // This has the effect of always declaring that this sig/publicKey combination is valid. // for Eth2.0 specs tests if (publicKeys.length === 1) { + const publicKey = publicKeys[0]; // eslint-disable-next-line prettier/prettier - const pk: PublicKey = publicKeys[0] instanceof Uint8Array - ? PublicKey.fromBytes(publicKeys[0]) - : (publicKeys[0] as PublicKey); // need to cast to blst-native key instead of IPublicKey + const pk: PublicKey = publicKey instanceof Uint8Array + ? PublicKey.fromBytes(publicKey) + : (publicKey as PublicKey); // need to cast to blst-native key instead of IPublicKey // @ts-expect-error Need to hack type to get access to the private `value` if (this.value.isInfinity() && pk.value.isInfinity()) { return runAsync ? Promise.resolve(true) : true; From a8bc3d8947e753be137bded5f62d0f2c0573c69e Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Mon, 22 Apr 2024 14:13:52 +0700 Subject: [PATCH 11/16] docs: add comment about validateBytes --- src/functional.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/functional.ts b/src/functional.ts index b9a62a7..18b2d55 100644 --- a/src/functional.ts +++ b/src/functional.ts @@ -2,6 +2,20 @@ import {IBls, PublicKeyArg, SignatureArg, SignatureSet} from "./types.js"; import {validateBytes} from "./helpers/index.js"; import {NotInitializedError} from "./errors.js"; +/** + * NOTE: + * + * This conditional block below is present in most functions and is Herumi specific to prevent + * downstream issues in the WASM code. It is not necessary to validateBytes for blst-native + * code. The check for blst-native is implemented in the native layer. All other byte checks + * for the herumi code paths are found in the herumi classes for performance reasons as they + * are byte-wise, only are required for the WASM and will unnecessarily slow down the + * blst-native side. + */ +// if (implementation === "herumi" && signature instanceof Uint8Array) { +// validateBytes(signature, "signature"); +// } + // Returned type is enforced at each implementation's index // eslint-disable-next-line max-len // eslint-disable-next-line @typescript-eslint/explicit-function-return-type,@typescript-eslint/explicit-module-boundary-types From c769c9a1e7b98891fb85d54e2a48a80ea806c139 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Mon, 22 Apr 2024 14:15:44 +0700 Subject: [PATCH 12/16] refactor: remove log statements in async functions --- src/herumi/signature.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/herumi/signature.ts b/src/herumi/signature.ts index e6143cf..7af834c 100644 --- a/src/herumi/signature.ts +++ b/src/herumi/signature.ts @@ -64,11 +64,6 @@ export class Signature implements ISignature { } static async asyncVerifyMultipleSignatures(sets: SignatureSet[]): Promise { - // eslint-disable-next-line no-console - console.log( - "asyncVerifyMultipleSignatures is not implemented by bls-eth-wasm.\n" + - "Please use the synchronous Signature.verifyMultipleSignatures instead" - ); return Signature.verifyMultipleSignatures(sets); } @@ -111,26 +106,14 @@ export class Signature implements ISignature { } async asyncVerify(publicKey: PublicKey, message: Uint8Array): Promise { - // eslint-disable-next-line no-console - console.log("asyncVerify is not implemented by bls-eth-wasm.\nPlease use the synchronous Signature.verify instead"); return this.verify(publicKey, message); } async asyncVerifyAggregate(publicKeys: PublicKey[], message: Uint8Array): Promise { - // eslint-disable-next-line no-console - console.log( - "asyncVerifyAggregate is not implemented by bls-eth-wasm.\n" + - "Please use the synchronous Signature.verifyAggregate instead" - ); return this.verifyAggregate(publicKeys, message); } async asyncVerifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): Promise { - // eslint-disable-next-line no-console - console.log( - "asyncVerifyMultiple is not implemented by bls-eth-wasm.\n" + - "Please use the synchronous Signature.verifyMultiple instead" - ); return this.verifyMultiple(publicKeys, messages); } From 07ef564d0f382eb1459257031712341eb9f38706 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Mon, 22 Apr 2024 14:24:49 +0700 Subject: [PATCH 13/16] docs: add comment about async herumi to readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 02b2bcb..0806d69 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ yarn add @chainsafe/bls @chainsafe/blst By default, native bindings will be used if in NodeJS and they are installed. A WASM implementation ("herumi") is used as a fallback in case any error occurs. +The `blst-native` implementation offers a multi-threaded approach to verification and utilizes the libuv worker pool to verification. It is a more performant options synchronously and FAR better when utilized asynchronously. All verification functions provide sync and async versions. Both the `blst-native` and `herumi` implementations offer verification functions with `async` prefixes as free functions and also on their respective classes. This was done to preserve the isomorphic architecture of this library. In reality however, only the `blst-native` bindings have the ability to implement a promise based approach. In the `herumi` version the async version just proxies to the sync version under the hood. + ```ts import bls from "@chainsafe/bls"; @@ -106,7 +108,7 @@ Results are in `ops/sec (x times slower)`, where `x times slower` = times slower \* `blst` and `herumi` performed 100 runs each, `noble` 10 runs. -Results from CI run https://github.com/ChainSafe/bls/runs/1513710175?check_suite_focus=true#step:12:13 +Results from CI run ## Spec versioning From 10f19e2c95b2bdc730463888d96f11b072d165c0 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Mon, 22 Apr 2024 14:27:42 +0700 Subject: [PATCH 14/16] feat: add await to all try/catch async functions --- src/functional.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/functional.ts b/src/functional.ts index 18b2d55..bb4383b 100644 --- a/src/functional.ts +++ b/src/functional.ts @@ -132,7 +132,7 @@ export function functionalInterfaceFactory({ try { // must be in try/catch in case sig is invalid const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature); - return sig.asyncVerify(publicKey, message); + return await sig.asyncVerify(publicKey, message); } catch (e) { if (e instanceof NotInitializedError) throw e; return false; @@ -150,7 +150,7 @@ export function functionalInterfaceFactory({ if (implementation === "herumi") return verifyAggregate(publicKeys, message, signature); try { const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature); - return sig.asyncVerifyAggregate(publicKeys, message); + return await sig.asyncVerifyAggregate(publicKeys, message); } catch (e) { if (e instanceof NotInitializedError) throw e; return false; @@ -168,7 +168,7 @@ export function functionalInterfaceFactory({ if (implementation === "herumi") return verifyMultiple(publicKeys, messages, signature); try { const sig = signature instanceof Signature ? signature : Signature.fromBytes(signature); - return sig.asyncVerifyMultiple(publicKeys, messages); + return await sig.asyncVerifyMultiple(publicKeys, messages); } catch (e) { if (e instanceof NotInitializedError) throw e; return false; @@ -188,7 +188,7 @@ export function functionalInterfaceFactory({ async function asyncVerifyMultipleSignatures(sets: SignatureSet[]): Promise { try { if (implementation === "herumi") return Signature.verifyMultipleSignatures(sets); - return Signature.asyncVerifyMultipleSignatures(sets); + return await Signature.asyncVerifyMultipleSignatures(sets); } catch (e) { if (e instanceof NotInitializedError) throw e; return false; From 74b5dd74cb6ca05537d1a970a2eb06cc15c1f542 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Mon, 22 Apr 2024 14:30:10 +0700 Subject: [PATCH 15/16] chore: lint --- src/functional.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/functional.ts b/src/functional.ts index bb4383b..e43afae 100644 --- a/src/functional.ts +++ b/src/functional.ts @@ -9,7 +9,7 @@ import {NotInitializedError} from "./errors.js"; * downstream issues in the WASM code. It is not necessary to validateBytes for blst-native * code. The check for blst-native is implemented in the native layer. All other byte checks * for the herumi code paths are found in the herumi classes for performance reasons as they - * are byte-wise, only are required for the WASM and will unnecessarily slow down the + * are byte-wise, only are required for the WASM and will unnecessarily slow down the * blst-native side. */ // if (implementation === "herumi" && signature instanceof Uint8Array) { From 2c78014e2e9cebe5c09ac974cb0f9727ac9d3bd3 Mon Sep 17 00:00:00 2001 From: matthewkeil Date: Mon, 22 Apr 2024 14:48:56 +0700 Subject: [PATCH 16/16] docs: add casting comment to Signature --- src/blst-native/signature.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/blst-native/signature.ts b/src/blst-native/signature.ts index 899843b..f0fa7a8 100644 --- a/src/blst-native/signature.ts +++ b/src/blst-native/signature.ts @@ -49,7 +49,8 @@ export class Signature implements ISignature { } static convertToBlstSignatureArg(signature: SignatureArg): blst.SignatureArg { - return signature instanceof Signature ? signature.value : (signature as Uint8Array); + // Need to cast to blst-native Signature instead of ISignature + return signature instanceof Uint8Array ? signature : (signature as Signature).value; } /**