From 5fbb1603d82ddf5874cfd5b656f1f66ec9d779aa Mon Sep 17 00:00:00 2001 From: sirasistant Date: Mon, 15 Jan 2024 12:31:25 +0000 Subject: [PATCH 1/4] feat: added efficient field comparisons --- noir_stdlib/src/eddsa.nr | 23 +---- noir_stdlib/src/field.nr | 2 + noir_stdlib/src/field/bn254.nr | 97 +++++++++++++++++++ .../field_comparisons/Nargo.toml | 6 ++ .../field_comparisons/Prover.toml | 1 + .../field_comparisons/src/main.nr | 86 ++++++++++++++++ .../field_comparisons/Nargo.toml | 5 + .../field_comparisons/Prover.toml | 0 .../field_comparisons/src/main.nr | 16 +++ 9 files changed, 216 insertions(+), 20 deletions(-) create mode 100644 noir_stdlib/src/field/bn254.nr create mode 100644 test_programs/execution_success/field_comparisons/Nargo.toml create mode 100644 test_programs/execution_success/field_comparisons/Prover.toml create mode 100644 test_programs/execution_success/field_comparisons/src/main.nr create mode 100644 test_programs/noir_test_success/field_comparisons/Nargo.toml create mode 100644 test_programs/noir_test_success/field_comparisons/Prover.toml create mode 100644 test_programs/noir_test_success/field_comparisons/src/main.nr diff --git a/noir_stdlib/src/eddsa.nr b/noir_stdlib/src/eddsa.nr index 39051e23233..b1b37b851ad 100644 --- a/noir_stdlib/src/eddsa.nr +++ b/noir_stdlib/src/eddsa.nr @@ -1,25 +1,8 @@ use crate::hash::poseidon; use crate::ec::consts::te::baby_jubjub; use crate::ec::tecurve::affine::Point as TEPoint; -// Returns true if x is less than y -fn lt_bytes32(x: Field, y: Field) -> bool { - let x_bytes = x.to_le_bytes(32); - let y_bytes = y.to_le_bytes(32); - let mut x_is_lt = false; - let mut done = false; - for i in 0..32 { - if (!done) { - let x_byte = x_bytes[31 - i] as u8; - let y_byte = y_bytes[31 - i] as u8; - let bytes_match = x_byte == y_byte; - if !bytes_match { - x_is_lt = x_byte < y_byte; - done = true; - } - } - } - x_is_lt -} +use crate::field::bn254::lt as field_lt; + // Returns true if signature is valid pub fn eddsa_poseidon_verify( pub_key_x: Field, @@ -39,7 +22,7 @@ pub fn eddsa_poseidon_verify( let signature_r8 = TEPoint::new(signature_r8_x, signature_r8_y); assert(bjj.curve.contains(signature_r8)); // Ensure S < Subgroup Order - assert(lt_bytes32(signature_s, bjj.suborder)); + assert(field_lt(signature_s, bjj.suborder)); // Calculate the h = H(R, A, msg) let hash: Field = poseidon::bn254::hash_5([signature_r8_x, signature_r8_y, pub_key_x, pub_key_y, message]); // Calculate second part of the right side: right2 = h*8*A diff --git a/noir_stdlib/src/field.nr b/noir_stdlib/src/field.nr index df00b3eb653..e36feda553d 100644 --- a/noir_stdlib/src/field.nr +++ b/noir_stdlib/src/field.nr @@ -1,3 +1,5 @@ +mod bn254; + impl Field { pub fn to_le_bits(self: Self, bit_size: u32) -> [u1] { crate::assert_constant(bit_size); diff --git a/noir_stdlib/src/field/bn254.nr b/noir_stdlib/src/field/bn254.nr new file mode 100644 index 00000000000..209d9eabc7d --- /dev/null +++ b/noir_stdlib/src/field/bn254.nr @@ -0,0 +1,97 @@ +global PLO: Field = 53438638232309528389504892708671455233; +global PHI: Field = 64323764613183177041862057485226039389; +global TWO_POW_128: Field = 0x100000000000000000000000000000000; + +unconstrained fn decompose_unsafe(x: Field) -> (Field, Field) { + let x_bytes = x.to_le_bytes(32); + + let mut low: Field = 0; + let mut high: Field = 0; + + let mut offset = 1; + for i in 0..16 { + low += (x_bytes[i] as Field) * offset; + high += (x_bytes[i + 16] as Field) * offset; + offset *= 256; + } + + (low, high) +} + +#[field(bn254)] +pub fn decompose(x: Field) -> (Field, Field) { + let (xlo, xhi) = decompose_unsafe(x); + let borrow = lt_unsafe(PLO, xlo, 16); + + xlo.assert_max_bit_size(128); + xhi.assert_max_bit_size(128); + + assert_eq(x, xlo + TWO_POW_128 * xhi); + let rlo = PLO - xlo + (borrow as Field) * TWO_POW_128; + let rhi = PHI - xhi - (borrow as Field); + + rlo.assert_max_bit_size(128); + rhi.assert_max_bit_size(128); + + (xlo, xhi) +} + +unconstrained fn lt_unsafe(x: Field, y: Field, num_bytes: u32) -> bool { + let x_bytes = x.__to_le_radix(256, num_bytes); + let y_bytes = y.__to_le_radix(256, num_bytes); + let mut x_is_lt = false; + let mut done = false; + for i in 0..num_bytes { + if (!done) { + let x_byte = x_bytes[num_bytes - 1 - i]; + let y_byte = y_bytes[num_bytes - 1 - i]; + let bytes_match = x_byte == y_byte; + if !bytes_match { + x_is_lt = x_byte < y_byte; + done = true; + } + } + } + x_is_lt +} + +unconstrained fn lte_unsafe(x: Field, y: Field, num_bytes: u32) -> bool { + lt_unsafe(x, y, num_bytes) | (x == y) +} + +#[field(bn254)] +pub fn assert_gt(a: Field, b: Field) { + let (alo, ahi) = decompose(a); + let (blo, bhi) = decompose(b); + + let borrow = lte_unsafe(alo, blo, 16); + + let rlo = alo - blo - 1 + (borrow as Field) * TWO_POW_128; + let rhi = ahi - bhi - (borrow as Field); + + rlo.assert_max_bit_size(128); + rhi.assert_max_bit_size(128); +} + +#[field(bn254)] +pub fn assert_lt(a: Field, b: Field) { + assert_gt(b, a); +} + +#[field(bn254)] +pub fn gt(a: Field, b: Field) -> bool { + if a == b { + false + } else if lt_unsafe(a, b, 32) { + assert_gt(b, a); + false + } else { + assert_gt(a, b); + true + } +} + +#[field(bn254)] +pub fn lt(a: Field, b: Field) -> bool { + gt(b, a) +} diff --git a/test_programs/execution_success/field_comparisons/Nargo.toml b/test_programs/execution_success/field_comparisons/Nargo.toml new file mode 100644 index 00000000000..e8b06655c58 --- /dev/null +++ b/test_programs/execution_success/field_comparisons/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "field_comparisons" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/execution_success/field_comparisons/Prover.toml b/test_programs/execution_success/field_comparisons/Prover.toml new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/test_programs/execution_success/field_comparisons/Prover.toml @@ -0,0 +1 @@ + diff --git a/test_programs/execution_success/field_comparisons/src/main.nr b/test_programs/execution_success/field_comparisons/src/main.nr new file mode 100644 index 00000000000..1ff4fef719a --- /dev/null +++ b/test_programs/execution_success/field_comparisons/src/main.nr @@ -0,0 +1,86 @@ +use dep::std::field::bn254::{PLO, PHI, TWO_POW_128, decompose, decompose_unsafe, lt_unsafe, lte_unsafe, assert_gt, gt}; + +fn check_plo_phi() { + assert_eq(PLO + PHI * TWO_POW_128, 0); + let p_bytes = dep::std::field::modulus_le_bytes(); + let mut p_low: Field = 0; + let mut p_high: Field = 0; + + let mut offset = 1; + for i in 0..16 { + p_low += (p_bytes[i] as Field) * offset; + p_high += (p_bytes[i + 16] as Field) * offset; + offset *= 256; + } + assert_eq(p_low, PLO); + assert_eq(p_high, PHI); +} + +fn test_decompose_unsafe() { + assert_eq(decompose_unsafe(TWO_POW_128), (0, 1)); + assert_eq(decompose_unsafe(TWO_POW_128 + 0x1234567890), (0x1234567890, 1)); + assert_eq(decompose_unsafe(0x1234567890), (0x1234567890, 0)); +} + +fn test_decompose() { + assert_eq(decompose(TWO_POW_128), (0, 1)); + assert_eq(decompose(TWO_POW_128 + 0x1234567890), (0x1234567890, 1)); + assert_eq(decompose(0x1234567890), (0x1234567890, 0)); +} + +fn test_lt_unsafe() { + assert(lt_unsafe(0, 1, 16)); + assert(lt_unsafe(0, 0x100, 16)); + assert(lt_unsafe(0x100, TWO_POW_128 - 1, 16)); + assert(!lt_unsafe(0, TWO_POW_128, 16)); +} + +fn test_lte_unsafe() { + assert(lte_unsafe(0, 1, 16)); + assert(lte_unsafe(0, 0x100, 16)); + assert(lte_unsafe(0x100, TWO_POW_128 - 1, 16)); + assert(!lte_unsafe(0, TWO_POW_128, 16)); + + assert(lte_unsafe(0, 0, 16)); + assert(lte_unsafe(0x100, 0x100, 16)); + assert(lte_unsafe(TWO_POW_128 - 1, TWO_POW_128 - 1, 16)); + assert(lte_unsafe(TWO_POW_128, TWO_POW_128, 16)); +} + +fn test_assert_gt() { + assert_gt(1, 0); + assert_gt(0x100, 0); + assert_gt((0 - 1), (0 - 2)); + assert_gt(TWO_POW_128, 0); + assert_gt(0 - 1, 0); +} + +fn test_gt() { + assert(gt(1, 0)); + assert(gt(0x100, 0)); + assert(gt((0 - 1), (0 - 2))); + assert(gt(TWO_POW_128, 0)); + assert(!gt(0, 0)); + assert(!gt(0, 0x100)); + assert(gt(0 - 1, 0 - 2)); + assert(!gt(0 - 2, 0 - 1)); +} + +fn checks() { + check_plo_phi(); + test_decompose_unsafe(); + test_decompose(); + test_lt_unsafe(); + test_lte_unsafe(); + test_assert_gt(); + test_gt(); +} + +unconstrained fn checks_in_brillig() { + checks(); +} + +fn main() { + checks(); + checks_in_brillig(); +} diff --git a/test_programs/noir_test_success/field_comparisons/Nargo.toml b/test_programs/noir_test_success/field_comparisons/Nargo.toml new file mode 100644 index 00000000000..e819464ca68 --- /dev/null +++ b/test_programs/noir_test_success/field_comparisons/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "field_comparisons" +type = "bin" +authors = [""] +[dependencies] diff --git a/test_programs/noir_test_success/field_comparisons/Prover.toml b/test_programs/noir_test_success/field_comparisons/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test_programs/noir_test_success/field_comparisons/src/main.nr b/test_programs/noir_test_success/field_comparisons/src/main.nr new file mode 100644 index 00000000000..105d82ca755 --- /dev/null +++ b/test_programs/noir_test_success/field_comparisons/src/main.nr @@ -0,0 +1,16 @@ +use dep::std::field::bn254::{TWO_POW_128, assert_gt}; + +#[test(should_fail)] +fn test_assert_gt_should_fail_eq() { + assert_gt(0, 0); +} + +#[test(should_fail)] +fn test_assert_gt_should_fail_low_lt() { + assert_gt(0, 0x100); +} + +#[test(should_fail)] +fn test_assert_gt_should_fail_high_lt() { + assert_gt(TWO_POW_128, TWO_POW_128 + 0x100); +} From 86d1a2ebbdeb4b1db64162bf6f1b13d167f770ba Mon Sep 17 00:00:00 2001 From: sirasistant Date: Mon, 15 Jan 2024 12:53:44 +0000 Subject: [PATCH 2/4] style: consistent naming --- .../field_comparisons/src/main.nr | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test_programs/execution_success/field_comparisons/src/main.nr b/test_programs/execution_success/field_comparisons/src/main.nr index 1ff4fef719a..48cca6c89fc 100644 --- a/test_programs/execution_success/field_comparisons/src/main.nr +++ b/test_programs/execution_success/field_comparisons/src/main.nr @@ -16,26 +16,26 @@ fn check_plo_phi() { assert_eq(p_high, PHI); } -fn test_decompose_unsafe() { +fn check_decompose_unsafe() { assert_eq(decompose_unsafe(TWO_POW_128), (0, 1)); assert_eq(decompose_unsafe(TWO_POW_128 + 0x1234567890), (0x1234567890, 1)); assert_eq(decompose_unsafe(0x1234567890), (0x1234567890, 0)); } -fn test_decompose() { +fn check_decompose() { assert_eq(decompose(TWO_POW_128), (0, 1)); assert_eq(decompose(TWO_POW_128 + 0x1234567890), (0x1234567890, 1)); assert_eq(decompose(0x1234567890), (0x1234567890, 0)); } -fn test_lt_unsafe() { +fn check_lt_unsafe() { assert(lt_unsafe(0, 1, 16)); assert(lt_unsafe(0, 0x100, 16)); assert(lt_unsafe(0x100, TWO_POW_128 - 1, 16)); assert(!lt_unsafe(0, TWO_POW_128, 16)); } -fn test_lte_unsafe() { +fn check_lte_unsafe() { assert(lte_unsafe(0, 1, 16)); assert(lte_unsafe(0, 0x100, 16)); assert(lte_unsafe(0x100, TWO_POW_128 - 1, 16)); @@ -47,7 +47,7 @@ fn test_lte_unsafe() { assert(lte_unsafe(TWO_POW_128, TWO_POW_128, 16)); } -fn test_assert_gt() { +fn check_assert_gt() { assert_gt(1, 0); assert_gt(0x100, 0); assert_gt((0 - 1), (0 - 2)); @@ -55,7 +55,7 @@ fn test_assert_gt() { assert_gt(0 - 1, 0); } -fn test_gt() { +fn check_gt() { assert(gt(1, 0)); assert(gt(0x100, 0)); assert(gt((0 - 1), (0 - 2))); @@ -68,12 +68,12 @@ fn test_gt() { fn checks() { check_plo_phi(); - test_decompose_unsafe(); - test_decompose(); - test_lt_unsafe(); - test_lte_unsafe(); - test_assert_gt(); - test_gt(); + check_decompose_unsafe(); + check_decompose(); + check_lt_unsafe(); + check_lte_unsafe(); + check_assert_gt(); + check_gt(); } unconstrained fn checks_in_brillig() { From 9c1d610920f939a2e789cb83cd78e9e1c78f7301 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Mon, 15 Jan 2024 13:20:50 +0000 Subject: [PATCH 3/4] fix: move test to empty result folder --- .../field_comparisons/Nargo.toml | 0 .../field_comparisons/Prover.toml | 0 .../field_comparisons/src/main.nr | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename test_programs/{execution_success => compile_success_empty}/field_comparisons/Nargo.toml (100%) rename test_programs/{execution_success => compile_success_empty}/field_comparisons/Prover.toml (100%) rename test_programs/{execution_success => compile_success_empty}/field_comparisons/src/main.nr (100%) diff --git a/test_programs/execution_success/field_comparisons/Nargo.toml b/test_programs/compile_success_empty/field_comparisons/Nargo.toml similarity index 100% rename from test_programs/execution_success/field_comparisons/Nargo.toml rename to test_programs/compile_success_empty/field_comparisons/Nargo.toml diff --git a/test_programs/execution_success/field_comparisons/Prover.toml b/test_programs/compile_success_empty/field_comparisons/Prover.toml similarity index 100% rename from test_programs/execution_success/field_comparisons/Prover.toml rename to test_programs/compile_success_empty/field_comparisons/Prover.toml diff --git a/test_programs/execution_success/field_comparisons/src/main.nr b/test_programs/compile_success_empty/field_comparisons/src/main.nr similarity index 100% rename from test_programs/execution_success/field_comparisons/src/main.nr rename to test_programs/compile_success_empty/field_comparisons/src/main.nr From f1005fd306354445e663c56cfb180233f9d750de Mon Sep 17 00:00:00 2001 From: sirasistant Date: Mon, 15 Jan 2024 13:43:26 +0000 Subject: [PATCH 4/4] feat: add generic lt that checks if the field is bn254 --- noir_stdlib/src/eddsa.nr | 3 +-- noir_stdlib/src/field.nr | 31 +++++++++++++++++++++++++++++++ noir_stdlib/src/field/bn254.nr | 5 ----- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/noir_stdlib/src/eddsa.nr b/noir_stdlib/src/eddsa.nr index b1b37b851ad..657e791e9c7 100644 --- a/noir_stdlib/src/eddsa.nr +++ b/noir_stdlib/src/eddsa.nr @@ -1,7 +1,6 @@ use crate::hash::poseidon; use crate::ec::consts::te::baby_jubjub; use crate::ec::tecurve::affine::Point as TEPoint; -use crate::field::bn254::lt as field_lt; // Returns true if signature is valid pub fn eddsa_poseidon_verify( @@ -22,7 +21,7 @@ pub fn eddsa_poseidon_verify( let signature_r8 = TEPoint::new(signature_r8_x, signature_r8_y); assert(bjj.curve.contains(signature_r8)); // Ensure S < Subgroup Order - assert(field_lt(signature_s, bjj.suborder)); + assert(signature_s.lt(bjj.suborder)); // Calculate the h = H(R, A, msg) let hash: Field = poseidon::bn254::hash_5([signature_r8_x, signature_r8_y, pub_key_x, pub_key_y, message]); // Calculate second part of the right side: right2 = h*8*A diff --git a/noir_stdlib/src/field.nr b/noir_stdlib/src/field.nr index e36feda553d..fbd76a1e8a2 100644 --- a/noir_stdlib/src/field.nr +++ b/noir_stdlib/src/field.nr @@ -1,4 +1,5 @@ mod bn254; +use bn254::lt as bn254_lt; impl Field { pub fn to_le_bits(self: Self, bit_size: u32) -> [u1] { @@ -76,6 +77,15 @@ impl Field { pub fn sgn0(self) -> u1 { self as u1 } + + pub fn lt(self, another: Field) -> bool { + if crate::compat::is_bn254() { + bn254_lt(self, another) + } else { + lt_fallback(self, another) + } + } + } #[builtin(modulus_num_bits)] @@ -107,3 +117,24 @@ pub fn bytes32_to_field(bytes32: [u8; 32]) -> Field { // Abuse that a % p + b % p = (a + b) % p and that low < p low + high * v } + +fn lt_fallback(x: Field, y: Field) -> bool { + let num_bytes = (modulus_num_bits() as u32 + 7) / 8; + let x_bytes = x.to_le_bytes(num_bytes); + let y_bytes = y.to_le_bytes(num_bytes); + let mut x_is_lt = false; + let mut done = false; + for i in 0..num_bytes { + if (!done) { + let x_byte = x_bytes[num_bytes - 1 - i] as u8; + let y_byte = y_bytes[num_bytes - 1 - i] as u8; + let bytes_match = x_byte == y_byte; + if !bytes_match { + x_is_lt = x_byte < y_byte; + done = true; + } + } + } + x_is_lt +} + diff --git a/noir_stdlib/src/field/bn254.nr b/noir_stdlib/src/field/bn254.nr index 209d9eabc7d..f6e23f8db0c 100644 --- a/noir_stdlib/src/field/bn254.nr +++ b/noir_stdlib/src/field/bn254.nr @@ -18,7 +18,6 @@ unconstrained fn decompose_unsafe(x: Field) -> (Field, Field) { (low, high) } -#[field(bn254)] pub fn decompose(x: Field) -> (Field, Field) { let (xlo, xhi) = decompose_unsafe(x); let borrow = lt_unsafe(PLO, xlo, 16); @@ -59,7 +58,6 @@ unconstrained fn lte_unsafe(x: Field, y: Field, num_bytes: u32) -> bool { lt_unsafe(x, y, num_bytes) | (x == y) } -#[field(bn254)] pub fn assert_gt(a: Field, b: Field) { let (alo, ahi) = decompose(a); let (blo, bhi) = decompose(b); @@ -73,12 +71,10 @@ pub fn assert_gt(a: Field, b: Field) { rhi.assert_max_bit_size(128); } -#[field(bn254)] pub fn assert_lt(a: Field, b: Field) { assert_gt(b, a); } -#[field(bn254)] pub fn gt(a: Field, b: Field) -> bool { if a == b { false @@ -91,7 +87,6 @@ pub fn gt(a: Field, b: Field) -> bool { } } -#[field(bn254)] pub fn lt(a: Field, b: Field) -> bool { gt(b, a) }