Skip to content

Commit

Permalink
chore: Schnorr signature verification in Noir (#5437)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

Implement Schnorr signature verification in Noir

## Summary\*
Using slices to prepend the message with the hash.


## Additional Context



## 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.

---------

Co-authored-by: Tom French <tom@tomfren.ch>
  • Loading branch information
guipublic and TomAFrench authored Sep 19, 2024
1 parent 4170c55 commit 023cd04
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 102 deletions.
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);
}

0 comments on commit 023cd04

Please sign in to comment.