Skip to content

Commit 3777b7a

Browse files
committed
sync aggregateWithRandomness
1 parent 0c2ef79 commit 3777b7a

File tree

4 files changed

+215
-102
lines changed

4 files changed

+215
-102
lines changed

bindings/napi/blst.zig

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -891,13 +891,15 @@ fn asyncAggregateExecute(_: napi.Env, data: *AsyncAggregateData) void {
891891
};
892892
}
893893

894-
// Generate 8-byte scalars (64 bits each), contiguous, matching Rust blst crate layout
894+
var prng = std.Random.DefaultPrng.init(std.crypto.random.int(u64));
895+
const rand = prng.random();
895896
var scalars: [8 * MAX_AGGREGATE_PER_JOB]u8 = undefined;
896-
std.crypto.random.bytes(scalars[0 .. n * nbytes]);
897+
rand.bytes(scalars[0 .. n * nbytes]);
898+
897899
// Ensure no zero scalars
898900
for (0..n) |i| {
899901
while (std.mem.allEqual(u8, scalars[i * nbytes ..][0..nbytes], 0)) {
900-
std.crypto.random.bytes(scalars[i * nbytes ..][0..nbytes]);
902+
rand.bytes(scalars[i * nbytes ..][0..nbytes]);
901903
}
902904
}
903905

@@ -978,6 +980,102 @@ fn asyncAggregateComplete(env: napi.Env, _: napi.status.Status, data: *AsyncAggr
978980
data.deferred.resolve(result) catch return;
979981
}
980982

