-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement verify and aggregate apis
- Loading branch information
Showing
14 changed files
with
761 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { binding, writeUint8ArrayArray } from "./binding"; | ||
import { MAX_AGGREGATE_PER_JOB } from "./const"; | ||
import {PublicKey, writePublicKeysReference} from "./publicKey"; | ||
import { Signature, writeSignaturesReference } from "./signature"; | ||
|
||
|
||
// global public keys reference to be reused across multiple calls | ||
// each 2 items are 8 bytes, store the reference of each public key | ||
const public_keys_ref = new Uint32Array(MAX_AGGREGATE_PER_JOB * 2); | ||
|
||
const signatures_ref = new Uint32Array(MAX_AGGREGATE_PER_JOB * 2); | ||
|
||
/** | ||
* Aggregate multiple public keys into a single public key. | ||
* | ||
* If `pks_validate` is `true`, the public keys will be infinity and group checked. | ||
*/ | ||
export function aggregatePublicKeys(pks: Array<PublicKey>, pksValidate?: boolean | undefined | null): PublicKey { | ||
if (pks.length > MAX_AGGREGATE_PER_JOB) { | ||
throw new Error("Too many public keys"); | ||
} | ||
|
||
const pks_ref = writePublicKeysReference(pks); | ||
|
||
const defaultPk = PublicKey.defaultPublicKey(); | ||
const res = binding.aggregatePublicKeys(defaultPk.blst_point, pks_ref, pks.length, pksValidate ?? false); | ||
if (res !== 0) { | ||
throw new Error(`Failed to aggregate public keys: ${res}`); | ||
} | ||
|
||
return defaultPk; | ||
} | ||
|
||
/** | ||
* Aggregate multiple signatures into a single signature. | ||
* | ||
* If `sigs_groupcheck` is `true`, the signatures will be group checked. | ||
*/ | ||
export function aggregateSignatures(sigs: Array<Signature>, sigsGroupcheck?: boolean | undefined | null): Signature { | ||
if (sigs.length > MAX_AGGREGATE_PER_JOB) { | ||
throw new Error("Too many signatures"); | ||
} | ||
|
||
const sigs_ref = writeSignaturesReference(sigs); | ||
|
||
const defaultSig = Signature.defaultSignature(); | ||
const res = binding.aggregateSignatures(defaultSig.blst_point, sigs_ref, sigs.length, sigsGroupcheck ?? false); | ||
if (res !== 0) { | ||
throw new Error(`Failed to aggregate signatures: ${res}`); | ||
} | ||
|
||
return defaultSig; | ||
} | ||
|
||
const pks_ref = new Uint32Array(2); | ||
|
||
/** | ||
* Aggregate multiple serialized public keys into a single public key. | ||
* | ||
* If `pks_validate` is `true`, the public keys will be infinity and group checked. | ||
*/ | ||
export function aggregateSerializedPublicKeys(pks: Array<Uint8Array>, pksValidate?: boolean | undefined | null): PublicKey { | ||
if (pks.length > MAX_AGGREGATE_PER_JOB) { | ||
throw new Error("Too many public keys"); | ||
} | ||
|
||
if (pks.length < 1) { | ||
throw new Error("At least one public key is required"); | ||
} | ||
|
||
const pks_ref = writeSerializedPublicKeysReference(pks); | ||
|
||
const defaultPk = PublicKey.defaultPublicKey(); | ||
const res = binding.aggregateSerializedPublicKeys(defaultPk.blst_point, pks_ref, pks.length, pks[0].length, pksValidate ?? false); | ||
if (res !== 0) { | ||
throw new Error(`Failed to aggregate serialized public keys: ${res}`); | ||
} | ||
|
||
return defaultPk; | ||
} | ||
|
||
/** | ||
* Aggregate multiple serialized signatures into a single signature. | ||
* | ||
* If `sigs_groupcheck` is `true`, the signatures will be group checked. | ||
*/ | ||
export function aggregateSerializedSignatures(sigs: Array<Uint8Array>, sigsGroupcheck?: boolean | undefined | null): Signature { | ||
if (sigs.length > MAX_AGGREGATE_PER_JOB) { | ||
throw new Error("Too many signatures"); | ||
} | ||
|
||
if (sigs.length < 1) { | ||
throw new Error("At least one signature is required"); | ||
} | ||
|
||
const sigs_ref = writeSerializedSignaturesReference(sigs); | ||
|
||
const defaultSig = Signature.defaultSignature(); | ||
const res = binding.aggregateSerializedSignatures(defaultSig.blst_point, sigs_ref, sigs.length, sigs[0].length, sigsGroupcheck ?? false); | ||
if (res !== 0) { | ||
throw new Error(`Failed to aggregate serialized signatures: ${res}`); | ||
} | ||
|
||
return defaultSig; | ||
} | ||
|
||
|
||
function writeSerializedPublicKeysReference(pks: Uint8Array[]): Uint32Array { | ||
writeUint8ArrayArray(pks, MAX_AGGREGATE_PER_JOB, "public key", public_keys_ref); | ||
return public_keys_ref.subarray(0, pks.length * 2); | ||
} | ||
|
||
function writeSerializedSignaturesReference(sigs: Uint8Array[]): Uint32Array { | ||
writeUint8ArrayArray(sigs, MAX_AGGREGATE_PER_JOB, "signature", signatures_ref); | ||
return signatures_ref.subarray(0, sigs.length * 2); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import {JSCallback} from "bun:ffi"; | ||
import { binding, writeNumber, writeReference } from "./binding"; | ||
import { MAX_AGGREGATE_WITH_RANDOMNESS_PER_JOB } from "./const"; | ||
import { PublicKey } from "./publicKey"; | ||
import { Signature } from "./signature"; | ||
|
||
export interface PkAndSerializedSig { | ||
pk: PublicKey; | ||
sig: Uint8Array; | ||
}; | ||
|
||
export interface PkAndSig { | ||
pk: PublicKey; | ||
sig: Signature; | ||
}; | ||
|
||
// global signature sets reference to be reused across multiple calls | ||
// each 2 tems are 8 bytes, store the reference of each PkAndSerializedSig | ||
const pk_and_serialized_sigs_refs = new Uint32Array(MAX_AGGREGATE_WITH_RANDOMNESS_PER_JOB * 2); | ||
|
||
const scratch_pk = new Uint8Array(binding.sizeOfScratchPk(MAX_AGGREGATE_WITH_RANDOMNESS_PER_JOB)); | ||
const scratch_sig = new Uint8Array(binding.sizeOfScratchSig(MAX_AGGREGATE_WITH_RANDOMNESS_PER_JOB)); | ||
|
||
/** | ||
* Aggregate multiple public keys and multiple serialized signatures into a single blinded public key and blinded signature. | ||
* | ||
* Signatures are deserialized and validated with infinity and group checks before aggregation. | ||
*/ | ||
export function aggregateWithRandomness(sets: Array<PkAndSerializedSig>): PkAndSig { | ||
if (sets.length > MAX_AGGREGATE_WITH_RANDOMNESS_PER_JOB) { | ||
throw new Error(`Number of PkAndSerializedSig exceeds the maximum of ${MAX_AGGREGATE_WITH_RANDOMNESS_PER_JOB}`); | ||
} | ||
|
||
if (sets.length === 0) { | ||
throw new Error("At least one PkAndSerializedSig is required"); | ||
} | ||
|
||
const refs = pk_and_serialized_sigs_refs.subarray(0, sets.length * 2); | ||
writePkAndSerializedSigsReference(sets, refs); | ||
const pkOut = PublicKey.defaultPublicKey(); | ||
const sigOut = Signature.defaultSignature(); | ||
|
||
const res = binding.aggregateWithRandomness(refs, sets.length, scratch_pk, scratch_pk.length, scratch_sig, scratch_sig.length, pkOut.blst_point, sigOut.blst_point); | ||
|
||
if (res !== 0) { | ||
throw new Error("Failed to aggregate with randomness res = " + res); | ||
} | ||
|
||
return { pk: pkOut, sig: sigOut}; | ||
} | ||
|
||
/** | ||
* Aggregate multiple public keys and multiple serialized signatures into a single blinded public key and blinded signature. | ||
* | ||
* Signatures are deserialized and validated with infinity and group checks before aggregation. | ||
*/ | ||
export function asyncAggregateWithRandomness(sets: Array<PkAndSerializedSig>): Promise<PkAndSig> { | ||
if (sets.length > MAX_AGGREGATE_WITH_RANDOMNESS_PER_JOB) { | ||
throw new Error(`Number of PkAndSerializedSig exceeds the maximum of ${MAX_AGGREGATE_WITH_RANDOMNESS_PER_JOB}`); | ||
} | ||
|
||
if (sets.length === 0) { | ||
throw new Error("At least one PkAndSerializedSig is required"); | ||
} | ||
|
||
return new Promise((resolve, reject) => { | ||
const pkOut = PublicKey.defaultPublicKey(); | ||
const sigOut = Signature.defaultSignature(); | ||
|
||
const jscallback = new JSCallback((res: number): void => { | ||
if (res === 0) { | ||
// setTimeout to unblock zig callback thread, not sure why "res" can only be accessed once | ||
setTimeout(() => { | ||
resolve({ pk: pkOut, sig: sigOut }); | ||
}, 0); | ||
} else { | ||
setTimeout(() => { | ||
// setTimeout to unblock zig callback thread, not sure why "res" can only be accessed once | ||
reject(new Error("Failed to aggregate with randomness")); | ||
}, 0); | ||
} | ||
}, { | ||
"args": ["u32"], | ||
"returns": "void", | ||
}); | ||
|
||
const refs = pk_and_serialized_sigs_refs.subarray(0, sets.length * 2); | ||
writePkAndSerializedSigsReference(sets, refs); | ||
|
||
const res = binding.asyncAggregateWithRandomness(pk_and_serialized_sigs_refs, sets.length, scratch_pk, scratch_pk.length, scratch_sig, scratch_sig.length, pkOut.blst_point, sigOut.blst_point, jscallback); | ||
|
||
if (res !== 0) { | ||
throw new Error("Failed to aggregate with randomness res = " + res); | ||
} | ||
}); | ||
} | ||
|
||
|
||
// global PkAndSerializedSig data to be reused across multiple calls | ||
// each PkAndSerializedSig are 24 bytes | ||
const sets_data = new Uint32Array(MAX_AGGREGATE_WITH_RANDOMNESS_PER_JOB * 6); | ||
function writePkAndSerializedSigsReference(sets: PkAndSerializedSig[], out: Uint32Array): void { | ||
let offset = 0; | ||
for (const [i, set] of sets.entries()) { | ||
writePkAndSerializedSigReference(set, sets_data, offset + i * 6); | ||
// write pointer, each PkAndSerializedSig takes 8 bytes = 2 * uint32 | ||
writeReference(sets_data.subarray(i * 6, i * 6 + 6), out, i * 2); | ||
} | ||
}; | ||
|
||
// each PkAndSerializedSig needs 16 bytes = 4 * uint32 for references | ||
/** | ||
* Map an instance of PkAndSerializedSig in typescript to this struct in Zig: | ||
* ```zig | ||
* const PkAndSerializedSigC = extern struct { | ||
pk: *pk_aff_type, | ||
sig: [*c]const u8, | ||
sig_len: usize, | ||
}; | ||
* ``` | ||
* | ||
*/ | ||
function writePkAndSerializedSigReference(set: PkAndSerializedSig, out: Uint32Array, offset: number): void { | ||
set.pk.writeReference(out, offset); | ||
writeReference(set.sig, out, offset + 2); | ||
writeNumber(set.sig.length, out, offset + 4); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,8 @@ | ||
export * from "./publicKey"; | ||
export * from "./secretKey"; | ||
export * from "./signature"; | ||
export * from "./aggregateWithRandomness"; | ||
export * from "./verifyMultipleAggregateSignatures"; | ||
export * from "./verify"; | ||
export * from "./aggregate"; | ||
export * from "./const"; |
Oops, something went wrong.