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