Merge pull request #85 from ChainSafe/dapplion/multi-threading

Enable multi-threading for all implementations
This commit is contained in:
Lion - dapplion 2021-04-05 20:54:20 +02:00 committed by GitHub
commit 0f895b4795
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 453 additions and 163 deletions

View File

@ -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,
});

View File

@ -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;

View File

@ -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==

View File

@ -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"
}
}

View File

@ -15,6 +15,7 @@ export function destroy(): void {
}
export const bls: IBls = {
implementation: "blst-native",
SecretKey,
PublicKey,
Signature,

View File

@ -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));
}
}

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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`);
}
}

View File

@ -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;

View File

@ -9,6 +9,7 @@ export * from "../constants";
export {SecretKey, PublicKey, Signature, init, destroy};
export const bls: IBls = {
implementation: "herumi",
SecretKey,
PublicKey,
Signature,

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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,
}

View File

@ -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);
});
});
}

View File

@ -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);
});
}
});

View File

@ -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),
}))
)
);
}
}

View File

@ -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);
});
});
});
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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);
});

View File

@ -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"