Skip to content

Commit 3b4db7d

Browse files
authored
Merge pull request #179 from ChainSafe/mkeil/fix-async-implementation
fix: async implementation
2 parents a5aa94c + 2c78014 commit 3b4db7d

File tree

9 files changed

+314
-177
lines changed

9 files changed

+314
-177
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ yarn add @chainsafe/bls @chainsafe/blst
2121

2222
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.
2323

24+
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.
25+
2426
```ts
2527
import bls from "@chainsafe/bls";
2628

@@ -106,7 +108,7 @@ Results are in `ops/sec (x times slower)`, where `x times slower` = times slower
106108

107109
\* `blst` and `herumi` performed 100 runs each, `noble` 10 runs.
108110

109-
Results from CI run https://github.com/ChainSafe/bls/runs/1513710175?check_suite_focus=true#step:12:13
111+
Results from CI run <https://github.com/ChainSafe/bls/runs/1513710175?check_suite_focus=true#step:12:13>
110112

111113
## Spec versioning
112114

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@chainsafe/bls",
3-
"version": "8.0.0",
3+
"version": "8.1.0",
44
"description": "Implementation of bls signature verification for ethereum 2.0",
55
"engines": {
66
"node": ">=18"

src/blst-native/publicKey.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import blst from "@chainsafe/blst";
22
import {EmptyAggregateError} from "../errors.js";
33
import {bytesToHex, hexToBytes} from "../helpers/index.js";
4-
import {CoordType, PointFormat, PublicKey as IPublicKey} from "../types.js";
4+
import {CoordType, PointFormat, PublicKey as IPublicKey, PublicKeyArg} from "../types.js";
55

66
export class PublicKey implements IPublicKey {
77
private constructor(private readonly value: blst.PublicKey) {}
@@ -18,15 +18,20 @@ export class PublicKey implements IPublicKey {
1818
return this.fromBytes(hexToBytes(hex));
1919
}
2020

21-
static aggregate(publicKeys: PublicKey[]): PublicKey {
21+
static aggregate(publicKeys: PublicKeyArg[]): PublicKey {
2222
if (publicKeys.length === 0) {
2323
throw new EmptyAggregateError();
2424
}
2525

26-
const pk = blst.aggregatePublicKeys(publicKeys.map(({value}) => value));
26+
const pk = blst.aggregatePublicKeys(publicKeys.map(PublicKey.convertToBlstPublicKeyArg));
2727
return new PublicKey(pk);
2828
}
2929

30+
static convertToBlstPublicKeyArg(publicKey: PublicKeyArg): blst.PublicKeyArg {
31+
// need to cast to blst-native key instead of IPublicKey
32+
return publicKey instanceof Uint8Array ? publicKey : (publicKey as PublicKey).value;
33+
}
34+
3035
/**
3136
* Implemented for SecretKey to be able to call .toPublicKey()
3237
*/

src/blst-native/signature.ts

+78-26
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import blst from "@chainsafe/blst";
22
import {bytesToHex, hexToBytes} from "../helpers/index.js";
3-
import {CoordType, PointFormat, Signature as ISignature} from "../types.js";
3+
import {SignatureSet, CoordType, PointFormat, Signature as ISignature, PublicKeyArg, SignatureArg} from "../types.js";
44
import {PublicKey} from "./publicKey.js";
55
import {EmptyAggregateError, ZeroSignatureError} from "../errors.js";
66

@@ -19,54 +19,83 @@ export class Signature implements ISignature {
1919
return this.fromBytes(hexToBytes(hex));
2020
}
2121

22-
static aggregate(signatures: Signature[]): Signature {
22+
static aggregate(signatures: SignatureArg[]): Signature {
2323
if (signatures.length === 0) {
2424
throw new EmptyAggregateError();
2525
}
2626

27-
const agg = blst.aggregateSignatures(signatures.map(({value}) => value));
27+
const agg = blst.aggregateSignatures(signatures.map(Signature.convertToBlstSignatureArg));
2828
return new Signature(agg);
2929
}
3030

31-
static verifyMultipleSignatures(sets: {publicKey: PublicKey; message: Uint8Array; signature: Signature}[]): boolean {
31+
static verifyMultipleSignatures(sets: SignatureSet[]): boolean {
3232
return blst.verifyMultipleAggregateSignatures(
33-
// @ts-expect-error Need to hack type to get access to the private `value`
34-
sets.map((s) => ({message: s.message, publicKey: s.publicKey.value, signature: s.signature.value}))
33+
sets.map((set) => ({
34+
message: set.message,
35+
publicKey: PublicKey.convertToBlstPublicKeyArg(set.publicKey),
36+
signature: Signature.convertToBlstSignatureArg(set.signature),
37+
}))
38+
);
39+
}
40+
41+
static asyncVerifyMultipleSignatures(sets: SignatureSet[]): Promise<boolean> {
42+
return blst.asyncVerifyMultipleAggregateSignatures(
43+
sets.map((set) => ({
44+
message: set.message,
45+
publicKey: PublicKey.convertToBlstPublicKeyArg(set.publicKey),
46+
signature: Signature.convertToBlstSignatureArg(set.signature),
47+
}))
3548
);
3649
}
3750

51+
static convertToBlstSignatureArg(signature: SignatureArg): blst.SignatureArg {
52+
// Need to cast to blst-native Signature instead of ISignature
53+
return signature instanceof Uint8Array ? signature : (signature as Signature).value;
54+
}
55+
3856
/**
3957
* Implemented for SecretKey to be able to call .sign()
4058
*/
4159
private static friendBuild(sig: blst.Signature): Signature {
4260
return new Signature(sig);
4361
}
4462

45-
verify(publicKey: PublicKey, message: Uint8Array): boolean {
63+
verify(publicKey: PublicKeyArg, message: Uint8Array): boolean {
64+
// TODO (@matthewkeil) The note in aggregateVerify and the checks in this method
65+
// do not seem to go together. Need to check the spec further.
66+
4667
// Individual infinity signatures are NOT okay. Aggregated signatures MAY be infinity
4768
if (this.value.isInfinity()) {
4869
throw new ZeroSignatureError();
4970
}
71+
return blst.verify(message, PublicKey.convertToBlstPublicKeyArg(publicKey), this.value);
72+
}
5073

51-
// @ts-expect-error Need to hack type to get access to the private `value`
52-
return blst.verify(message, publicKey.value, this.value);
74+
verifyAggregate(publicKeys: PublicKeyArg[], message: Uint8Array): boolean {
75+
return blst.fastAggregateVerify(message, publicKeys.map(PublicKey.convertToBlstPublicKeyArg), this.value);
5376
}
5477

55-
verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean {
56-
return blst.fastAggregateVerify(
57-
message,
58-
// @ts-expect-error Need to hack type to get access to the private `value`
59-
publicKeys.map((pk) => pk.value),
60-
this.value
61-
);
78+
verifyMultiple(publicKeys: PublicKeyArg[], messages: Uint8Array[]): boolean {
79+
return this.aggregateVerify(publicKeys, messages, false);
6280
}
6381

64-
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean {
65-
return this.aggregateVerify(
66-
messages,
67-
// @ts-expect-error Need to hack type to get access to the private `value`
68-
publicKeys.map((pk) => pk.value)
69-
);
82+
async asyncVerify(publicKey: PublicKeyArg, message: Uint8Array): Promise<boolean> {
83+
// TODO (@matthewkeil) The note in aggregateVerify and the checks in this method
84+
// do not seem to go together. Need to check the spec further.
85+
86+
// Individual infinity signatures are NOT okay. Aggregated signatures MAY be infinity
87+
if (this.value.isInfinity()) {
88+
throw new ZeroSignatureError();
89+
}
90+
return blst.asyncVerify(message, PublicKey.convertToBlstPublicKeyArg(publicKey), this.value);
91+
}
92+
93+
async asyncVerifyAggregate(publicKeys: PublicKeyArg[], message: Uint8Array): Promise<boolean> {
94+
return blst.asyncFastAggregateVerify(message, publicKeys.map(PublicKey.convertToBlstPublicKeyArg), this.value);
95+
}
96+
97+
async asyncVerifyMultiple(publicKeys: PublicKeyArg[], messages: Uint8Array[]): Promise<boolean> {
98+
return this.aggregateVerify(publicKeys, messages, true);
7099
}
71100

72101
toBytes(format?: PointFormat): Uint8Array {
@@ -85,14 +114,37 @@ export class Signature implements ISignature {
85114
return new Signature(this.value.multiplyBy(bytes));
86115
}
87116

88-
private aggregateVerify(msgs: Uint8Array[], pks: blst.PublicKey[]): boolean {
117+
private aggregateVerify<T extends false>(publicKeys: PublicKeyArg[], messages: Uint8Array[], runAsync: T): boolean;
118+
private aggregateVerify<T extends true>(
119+
publicKeys: PublicKeyArg[],
120+
messages: Uint8Array[],
121+
runAsync: T
122+
): Promise<boolean>;
123+
private aggregateVerify<T extends boolean>(
124+
publicKeys: PublicKeyArg[],
125+
messages: Uint8Array[],
126+
runAsync: T
127+
): Promise<boolean> | boolean {
128+
// TODO (@matthewkeil) The note in verify and the checks in this method
129+
// do not seem to go together. Need to check the spec further.
130+
89131
// If this set is simply an infinity signature and infinity publicKey then skip verification.
90132
// This has the effect of always declaring that this sig/publicKey combination is valid.
91133
// for Eth2.0 specs tests
92-
if (this.value.isInfinity() && pks.length === 1 && pks[0].isInfinity()) {
93-
return true;
134+
if (publicKeys.length === 1) {
135+
const publicKey = publicKeys[0];
136+
// eslint-disable-next-line prettier/prettier
137+
const pk: PublicKey = publicKey instanceof Uint8Array
138+
? PublicKey.fromBytes(publicKey)
139+
: (publicKey as PublicKey); // need to cast to blst-native key instead of IPublicKey
140+
// @ts-expect-error Need to hack type to get access to the private `value`
141+
if (this.value.isInfinity() && pk.value.isInfinity()) {
142+
return runAsync ? Promise.resolve(true) : true;
143+
}
94144
}
95145

96-
return blst.aggregateVerify(msgs, pks, this.value);
146+
return runAsync
147+
? blst.asyncAggregateVerify(messages, publicKeys.map(PublicKey.convertToBlstPublicKeyArg), this.value)
148+
: blst.aggregateVerify(messages, publicKeys.map(PublicKey.convertToBlstPublicKeyArg), this.value);
97149
}
98150
}

0 commit comments

Comments
 (0)