Skip to content
Open
Changes from 5 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
138 changes: 120 additions & 18 deletions src/state_transition/utils/bls.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,147 @@ const SecretKey = bls.SecretKey;
/// See https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#bls-signatures
const DST: []const u8 = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_";

pub fn sign(secret_key: SecretKey, msg: []const u8) Signature {
return secret_key.sign(msg, DST, null);
pub fn sign(secret_key: SecretKey, message: []const u8) Signature {
return secret_key.sign(message, DST, null);
}
Comment on lines +10 to 12
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The repository style guide requires asserting all function arguments and maintaining an average of at least two assertions per function to ensure safety and document invariants (lines 51-55). Consider adding assertions for the message length and the DST constant to meet this requirement.

pub fn sign(secret_key: SecretKey, message: []const u8) Signature {
    std.debug.assert(message.len > 0);
    std.debug.assert(DST.len > 0);

    return secret_key.sign(message, DST, null);
}
References
  1. Assert all function arguments and return values, pre/postconditions and invariants. The assertion density of the code must average a minimum of two assertions per function. (link)


/// Verify a signature against a message and public key.
///
/// If `pk_validate` is `true`, the public key will be infinity and group checked.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: comment arg name not same with function arg name

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@spiral-ladder is in_ pk_validate better or pk_validate?

///
/// If `sig_groupcheck` is `true`, the signature will be group checked.
pub fn verify(msg: []const u8, pk: *const PublicKey, sig: *const Signature, in_pk_validate: ?bool, in_sig_groupcheck: ?bool) bls.BlstError!void {
pub fn verify(message: []const u8, public_key: *const PublicKey, signature: *const Signature, in_pk_validate: ?bool, in_sig_groupcheck: ?bool) bls.BlstError!void {
const sig_groupcheck = in_sig_groupcheck orelse false;
const pk_validate = in_pk_validate orelse false;
try sig.verify(sig_groupcheck, msg, DST, null, pk, pk_validate);
try signature.verify(sig_groupcheck, message, DST, null, public_key, pk_validate);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Following the repository style guide:

  1. Assertions: Added assertions for message.len and DST.len to comply with the requirement of at least two assertions per function (lines 51-55).
  2. Naming: Renamed in_pk_validate and in_sig_groupcheck to avoid abbreviations (pk, sig) as per line 221.
  3. Options Pattern: Since this function takes multiple optional boolean flags that can be easily mixed up, it is a strong candidate for the options: struct pattern recommended in the guide (line 287).
pub fn verify(
    message: []const u8,
    public_key: *const PublicKey,
    signature: *const Signature,
    public_key_validate: ?bool,
    signature_group_check: ?bool,
) bls.BlstError!void {
    std.debug.assert(message.len > 0);
    std.debug.assert(DST.len > 0);

    const sig_groupcheck = signature_group_check orelse false;
    const pk_validate = public_key_validate orelse false;
    try signature.verify(sig_groupcheck, message, DST, null, public_key, pk_validate);
}
References
  1. Assert all function arguments and return values, pre/postconditions and invariants. The assertion density of the code must average a minimum of two assertions per function. (link)
  2. Do not abbreviate variable names. Add units or qualifiers to variable names, and put the units or qualifiers last. (link)
  3. Zig has named arguments through the options: struct pattern. Use it when arguments can be mixed up. (link)


pub fn fastAggregateVerify(msg: []const u8, pks: []const PublicKey, sig: *const Signature, in_pk_validate: ?bool, in_sigs_group_check: ?bool) !bool {
/// The `message` must be at least 32 bytes; only the first 32 are passed to
/// fast aggregate verification.
pub fn fastAggregateVerify(message: []const u8, public_keys: []const PublicKey, signature: *const Signature, in_pk_validate: ?bool, in_sigs_group_check: ?bool) !bool {
std.debug.assert(message.len >= 32);

var pairing_buf: [bls.Pairing.sizeOf()]u8 align(bls.Pairing.buf_align) = undefined;

const sigs_groupcheck = in_sigs_group_check orelse false;
const pks_validate = in_pk_validate orelse false;
return sig.fastAggregateVerify(sigs_groupcheck, &pairing_buf, msg[0..32], DST, pks, pks_validate) catch return false;
const public_keys_validate = in_pk_validate orelse false;
return signature.fastAggregateVerify(sigs_groupcheck, &pairing_buf, message[0..32], DST, public_keys, public_keys_validate) catch return false;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Following the repository style guide:

  1. Assertions: Added an assertion for public_keys.len to ensure the function does not operate on empty data and to meet the assertion density requirement (lines 51-55).
  2. Naming: Renamed in_pk_validate and in_sigs_group_check to avoid abbreviations.
  3. Return Type: Since the function catches all errors and returns false (line 34), it effectively never returns an error. Consider changing the return type to bool for simplicity, as simpler return types are preferred (line 365).
pub fn fastAggregateVerify(
    message: []const u8,
    public_keys: []const PublicKey,
    signature: *const Signature,
    public_key_validate: ?bool,
    signatures_group_check: ?bool,
) !bool {
    std.debug.assert(message.len >= 32);
    std.debug.assert(public_keys.len > 0);

    var pairing_buf: [bls.Pairing.sizeOf()]u8 align(bls.Pairing.buf_align) = undefined;

    const sigs_groupcheck = signatures_group_check orelse false;
    const pks_validate = public_key_validate orelse false;
    return signature.fastAggregateVerify(sigs_groupcheck, &pairing_buf, message[0..32], DST, public_keys, pks_validate) catch return false;
}
References
  1. Assert all function arguments and return values, pre/postconditions and invariants. The assertion density of the code must average a minimum of two assertions per function. (link)
  2. Use simpler function signatures and return types to reduce dimensionality at the call site... void trumps bool, bool trumps u64, u64 trumps ?u64, and ?u64 trumps !u64. (link)


// TODO: unit tests
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps adding more positive tests for other functions if dropped this comment

test "bls - sanity" {
const ikm: [32]u8 = [_]u8{
const input_key_material: [32]u8 = [_]u8{
0x93, 0xad, 0x7e, 0x65, 0xde, 0xad, 0x05, 0x2a, 0x08, 0x3a,
0x91, 0x0c, 0x8b, 0x72, 0x85, 0x91, 0x46, 0x4c, 0xca, 0x56,
0x60, 0x5b, 0xb0, 0x56, 0xed, 0xfe, 0x2b, 0x60, 0xa6, 0x3c,
0x48, 0x99,
};
const secret_key = try SecretKey.keyGen(input_key_material[0..], null);
const message = [_]u8{1} ** 32;
const signature = sign(secret_key, &message);
const public_key = secret_key.toPublicKey();
try verify(&message, &public_key, &signature, null, null);

const public_keys = [_]PublicKey{public_key};
const public_keys_slice: []const PublicKey = public_keys[0..1];
const fast_aggregate_verified = try fastAggregateVerify(&message, public_keys_slice, &signature, null, null);
try std.testing.expectEqual(true, fast_aggregate_verified);
}

test "bls - sign and verify round trip variable-length message" {
const input_key_material: [32]u8 = [_]u8{
0x93, 0xad, 0x7e, 0x65, 0xde, 0xad, 0x05, 0x2a, 0x08, 0x3a,
0x91, 0x0c, 0x8b, 0x72, 0x85, 0x91, 0x46, 0x4c, 0xca, 0x56,
0x60, 0x5b, 0xb0, 0x56, 0xed, 0xfe, 0x2b, 0x60, 0xa6, 0x3c,
0x48, 0x99,
};
const secret_key = try SecretKey.keyGen(input_key_material[0..], null);
const message = "ethereum consensus";
const signature = sign(secret_key, message);
const public_key = secret_key.toPublicKey();
try verify(message, &public_key, &signature, null, null);
}

test "bls - verify with pubkey and signature subgroup checks" {
const input_key_material: [32]u8 = [_]u8{
0x93, 0xad, 0x7e, 0x65, 0xde, 0xad, 0x05, 0x2a, 0x08, 0x3a,
0x91, 0x0c, 0x8b, 0x72, 0x85, 0x91, 0x46, 0x4c, 0xca, 0x56,
0x60, 0x5b, 0xb0, 0x56, 0xed, 0xfe, 0x2b, 0x60, 0xa6, 0x3c,
0x48, 0x99,
};
const secret_key = try SecretKey.keyGen(input_key_material[0..], null);
const message = [_]u8{0xab} ** 32;
const signature = sign(secret_key, &message);
const public_key = secret_key.toPublicKey();
try verify(&message, &public_key, &signature, true, true);
}

test "bls - fastAggregateVerify uses only first 32 bytes of longer buffer" {
const input_key_material: [32]u8 = [_]u8{
0x93, 0xad, 0x7e, 0x65, 0xde, 0xad, 0x05, 0x2a, 0x08, 0x3a,
0x91, 0x0c, 0x8b, 0x72, 0x85, 0x91, 0x46, 0x4c, 0xca, 0x56,
0x60, 0x5b, 0xb0, 0x56, 0xed, 0xfe, 0x2b, 0x60, 0xa6, 0x3c,
0x48, 0x99,
};
const secret_key = try SecretKey.keyGen(input_key_material[0..], null);
var message_64: [64]u8 = undefined;
@memset(message_64[32..], 0xcd);
@memset(message_64[0..32], 0x42);
const signature = sign(secret_key, message_64[0..32]);
const public_key = secret_key.toPublicKey();
var public_keys = [_]PublicKey{public_key};
const fast_aggregate_verified = try fastAggregateVerify(&message_64, public_keys[0..], &signature, null, null);
try std.testing.expectEqual(true, fast_aggregate_verified);
}

test "bls - verify fails on wrong message" {
const input_key_material: [32]u8 = [_]u8{
0x93, 0xad, 0x7e, 0x65, 0xde, 0xad, 0x05, 0x2a, 0x08, 0x3a,
0x91, 0x0c, 0x8b, 0x72, 0x85, 0x91, 0x46, 0x4c, 0xca, 0x56,
0x60, 0x5b, 0xb0, 0x56, 0xed, 0xfe, 0x2b, 0x60, 0xa6, 0x3c,
0x48, 0x99,
};
const secret_key = try SecretKey.keyGen(input_key_material[0..], null);
var message = [_]u8{1} ** 32;
const signature = sign(secret_key, &message);
const public_key = secret_key.toPublicKey();
message[0] ^= 1;
try std.testing.expectError(bls.BlstError.VerifyFail, verify(&message, &public_key, &signature, null, null));
}

test "bls - verify fails on wrong public key" {
const input_key_material_a: [32]u8 = [_]u8{
0x93, 0xad, 0x7e, 0x65, 0xde, 0xad, 0x05, 0x2a, 0x08, 0x3a,
0x91, 0x0c, 0x8b, 0x72, 0x85, 0x91, 0x46, 0x4c, 0xca, 0x56,
0x60, 0x5b, 0xb0, 0x56, 0xed, 0xfe, 0x2b, 0x60, 0xa6, 0x3c,
0x48, 0x99,
};
const sk = try SecretKey.keyGen(ikm[0..], null);
const msg = [_]u8{1} ** 32;
const sig = sign(sk, &msg);
const pk = sk.toPublicKey();
try verify(&msg, &pk, &sig, null, null);
const input_key_material_b: [32]u8 = [_]u8{
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa,
0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
};
const secret_key_a = try SecretKey.keyGen(input_key_material_a[0..], null);
const secret_key_b = try SecretKey.keyGen(input_key_material_b[0..], null);
const message = [_]u8{1} ** 32;
const signature = sign(secret_key_a, &message);
const public_key_b = secret_key_b.toPublicKey();
try std.testing.expectError(bls.BlstError.VerifyFail, verify(&message, &public_key_b, &signature, null, null));
}

var pks = [_]PublicKey{pk};
var pks_slice: []const PublicKey = pks[0..1];
const result = try fastAggregateVerify(&msg, pks_slice[0..], &sig, null, null);
try std.testing.expect(result);
test "bls - fastAggregateVerify false on wrong message" {
const input_key_material: [32]u8 = [_]u8{
0x93, 0xad, 0x7e, 0x65, 0xde, 0xad, 0x05, 0x2a, 0x08, 0x3a,
0x91, 0x0c, 0x8b, 0x72, 0x85, 0x91, 0x46, 0x4c, 0xca, 0x56,
0x60, 0x5b, 0xb0, 0x56, 0xed, 0xfe, 0x2b, 0x60, 0xa6, 0x3c,
0x48, 0x99,
};
const secret_key = try SecretKey.keyGen(input_key_material[0..], null);
var message = [_]u8{1} ** 32;
const signature = sign(secret_key, &message);
const public_key = secret_key.toPublicKey();
message[31] ^= 0xff;
var public_keys = [_]PublicKey{public_key};
const fast_aggregate_verified = try fastAggregateVerify(&message, public_keys[0..], &signature, null, null);
try std.testing.expectEqual(false, fast_aggregate_verified);
}
Loading