Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: async implementation #179

Merged
merged 16 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
10 changes: 7 additions & 3 deletions src/blst-native/publicKey.ts
Original file line number Diff line number Diff line change
@@ -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) {}
Expand All @@ -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);
twoeths marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Implemented for SecretKey to be able to call .toPublicKey()
*/
Expand Down
102 changes: 76 additions & 26 deletions src/blst-native/signature.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -19,54 +19,82 @@ 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 asyncVerifyMultipleSignatures(sets: SignatureSet[]): Promise<boolean> {
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);
}

/**
* Implemented for SecretKey to be able to call .sign()
*/
private static friendBuild(sig: blst.Signature): Signature {
return new Signature(sig);
}

verify(publicKey: PublicKey, message: Uint8Array): boolean {
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.

// Individual infinity signatures are NOT okay. Aggregated signatures MAY be infinity
if (this.value.isInfinity()) {
throw new ZeroSignatureError();
}
return blst.verify(message, PublicKey.convertToBlstPublicKeyArg(publicKey), this.value);
}

// @ts-expect-error Need to hack type to get access to the private `value`
return blst.verify(message, publicKey.value, this.value);
verifyAggregate(publicKeys: PublicKeyArg[], message: Uint8Array): boolean {
return blst.fastAggregateVerify(message, publicKeys.map(PublicKey.convertToBlstPublicKeyArg), 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
);
verifyMultiple(publicKeys: PublicKeyArg[], messages: Uint8Array[]): boolean {
return this.aggregateVerify(publicKeys, messages, false);
}

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)
);
async asyncVerify(publicKey: PublicKeyArg, message: Uint8Array): Promise<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.

// Individual infinity signatures are NOT okay. Aggregated signatures MAY be infinity
if (this.value.isInfinity()) {
throw new ZeroSignatureError();
}
return blst.asyncVerify(message, PublicKey.convertToBlstPublicKeyArg(publicKey), this.value);
}

async asyncVerifyAggregate(publicKeys: PublicKeyArg[], message: Uint8Array): Promise<boolean> {
return blst.asyncFastAggregateVerify(message, publicKeys.map(PublicKey.convertToBlstPublicKeyArg), this.value);
}

async asyncVerifyMultiple(publicKeys: PublicKeyArg[], messages: Uint8Array[]): Promise<boolean> {
return this.aggregateVerify(publicKeys, messages, true);
}

toBytes(format?: PointFormat): Uint8Array {
Expand All @@ -85,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<T extends false>(publicKeys: PublicKeyArg[], messages: Uint8Array[], runAsync: T): boolean;
private aggregateVerify<T extends true>(
publicKeys: PublicKeyArg[],
messages: Uint8Array[],
runAsync: T
): Promise<boolean>;
private aggregateVerify<T extends boolean>(
publicKeys: PublicKeyArg[],
messages: Uint8Array[],
runAsync: T
): Promise<boolean> | 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
matthewkeil marked this conversation as resolved.
Show resolved Hide resolved
? 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);
}
}
Loading
Loading