diff --git a/noir/.github/scripts/noir-wasm-build.sh b/noir/.github/scripts/noir-wasm-build.sh index ccd49863f77..7c7e86e8c03 100755 --- a/noir/.github/scripts/noir-wasm-build.sh +++ b/noir/.github/scripts/noir-wasm-build.sh @@ -3,5 +3,4 @@ set -eu .github/scripts/noirc-abi-build.sh -.github/scripts/install_wasm-bindgen.sh yarn workspace @noir-lang/noir_wasm build diff --git a/noir/.github/workflows/test-js-packages.yml b/noir/.github/workflows/test-js-packages.yml index 7f8c5df3139..addc9ce3d83 100644 --- a/noir/.github/workflows/test-js-packages.yml +++ b/noir/.github/workflows/test-js-packages.yml @@ -70,11 +70,6 @@ jobs: name: noirc_abi_wasm path: ./tooling/noirc_abi_wasm - - name: Install wasm-pack - uses: taiki-e/install-action@v2 - with: - tool: wasm-pack@0.12.1 - - name: Install Yarn dependencies uses: ./.github/actions/setup diff --git a/noir/.gitrepo b/noir/.gitrepo index 13b9dfced35..70fa5fdcf31 100644 --- a/noir/.gitrepo +++ b/noir/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/noir-lang/noir branch = aztec-packages - commit = 45165d10a63bb9254549c5eeefddccd5dd0efb17 - parent = 4cdc0963777de138bf5275dd657a738ae6f020d3 + commit = 9995d2ba22c123a2529b1783d7ed098889a33732 + parent = cc4b9c636241c5ac8abee6744c96960c40803b07 method = merge cmdver = 0.4.6 diff --git a/noir/Dockerfile.packages b/noir/Dockerfile.packages index 17eb0bcd648..f40670c19e4 100644 --- a/noir/Dockerfile.packages +++ b/noir/Dockerfile.packages @@ -2,14 +2,15 @@ FROM rust:alpine3.17 RUN apk update \ && apk upgrade \ && apk add --no-cache \ - build-base \ - pkgconfig \ - openssl-dev \ - npm \ - yarn \ - bash \ - jq \ - git + build-base \ + pkgconfig \ + openssl-dev \ + npm \ + yarn \ + bash \ + jq \ + git \ + curl WORKDIR /usr/src/noir COPY . . @@ -18,4 +19,4 @@ RUN ./scripts/bootstrap_packages.sh FROM scratch COPY --from=0 /usr/src/noir/packages /usr/src/noir/packages # For some unknown reason, on alpine only, we need this to exist. -COPY --from=0 /usr/src/noir/node_modules/@noir-lang /usr/src/noir/node_modules/@noir-lang \ No newline at end of file +COPY --from=0 /usr/src/noir/node_modules/@noir-lang /usr/src/noir/node_modules/@noir-lang diff --git a/noir/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/noir/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 73f30054cce..efc64c5286e 100644 --- a/noir/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/noir/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -31,7 +31,7 @@ pub(crate) struct GeneratedAcir { /// If witness index is `None` then we have not yet created a witness /// and thus next witness index that be declared is zero. /// This field is private should only ever be accessed through its getter and setter. - /// + /// /// Equivalent to acvm::acir::circuit::Circuit's field of the same name. current_witness_index: Option, diff --git a/noir/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/noir/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 35782ea85ae..852848afb81 100644 --- a/noir/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/noir/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -313,9 +313,7 @@ impl FunctionBuilder { let bit_size_var = self.numeric_constant(FieldElement::from(bit_size as u128), typ.clone()); let overflow = self.insert_binary(rhs, BinaryOp::Lt, bit_size_var); - let one = self.numeric_constant(FieldElement::one(), Type::unsigned(1)); - let predicate = self.insert_binary(overflow, BinaryOp::Eq, one); - let predicate = self.insert_cast(predicate, typ.clone()); + let predicate = self.insert_cast(overflow, typ.clone()); // we can safely cast to unsigned because overflow_checks prevent bit-shift with a negative value let rhs_unsigned = self.insert_cast(rhs, Type::unsigned(bit_size)); let pow = self.pow(base, rhs_unsigned); diff --git a/noir/compiler/noirc_frontend/src/resolve_locations.rs b/noir/compiler/noirc_frontend/src/resolve_locations.rs index bfacee0ef96..95ced906984 100644 --- a/noir/compiler/noirc_frontend/src/resolve_locations.rs +++ b/noir/compiler/noirc_frontend/src/resolve_locations.rs @@ -75,6 +75,7 @@ impl NodeInterner { Some(self.function_meta(&func_id).location) } DefinitionKind::Local(_local_id) => Some(definition_info.location), + DefinitionKind::Global(_global_id) => Some(definition_info.location), _ => None, } } diff --git a/noir/compiler/wasm/package.json b/noir/compiler/wasm/package.json index 4a71d8aa77d..a8abf26a7e2 100644 --- a/noir/compiler/wasm/package.json +++ b/noir/compiler/wasm/package.json @@ -29,9 +29,10 @@ "url": "https://github.com/noir-lang/noir/issues" }, "scripts": { - "build": "WASM_OPT=$(bash -c ./wasm-opt-check.sh) webpack", + "install:wasm_pack": "./scripts/install_wasm-pack.sh", + "build": "yarn install:wasm_pack && WASM_OPT=$(./scripts/command-check.sh wasm-opt) webpack", "test": "yarn test:build_fixtures && yarn test:node && yarn test:browser", - "test:build_fixtures": "./build-fixtures.sh", + "test:build_fixtures": "./scripts/build-fixtures.sh", "test:browser": "web-test-runner", "test:node": "NODE_NO_WARNINGS=1 mocha --config ./.mocharc.json", "clean": "rm -rf ./build ./target ./dist public/fixtures/simple/target public/fixtures/with-deps/target", diff --git a/noir/compiler/wasm/build-fixtures.sh b/noir/compiler/wasm/scripts/build-fixtures.sh similarity index 100% rename from noir/compiler/wasm/build-fixtures.sh rename to noir/compiler/wasm/scripts/build-fixtures.sh diff --git a/noir/compiler/wasm/scripts/command-check.sh b/noir/compiler/wasm/scripts/command-check.sh new file mode 100755 index 00000000000..51d342a8bd0 --- /dev/null +++ b/noir/compiler/wasm/scripts/command-check.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -eu + +cd $(dirname "$0")/.. + +command -v $1 >/dev/null 2>&1 && echo "true" || { echo >&2 "$1 is not installed" && echo "false"; } \ No newline at end of file diff --git a/noir/compiler/wasm/scripts/install_wasm-pack.sh b/noir/compiler/wasm/scripts/install_wasm-pack.sh new file mode 100755 index 00000000000..28721e62fe2 --- /dev/null +++ b/noir/compiler/wasm/scripts/install_wasm-pack.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -eu + +cd $(dirname "$0")/.. + +# Install wasm-pack +CARGO_BINSTALL_CHECK=$(./scripts/command-check.sh cargo-binstall) +if [ $CARGO_BINSTALL_CHECK != "true" ]; then + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash +fi + +cargo-binstall wasm-pack@0.12.1 -y \ No newline at end of file diff --git a/noir/compiler/wasm/wasm-opt-check.sh b/noir/compiler/wasm/wasm-opt-check.sh deleted file mode 100755 index 8612b48d3f1..00000000000 --- a/noir/compiler/wasm/wasm-opt-check.sh +++ /dev/null @@ -1 +0,0 @@ -command -v wasm-opt >/dev/null 2>&1 && echo "true" || { echo >&2 "wasm-opt is not installed, building in dev mode" && echo "false"; exit 1; } \ No newline at end of file diff --git a/noir/noir_stdlib/src/eddsa.nr b/noir/noir_stdlib/src/eddsa.nr index 39051e23233..657e791e9c7 100644 --- a/noir/noir_stdlib/src/eddsa.nr +++ b/noir/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/noir_stdlib/src/field.nr b/noir/noir_stdlib/src/field.nr index df00b3eb653..fbd76a1e8a2 100644 --- a/noir/noir_stdlib/src/field.nr +++ b/noir/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/noir_stdlib/src/field/bn254.nr b/noir/noir_stdlib/src/field/bn254.nr new file mode 100644 index 00000000000..f6e23f8db0c --- /dev/null +++ b/noir/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/noir/test_programs/compile_success_empty/field_comparisons/Nargo.toml b/noir/test_programs/compile_success_empty/field_comparisons/Nargo.toml new file mode 100644 index 00000000000..e8b06655c58 --- /dev/null +++ b/noir/test_programs/compile_success_empty/field_comparisons/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "field_comparisons" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/test_programs/compile_success_empty/field_comparisons/Prover.toml b/noir/test_programs/compile_success_empty/field_comparisons/Prover.toml new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/noir/test_programs/compile_success_empty/field_comparisons/Prover.toml @@ -0,0 +1 @@ + diff --git a/noir/test_programs/compile_success_empty/field_comparisons/src/main.nr b/noir/test_programs/compile_success_empty/field_comparisons/src/main.nr new file mode 100644 index 00000000000..48cca6c89fc --- /dev/null +++ b/noir/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/noir/test_programs/noir_test_success/field_comparisons/Nargo.toml b/noir/test_programs/noir_test_success/field_comparisons/Nargo.toml new file mode 100644 index 00000000000..e819464ca68 --- /dev/null +++ b/noir/test_programs/noir_test_success/field_comparisons/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "field_comparisons" +type = "bin" +authors = [""] +[dependencies] diff --git a/noir/test_programs/noir_test_success/field_comparisons/Prover.toml b/noir/test_programs/noir_test_success/field_comparisons/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/test_programs/noir_test_success/field_comparisons/src/main.nr b/noir/test_programs/noir_test_success/field_comparisons/src/main.nr new file mode 100644 index 00000000000..105d82ca755 --- /dev/null +++ b/noir/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); +} diff --git a/noir/tooling/bb_abstraction_leaks/build.rs b/noir/tooling/bb_abstraction_leaks/build.rs index 965a57747f9..47d7fff7929 100644 --- a/noir/tooling/bb_abstraction_leaks/build.rs +++ b/noir/tooling/bb_abstraction_leaks/build.rs @@ -10,7 +10,7 @@ use const_format::formatcp; const USERNAME: &str = "AztecProtocol"; const REPO: &str = "aztec-packages"; -const VERSION: &str = "0.17.0"; +const VERSION: &str = "0.18.0"; const TAG: &str = formatcp!("aztec-packages-v{}", VERSION); const API_URL: &str = diff --git a/yarn-project/aztec-nr/aztec/src/utils.nr b/yarn-project/aztec-nr/aztec/src/utils.nr index 38790a00181..58664c32c5c 100644 --- a/yarn-project/aztec-nr/aztec/src/utils.nr +++ b/yarn-project/aztec-nr/aztec/src/utils.nr @@ -10,11 +10,11 @@ pub fn arr_copy_slice(src: [T; N], mut dst: [T; M], offset: Field) -> [ // TODO to radix returns u8, so we cannot use bigger radixes. It'd be ideal to use a radix of the maximum range-constrained integer noir supports pub fn full_field_less_than(lhs: Field, rhs: Field) -> bool { - dep::std::eddsa::lt_bytes32(lhs, rhs) + lhs.lt(rhs) } pub fn full_field_greater_than(lhs: Field, rhs: Field) -> bool { - dep::std::eddsa::lt_bytes32(rhs, lhs) + rhs.lt(lhs) } struct Reader { diff --git a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/base/base_rollup_inputs.nr b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/base/base_rollup_inputs.nr index 9699f07a24b..a4c05284eea 100644 --- a/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/base/base_rollup_inputs.nr +++ b/yarn-project/noir-protocol-circuits/src/crates/rollup-lib/src/base/base_rollup_inputs.nr @@ -521,11 +521,11 @@ fn consistent_call_data_hash_full_fields() { // TODO to radix returns u8, so we cannot use bigger radixes. It'd be ideal to use a radix of the maximum range-constrained integer noir supports pub fn full_field_less_than(lhs: Field, rhs: Field) -> bool { - dep::std::eddsa::lt_bytes32(lhs, rhs) + lhs.lt(rhs) } pub fn full_field_greater_than(lhs: Field, rhs: Field) -> bool { - dep::std::eddsa::lt_bytes32(rhs, lhs) + rhs.lt(lhs) } #[test]