From 070d7e71d6587679721437dd5b996478bd17ed85 Mon Sep 17 00:00:00 2001 From: guipublic <47281315+guipublic@users.noreply.github.com> Date: Thu, 6 Jun 2024 11:55:14 +0200 Subject: [PATCH] chore: schnorr signature verification in noir (#5188) # 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. --- .../execution_success/schnorr/src/main.nr | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/test_programs/execution_success/schnorr/src/main.nr b/test_programs/execution_success/schnorr/src/main.nr index 9e2838c34d9..8ff868bc61b 100644 --- a/test_programs/execution_success/schnorr/src/main.nr +++ b/test_programs/execution_success/schnorr/src/main.nr @@ -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( @@ -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 @@ -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( + 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( + 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]); + } }