diff --git a/rebuild/lib/index.d.ts b/rebuild/lib/index.d.ts index a37356a0..92848399 100644 --- a/rebuild/lib/index.d.ts +++ b/rebuild/lib/index.d.ts @@ -83,6 +83,7 @@ export class PublicKey implements Serializable { toHex(compress?: boolean): string; keyValidate(): void; isInfinity(): boolean; + multiplyBy(randomBytes: BlstBuffer): PublicKey; } export class Signature implements Serializable { @@ -92,6 +93,7 @@ export class Signature implements Serializable { toHex(compress?: boolean): string; sigValidate(): void; isInfinity(): boolean; + multiplyBy(randomBytes: BlstBuffer): Signature; } /** diff --git a/rebuild/src/public_key.cc b/rebuild/src/public_key.cc index 6828589f..247b66c0 100644 --- a/rebuild/src/public_key.cc +++ b/rebuild/src/public_key.cc @@ -21,6 +21,10 @@ void PublicKey::Init( "isInfinity", &PublicKey::IsInfinity, static_cast(napi_enumerable)), + InstanceMethod( + "multiplyBy", + &PublicKey::MultiplyBy, + static_cast(napi_enumerable)), }; Napi::Function ctr = DefineClass(env, "PublicKey", proto, module); @@ -130,6 +134,21 @@ Napi::Value PublicKey::KeyValidate(const Napi::CallbackInfo &info) { return info.Env().Undefined(); } -Napi::Value PublicKey::IsInfinity(const Napi::CallbackInfo &info) { - BLST_TS_IS_INFINITY +Napi::Value PublicKey::IsInfinity(const Napi::CallbackInfo &info){ + BLST_TS_IS_INFINITY} + +Napi::Value PublicKey::MultiplyBy(const Napi::CallbackInfo &info) { + BLST_TS_FUNCTION_PREAMBLE(info, env, module) + Napi::Value rand_bytes_value = info[0]; + BLST_TS_UNWRAP_UINT_8_ARRAY(rand_bytes_value, rand_bytes, "randomBytes") + + Napi::Object pk_obj = module->_public_key_ctr.New( + // Default to jacobian coordinates + {Napi::External::New( + env, + new P1{_point->MultiplyBy( + rand_bytes.Data(), rand_bytes.ByteLength())}), + Napi::Boolean::New(env, false)}); + + return scope.Escape(pk_obj); } diff --git a/rebuild/src/public_key.h b/rebuild/src/public_key.h index 2bc50b66..50ebf9e4 100644 --- a/rebuild/src/public_key.h +++ b/rebuild/src/public_key.h @@ -22,6 +22,8 @@ class P1Wrapper { virtual bool InGroup() = 0; virtual void Serialize(bool compress, blst::byte *out) = 0; virtual void AddTo(blst::P1 &point) = 0; + virtual blst::P1 MultiplyBy( + blst::byte *rand_bytes, size_t rand_bytes_length) = 0; virtual P1AffineGroup AsAffine() = 0; }; @@ -36,6 +38,15 @@ class P1 : public P1Wrapper { compress ? _point.compress(out) : _point.serialize(out); } void AddTo(blst::P1 &point) override { point.add(_point); }; + blst::P1 MultiplyBy( + blst::byte *rand_bytes, size_t rand_bytes_length) override { + blst::byte out[96]; + _point.serialize(out); + // this should get std::move all the way into the P1 member value + blst::P1 point{out, 96}; + point.mult(rand_bytes, rand_bytes_length); + return point; + }; P1AffineGroup AsAffine() override { P1AffineGroup group{std::make_unique(_point), nullptr}; group.raw_point = group.smart_pointer.get(); @@ -54,6 +65,15 @@ class P1Affine : public P1Wrapper { compress ? _point.compress(out) : _point.serialize(out); } void AddTo(blst::P1 &point) override { point.add(_point); }; + blst::P1 MultiplyBy( + blst::byte *rand_bytes, size_t rand_bytes_length) override { + blst::byte out[96]; + _point.serialize(out); + // this should get std::move all the way into the P1 member value + blst::P1 point{out, 96}; + point.mult(rand_bytes, rand_bytes_length); + return point; + }; P1AffineGroup AsAffine() override { P1AffineGroup group{nullptr, &_point}; return group; @@ -70,6 +90,7 @@ class PublicKey : public Napi::ObjectWrap { Napi::Value Serialize(const Napi::CallbackInfo &info); Napi::Value KeyValidate(const Napi::CallbackInfo &info); Napi::Value IsInfinity(const Napi::CallbackInfo &info); + Napi::Value MultiplyBy(const Napi::CallbackInfo &info); }; #endif /* BLST_TS_PUBLIC_KEY_H__ */ diff --git a/rebuild/src/signature.cc b/rebuild/src/signature.cc index 067920e2..aa4b00ca 100644 --- a/rebuild/src/signature.cc +++ b/rebuild/src/signature.cc @@ -22,6 +22,10 @@ void Signature::Init( "isInfinity", &Signature::IsInfinity, static_cast(napi_enumerable)), + InstanceMethod( + "multiplyBy", + &Signature::MultiplyBy, + static_cast(napi_enumerable)), }; Napi::Function ctr = DefineClass(env, "Signature", proto, module); @@ -127,6 +131,21 @@ Napi::Value Signature::SigValidate(const Napi::CallbackInfo &info) { return env.Undefined(); } -Napi::Value Signature::IsInfinity(const Napi::CallbackInfo &info) { - BLST_TS_IS_INFINITY +Napi::Value Signature::IsInfinity(const Napi::CallbackInfo &info){ + BLST_TS_IS_INFINITY} + +Napi::Value Signature::MultiplyBy(const Napi::CallbackInfo &info) { + BLST_TS_FUNCTION_PREAMBLE(info, env, module) + Napi::Value rand_bytes_value = info[0]; + BLST_TS_UNWRAP_UINT_8_ARRAY(rand_bytes_value, rand_bytes, "randomBytes") + + Napi::Object sig_obj = module->_signature_ctr.New( + // Default to jacobian coordinates + {Napi::External::New( + env, + new P2{_point->MultiplyBy( + rand_bytes.Data(), rand_bytes.ByteLength())}), + Napi::Boolean::New(env, false)}); + + return scope.Escape(sig_obj); } diff --git a/rebuild/src/signature.h b/rebuild/src/signature.h index fded123d..47f968f9 100644 --- a/rebuild/src/signature.h +++ b/rebuild/src/signature.h @@ -22,6 +22,8 @@ class P2Wrapper { virtual bool InGroup() = 0; virtual void Serialize(bool compress, blst::byte *out) = 0; virtual void AddTo(blst::P2 &point) = 0; + virtual blst::P2 MultiplyBy( + blst::byte *rand_bytes, size_t rand_bytes_length) = 0; virtual P2AffineGroup AsAffine() = 0; virtual void Sign( const blst::SecretKey &key, @@ -41,6 +43,15 @@ class P2 : public P2Wrapper { compress ? _point.compress(out) : _point.serialize(out); } void AddTo(blst::P2 &point) override { point.add(_point); }; + blst::P2 MultiplyBy( + blst::byte *rand_bytes, size_t rand_bytes_length) override { + blst::byte out[192]; + _point.serialize(out); + // this should get std::move all the way into the P2 member value + blst::P2 point{out, 192}; + point.mult(rand_bytes, rand_bytes_length); + return point; + }; P2AffineGroup AsAffine() override { P2AffineGroup group{std::make_unique(_point), nullptr}; group.raw_point = group.smart_pointer.get(); @@ -67,6 +78,15 @@ class P2Affine : public P2Wrapper { compress ? _point.compress(out) : _point.serialize(out); } void AddTo(blst::P2 &point) override { point.add(_point); }; + blst::P2 MultiplyBy( + blst::byte *rand_bytes, size_t rand_bytes_length) override { + blst::byte out[192]; + _point.serialize(out); + // this should get std::move all the way into the P2 member value + blst::P2 point{out, 192}; + point.mult(rand_bytes, rand_bytes_length); + return point; + }; P2AffineGroup AsAffine() override { P2AffineGroup group{nullptr, &_point}; return group; @@ -93,6 +113,7 @@ class Signature : public Napi::ObjectWrap { Napi::Value Serialize(const Napi::CallbackInfo &info); Napi::Value SigValidate(const Napi::CallbackInfo &info); Napi::Value IsInfinity(const Napi::CallbackInfo &info); + Napi::Value MultiplyBy(const Napi::CallbackInfo &info); }; #endif /* BLST_TS_SIGNATURE_H__ */ diff --git a/src/lib.ts b/src/lib.ts index 39e8e39d..55305668 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -300,7 +300,7 @@ export function verifyMultipleAggregateSignatures(signatureSets: SignatureSet[]) * `rand` must not be exactly zero. Otherwise it would allow the verification of invalid signatures * See https://github.com/ChainSafe/blst-ts/issues/45 */ -function randomBytesNonZero(BYTES_COUNT: number): Buffer { +export function randomBytesNonZero(BYTES_COUNT: number): Buffer { const rand = crypto.randomBytes(BYTES_COUNT); for (let i = 0; i < BYTES_COUNT; i++) { if (rand[0] !== 0) return rand; diff --git a/test/perf/multithreading.test.ts b/test/perf/multithreading.test.ts index fdaa4c09..1c6eec46 100644 --- a/test/perf/multithreading.test.ts +++ b/test/perf/multithreading.test.ts @@ -2,28 +2,34 @@ import {itBench} from "@dapplion/benchmark"; import {expect} from "chai"; import {BlsMultiThreading, BlsPoolType, getGroupsOfBatchesOfSignatureSets} from "../utils"; -describe("multithreading perf", function () { - const minutes = 10; - this.timeout(minutes * 60 * 1000); - const getGroupsInfo = (isSwig: boolean): Parameters => [ - isSwig, - 16, - 128, - 256, - 256, - ]; +const minutes = 10; +const getGroupsInfo = (isSwig: boolean): Parameters => [ + isSwig, + 16, + 128, + 256, + 256, +]; - let libuvPool: BlsMultiThreading; - let workerPool: BlsMultiThreading; - - describe("libuv", () => { +for (const addVerificationRandomness of [true, false]) { + describe.only(`multithreading perf - addVerificationRandomness ${addVerificationRandomness}`, function () { + this.timeout(minutes * 60 * 1000); + let libuvPool: BlsMultiThreading; + let workerPool: BlsMultiThreading; let napiGroups: ReturnType; + let swigGroups: ReturnType; + before(async () => { - libuvPool = new BlsMultiThreading({blsPoolType: BlsPoolType.libuv}); + libuvPool = new BlsMultiThreading({blsPoolType: BlsPoolType.libuv, addVerificationRandomness}); napiGroups = getGroupsOfBatchesOfSignatureSets(...getGroupsInfo(false)); + + workerPool = new BlsMultiThreading({blsPoolType: BlsPoolType.workers, addVerificationRandomness}); + await workerPool.waitTillInitialized(); + swigGroups = getGroupsOfBatchesOfSignatureSets(...getGroupsInfo(true)); }); + itBench({ - id: "libuv multithreading - napi", + id: `libuv multithreading - napi - addVerificationRandomness ${addVerificationRandomness}`, fn: async () => { const responses = [] as Promise[]; for (const sets of napiGroups) { @@ -33,20 +39,9 @@ describe("multithreading perf", function () { expect(results.every((r) => r)).to.be.true; }, }); - }); - describe("workers", () => { - let swigGroups: ReturnType; - before(async () => { - workerPool = new BlsMultiThreading({blsPoolType: BlsPoolType.workers}); - await workerPool.waitTillInitialized(); - swigGroups = getGroupsOfBatchesOfSignatureSets(...getGroupsInfo(true)); - }); - after(async () => { - await workerPool.close(); - }); itBench({ - id: "worker multithreading - swig", + id: `worker multithreading - swig - addVerificationRandomness ${addVerificationRandomness}`, fn: async () => { const responses = [] as Promise[]; for (const sets of swigGroups) { @@ -56,14 +51,14 @@ describe("multithreading perf", function () { expect(results.every((r) => r)).to.be.true; }, }); - }); - after(async () => { - console.log({ - libuvPoolSize: libuvPool.blsPoolSize, - workerPoolSize: workerPool.blsPoolSize, + after(async () => { + console.log({ + libuvPoolSize: libuvPool.blsPoolSize, + workerPoolSize: workerPool.blsPoolSize, + }); + await libuvPool.close(); + await workerPool.close(); }); - await libuvPool.close(); - await workerPool.close(); }); -}); +} diff --git a/test/utils/multithreading/blsMultiThreading.ts b/test/utils/multithreading/blsMultiThreading.ts index 236454b1..ccf6915e 100644 --- a/test/utils/multithreading/blsMultiThreading.ts +++ b/test/utils/multithreading/blsMultiThreading.ts @@ -46,6 +46,7 @@ const MAX_JOBS_CAN_ACCEPT_WORK = 512; export class BlsMultiThreading { readonly blsPoolSize: number; + private readonly addVerificationRandomness: boolean; private readonly blsVerifyAllInQueue: boolean; private readonly blsPoolType: BlsPoolType; private readonly workers: WorkerDescriptor[] = []; @@ -63,6 +64,7 @@ export class BlsMultiThreading { private workersBusy = 0; constructor(options: BlsMultiThreadWorkerPoolOptions /*, modules: BlsMultiThreadWorkerPoolModules */) { + this.addVerificationRandomness = options.addVerificationRandomness ?? false; this.blsVerifyAllInQueue = options.blsVerifyAllInQueue ?? false; this.blsPoolType = options.blsPoolType ?? BlsPoolType.workers; @@ -122,7 +124,7 @@ export class BlsMultiThreading { resolve, reject, addedTimeMs: Date.now(), - opts, + opts: {...opts, addVerificationRandomness: this.addVerificationRandomness}, sets: setsChunk, message, }); diff --git a/test/utils/multithreading/queuedJob.ts b/test/utils/multithreading/queuedJob.ts index 0a9bc2ae..58fbe389 100644 --- a/test/utils/multithreading/queuedJob.ts +++ b/test/utils/multithreading/queuedJob.ts @@ -1,4 +1,5 @@ -import * as swig from "../../../src"; +import * as swig from "../../../src/lib"; +import * as swigBindings from "../../../src/bindings"; import napi from "../../../rebuild/lib"; import {PublicKey} from "../types"; import {BlsWorkRequest, ISignatureSet, SignatureSetType, VerifySignatureOpts} from "./types"; @@ -47,6 +48,28 @@ export function prepareSwigWorkReqFromJob(job: QueuedJob): BlsWorkRequest { }; } + if (job.opts.addVerificationRandomness) { + const pk_point = new swigBindings.blst.P1(); + const sig_point = new swigBindings.blst.P2(); + for (let i = 0; i < job.sets.length; i++) { + const randomness = swig.randomBytesNonZero(8); + pk_point.add((job.sets[i].publicKey as swig.PublicKey).jacobian.mult(randomness)); + const sig = swig.Signature.fromBytes(job.sets[i].signature, swig.CoordType.affine); + sig.sigValidate(); + sig_point.add(sig.jacobian.mult(randomness)); + } + return { + opts: job.opts, + sets: [ + { + pk: pk_point.serialize(), + sig: sig_point.serialize(), + msg: job.message, + }, + ], + }; + } + const publicKey = swig.aggregatePubkeys(job.sets.map((set) => set.publicKey as swig.PublicKey)); const signature = swig.aggregateSignatures( job.sets.map((set) => { @@ -82,11 +105,28 @@ export function prepareNapiWorkReqFromJob(job: QueuedJob): BlsWorkRequest { }; } - const publicKey = napi.aggregatePublicKeys(job.sets.map((set) => set.publicKey as napi.PublicKey)); + const randomness: Uint8Array[] = []; + if (job.opts.addVerificationRandomness) { + for (let i = 0; i < job.sets.length; i++) { + randomness.push(swig.randomBytesNonZero(8)); + } + } + + const publicKey = napi.aggregatePublicKeys( + job.sets.map((set, i) => { + if (job.opts.addVerificationRandomness) { + return (set.publicKey as napi.PublicKey).multiplyBy(randomness[i]); + } + return set.publicKey as napi.PublicKey; + }) + ); const signature = napi.aggregateSignatures( - job.sets.map((set) => { + job.sets.map((set, i) => { const sig = napi.Signature.deserialize(set.signature, napi.CoordType.affine); sig.sigValidate(); + if (job.opts.addVerificationRandomness) { + return sig.multiplyBy(randomness[i]); + } return sig; }) ); diff --git a/test/utils/multithreading/types.ts b/test/utils/multithreading/types.ts index b331846b..f6153adc 100644 --- a/test/utils/multithreading/types.ts +++ b/test/utils/multithreading/types.ts @@ -81,6 +81,7 @@ export enum BlsPoolType { export type BlsMultiThreadWorkerPoolOptions = { blsVerifyAllInQueue?: boolean; blsPoolType?: BlsPoolType; + addVerificationRandomness?: boolean; }; export type BlsMultiThreadWorkerPoolModules = Record; @@ -89,6 +90,7 @@ export interface VerifySignatureOpts { batchable?: boolean; verifyOnMainThread?: boolean; priority?: boolean; + addVerificationRandomness?: boolean; } export interface SerializedSwigSet {