Skip to content

Commit

Permalink
chore: schnorr signature verification in noir (#5188)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

Resolves #5054 and #4929

## Summary\*
Schnorr signature verification in Noir, using the MSM blackbox.


## Additional Context
The code is added to the schnorr test case, and it has also an assert
version.
This should be moved to the stdlib once we have numeric generics.
Meanwhile you need to pass a message with 32 additional bytes so we can
create an 'hash_input' array of the proper size.


## Documentation\*

Check one:
- [X] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[For Experimental Features]** Documentation to be submitted in a
separate PR.

# PR Checklist\*

- [X] I have tested the changes locally.
- [X] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
guipublic authored Jun 6, 2024
1 parent ab0b1a8 commit 070d7e7
Showing 1 changed file with 104 additions and 0 deletions.
104 changes: 104 additions & 0 deletions test_programs/execution_success/schnorr/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use dep::std;
use dep::std::embedded_curve_ops;

// Note: If main has any unsized types, then the verifier will never be able
// to figure out the circuit instance
fn main(
Expand All @@ -11,9 +13,12 @@ fn main(
// Regression for issue #2421
// We want to make sure that we can accurately verify a signature whose message is a slice vs. an array
let message_field_bytes = message_field.to_be_bytes(10);
let mut message2 = [0; 42];
for i in 0..10 {
assert(message[i] == message_field_bytes[i]);
message2[i] = message[i];
}

// Is there ever a situation where someone would want
// to ensure that a signature was invalid?
// Check that passing a slice as the message is valid
Expand All @@ -22,4 +27,103 @@ fn main(
// Check that passing an array as the message is valid
let valid_signature = std::schnorr::verify_signature(pub_key_x, pub_key_y, signature, message);
assert(valid_signature);
let pub_key = embedded_curve_ops::EmbeddedCurvePoint { x: pub_key_x, y: pub_key_y, is_infinite: false };
let valid_signature = verify_signature_noir(pub_key, signature, message2);
assert(valid_signature);
assert_valid_signature(pub_key,signature,message2);
}

// TODO: to put in the stdlib once we have numeric generics
// Meanwhile, you have to use a message with 32 additional bytes:
// If you want to verify a signature on a message of 10 bytes, you need to pass a message of length 42,
// where the first 10 bytes are the one from the original message (the other bytes are not used)
pub fn verify_signature_noir<M>(
public_key: embedded_curve_ops::EmbeddedCurvePoint,
signature: [u8; 64],
message: [u8; M]
) -> bool {
let N = message.len() - 32;

//scalar lo/hi from bytes
let sig_s = bytes_to_scalar(signature, 0);
let sig_e = bytes_to_scalar(signature, 32);
// pub_key is on Grumpkin curve
let mut is_ok = (public_key.y * public_key.y == public_key.x * public_key.x * public_key.x - 17)
& (!public_key.is_infinite);

if ((sig_s.lo != 0) | (sig_s.hi != 0)) & ((sig_e.lo != 0) | (sig_e.hi != 0)) {
let g1 = embedded_curve_ops::EmbeddedCurvePoint { x: 1, y: 17631683881184975370165255887551781615748388533673675138860, is_infinite: false };
let r = embedded_curve_ops::multi_scalar_mul([g1, public_key], [sig_s, sig_e]);
// compare the _hashes_ rather than field elements modulo r
let pedersen_hash = std::hash::pedersen_hash([r[0], public_key.x, public_key.y]);
let mut hash_input = [0; M];
let pde = pedersen_hash.to_be_bytes(32);

for i in 0..32 {
hash_input[i] = pde[i];
}
for i in 0..N {
hash_input[32+i] = message[i];
}
let result = std::hash::blake2s(hash_input);

is_ok = (r[2] == 0);
for i in 0..32 {
if result[i] != signature[32 + i] {
is_ok = false;
}
}
}
is_ok
}

pub fn bytes_to_scalar(bytes: [u8; 64], offset: u32) -> embedded_curve_ops::EmbeddedCurveScalar {
let mut v = 1;
let mut lo = 0 as Field;
let mut hi = 0 as Field;
for i in 0..16 {
lo = lo + (bytes[offset+31 - i] as Field) * v;
hi = hi + (bytes[offset+15 - i] as Field) * v;
v = v * 256;
}
let sig_s = embedded_curve_ops::EmbeddedCurveScalar { lo, hi };
sig_s
}

pub fn assert_valid_signature<M>(
public_key: embedded_curve_ops::EmbeddedCurvePoint,
signature: [u8; 64],
message: [u8; M]
) {
let N = message.len() - 32;
//scalar lo/hi from bytes
let sig_s = bytes_to_scalar(signature, 0);
let sig_e = bytes_to_scalar(signature, 32);

// assert pub_key is on Grumpkin curve
assert(public_key.y * public_key.y == public_key.x * public_key.x * public_key.x - 17);
assert(public_key.is_infinite == false);
// assert signature is not null
assert((sig_s.lo != 0) | (sig_s.hi != 0));
assert((sig_e.lo != 0) | (sig_e.hi != 0));

let g1 = embedded_curve_ops::EmbeddedCurvePoint { x: 1, y: 17631683881184975370165255887551781615748388533673675138860, is_infinite: false };
let r = embedded_curve_ops::multi_scalar_mul([g1, public_key], [sig_s, sig_e]);
// compare the _hashes_ rather than field elements modulo r
let pedersen_hash = std::hash::pedersen_hash([r[0], public_key.x, public_key.y]);
let mut hash_input = [0; M];
let pde = pedersen_hash.to_be_bytes(32);

for i in 0..32 {
hash_input[i] = pde[i];
}
for i in 0..N {
hash_input[32+i] = message[i];
}
let result = std::hash::blake2s(hash_input);

assert(r[2] == 0);
for i in 0..32 {
assert(result[i] == signature[32 + i]);
}
}

0 comments on commit 070d7e7

Please sign in to comment.