Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions bindings/napi/blst.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const bls = @import("bls");
const NativePublicKey = bls.PublicKey;
const NativeSignature = bls.Signature;
const NativeSecretKey = bls.SecretKey;
const SigningRoot = bls.SigningRoot;
const Pairing = bls.Pairing;
const AggregatePublicKey = bls.AggregatePublicKey;
const AggregateSignature = bls.AggregateSignature;
Expand Down Expand Up @@ -116,6 +117,12 @@ fn uint8SliceFromValue(value: js.Value) ![]u8 {
return info.data;
}

/// Validates a dynamic N-API byte slice and narrows it to the fixed BLS message type.
fn signingRootFromBytes(bytes: []const u8) !*const SigningRoot {
if (bytes.len != @sizeOf(SigningRoot)) return error.InvalidMessageLength;
return bytes[0..@sizeOf(SigningRoot)];
Comment thread
wemeetagain marked this conversation as resolved.
Resolved
}

pub const PublicKey = struct {
pub const js_meta = js.class(.{});

Expand Down Expand Up @@ -328,8 +335,8 @@ pub const SecretKey = struct {

/// Signs a message with this `SecretKey`, returns a `Signature`.
pub fn sign(self: *const SecretKey, msg: js.Uint8Array) !Signature {
const slice = try msg.toSlice();
return .{ .raw = self.raw.sign(slice, DST, null) };
const signing_root = try signingRootFromBytes(try msg.toSlice());
return .{ .raw = self.raw.sign(signing_root, DST, null) };
}

/// Derives the PublicKey from this SecretKey.
Expand Down Expand Up @@ -359,11 +366,11 @@ pub const SecretKey = struct {
/// 4) pk_validate: ?bool
/// 5) sig_groupcheck: ?bool
pub fn verify(msg: js.Uint8Array, pk: PublicKey, sig: Signature, pk_validate: ?js.Boolean, sig_groupcheck: ?js.Boolean) !js.Boolean {
const msg_slice = try msg.toSlice();
const signing_root = try signingRootFromBytes(try msg.toSlice());

sig.raw.verify(
try boolOrDefault(sig_groupcheck, false),
msg_slice,
signing_root,
DST,
null,
&pk.raw,
Expand All @@ -386,7 +393,7 @@ pub fn aggregateVerify(msgs: js.Array, pks: js.Array, sig: Signature, pks_valida
return error.InvalidAggregateVerifyInput;
}

const msg_bufs = try allocator.alloc([32]u8, msgs_len);
const msg_bufs = try allocator.alloc(SigningRoot, msgs_len);
defer allocator.free(msg_bufs);

const pk_ptrs = try allocator.alloc(*NativePublicKey, pks_len);
Expand All @@ -395,8 +402,7 @@ pub fn aggregateVerify(msgs: js.Array, pks: js.Array, sig: Signature, pks_valida
for (0..msgs_len) |i| {
const msg_value = try msgs.get(@intCast(i));
const msg_bytes = try uint8SliceFromValue(msg_value);
if (msg_bytes.len != 32) return error.InvalidMessageLength;
@memcpy(&msg_bufs[i], msg_bytes[0..32]);
msg_bufs[i] = (try signingRootFromBytes(msg_bytes)).*;

const wrapped_pk = try unwrapClass(PublicKey, try pks.get(@intCast(i)));
pk_ptrs[i] = &wrapped_pk.raw;
Expand Down Expand Up @@ -427,8 +433,7 @@ pub fn aggregateVerify(msgs: js.Array, pks: js.Array, sig: Signature, pks_valida
/// 3) sig: Signature
/// 4) sigs_groupcheck: ?bool
pub fn fastAggregateVerify(msg: js.Uint8Array, pks: js.Array, sig: Signature, sigs_groupcheck: ?js.Boolean) !js.Boolean {
const msg_slice = try msg.toSlice();
if (msg_slice.len != 32) return error.InvalidMessageLength;
const signing_root = try signingRootFromBytes(try msg.toSlice());

const pks_len = try pks.length();
if (pks_len == 0) return js.Boolean.from(false);
Expand All @@ -446,7 +451,7 @@ pub fn fastAggregateVerify(msg: js.Uint8Array, pks: js.Array, sig: Signature, si
const result = sig.raw.fastAggregateVerify(
try boolOrDefault(sigs_groupcheck, false),
&pairing_buf,
msg_slice[0..32],
signing_root,
DST,
native_pks,
false,
Expand All @@ -466,12 +471,12 @@ pub fn verifyMultipleAggregateSignatures(sets: js.Array, pks_validate: ?js.Boole
const n_elems = try sets.length();
if (n_elems == 0) return js.Boolean.from(false);

var msgs_stack: [BATCH_VERIFY_SIZE][]const u8 = undefined;
var msgs_stack: [BATCH_VERIFY_SIZE]SigningRoot = undefined;
var pks_stack: [BATCH_VERIFY_SIZE]*NativePublicKey = undefined;
var sigs_stack: [BATCH_VERIFY_SIZE]*NativeSignature = undefined;
var rands_stack: [BATCH_VERIFY_SIZE][32]u8 = undefined;

var msgs_heap: ?[][]const u8 = null;
var msgs_heap: ?[]SigningRoot = null;
defer if (msgs_heap) |buf| allocator.free(buf);
var pks_heap: ?[]*NativePublicKey = null;
defer if (pks_heap) |buf| allocator.free(buf);
Expand All @@ -481,7 +486,7 @@ pub fn verifyMultipleAggregateSignatures(sets: js.Array, pks_validate: ?js.Boole
defer if (rands_heap) |buf| allocator.free(buf);

const msgs = if (n_elems <= BATCH_VERIFY_SIZE) msgs_stack[0..n_elems] else blk: {
const buf = try allocator.alloc([]const u8, n_elems);
const buf = try allocator.alloc(SigningRoot, n_elems);
msgs_heap = buf;
break :blk buf;
};
Expand Down Expand Up @@ -512,8 +517,7 @@ pub fn verifyMultipleAggregateSignatures(sets: js.Array, pks_validate: ?js.Boole

const msg_napi = try set.getNamedProperty("msg");
const msg_bytes = try uint8SliceFromValue(.{ .val = msg_napi });
if (msg_bytes.len != 32) return error.InvalidMessageLength;
msgs[i] = msg_bytes;
msgs[i] = (try signingRootFromBytes(msg_bytes)).*;

const pk_napi = try set.getNamedProperty("pk");
const wrapped_pk = try unwrapClass(PublicKey, .{ .val = pk_napi });
Expand Down
10 changes: 6 additions & 4 deletions bindings/src/blst.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class SecretKey {
static fromBytes(bytes: Uint8Array): SecretKey;
static fromHex(hex: string): SecretKey;
static fromKeygen(ikm: Uint8Array, keyInfo?: Uint8Array): SecretKey;
/** Sign an exact 32-byte Ethereum consensus signing root. */
sign(msg: Uint8Array): Signature;
toPublicKey(): PublicKey;
toBytes(): Uint8Array;
Expand Down Expand Up @@ -85,6 +86,7 @@ export class Signature {
}

export interface SignatureSet {
/** Exact 32-byte Ethereum consensus signing root. */
msg: Uint8Array;
pk: PublicKey;
sig: Signature;
Expand All @@ -101,7 +103,7 @@ export interface PkAndSig {
}

/**
* Verify a signature against a message and public key.
* Verify a signature against an exact 32-byte Ethereum consensus signing root and public key.
*
* If `pkValidate` is `true`, the public key will be infinity and group checked.
*
Expand All @@ -116,7 +118,7 @@ export function verify(
): boolean;

/**
* Verify an aggregated signature against multiple messages and multiple public keys.
* Verify an aggregated signature against exact 32-byte Ethereum consensus signing roots and multiple public keys.
*
* If `pksValidate` is `true`, the public keys will be infinity and group checked.
*
Expand All @@ -131,7 +133,7 @@ export function aggregateVerify(
): boolean;

/**
* Verify an aggregated signature against a single message and multiple public keys.
* Verify an aggregated signature against a single exact 32-byte Ethereum consensus signing root and multiple public keys.
*
* Proof-of-possession is required for public keys.
*
Expand All @@ -145,7 +147,7 @@ export function fastAggregateVerify(
): boolean;

/**
* Verify multiple aggregated signatures against multiple messages and multiple public keys.
* Verify multiple aggregated signatures against exact 32-byte Ethereum consensus signing roots and multiple public keys.
*
* If `pksValidate` is `true`, the public keys will be infinity and group checked.
*
Expand Down
48 changes: 41 additions & 7 deletions bindings/test/blst.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,18 @@ describe("blst", () => {
});
});
describe("sign", () => {
it("should create a valid Signature", () => {
const sig = SecretKey.fromKeygen(KEY_MATERIAL, undefined).sign(Buffer.from("some fancy message"));
it("should create a valid Signature for a 32-byte signing root", () => {
const sig = SecretKey.fromKeygen(KEY_MATERIAL, undefined).sign(new Uint8Array(32));
expect(sig).to.be.instanceOf(Signature);
expect(sig.validate(false)).to.be.undefined;
});

for (const length of [31, 33]) {
it(`should throw InvalidMessageLength for a ${length}-byte signing root`, () => {
const sk = SecretKey.fromKeygen(KEY_MATERIAL, undefined);
expect(() => sk.sign(new Uint8Array(length))).toThrow("InvalidMessageLength");
});
}
});
});
});
Expand All @@ -258,6 +265,14 @@ describe("blst", () => {
const result = verify(wrongMessage, pk, sig, false, false);
expect(result).toBe(false);
});

for (const length of [31, 33]) {
it(`should throw InvalidMessageLength for a ${length}-byte signing root`, () => {
const pk = PublicKey.fromHex(TEST_VECTORS.publicKey.compressed);
const sig = Signature.fromHex(TEST_VECTORS.signature.compressed);
expect(() => verify(new Uint8Array(length), pk, sig, false, false)).toThrow("InvalidMessageLength");
});
}
});

describe("aggregateVerify", () => {
Expand All @@ -278,6 +293,14 @@ describe("blst", () => {
const sig = Signature.fromHex(TEST_VECTORS.signature.compressed);
expect(aggregateVerify([TEST_VECTORS.message], [pk], sig)).to.be.true;
});

for (const length of [31, 33]) {
it(`should throw InvalidMessageLength for a ${length}-byte signing root`, () => {
const pk = PublicKey.fromHex(TEST_VECTORS.publicKey.compressed);
const sig = Signature.fromHex(TEST_VECTORS.signature.compressed);
expect(() => aggregateVerify([new Uint8Array(length)], [pk], sig)).toThrow("InvalidMessageLength");
});
}
});

describe("fastAggregateVerify", () => {
Expand All @@ -302,11 +325,13 @@ describe("blst", () => {
expect(result).toBe(false);
});

it("should throw on wrong message length", () => {
const pk = PublicKey.fromHex(TEST_VECTORS.publicKey.compressed);
const sig = Signature.fromHex(TEST_VECTORS.signature.compressed);
expect(() => fastAggregateVerify(new Uint8Array(31), [pk], sig, false)).toThrow();
});
for (const length of [31, 33]) {
it(`should throw InvalidMessageLength for a ${length}-byte signing root`, () => {
const pk = PublicKey.fromHex(TEST_VECTORS.publicKey.compressed);
const sig = Signature.fromHex(TEST_VECTORS.signature.compressed);
expect(() => fastAggregateVerify(new Uint8Array(length), [pk], sig, false)).toThrow("InvalidMessageLength");
});
}
});

describe("verifyMultipleAggregateSignatures", () => {
Expand All @@ -329,6 +354,15 @@ describe("blst", () => {
verifyMultipleAggregateSignatures([{msg, pk, sig: pk as unknown as Signature}], false, false)
).toThrow("TypeMismatch");
});

for (const length of [31, 33]) {
it(`should throw InvalidMessageLength for a ${length}-byte signing root`, () => {
const [set] = getTestSets(1);
expect(() => verifyMultipleAggregateSignatures([{...set, msg: new Uint8Array(length)}], false, false)).toThrow(
"InvalidMessageLength"
);
});
}
});

describe("aggregatePublicKeys", () => {
Expand Down
13 changes: 7 additions & 6 deletions src/bls/Pairing.zig
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub fn aggregate(
pk_validate: bool,
sig: ?*const Signature,
sig_groupcheck: bool,
msg: []const u8,
msg: *const SigningRoot,
aug: ?[]const u8,
) BlstError!void {
try errorFromInt(
Expand All @@ -64,8 +64,8 @@ pub fn aggregate(
pk_validate,
if (sig) |s| &s.point else null,
sig_groupcheck,
msg.ptr,
msg.len,
msg,
@sizeOf(SigningRoot),
if (aug) |a| a.ptr else null,
if (aug) |a| a.len else 0,
),
Expand All @@ -83,7 +83,7 @@ pub fn mulAndAggregate(
sig_groupcheck: bool,
scalar: []const u8,
nbits: usize,
msg: []const u8,
msg: *const SigningRoot,
) BlstError!void {
try errorFromInt(
c.blst_pairing_chk_n_mul_n_aggr_pk_in_g1(
Expand All @@ -94,8 +94,8 @@ pub fn mulAndAggregate(
sig_groupcheck,
scalar.ptr,
nbits,
msg.ptr,
32,
msg,
@sizeOf(SigningRoot),
null,
0,
),
Expand Down Expand Up @@ -153,3 +153,4 @@ const errorFromInt = @import("error.zig").errorFromInt;
const blst = @import("root.zig");
const PublicKey = blst.PublicKey;
const Signature = blst.Signature;
const SigningRoot = blst.SigningRoot;
7 changes: 4 additions & 3 deletions src/bls/SecretKey.zig
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,13 @@ pub fn toPublicKey(self: *const Self) PublicKey {
}

/// Sign a message with this `SecretKey`. Returns the `Signature` for the message.
pub fn sign(self: *const Self, msg: []const u8, dst: []const u8, aug: ?[]const u8) Signature {
pub fn sign(self: *const Self, msg: *const SigningRoot, dst: []const u8, aug: ?[]const u8) Signature {
var sig = Signature{};
var q = @import("AggregateSignature.zig"){};
c.blst_hash_to_g2(
@ptrCast(&q.point),
msg.ptr,
msg.len,
msg,
@sizeOf(SigningRoot),
dst.ptr,
dst.len,
if (aug) |a| a.ptr else null,
Expand Down Expand Up @@ -163,6 +163,7 @@ const check = @import("error.zig").check;
const blst = @import("root.zig");
const PublicKey = blst.PublicKey;
const Signature = blst.Signature;
const SigningRoot = blst.SigningRoot;

const c = @import("root.zig").c;

Expand Down
Loading
Loading