diff --git a/.github/NIGHTLY_CANARY_DIED.md b/.github/NIGHTLY_CANARY_DIED.md index 4b20ca0..4300ee3 100644 --- a/.github/NIGHTLY_CANARY_DIED.md +++ b/.github/NIGHTLY_CANARY_DIED.md @@ -1,7 +1,8 @@ --- title: "Tests fail on latest Nargo nightly release" +assignees: TomAFrench, kashbrti, jtriley-eth --- The tests on this Noir project have started failing when using the latest nightly release of the Noir compiler. This likely means that there have been breaking changes for which this project needs to be updated to take into account. -Check the [{{env.WORKFLOW_NAME}}]({{env.WORKFLOW_URL}}) workflow for details. \ No newline at end of file +Check the [{{env.WORKFLOW_NAME}}]({{env.WORKFLOW_URL}}) workflow for details. diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 850ac38..3a50e0f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -17,12 +17,12 @@ jobs: - name: Install Nargo uses: noir-lang/noirup@v0.1.3 with: - toolchain: 0.34.0 + toolchain: 1.0.0-beta.0 - name: Install bb run: | npm install -g bbup - bbup -nv 0.34.0 + bbup -nv 1.0.0-beta.0 - name: Build Noir benchmark programs run: nargo export diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1dec562..11f936c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - toolchain: [nightly, 0.34.0] + toolchain: [nightly, 1.0.0-beta.0] steps: - name: Checkout sources uses: actions/checkout@v4 @@ -38,7 +38,7 @@ jobs: - name: Install Nargo uses: noir-lang/noirup@v0.1.3 with: - toolchain: 0.34.0 + toolchain: 1.0.0-beta.0 - name: Run formatter run: nargo fmt --check diff --git a/Nargo.toml b/Nargo.toml index ca37ec6..63505a4 100644 --- a/Nargo.toml +++ b/Nargo.toml @@ -1,7 +1,7 @@ [package] -name = "noir_library" +name = "schnorr" type = "lib" authors = [""] -compiler_version = ">=0.34.0" +compiler_version = ">=1.0.0" [dependencies] diff --git a/README.md b/README.md index 7e63393..3742f67 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,10 @@ -# noir-library-starter +# Schnorr -This repository is a template used by the noir-lang org when creating internally maintained libraries. - -This provides out of the box: - -- A simple CI setup to test and format the library -- A canary flagging up compilation failures on nightly releases. -- A [release-please](https://github.com/googleapis/release-please) setup to ease creating releases for the library. - -Feel free to use this template as a starting point to create your own Noir libraries. - ---- - -# LIBRARY_NAME - -Add a brief description of the library +A Noir implementation for verification of Schnorr signatures. ## Noir version compatibility -TODO: Update Noir version numbers tested compatible with - -This library is tested to work as of Noir version 1.0.0-beta.x. +This library is tested to work as of Noir version 1.0.0-beta.0. ## Benchmarks diff --git a/src/lib.nr b/src/lib.nr index 80b968c..a2b4093 100644 --- a/src/lib.nr +++ b/src/lib.nr @@ -1,15 +1,151 @@ -/// This doesn't really do anything by ensures that there is a test for CI to run. -#[test] -fn smoke_test() { - assert(true); +use std::embedded_curve_ops::{EmbeddedCurvePoint, EmbeddedCurveScalar, multi_scalar_mul}; +use std::hash::{blake2s, pedersen_hash}; + +pub fn verify_signature( + public_key: EmbeddedCurvePoint, + signature: [u8; 64], + message: [u8; N], +) -> bool { + //scalar lo/hi from bytes + let sig_s = scalar_from_bytes(signature, 0); + let sig_e = scalar_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]; + } + } else { + is_ok = false; + } + is_ok +} + +pub fn assert_valid_signature( + public_key: EmbeddedCurvePoint, + signature: [u8; 64], + message: [u8; N], +) { + //scalar lo/hi from bytes + let sig_s = scalar_from_bytes(signature, 0); + let sig_e = scalar_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( + 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 = multi_scalar_mul([g1, public_key], [sig_s, sig_e]); + // compare the _hashes_ rather than field elements modulo r + let pedersen_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 = blake2s(hash_input); + (r.is_infinite, result) } -// This is an example benchmark. -// Changes to the number of constraints generated by this function will show in PRs. -#[export] -fn bench_test(mut x: Field) -> Field { - for _ in 0..100 { - x *= x; +//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 +fn scalar_from_bytes(bytes: [u8; 64], offset: u32) -> EmbeddedCurveScalar { + let mut v: Field = 1; + let mut lo: Field = 0; + let mut hi: Field = 0; + 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; } - x + let sig_s = EmbeddedCurveScalar::new(lo, hi); + sig_s +} + +mod test { + use std::embedded_curve_ops::EmbeddedCurvePoint; + + use super::verify_signature; + + #[test] + fn test_zero_signature() { + let public_key: EmbeddedCurvePoint = EmbeddedCurvePoint { + x: 1, + y: 17631683881184975370165255887551781615748388533673675138860, + is_infinite: false, + }; + let signature: [u8; 64] = [0; 64]; + let message: [u8; _] = [2; 64]; // every message + let verified = verify_signature(public_key, signature, message); + assert(!verified); + } + + #[test] + fn smoke_test() { + let message: [u8; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let message_field: Field = 0x010203040506070809; + let pub_key_x: Field = 0x04b260954662e97f00cab9adb773a259097f7a274b83b113532bce27fa3fb96a; + let pub_key_y: Field = 0x2fd51571db6c08666b0edfbfbc57d432068bccd0110a39b166ab243da0037197; + let signature: [u8; 64] = [ + 1, 13, 119, 112, 212, 39, 233, 41, 84, 235, 255, 93, 245, 172, 186, 83, 157, 253, 76, + 77, 33, 128, 178, 15, 214, 67, 105, 107, 177, 234, 77, 48, 27, 237, 155, 84, 39, 84, + 247, 27, 22, 8, 176, 230, 24, 115, 145, 220, 254, 122, 135, 179, 171, 4, 214, 202, 64, + 199, 19, 84, 239, 138, 124, 12, + ]; + + // 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(); + + // 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 + let valid_signature = std::schnorr::verify_signature_slice( + pub_key_x, + pub_key_y, + signature, + message_field_bytes, + ); + assert(valid_signature); + // 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 = EmbeddedCurvePoint { x: pub_key_x, y: pub_key_y, is_infinite: false }; + let valid_signature = std::schnorr::verify_signature_noir(pub_key, signature, message); + assert(valid_signature); + std::schnorr::assert_valid_signature(pub_key, signature, message); + } + }