Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Schnorr signature verification in Noir #5437

Merged
merged 23 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
95181ab
schoor signature verification in Noir
guipublic Jul 8, 2024
f84c67e
format
guipublic Jul 8, 2024
1c72fb3
Merge branch 'master' into gd/noir_schnoor
guipublic Jul 8, 2024
e2c63e1
Merge branch 'master' into gd/noir_schnoor
guipublic Jul 10, 2024
08bc393
Merge branch 'master' into gd/noir_schnoor
guipublic Jul 10, 2024
88f88d6
fix doc issue
guipublic Jul 10, 2024
6ec1c60
Merge branch 'master' into gd/noir_schnoor
guipublic Jul 10, 2024
9efe878
Merge branch 'master' into gd/noir_schnoor
guipublic Jul 10, 2024
c760590
Merge branch 'master' into gd/noir_schnoor
guipublic Jul 10, 2024
37a5ca0
Merge branch 'master' into gd/noir_schnoor
guipublic Jul 11, 2024
5b9f5c6
Merge branch 'master' into gd/noir_schnoor
guipublic Jul 29, 2024
793d0bf
makes the schnorr noir version private.
guipublic Jul 29, 2024
adf2ba3
Merge branch 'master' into gd/noir_schnoor
guipublic Aug 29, 2024
d57eb6f
fix for merge from master
guipublic Aug 29, 2024
3b832f3
Merge branch 'master' into gd/noir_schnoor
TomAFrench Sep 6, 2024
0e0e21c
.
TomAFrench Sep 6, 2024
a7a3961
chore: factor out the challenge calculation
TomAFrench Sep 6, 2024
ad07499
Merge branch 'master' into gd/noir_schnoor
guipublic Sep 19, 2024
11e5d51
fix merge from master
guipublic Sep 19, 2024
e173923
Merge branch 'master' into gd/noir_schnoor
guipublic Sep 19, 2024
6a511fa
Merge branch 'master' into gd/noir_schnoor
guipublic Sep 19, 2024
d19ef43
remove slice version of blake2s
guipublic Sep 19, 2024
7375e51
Merge branch 'master' into gd/noir_schnoor
guipublic Sep 19, 2024
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
15 changes: 15 additions & 0 deletions noir_stdlib/src/embedded_curve_ops.nr
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ impl EmbeddedCurveScalar {
let (a,b) = crate::field::bn254::decompose(scalar);
EmbeddedCurveScalar { lo: a, hi: b }
}

//Bytes to scalar: take the first (after the specified offset) 16 bytes of the input as the lo value, and the next 16 bytes as the hi value
#[field(bn254)]
fn from_bytes(bytes: [u8; 64], offset: u32) -> 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 = crate::embedded_curve_ops::EmbeddedCurveScalar { lo, hi };
sig_s
}
}

impl Eq for EmbeddedCurveScalar {
Expand Down
65 changes: 65 additions & 0 deletions noir_stdlib/src/schnorr.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use crate::collections::vec::Vec;
use crate::embedded_curve_ops::{EmbeddedCurvePoint, EmbeddedCurveScalar};

#[foreign(schnorr_verify)]
// docs:start:schnorr_verify
pub fn verify_signature<let N: u32>(
Expand All @@ -20,3 +23,65 @@ pub fn verify_signature_slice(
// docs:end:schnorr_verify_slice
{}

pub fn verify_signature_noir<let N: u32>(public_key: EmbeddedCurvePoint, signature: [u8; 64], message: [u8; N]) -> bool {
//scalar lo/hi from bytes
let sig_s = EmbeddedCurveScalar::from_bytes(signature, 0);
let sig_e = EmbeddedCurveScalar::from_bytes(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 (r_is_infinite, result) = calculate_signature_challenge(public_key, sig_s, sig_e, message);

is_ok = !r_is_infinite;
for i in 0..32 {
is_ok &= result[i] == signature[32 + i];
}
}
is_ok
}

pub fn assert_valid_signature<let N: u32>(public_key: EmbeddedCurvePoint, signature: [u8; 64], message: [u8; N]) {
//scalar lo/hi from bytes
let sig_s = EmbeddedCurveScalar::from_bytes(signature, 0);
let sig_e = EmbeddedCurveScalar::from_bytes(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 (r_is_infinite, result) = calculate_signature_challenge(public_key, sig_s, sig_e, message);

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

fn calculate_signature_challenge<let N: u32>(
public_key: EmbeddedCurvePoint,
sig_s: EmbeddedCurveScalar,
sig_e: EmbeddedCurveScalar,
message: [u8; N]
) -> (bool, [u8; 32]) {
let g1 = EmbeddedCurvePoint { x: 1, y: 17631683881184975370165255887551781615748388533673675138860, is_infinite: false };
let r = crate::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 = crate::hash::pedersen_hash([r.x, public_key.x, public_key.y]);
let pde: [u8; 32] = pedersen_hash.to_be_bytes();

let mut hash_input = [0; N + 32];
for i in 0..32 {
hash_input[i] = pde[i];
}
for i in 0..N {
hash_input[32+i] = message[i];
}

let result = crate::hash::blake2s(hash_input);
(r.is_infinite, result)
}
104 changes: 2 additions & 102 deletions test_programs/execution_success/schnorr/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ 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: [u8; 10] = message_field.to_be_bytes();
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?
Expand All @@ -27,102 +22,7 @@ fn main(
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);
let valid_signature = std::schnorr::verify_signature_noir(pub_key, signature, message);
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<let M: u32>(
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.x, public_key.x, public_key.y]);
let mut hash_input = [0; M];
let pde: [u8; 32] = pedersen_hash.to_be_bytes();

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.is_infinite;
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<let M: u32>(
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.x, public_key.x, public_key.y]);
let mut hash_input = [0; M];
let pde: [u8; 32] = pedersen_hash.to_be_bytes();

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.is_infinite);
for i in 0..32 {
assert(result[i] == signature[32 + i]);
}
std::schnorr::assert_valid_signature(pub_key, signature, message);
}
Loading