diff --git a/noir_stdlib/src/eddsa.nr b/noir_stdlib/src/eddsa.nr index 39051e23233..657e791e9c7 100644 --- a/noir_stdlib/src/eddsa.nr +++ b/noir_stdlib/src/eddsa.nr @@ -1,25 +1,7 @@ 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 -} + // Returns true if signature is valid pub fn eddsa_poseidon_verify( pub_key_x: Field, @@ -39,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(lt_bytes32(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 df00b3eb653..fbd76a1e8a2 100644 --- a/noir_stdlib/src/field.nr +++ b/noir_stdlib/src/field.nr @@ -1,3 +1,6 @@ +mod bn254; +use bn254::lt as bn254_lt; + impl Field { pub fn to_le_bits(self: Self, bit_size: u32) -> [u1] { crate::assert_constant(bit_size); @@ -74,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)] @@ -105,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 new file mode 100644 index 00000000000..f6e23f8db0c --- /dev/null +++ b/noir_stdlib/src/field/bn254.nr @@ -0,0 +1,92 @@ +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) +} + +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) +} + +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); +} + +pub fn assert_lt(a: Field, b: Field) { + assert_gt(b, a); +} + +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 + } +} + +pub fn lt(a: Field, b: Field) -> bool { + gt(b, a) +} diff --git a/test_programs/compile_success_empty/field_comparisons/Nargo.toml b/test_programs/compile_success_empty/field_comparisons/Nargo.toml new file mode 100644 index 00000000000..e8b06655c58 --- /dev/null +++ b/test_programs/compile_success_empty/field_comparisons/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "field_comparisons" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/compile_success_empty/field_comparisons/Prover.toml b/test_programs/compile_success_empty/field_comparisons/Prover.toml new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/test_programs/compile_success_empty/field_comparisons/Prover.toml @@ -0,0 +1 @@ + diff --git a/test_programs/compile_success_empty/field_comparisons/src/main.nr b/test_programs/compile_success_empty/field_comparisons/src/main.nr new file mode 100644 index 00000000000..48cca6c89fc --- /dev/null +++ b/test_programs/compile_success_empty/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 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 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 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 check_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 check_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 check_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(); + check_decompose_unsafe(); + check_decompose(); + check_lt_unsafe(); + check_lte_unsafe(); + check_assert_gt(); + check_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); +}