983+
/// Synchronously aggregates public keys and signatures with randomness using
984+
/// Pippenger multi-scalar multiplication. Runs on the main thread.
985+
///
986+
/// Arguments:
987+
/// 1) sets: Array of {pk: PublicKey, sig: Uint8Array}
988+
///
989+
/// Returns: {pk: PublicKey, sig: Signature}
990+
pub fn blst_aggregateWithRandomness(env: napi.Env, cb: napi.CallbackInfo(1)) !napi.Value {
991+
const sets = cb.arg(0);
992+
const n = try sets.getArrayLength();
993+
994+
if (n == 0) return error.EmptyArray;
995+
if (n > MAX_AGGREGATE_PER_JOB) return error.TooManySets;
996+
997+
const nbits: usize = 64;
998+
const nbytes: usize = 8;
999+
1000+
var pk_ptrs: [MAX_AGGREGATE_PER_JOB]*const bls.c.blst_p1_affine = undefined;
1001+
var sigs: [MAX_AGGREGATE_PER_JOB]Signature = undefined;
1002+
var sig_ptrs: [MAX_AGGREGATE_PER_JOB]*const bls.c.blst_p2_affine = undefined;
1003+
1004+
// Generate 8-byte scalars (64 bits each) using a fast PRNG seeded from OS entropy
1005+
var prng = std.Random.DefaultPrng.init(std.crypto.random.int(u64));
1006+
const rand = prng.random();
1007+
var scalars: [8 * MAX_AGGREGATE_PER_JOB]u8 = undefined;
1008+
var sca_ptrs: [MAX_AGGREGATE_PER_JOB]*const u8 = undefined;
1009+
rand.bytes(scalars[0 .. n * nbytes]);
1010+
1011+
for (0..n) |i| {
1012+
const set_value = try sets.getElement(@intCast(i));
1013+
1014+
const pk_value = try set_value.getNamedProperty("pk");
1015+
const unwrapped_pk = try env.unwrap(PublicKey, pk_value);
1016+
pk_ptrs[i] = &unwrapped_pk.point;
1017+
1018+
const sig_value = try set_value.getNamedProperty("sig");
1019+
const sig_bytes = try sig_value.getTypedarrayInfo();
1020+
sigs[i] = Signature.deserialize(sig_bytes.data[0..]) catch return error.DeserializationFailed;
1021+
sigs[i].validate(true) catch return error.InvalidSignature;
1022+
sig_ptrs[i] = &sigs[i].point;
1023+
1024+
while (std.mem.allEqual(u8, scalars[i * nbytes ..][0..nbytes], 0)) {
1025+
rand.bytes(scalars[i * nbytes ..][0..nbytes]);
1026+
}
1027+
sca_ptrs[i] = &scalars[i * nbytes];
1028+
}
1029+
1030+
// Per-call scratch allocation
1031+
const scratch_size = @max(
1032+
bls.c.blst_p1s_mult_pippenger_scratch_sizeof(n),
1033+
bls.c.blst_p2s_mult_pippenger_scratch_sizeof(n),
1034+
);
1035+
const scratch = try allocator.alloc(u64, scratch_size);
1036+
defer allocator.free(scratch);
1037+
1038+
// Pippenger multi-scalar multiplication on G1 (pubkeys)
1039+
var p1_ret: bls.c.blst_p1 = std.mem.zeroes(bls.c.blst_p1);
1040+
bls.c.blst_p1s_mult_pippenger(
1041+
&p1_ret,
1042+
@ptrCast(&pk_ptrs),
1043+
n,
1044+
@ptrCast(&sca_ptrs),
1045+
nbits,
1046+
scratch.ptr,
1047+
);
1048+
var result_pk: PublicKey = .{};
1049+
bls.c.blst_p1_to_affine(&result_pk.point, &p1_ret);
1050+
1051+
// Pippenger multi-scalar multiplication on G2 (signatures)
1052+
var p2_ret: bls.c.blst_p2 = std.mem.zeroes(bls.c.blst_p2);
1053+
bls.c.blst_p2s_mult_pippenger(
1054+
&p2_ret,
1055+
@ptrCast(&sig_ptrs),
1056+
n,
1057+
@ptrCast(&sca_ptrs),
1058+
nbits,
1059+
scratch.ptr,
1060+
);
1061+
var result_sig: Signature = .{};
1062+
bls.c.blst_p2_to_affine(&result_sig.point, &p2_ret);
1063+
1064+
// Wrap results as NAPI PublicKey/Signature instances
1065+
const pk_result = try newPublicKeyInstance(env);
1066+
const pk_out = try env.unwrap(PublicKey, pk_result);
1067+
pk_out.* = result_pk;
1068+
1069+
const sig_result = try newSignatureInstance(env);
1070+
const sig_out = try env.unwrap(Signature, sig_result);
1071+
sig_out.* = result_sig;
1072+
1073+
const result = try env.createObject();
1074+
try result.setNamedProperty("pk", pk_result);
1075+
try result.setNamedProperty("sig", sig_result);
1076+
return result;
1077+
}
1078+
9811079
/// Asynchronously aggregates public keys and signatures with randomness using
9821080
/// Pippenger multi-scalar multiplication. Heavy math runs on the libuv thread pool.
9831081
///
@@ -1106,6 +1204,7 @@ pub fn register(env: napi.Env, exports: napi.Value) !void {
11061204
try blst_obj.setNamedProperty("aggregateSignatures", try env.createFunction("aggregateSignatures", 2, blst_aggregateSignatures, null));
11071205
try blst_obj.setNamedProperty("aggregatePublicKeys", try env.createFunction("aggregatePublicKeys", 2, blst_aggregatePublicKeys, null));
11081206
try blst_obj.setNamedProperty("aggregateSerializedPublicKeys", try env.createFunction("aggregateSerializedPublicKeys", 2, blst_aggregateSerializedPublicKeys, null));
1207+
try blst_obj.setNamedProperty("aggregateWithRandomness", try env.createFunction("aggregateWithRandomness", 1, blst_aggregateWithRandomness, null));
11091208
try blst_obj.setNamedProperty("asyncAggregateWithRandomness", try env.createFunction("asyncAggregateWithRandomness", 1, blst_asyncAggregateWithRandomness, null));
11101209

11111210
try exports.setNamedProperty("blst", blst_obj);

0 commit comments

Comments
 (0)