diff --git a/.noir-sync-commit b/.noir-sync-commit index 7040dbf0e9d3..73dc17fe6af1 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -826b18a10630471c19c25ab745f9bfe045813e69 +5a3d2bc0e13a12b039c793c73d7817924c13e159 diff --git a/.test_patterns.yml b/.test_patterns.yml index 63d4249fb719..3fe5f267f717 100644 --- a/.test_patterns.yml +++ b/.test_patterns.yml @@ -22,6 +22,13 @@ tests: owners: - "U03JYU7AQET" # luke + # Sumcheck is failing for some reason + - regex: "barretenberg/acir_tests/run_test.sh ram_blowup_regression" + skip: true + owners: + - "U04LLT331NK" # Tom + + # noir # Something to do with how I run the tests now. Think these are fine in nextest. - regex: "noir_lsp-.* notifications::notification_tests::test_caches_open_files" diff --git a/noir/bb-version b/noir/bb-version index fac6d18325ad..548a9079fc97 100644 --- a/noir/bb-version +++ b/noir/bb-version @@ -1 +1 @@ -0.72.1 +0.77.1 diff --git a/noir/noir-repo/.github/actions/download-noir-execute/action.yml b/noir/noir-repo/.github/actions/download-noir-execute/action.yml new file mode 100644 index 000000000000..470edc045384 --- /dev/null +++ b/noir/noir-repo/.github/actions/download-noir-execute/action.yml @@ -0,0 +1,18 @@ +name: Download noir-execute +description: Downloads the noir-execute binary from an artifact and adds it to the path + +runs: + using: composite + steps: + - name: Download noir-execute binary + uses: actions/download-artifact@v4 + with: + name: noir-execute + path: ./noir-execute + + - name: Set noir-execute on PATH + shell: bash + run: | + noir_binary="${{ github.workspace }}/noir-execute/noir-execute" + chmod +x $noir_binary + echo "$(dirname $noir_binary)" >> $GITHUB_PATH diff --git a/noir/noir-repo/.github/benchmark_projects.yml b/noir/noir-repo/.github/benchmark_projects.yml index 3a67bf99e8d2..5d9266a2d1a5 100644 --- a/noir/noir-repo/.github/benchmark_projects.yml +++ b/noir/noir-repo/.github/benchmark_projects.yml @@ -1,4 +1,4 @@ -define: &AZ_COMMIT a90f08e245add379fa0257c81f8e2819beb190cb +define: &AZ_COMMIT 3b981f9217f9b859bdfbcdba2f5c080392c98da6 projects: private-kernel-inner: repo: AztecProtocol/aztec-packages @@ -16,7 +16,7 @@ projects: num_runs: 5 timeout: 4 compilation-timeout: 1.2 - execution-timeout: 0.02 + execution-timeout: 0.04 compilation-memory-limit: 250 execution-memory-limit: 230 private-kernel-reset: @@ -35,19 +35,19 @@ projects: path: noir-projects/noir-protocol-circuits/crates/rollup-base-private num_runs: 5 timeout: 15 - compilation-timeout: 10 - execution-timeout: 0.5 - compilation-memory-limit: 1100 - execution-memory-limit: 500 + compilation-timeout: 20 + execution-timeout: 1 + compilation-memory-limit: 1500 + execution-memory-limit: 650 rollup-base-public: repo: AztecProtocol/aztec-packages ref: *AZ_COMMIT path: noir-projects/noir-protocol-circuits/crates/rollup-base-public num_runs: 5 timeout: 15 - compilation-timeout: 8 - execution-timeout: 0.4 - compilation-memory-limit: 1000 + compilation-timeout: 15 + execution-timeout: 0.75 + compilation-memory-limit: 1500 execution-memory-limit: 500 rollup-block-root-empty: repo: AztecProtocol/aztec-packages @@ -65,7 +65,7 @@ projects: cannot_execute: true num_runs: 1 timeout: 60 - compilation-timeout: 110 + compilation-timeout: 135 compilation-memory-limit: 8000 rollup-block-root: repo: AztecProtocol/aztec-packages @@ -73,7 +73,7 @@ projects: path: noir-projects/noir-protocol-circuits/crates/rollup-block-root num_runs: 1 timeout: 60 - compilation-timeout: 110 + compilation-timeout: 135 execution-timeout: 40 compilation-memory-limit: 8000 execution-memory-limit: 1500 diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/keccak256/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/keccak256/.failures.jsonl new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/sha512/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/sha512/.failures.jsonl new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/noir/noir-repo/.github/scripts/integration-test-node.sh b/noir/noir-repo/.github/scripts/integration-test-node.sh index b7f00c65620b..0d070b8001dd 100755 --- a/noir/noir-repo/.github/scripts/integration-test-node.sh +++ b/noir/noir-repo/.github/scripts/integration-test-node.sh @@ -1,5 +1,5 @@ #!/bin/bash set -eu -apt-get install libc++-dev -y +apt-get install libc6 libstdc++6 -y yarn workspace integration-tests test:node diff --git a/noir/noir-repo/.github/workflows/deny.yml b/noir/noir-repo/.github/workflows/deny.yml index 11dbc3eef4bc..c1b1da95ea26 100644 --- a/noir/noir-repo/.github/workflows/deny.yml +++ b/noir/noir-repo/.github/workflows/deny.yml @@ -21,6 +21,6 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: EmbarkStudios/cargo-deny-action@8d73959fce1cdc8989f23fdf03bec6ae6a6576ef with: - command: check all \ No newline at end of file + command: check all diff --git a/noir/noir-repo/.github/workflows/publish-nargo.yml b/noir/noir-repo/.github/workflows/publish-nargo.yml index a3bd1ee6ae37..e18dac52ca48 100644 --- a/noir/noir-repo/.github/workflows/publish-nargo.yml +++ b/noir/noir-repo/.github/workflows/publish-nargo.yml @@ -54,6 +54,7 @@ jobs: cargo build --package nargo_cli --release --target ${{ matrix.target }} --no-default-features --features "${{ inputs.features }}" cargo build --package noir_profiler --release --target ${{ matrix.target }} --no-default-features --features "${{ inputs.features }}" cargo build --package noir_inspector --release --target ${{ matrix.target }} --no-default-features --features "${{ inputs.features }}" + - name: Package artifacts run: | mkdir dist @@ -237,3 +238,4 @@ jobs: overwrite: true tag: ${{ format('{0}-{1}', 'nightly', steps.date.outputs.date) }} + diff --git a/noir/noir-repo/.github/workflows/reports.yml b/noir/noir-repo/.github/workflows/reports.yml index 146dc73f225d..9219f90e4d56 100644 --- a/noir/noir-repo/.github/workflows/reports.yml +++ b/noir/noir-repo/.github/workflows/reports.yml @@ -58,7 +58,7 @@ jobs: compare_gates_reports: name: Circuit sizes needs: [build-nargo] - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 permissions: pull-requests: write diff --git a/noir/noir-repo/.github/workflows/test-js-packages.yml b/noir/noir-repo/.github/workflows/test-js-packages.yml index ba8e5f78942a..9c523554167f 100644 --- a/noir/noir-repo/.github/workflows/test-js-packages.yml +++ b/noir/noir-repo/.github/workflows/test-js-packages.yml @@ -78,6 +78,39 @@ jobs: path: ./dist/* retention-days: 3 + build-noir-execute: + runs-on: ubuntu-22.04 + timeout-minutes: 30 + + steps: + - name: Checkout Noir repo + uses: actions/checkout@v4 + + - name: Setup toolchain + uses: dtolnay/rust-toolchain@1.85.0 + + - uses: Swatinem/rust-cache@v2 + with: + key: x86_64-unknown-linux-gnu + cache-on-failure: true + save-if: ${{ github.event_name != 'merge_group' }} + + - name: Build noir-execute + run: cargo build --package noir_artifact_cli --release + + - name: Package artifacts + run: | + mkdir dist + cp ./target/release/noir-execute ./dist/noir-execute + 7z a -ttar -so -an ./dist/* | 7z a -si ./noir-execute-x86_64-unknown-linux-gnu.tar.gz + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: noir-execute + path: ./dist/* + retention-days: 3 + build-noirc-abi: runs-on: ubuntu-22.04 timeout-minutes: 30 @@ -361,7 +394,7 @@ jobs: test-integration-node: name: Integration Tests (Node) - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: [build-acvm-js, build-noir-wasm, build-nargo, build-noirc-abi] timeout-minutes: 30 @@ -373,6 +406,7 @@ jobs: run: | ./scripts/install_bb.sh echo "$HOME/.bb/" >> $GITHUB_PATH + sudo apt-get install libc6 libstdc++6 -y - name: Download nargo binary uses: ./.github/actions/download-nargo @@ -456,8 +490,8 @@ jobs: test-examples: name: Example scripts - runs-on: ubuntu-22.04 - needs: [build-nargo] + runs-on: ubuntu-24.04 + needs: [build-nargo, build-noir-execute] timeout-minutes: 30 steps: @@ -473,10 +507,14 @@ jobs: run: | ./scripts/install_bb.sh echo "$HOME/.bb/" >> $GITHUB_PATH + sudo apt-get install libc6 libstdc++6 -y - name: Download nargo binary uses: ./.github/actions/download-nargo + - name: Download noir-execute binary + uses: ./.github/actions/download-noir-execute + - name: Run `prove_and_verify` working-directory: ./examples/prove_and_verify run: ./test.sh @@ -485,6 +523,10 @@ jobs: working-directory: ./examples/codegen_verifier run: ./test.sh + - name: Run `oracle_transcript` + working-directory: ./examples/oracle_transcript + run: ./test.sh + external-repo-checks: needs: [build-nargo, critical-library-list] runs-on: ubuntu-22.04 diff --git a/noir/noir-repo/.gitignore b/noir/noir-repo/.gitignore index 1d76c98a1ac4..334901860d91 100644 --- a/noir/noir-repo/.gitignore +++ b/noir/noir-repo/.gitignore @@ -56,3 +56,6 @@ codegen **/cspell.json !./cspell.json + +mutants.out +mutants.out.old diff --git a/noir/noir-repo/CRITICAL_NOIR_LIBRARIES b/noir/noir-repo/CRITICAL_NOIR_LIBRARIES index 7637d9ac6df3..442f51949697 100644 --- a/noir/noir-repo/CRITICAL_NOIR_LIBRARIES +++ b/noir/noir-repo/CRITICAL_NOIR_LIBRARIES @@ -12,3 +12,6 @@ https://github.com/noir-lang/noir_string_search https://github.com/noir-lang/sparse_array https://github.com/noir-lang/noir_rsa https://github.com/noir-lang/noir_json_parser +https://github.com/noir-lang/sha256 +https://github.com/noir-lang/sha512 +https://github.com/noir-lang/keccak256 diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index 7961f8ecc801..fff8a72fd922 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -2828,12 +2828,12 @@ dependencies = [ [[package]] name = "light-poseidon" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +checksum = "39e3d87542063daaccbfecd78b60f988079b6ec4e089249658b9455075c78d42" dependencies = [ - "ark-bn254 0.4.0", - "ark-ff 0.4.2", + "ark-bn254 0.5.0", + "ark-ff 0.5.0", "num-bigint", "thiserror", ] @@ -3030,9 +3030,7 @@ name = "nargo_cli" version = "1.0.0-beta.3" dependencies = [ "acvm", - "ark-bn254 0.4.0", "ark-bn254 0.5.0", - "ark-ff 0.4.2", "assert_cmd", "assert_fs", "async-lsp", diff --git a/noir/noir-repo/EXTERNAL_NOIR_LIBRARIES.yml b/noir/noir-repo/EXTERNAL_NOIR_LIBRARIES.yml index 0481c5398051..1d86c3e5d5b1 100644 --- a/noir/noir-repo/EXTERNAL_NOIR_LIBRARIES.yml +++ b/noir/noir-repo/EXTERNAL_NOIR_LIBRARIES.yml @@ -1,4 +1,4 @@ -define: &AZ_COMMIT a90f08e245add379fa0257c81f8e2819beb190cb +define: &AZ_COMMIT 3b981f9217f9b859bdfbcdba2f5c080392c98da6 libraries: noir_check_shuffle: repo: noir-lang/noir_check_shuffle @@ -29,7 +29,7 @@ libraries: timeout: 250 noir_base64: repo: noir-lang/noir_base64 - timeout: 3 + timeout: 5 noir_string_search: repo: noir-lang/noir_string_search timeout: 2 @@ -45,6 +45,12 @@ libraries: sha256: repo: noir-lang/sha256 timeout: 3 + sha512: + repo: noir-lang/sha512 + timeout: 30 + keccak256: + repo: noir-lang/keccak256 + timeout: 3 aztec_nr: repo: AztecProtocol/aztec-packages ref: *AZ_COMMIT diff --git a/noir/noir-repo/acvm-repo/acir/src/native_types/witness_stack.rs b/noir/noir-repo/acvm-repo/acir/src/native_types/witness_stack.rs index d048e0509956..6338ad630d6c 100644 --- a/noir/noir-repo/acvm-repo/acir/src/native_types/witness_stack.rs +++ b/noir/noir-repo/acvm-repo/acir/src/native_types/witness_stack.rs @@ -12,6 +12,9 @@ use super::WitnessMap; enum SerializationError { #[error(transparent)] Deflate(#[from] std::io::Error), + + #[error(transparent)] + BincodeError(#[from] bincode::Error), } #[derive(Debug, Error)] @@ -57,11 +60,11 @@ impl From> for WitnessStack { } } -impl TryFrom> for Vec { +impl TryFrom<&WitnessStack> for Vec { type Error = WitnessStackError; - fn try_from(val: WitnessStack) -> Result { - let buf = bincode::serialize(&val).unwrap(); + fn try_from(val: &WitnessStack) -> Result { + let buf = bincode::serialize(val).map_err(|e| WitnessStackError(e.into()))?; let mut deflater = GzEncoder::new(buf.as_slice(), Compression::best()); let mut buf_c = Vec::new(); deflater.read_to_end(&mut buf_c).map_err(|err| WitnessStackError(err.into()))?; @@ -69,6 +72,14 @@ impl TryFrom> for Vec { } } +impl TryFrom> for Vec { + type Error = WitnessStackError; + + fn try_from(val: WitnessStack) -> Result { + Self::try_from(&val) + } +} + impl Deserialize<'a>> TryFrom<&[u8]> for WitnessStack { type Error = WitnessStackError; @@ -76,7 +87,8 @@ impl Deserialize<'a>> TryFrom<&[u8]> for WitnessStack { let mut deflater = GzDecoder::new(bytes); let mut buf_d = Vec::new(); deflater.read_to_end(&mut buf_d).map_err(|err| WitnessStackError(err.into()))?; - let witness_stack = bincode::deserialize(&buf_d).unwrap(); + let witness_stack = + bincode::deserialize(&buf_d).map_err(|e| WitnessStackError(e.into()))?; Ok(witness_stack) } } diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/arithmetic.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/arithmetic.rs index 78da63ddc0f8..a2921bcbc9b1 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/arithmetic.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/arithmetic.rs @@ -254,7 +254,52 @@ mod tests { use acir::FieldElement; #[test] - fn expression_solver_smoke_test() { + fn solves_simple_assignment() { + let a = Witness(0); + + // a - 1 == 0; + let opcode_a = Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), a)], + q_c: -FieldElement::one(), + }; + + let mut values = WitnessMap::new(); + assert_eq!(ExpressionSolver::solve(&mut values, &opcode_a), Ok(())); + + assert_eq!(values.get(&a).unwrap(), &FieldElement::from(1_i128)); + } + + #[test] + fn solves_unknown_in_mul_term() { + let a = Witness(0); + let b = Witness(1); + let c = Witness(2); + let d = Witness(3); + + // a * b - b - c - d == 0; + let opcode_a = Expression { + mul_terms: vec![(FieldElement::one(), a, b)], + linear_combinations: vec![ + (-FieldElement::one(), b), + (-FieldElement::one(), c), + (-FieldElement::one(), d), + ], + q_c: FieldElement::zero(), + }; + + let mut values = WitnessMap::new(); + values.insert(b, FieldElement::from(2_i128)); + values.insert(c, FieldElement::from(1_i128)); + values.insert(d, FieldElement::from(1_i128)); + + assert_eq!(ExpressionSolver::solve(&mut values, &opcode_a), Ok(())); + + assert_eq!(values.get(&a).unwrap(), &FieldElement::from(2_i128)); + } + + #[test] + fn solves_unknown_in_linear_term() { let a = Witness(0); let b = Witness(1); let c = Witness(2); diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/hash.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/hash.rs index 93f9ea410bc4..a4af9de55cfe 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/hash.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/hash.rs @@ -136,11 +136,10 @@ pub(crate) fn solve_poseidon2_permutation_opcode( } // Read witness assignments - let mut state = Vec::new(); - for input in inputs.iter() { - let witness_assignment = input_to_value(initial_witness, *input, false)?; - state.push(witness_assignment); - } + let state: Vec = inputs + .iter() + .map(|input| input_to_value(initial_witness, *input, false)) + .collect::>()?; let state = backend.poseidon2_permutation(&state, len)?; diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/range.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/range.rs index 927706f46714..039a04b9063d 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/range.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/range.rs @@ -21,3 +21,36 @@ pub(crate) fn solve_range_opcode( } Ok(()) } + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use acir::{ + FieldElement, + circuit::opcodes::FunctionInput, + native_types::{Witness, WitnessMap}, + }; + + use crate::pwg::blackbox::solve_range_opcode; + + #[test] + fn rejects_too_large_inputs() { + let witness_map = + WitnessMap::from(BTreeMap::from([(Witness(0), FieldElement::from(256u32))])); + let input: FunctionInput = FunctionInput::witness(Witness(0), 8); + assert!(solve_range_opcode(&witness_map, &input, false).is_err()); + } + + #[test] + fn accepts_valid_inputs() { + let values: [u32; 4] = [0, 1, 8, 255]; + + for value in values { + let witness_map = + WitnessMap::from(BTreeMap::from([(Witness(0), FieldElement::from(value))])); + let input: FunctionInput = FunctionInput::witness(Witness(0), 8); + assert!(solve_range_opcode(&witness_map, &input, false).is_ok()); + } + } +} diff --git a/noir/noir-repo/acvm-repo/blackbox_solver/src/curve_specific_solver.rs b/noir/noir-repo/acvm-repo/blackbox_solver/src/curve_specific_solver.rs index 37fe5d053633..af0104b54f0a 100644 --- a/noir/noir-repo/acvm-repo/blackbox_solver/src/curve_specific_solver.rs +++ b/noir/noir-repo/acvm-repo/blackbox_solver/src/curve_specific_solver.rs @@ -25,8 +25,8 @@ pub trait BlackBoxFunctionSolver { ) -> Result<(F, F, F), BlackBoxResolutionError>; fn poseidon2_permutation( &self, - _inputs: &[F], - _len: u32, + inputs: &[F], + len: u32, ) -> Result, BlackBoxResolutionError>; } diff --git a/noir/noir-repo/compiler/integration-tests/scripts/codegen-verifiers.sh b/noir/noir-repo/compiler/integration-tests/scripts/codegen-verifiers.sh index de1f71a4cc0b..f1fc321d1ff3 100755 --- a/noir/noir-repo/compiler/integration-tests/scripts/codegen-verifiers.sh +++ b/noir/noir-repo/compiler/integration-tests/scripts/codegen-verifiers.sh @@ -17,19 +17,19 @@ KEYS=$(mktemp -d) # Codegen verifier contract for 1_mul mul_dir=$repo_root/test_programs/execution_success/1_mul nargo --program-dir $mul_dir compile -$NARGO_BACKEND_PATH write_vk -b $mul_dir/target/1_mul.json -o $KEYS/1_mul -$NARGO_BACKEND_PATH contract -k $KEYS/1_mul -o $contracts_dir/1_mul.sol +$NARGO_BACKEND_PATH OLD_API write_vk -b $mul_dir/target/1_mul.json -o $KEYS/1_mul +$NARGO_BACKEND_PATH OLD_API contract -k $KEYS/1_mul -o $contracts_dir/1_mul.sol # Codegen verifier contract for assert_statement assert_statement_dir=$repo_root/test_programs/execution_success/assert_statement nargo --program-dir $assert_statement_dir compile -$NARGO_BACKEND_PATH write_vk -b $assert_statement_dir/target/assert_statement.json -o $KEYS/assert_statement -$NARGO_BACKEND_PATH contract -k $KEYS/assert_statement -o $contracts_dir/assert_statement.sol +$NARGO_BACKEND_PATH OLD_API write_vk -b $assert_statement_dir/target/assert_statement.json -o $KEYS/assert_statement +$NARGO_BACKEND_PATH OLD_API contract -k $KEYS/assert_statement -o $contracts_dir/assert_statement.sol # Codegen verifier contract for recursion recursion_dir=$repo_root/compiler/integration-tests/circuits/recursion nargo --program-dir $recursion_dir compile -$NARGO_BACKEND_PATH write_vk -b $recursion_dir/target/recursion.json -o $KEYS/recursion -$NARGO_BACKEND_PATH contract -k $KEYS/recursion ./ -o $contracts_dir/recursion.sol +$NARGO_BACKEND_PATH OLD_API write_vk -b $recursion_dir/target/recursion.json -o $KEYS/recursion +$NARGO_BACKEND_PATH OLD_API contract -k $KEYS/recursion -o $contracts_dir/recursion.sol -rm -rf $KEYS \ No newline at end of file +rm -rf $KEYS diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index a70d9fc72b62..3712634d707f 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -10,7 +10,7 @@ use clap::Args; use fm::{FileId, FileManager}; use iter_extended::vecmap; use noirc_abi::{AbiParameter, AbiType, AbiValue}; -use noirc_errors::{CustomDiagnostic, DiagnosticKind, FileDiagnostic}; +use noirc_errors::{CustomDiagnostic, DiagnosticKind}; use noirc_evaluator::brillig::BrilligOptions; use noirc_evaluator::create_program; use noirc_evaluator::errors::RuntimeError; @@ -133,20 +133,16 @@ pub struct CompileOptions { #[arg(long)] pub skip_underconstrained_check: bool, - /// Flag to turn on the compiler check for missing Brillig call constraints. - /// Warning: This can degrade compilation speed but will also find some correctness errors. + /// Flag to turn off the compiler check for missing Brillig call constraints. + /// Warning: This can improve compilation speed but can also lead to correctness errors. /// This check should always be run on production code. #[arg(long)] - pub enable_brillig_constraints_check: bool, + pub skip_brillig_constraints_check: bool, /// Flag to turn on extra Brillig bytecode to be generated to guard against invalid states in testing. #[arg(long, hide = true)] pub enable_brillig_debug_assertions: bool, - /// Hidden Brillig call check flag to maintain CI compatibility (currently ignored) - #[arg(long, hide = true)] - pub skip_brillig_constraints_check: bool, - /// Flag to turn on the lookback feature of the Brillig call constraints /// check, allowing tracking argument values before the call happens preventing /// certain rare false positives (leads to a slowdown on large rollout functions) @@ -227,8 +223,8 @@ impl From for CompileError { } } -impl From for FileDiagnostic { - fn from(error: CompileError) -> FileDiagnostic { +impl From for CustomDiagnostic { + fn from(error: CompileError) -> CustomDiagnostic { match error { CompileError::RuntimeError(err) => err.into(), CompileError::MonomorphizationError(err) => err.into(), @@ -237,10 +233,10 @@ impl From for FileDiagnostic { } /// Helper type used to signify where only warnings are expected in file diagnostics -pub type Warnings = Vec; +pub type Warnings = Vec; /// Helper type used to signify where errors or warnings are expected in file diagnostics -pub type ErrorsAndWarnings = Vec; +pub type ErrorsAndWarnings = Vec; /// Helper type for connecting a compilation artifact to the errors or warnings which were produced during compilation. pub type CompilationResult = Result<(T, Warnings), ErrorsAndWarnings>; @@ -350,20 +346,16 @@ pub fn check_crate( ) -> CompilationResult<()> { let diagnostics = CrateDefMap::collect_defs(crate_id, context, options.frontend_options()); let crate_files = context.crate_files(&crate_id); - let warnings_and_errors: Vec = diagnostics - .into_iter() - .map(|error| { - let location = error.location(); - let diagnostic = CustomDiagnostic::from(&error); - diagnostic.in_file(location.file) - }) + let warnings_and_errors: Vec = diagnostics + .iter() + .map(CustomDiagnostic::from) .filter(|diagnostic| { // We filter out any warnings if they're going to be ignored later on to free up memory. - !options.silence_warnings || diagnostic.diagnostic.kind != DiagnosticKind::Warning + !options.silence_warnings || diagnostic.kind != DiagnosticKind::Warning }) .filter(|error| { // Only keep warnings from the crate we are checking - if error.diagnostic.is_warning() { crate_files.contains(&error.file_id) } else { true } + if error.is_warning() { crate_files.contains(&error.file) } else { true } }) .collect(); @@ -401,16 +393,16 @@ pub fn compile_main( // TODO(#2155): This error might be a better to exist in Nargo let err = CustomDiagnostic::from_message( "cannot compile crate into a program as it does not contain a `main` function", - ) - .in_file(FileId::default()); + FileId::default(), + ); vec![err] })?; let compiled_program = compile_no_check(context, options, main, cached_program, options.force_compile) - .map_err(FileDiagnostic::from)?; + .map_err(|error| vec![CustomDiagnostic::from(error)])?; - let compilation_warnings = vecmap(compiled_program.warnings.clone(), FileDiagnostic::from); + let compilation_warnings = vecmap(compiled_program.warnings.clone(), CustomDiagnostic::from); if options.deny_warnings && !compilation_warnings.is_empty() { return Err(compilation_warnings); } @@ -439,14 +431,16 @@ pub fn compile_contract( let mut errors = warnings; if contracts.len() > 1 { - let err = CustomDiagnostic::from_message("Packages are limited to a single contract") - .in_file(FileId::default()); + let err = CustomDiagnostic::from_message( + "Packages are limited to a single contract", + FileId::default(), + ); return Err(vec![err]); } else if contracts.is_empty() { let err = CustomDiagnostic::from_message( "cannot compile crate into a contract as it does not contain any contracts", - ) - .in_file(FileId::default()); + FileId::default(), + ); return Err(vec![err]); }; @@ -483,12 +477,8 @@ pub fn compile_contract( } /// True if there are (non-warning) errors present and we should halt compilation -fn has_errors(errors: &[FileDiagnostic], deny_warnings: bool) -> bool { - if deny_warnings { - !errors.is_empty() - } else { - errors.iter().any(|error| error.diagnostic.is_error()) - } +fn has_errors(errors: &[CustomDiagnostic], deny_warnings: bool) -> bool { + if deny_warnings { !errors.is_empty() } else { errors.iter().any(|error| error.is_error()) } } /// Compile all of the functions associated with a Noir contract. @@ -525,7 +515,7 @@ fn compile_contract_inner( let function = match compile_no_check(context, &options, function_id, None, true) { Ok(function) => function, Err(new_error) => { - errors.push(FileDiagnostic::from(new_error)); + errors.push(new_error.into()); continue; } }; @@ -704,7 +694,7 @@ pub fn compile_no_check( skip_underconstrained_check: options.skip_underconstrained_check, enable_brillig_constraints_check_lookback: options .enable_brillig_constraints_check_lookback, - enable_brillig_constraints_check: options.enable_brillig_constraints_check, + skip_brillig_constraints_check: options.skip_brillig_constraints_check, inliner_aggressiveness: options.inliner_aggressiveness, max_bytecode_increase_percent: options.max_bytecode_increase_percent, }; diff --git a/noir/noir-repo/compiler/noirc_driver/tests/contracts.rs b/noir/noir-repo/compiler/noirc_driver/tests/contracts.rs index ea42cb23376b..0732a7728ca5 100644 --- a/noir/noir-repo/compiler/noirc_driver/tests/contracts.rs +++ b/noir/noir-repo/compiler/noirc_driver/tests/contracts.rs @@ -33,10 +33,10 @@ contract Bar {}"; assert_eq!( errors, - vec![ - CustomDiagnostic::from_message("Packages are limited to a single contract") - .in_file(FileId::default()) - ], + vec![CustomDiagnostic::from_message( + "Packages are limited to a single contract", + FileId::default() + )], "stdlib is producing warnings" ); diff --git a/noir/noir-repo/compiler/noirc_errors/src/lib.rs b/noir/noir-repo/compiler/noirc_errors/src/lib.rs index 146217f91a0f..91d121603baa 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/lib.rs @@ -8,21 +8,3 @@ mod position; pub mod reporter; pub use position::{Located, Location, Position, Span, Spanned}; pub use reporter::{CustomDiagnostic, DiagnosticKind}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct FileDiagnostic { - pub file_id: fm::FileId, - pub diagnostic: CustomDiagnostic, -} - -impl FileDiagnostic { - pub fn new(file_id: fm::FileId, diagnostic: CustomDiagnostic) -> FileDiagnostic { - FileDiagnostic { file_id, diagnostic } - } -} - -impl From for Vec { - fn from(value: FileDiagnostic) -> Self { - vec![value] - } -} diff --git a/noir/noir-repo/compiler/noirc_errors/src/reporter.rs b/noir/noir-repo/compiler/noirc_errors/src/reporter.rs index e516f690ddc1..d406e897d656 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/reporter.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/reporter.rs @@ -1,6 +1,6 @@ use std::io::IsTerminal; -use crate::{FileDiagnostic, Location, Span}; +use crate::{Location, Span}; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::files::Files; use codespan_reporting::term; @@ -8,6 +8,7 @@ use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct CustomDiagnostic { + pub file: fm::FileId, pub message: String, pub secondaries: Vec, pub notes: Vec, @@ -35,8 +36,9 @@ pub struct ReportedErrors { } impl CustomDiagnostic { - pub fn from_message(msg: &str) -> CustomDiagnostic { + pub fn from_message(msg: &str, file: fm::FileId) -> CustomDiagnostic { Self { + file, message: msg.to_owned(), secondaries: Vec::new(), notes: Vec::new(), @@ -54,6 +56,7 @@ impl CustomDiagnostic { kind: DiagnosticKind, ) -> CustomDiagnostic { CustomDiagnostic { + file: secondary_location.file, message: primary_message, secondaries: vec![CustomLabel::new(secondary_message, secondary_location)], notes: Vec::new(), @@ -109,6 +112,7 @@ impl CustomDiagnostic { secondary_location: Location, ) -> CustomDiagnostic { CustomDiagnostic { + file: secondary_location.file, message: primary_message, secondaries: vec![CustomLabel::new(secondary_message, secondary_location)], notes: Vec::new(), @@ -119,10 +123,6 @@ impl CustomDiagnostic { } } - pub fn in_file(self, file_id: fm::FileId) -> FileDiagnostic { - FileDiagnostic::new(file_id, self) - } - pub fn with_call_stack(mut self, call_stack: Vec) -> Self { self.call_stack = call_stack; self @@ -185,16 +185,16 @@ impl CustomLabel { /// of diagnostics that were errors. pub fn report_all<'files>( files: &'files impl Files<'files, FileId = fm::FileId>, - diagnostics: &[FileDiagnostic], + diagnostics: &[CustomDiagnostic], deny_warnings: bool, silence_warnings: bool, ) -> ReportedErrors { // Report warnings before any errors let (warnings_and_bugs, mut errors): (Vec<_>, _) = - diagnostics.iter().partition(|item| !item.diagnostic.is_error()); + diagnostics.iter().partition(|item| !item.is_error()); let (warnings, mut bugs): (Vec<_>, _) = - warnings_and_bugs.iter().partition(|item| item.diagnostic.is_warning()); + warnings_and_bugs.iter().partition(|item| item.is_warning()); let mut diagnostics = if silence_warnings { Vec::new() } else { warnings }; diagnostics.append(&mut bugs); diagnostics.append(&mut errors); @@ -205,14 +205,14 @@ pub fn report_all<'files>( ReportedErrors { error_count } } -impl FileDiagnostic { +impl CustomDiagnostic { /// Print the report; return true if it was an error. pub fn report<'files>( &self, files: &'files impl Files<'files, FileId = fm::FileId>, deny_warnings: bool, ) -> bool { - report(files, &self.diagnostic, deny_warnings) + report(files, self, deny_warnings) } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 742ccb4c9de8..d8f1f9d0997e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -1,6 +1,6 @@ use crate::brillig::brillig_ir::artifact::Label; use crate::brillig::brillig_ir::brillig_variable::{ - BrilligArray, BrilligVariable, SingleAddrVariable, type_to_heap_value_type, + BrilligArray, BrilligVariable, BrilligVector, SingleAddrVariable, type_to_heap_value_type, }; use crate::brillig::brillig_ir::registers::RegisterAllocator; @@ -935,8 +935,64 @@ impl<'block, Registers: RegisterAllocator> BrilligBlock<'block, Registers> { Instruction::EnableSideEffectsIf { .. } => { unreachable!("enable_side_effects not supported by brillig") } - Instruction::IfElse { .. } => { - unreachable!("IfElse instructions should not be possible in brillig") + Instruction::IfElse { then_condition, then_value, else_condition: _, else_value } => { + let then_condition = self.convert_ssa_single_addr_value(*then_condition, dfg); + let then_value = self.convert_ssa_value(*then_value, dfg); + let else_value = self.convert_ssa_value(*else_value, dfg); + let result = self.variables.define_variable( + self.function_context, + self.brillig_context, + dfg.instruction_results(instruction_id)[0], + dfg, + ); + match (then_value, else_value) { + ( + BrilligVariable::SingleAddr(then_address), + BrilligVariable::SingleAddr(else_address), + ) => { + self.brillig_context.conditional_move_instruction( + then_condition, + then_address, + else_address, + result.extract_single_addr(), + ); + } + ( + BrilligVariable::BrilligArray(then_array), + BrilligVariable::BrilligArray(else_array), + ) => { + // Pointer to the array which result from the if-else + let pointer = self.brillig_context.allocate_register(); + self.brillig_context.conditional_move_instruction( + then_condition, + SingleAddrVariable::new_usize(then_array.pointer), + SingleAddrVariable::new_usize(else_array.pointer), + SingleAddrVariable::new_usize(pointer), + ); + let if_else_array = BrilligArray { pointer, size: then_array.size }; + // Copy the if-else array to the result + self.brillig_context + .call_array_copy_procedure(if_else_array, result.extract_array()); + } + ( + BrilligVariable::BrilligVector(then_vector), + BrilligVariable::BrilligVector(else_vector), + ) => { + // Pointer to the vector which result from the if-else + let pointer = self.brillig_context.allocate_register(); + self.brillig_context.conditional_move_instruction( + then_condition, + SingleAddrVariable::new_usize(then_vector.pointer), + SingleAddrVariable::new_usize(else_vector.pointer), + SingleAddrVariable::new_usize(pointer), + ); + let if_else_vector = BrilligVector { pointer }; + // Copy the if-else vector to the result + self.brillig_context + .call_vector_copy_procedure(if_else_vector, result.extract_vector()); + } + _ => unreachable!("ICE - then and else values must have the same type"), + } } Instruction::MakeArray { elements: array, typ } => { let value_id = dfg.instruction_results(instruction_id)[0]; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs index b547e56abefd..c0b6492618c1 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs @@ -126,6 +126,24 @@ impl DebugShow { debug_println!(self.enable_debug_trace, " MOV {}, {}", destination, source); } + /// Emits a `conditional mov` instruction. + pub(crate) fn conditional_mov_instruction( + &self, + destination: MemoryAddress, + source_then: MemoryAddress, + source_else: MemoryAddress, + condition: MemoryAddress, + ) { + debug_println!( + self.enable_debug_trace, + " {} = MOV if {} then {}, else {}", + destination, + condition, + source_then, + source_else + ); + } + /// Emits a `cast` instruction. pub(crate) fn cast_instruction( &self, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs index 6481972d707f..fad9892cfb16 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs @@ -75,6 +75,28 @@ impl BrilligContext< ); } + /// Insert a conditional move instruction + pub(crate) fn conditional_move_instruction( + &mut self, + condition: SingleAddrVariable, + then_address: SingleAddrVariable, + else_address: SingleAddrVariable, + destination: SingleAddrVariable, + ) { + self.debug_show.conditional_mov_instruction( + destination.address, + then_address.address, + else_address.address, + condition.address, + ); + self.push_opcode(BrilligOpcode::ConditionalMov { + destination: destination.address, + source_a: then_address.address, + source_b: else_address.address, + condition: condition.address, + }); + } + fn binary( &mut self, lhs: SingleAddrVariable, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs b/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs index 202124f7931d..deaefd40ae32 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs @@ -8,7 +8,7 @@ //! //! An Error of the latter is an error in the implementation of the compiler use iter_extended::vecmap; -use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic, Location}; +use noirc_errors::{CustomDiagnostic, Location}; use noirc_frontend::signed_field::SignedField; use thiserror::Error; @@ -73,8 +73,8 @@ pub enum SsaReport { Bug(InternalBug), } -impl From for FileDiagnostic { - fn from(error: SsaReport) -> FileDiagnostic { +impl From for CustomDiagnostic { + fn from(error: SsaReport) -> CustomDiagnostic { match error { SsaReport::Warning(warning) => { let message = warning.to_string(); @@ -87,10 +87,10 @@ impl From for FileDiagnostic { }, }; let call_stack = vecmap(call_stack, |location| location); - let file_id = call_stack.last().map(|location| location.file).unwrap_or_default(); let location = call_stack.last().expect("Expected RuntimeError to have a location"); - let diagnostic = Diagnostic::simple_warning(message, secondary_message, *location); - diagnostic.with_call_stack(call_stack).in_file(file_id) + let diagnostic = + CustomDiagnostic::simple_warning(message, secondary_message, *location); + diagnostic.with_call_stack(call_stack) } SsaReport::Bug(bug) => { let message = bug.to_string(); @@ -104,10 +104,10 @@ impl From for FileDiagnostic { InternalBug::AssertFailed { call_stack } => ("As a result, the compiled circuit is ensured to fail. Other assertions may also fail during execution".to_string(), call_stack) }; let call_stack = vecmap(call_stack, |location| location); - let file_id = call_stack.last().map(|location| location.file).unwrap_or_default(); let location = call_stack.last().expect("Expected RuntimeError to have a location"); - let diagnostic = Diagnostic::simple_bug(message, secondary_message, *location); - diagnostic.with_call_stack(call_stack).in_file(file_id) + let diagnostic = + CustomDiagnostic::simple_bug(message, secondary_message, *location); + diagnostic.with_call_stack(call_stack) } } } @@ -181,20 +181,19 @@ impl RuntimeError { } } -impl From for FileDiagnostic { - fn from(error: RuntimeError) -> FileDiagnostic { +impl From for CustomDiagnostic { + fn from(error: RuntimeError) -> CustomDiagnostic { let call_stack = vecmap(error.call_stack(), |location| *location); - let file_id = call_stack.last().map(|location| location.file).unwrap_or_default(); let diagnostic = error.into_diagnostic(); - diagnostic.with_call_stack(call_stack).in_file(file_id) + diagnostic.with_call_stack(call_stack) } } impl RuntimeError { - fn into_diagnostic(self) -> Diagnostic { + fn into_diagnostic(self) -> CustomDiagnostic { match self { RuntimeError::InternalError(cause) => { - Diagnostic::simple_error( + CustomDiagnostic::simple_error( "Internal Consistency Evaluators Errors: \n This is likely a bug. Consider opening an issue at https://github.com/noir-lang/noir/issues".to_owned(), cause.to_string(), @@ -206,7 +205,7 @@ impl RuntimeError { let location = self.call_stack().last().expect("Expected RuntimeError to have a location"); - Diagnostic::simple_error( + CustomDiagnostic::simple_error( primary_message, "If attempting to fetch the length of a slice, try converting to an array. Slices only use dynamic lengths.".to_string(), *location, @@ -217,7 +216,7 @@ impl RuntimeError { let location = self.call_stack().last().unwrap_or_else(|| panic!("Expected RuntimeError to have a location. Error message: {message}")); - Diagnostic::simple_error(message, String::new(), *location) + CustomDiagnostic::simple_error(message, String::new(), *location) } } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/lib.rs b/noir/noir-repo/compiler/noirc_evaluator/src/lib.rs index 75ea557d3de0..5c212e8f38a9 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/lib.rs @@ -20,10 +20,20 @@ pub(crate) fn trim_leading_whitespace_from_lines(src: &str) -> String { while first_line.is_empty() { first_line = lines.next().unwrap(); } + let first_line_original_length = first_line.len(); let mut result = first_line.trim_start().to_string(); + let first_line_trimmed_length = result.len(); + + // Try to see how many spaces we chopped off the first line + let difference = first_line_original_length - first_line_trimmed_length; for line in lines { result.push('\n'); - result.push_str(line.trim_start()); + // Try to remove just `difference` spaces to preserve indents + if line.len() - line.trim_start().len() >= difference { + result.push_str(&line.chars().skip(difference).collect::()); + } else { + result.push_str(line.trim_start()); + } } result } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index b4219ce278f0..935918c6b7e8 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -72,8 +72,8 @@ pub struct SsaEvaluatorOptions { /// Skip the check for under constrained values pub skip_underconstrained_check: bool, - /// Enable the missing Brillig call constraints check - pub enable_brillig_constraints_check: bool, + /// Skip the missing Brillig call constraints check + pub skip_brillig_constraints_check: bool, /// Enable the lookback feature of the Brillig call constraints /// check (prevents some rare false positives, leads to a slowdown @@ -143,7 +143,7 @@ pub(crate) fn optimize_into_acir( )); } - if options.enable_brillig_constraints_check { + if !options.skip_brillig_constraints_check { ssa_level_warnings.extend(time( "After Check for Missing Brillig Call Constraints", options.print_codegen_timings, @@ -211,6 +211,7 @@ fn optimize_all(builder: SsaBuilder, options: &SsaEvaluatorOptions) -> Result, // Map of argument value ids to the Brillig call ids employing them call_arguments: HashMap>, - // Maintains count of calls being tracked - tracking_count: usize, + // The set of calls currently being tracked + tracking: HashSet, // Opt-in to use the lookback feature (tracking the argument values // of a Brillig call before the call happens if their usage precedes // it). Can prevent certain false positives, at the cost of @@ -138,8 +138,6 @@ struct BrilligTaintedIds { array_elements: HashMap>, // Initial result value ids, along with element ids for arrays root_results: HashSet, - // The flag signaling that the call should be now tracked - tracking: bool, } #[derive(Clone, Debug)] @@ -195,7 +193,6 @@ impl BrilligTaintedIds { results: results_status, array_elements, root_results: HashSet::from_iter(results.iter().copied()), - tracking: false, } } @@ -394,23 +391,19 @@ impl DependencyContext { for argument in &arguments { if let Some(calls) = self.call_arguments.get(argument) { for call in calls { - if let Some(tainted_ids) = self.tainted.get_mut(call) { - tainted_ids.tracking = true; - self.tracking_count += 1; + if self.tainted.contains_key(call) { + self.tracking.insert(*call); } } } } } - if let Some(tainted_ids) = self.tainted.get_mut(instruction) { - if !tainted_ids.tracking { - tainted_ids.tracking = true; - self.tracking_count += 1; - } + if self.tainted.contains_key(instruction) { + self.tracking.insert(*instruction); } // We can skip over instructions while nothing is being tracked - if self.tracking_count > 0 { + if !self.tracking.is_empty() { let mut results = Vec::new(); // Collect non-constant instruction results @@ -524,7 +517,7 @@ impl DependencyContext { // results involving the array in question, to properly // populate the array element tainted sets Instruction::ArrayGet { array, index } => { - self.process_array_get(function, *array, *index, &results); + self.process_array_get(*array, *index, &results, function); // Record all the used arguments as parents of the results self.update_children(&arguments, &results); } @@ -563,7 +556,10 @@ impl DependencyContext { .tainted .keys() .map(|brillig_call| { - trace!("tainted structure for {}: {:?}", brillig_call, self.tainted[brillig_call]); + trace!( + "tainted structure for {:?}: {:?}", + brillig_call, self.tainted[brillig_call] + ); SsaReport::Bug(InternalBug::UncheckedBrilligCall { call_stack: function.dfg.get_instruction_call_stack(*brillig_call), }) @@ -587,8 +583,8 @@ impl DependencyContext { self.side_effects_condition.map(|v| parents.insert(v)); // Don't update sets for the calls not yet being tracked - for (_, tainted_ids) in self.tainted.iter_mut() { - if tainted_ids.tracking { + for call in &self.tracking { + if let Some(tainted_ids) = self.tainted.get_mut(call) { tainted_ids.update_children(&parents, children); } } @@ -605,15 +601,15 @@ impl DependencyContext { .collect(); // Skip untracked calls - for (_, tainted_ids) in self.tainted.iter_mut() { - if tainted_ids.tracking { + for call in &self.tracking { + if let Some(tainted_ids) = self.tainted.get_mut(call) { tainted_ids.store_partial_constraints(&constrained_values); } } - self.tainted.retain(|_, tainted_ids| { + self.tainted.retain(|call, tainted_ids| { if tainted_ids.check_constrained() { - self.tracking_count -= 1; + self.tracking.remove(call); false } else { true @@ -624,10 +620,10 @@ impl DependencyContext { /// Process ArrayGet instruction for tracked Brillig calls fn process_array_get( &mut self, - function: &Function, array: ValueId, index: ValueId, element_results: &[ValueId], + function: &Function, ) { use acvm::acir::AcirField; @@ -635,8 +631,8 @@ impl DependencyContext { if let Some(value) = function.dfg.get_numeric_constant(index) { if let Some(index) = value.try_to_u32() { // Skip untracked calls - for (_, tainted_ids) in self.tainted.iter_mut() { - if tainted_ids.tracking { + for call in &self.tracking { + if let Some(tainted_ids) = self.tainted.get_mut(call) { tainted_ids.process_array_get(array, index as usize, element_results); } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs index 6b9ed3932e3a..132985830ebf 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs @@ -239,10 +239,9 @@ impl DataFlowGraph { instruction, Instruction::IncrementRc { .. } | Instruction::DecrementRc { .. } ), - RuntimeType::Brillig(_) => !matches!( - instruction, - Instruction::EnableSideEffectsIf { .. } | Instruction::IfElse { .. } - ), + RuntimeType::Brillig(_) => { + !matches!(instruction, Instruction::EnableSideEffectsIf { .. }) + } } } @@ -377,6 +376,11 @@ impl DataFlowGraph { } } + /// Replace an existing instruction with a new one. + pub(crate) fn set_instruction(&mut self, id: InstructionId, instruction: Instruction) { + self.instructions[id] = instruction; + } + /// Set the value of value_to_replace to refer to the value referred to by new_value. /// /// This is the preferred method to call for optimizations simplifying diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs index 9e4557e06a66..13b5ead5eb61 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/function_inserter.rs @@ -79,6 +79,13 @@ impl<'f> FunctionInserter<'f> { (instruction, self.function.dfg.get_instruction_call_stack_id(id)) } + /// Get an instruction, map all its values, and replace it with the resolved instruction. + pub(crate) fn map_instruction_in_place(&mut self, id: InstructionId) { + let mut instruction = self.function.dfg[id].clone(); + instruction.map_values_mut(|id| self.resolve(id)); + self.function.dfg.set_instruction(id, instruction); + } + /// Maps a terminator in place, replacing any ValueId in the terminator with the /// resolved version of that value id from this FunctionInserter's internal value mapping. pub(crate) fn map_terminator_in_place(&mut self, block: BasicBlockId) { @@ -251,4 +258,22 @@ impl<'f> FunctionInserter<'f> { self.values.entry(*param).or_insert(*new_param); } } + + /// Merge the internal mapping into the given mapping + /// The merge is guaranteed to be coherent because ambiguous cases are prevented + pub(crate) fn extract_mapping(&self, mapping: &mut HashMap) { + for (k, v) in &self.values { + if mapping.contains_key(k) { + unreachable!("cannot merge key"); + } + if mapping.contains_key(v) { + unreachable!("cannot merge value"); + } + mapping.insert(*k, *v); + } + } + + pub(crate) fn set_mapping(&mut self, mapping: HashMap) { + self.values = mapping; + } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 73d6d9c2a6a5..d32a562a037e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -610,7 +610,9 @@ fn simplify_black_box_func( "ICE: `BlackBoxFunc::RANGE` calls should be transformed into a `Instruction::Cast`" ) } - BlackBoxFunc::Sha256Compression => SimplifyResult::None, //TODO(Guillaume) + BlackBoxFunc::Sha256Compression => { + blackbox::simplify_sha256_compression(dfg, arguments, block, call_stack) + } BlackBoxFunc::AES128Encrypt => SimplifyResult::None, } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call/blackbox.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call/blackbox.rs index ea9daf5e4c57..b3696610b17f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call/blackbox.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call/blackbox.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use acvm::blackbox_solver::sha256_compression; use acvm::{BlackBoxFunctionSolver, BlackBoxResolutionError, FieldElement, acir::AcirField}; use crate::ssa::ir::call_stack::CallStackId; @@ -233,6 +234,55 @@ pub(super) fn simplify_poseidon2_permutation( } } +pub(super) fn simplify_sha256_compression( + dfg: &mut DataFlowGraph, + arguments: &[ValueId], + block: BasicBlockId, + call_stack: CallStackId, +) -> SimplifyResult { + match (dfg.get_array_constant(arguments[0]), dfg.get_array_constant(arguments[1])) { + (Some((state, _)), Some((msg_blocks, _))) + if array_is_constant(dfg, &state) && array_is_constant(dfg, &msg_blocks) => + { + let state: Option> = state + .iter() + .map(|id| { + dfg.get_numeric_constant(*id) + .expect("value id from array should point at constant") + .try_to_u32() + }) + .collect(); + + let Some(mut state) = state.and_then(|vec| <[u32; 8]>::try_from(vec).ok()) else { + return SimplifyResult::None; + }; + + let msg_blocks: Option> = msg_blocks + .iter() + .map(|id| { + dfg.get_numeric_constant(*id) + .expect("value id from array should point at constant") + .try_to_u32() + }) + .collect(); + + let Some(msg_blocks) = msg_blocks.and_then(|vec| <[u32; 16]>::try_from(vec).ok()) + else { + return SimplifyResult::None; + }; + + sha256_compression(&mut state, &msg_blocks); + + let new_state = state.into_iter().map(FieldElement::from); + let typ = NumericType::Unsigned { bit_size: 32 }; + let result_array = make_constant_array(dfg, new_state, typ, block, call_stack); + + SimplifyResult::SimplifiedTo(result_array) + } + _ => SimplifyResult::None, + } +} + pub(super) fn simplify_hash( dfg: &mut DataFlowGraph, arguments: &[ValueId], @@ -308,7 +358,7 @@ pub(super) fn simplify_signature( #[cfg(feature = "bn254")] #[cfg(test)] -mod test { +mod multi_scalar_mul { use crate::ssa::Ssa; use crate::ssa::opt::assert_normalized_ssa_equals; @@ -317,7 +367,7 @@ mod test { fn full_constant_folding() { let src = r#" acir(inline) fn main f0 { - b0(): + b0(): v0 = make_array [Field 2, Field 3, Field 5, Field 5] : [Field; 4] v1 = make_array [Field 1, Field 17631683881184975370165255887551781615748388533673675138860, Field 0, Field 1, Field 17631683881184975370165255887551781615748388533673675138860, Field 0] : [Field; 6] v2 = call multi_scalar_mul (v1, v0) -> [Field; 3] @@ -327,7 +377,7 @@ mod test { let expected_src = r#" acir(inline) fn main f0 { - b0(): + b0(): v3 = make_array [Field 2, Field 3, Field 5, Field 5] : [Field; 4] v7 = make_array [Field 1, Field 17631683881184975370165255887551781615748388533673675138860, Field 0, Field 1, Field 17631683881184975370165255887551781615748388533673675138860, Field 0] : [Field; 6] v10 = make_array [Field 1478523918288173385110236399861791147958001875200066088686689589556927843200, Field 700144278551281040379388961242974992655630750193306467120985766322057145630, Field 0] : [Field; 3] @@ -342,7 +392,7 @@ mod test { fn simplify_zero() { let src = r#" acir(inline) fn main f0 { - b0(v0: Field, v1: Field): + b0(v0: Field, v1: Field): v2 = make_array [v0, Field 0, Field 0, Field 0, v0, Field 0] : [Field; 6] v3 = make_array [ Field 0, Field 0, Field 1, v0, v1, Field 0, Field 1, v0, Field 0] : [Field; 9] @@ -355,7 +405,7 @@ mod test { //First point is zero, second scalar is zero, so we should be left with the scalar mul of the last point. let expected_src = r#" acir(inline) fn main f0 { - b0(v0: Field, v1: Field): + b0(v0: Field, v1: Field): v3 = make_array [v0, Field 0, Field 0, Field 0, v0, Field 0] : [Field; 6] v5 = make_array [Field 0, Field 0, Field 1, v0, v1, Field 0, Field 1, v0, Field 0] : [Field; 9] v6 = make_array [v0, Field 0] : [Field; 2] @@ -372,7 +422,7 @@ mod test { fn partial_constant_folding() { let src = r#" acir(inline) fn main f0 { - b0(v0: Field, v1: Field): + b0(v0: Field, v1: Field): v2 = make_array [Field 1, Field 0, v0, Field 0, Field 2, Field 0] : [Field; 6] v3 = make_array [ Field 1, Field 17631683881184975370165255887551781615748388533673675138860, Field 0, v0, v1, Field 0, Field 1, Field 17631683881184975370165255887551781615748388533673675138860, Field 0] : [Field; 9] @@ -383,7 +433,7 @@ mod test { //First and last scalar/point are constant, so we should be left with the msm of the middle point and the folded constant point let expected_src = r#" acir(inline) fn main f0 { - b0(v0: Field, v1: Field): + b0(v0: Field, v1: Field): v5 = make_array [Field 1, Field 0, v0, Field 0, Field 2, Field 0] : [Field; 6] v7 = make_array [Field 1, Field 17631683881184975370165255887551781615748388533673675138860, Field 0, v0, v1, Field 0, Field 1, Field 17631683881184975370165255887551781615748388533673675138860, Field 0] : [Field; 9] v8 = make_array [v0, Field 0, Field 1, Field 0] : [Field; 4] @@ -395,3 +445,32 @@ mod test { assert_normalized_ssa_equals(ssa, expected_src); } } + +#[cfg(test)] +mod sha256_compression { + use crate::ssa::Ssa; + use crate::ssa::opt::assert_normalized_ssa_equals; + + #[test] + fn is_optimized_out_with_constant_arguments() { + let src = r#" + acir(inline) fn main f0 { + b0(): + v0 = make_array [u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0] : [u32; 8] + v1 = make_array [u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0] : [u32; 16] + v2 = call sha256_compression(v0, v1) -> [u32; 8] + return v2 + }"#; + let ssa = Ssa::from_str_simplifying(src).unwrap(); + let expected_src = r#" + acir(inline) fn main f0 { + b0(): + v1 = make_array [u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0] : [u32; 8] + v2 = make_array [u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0, u32 0] : [u32; 16] + v11 = make_array [u32 2091193876, u32 1113340840, u32 3461668143, u32 3254913767, u32 3068490961, u32 2551409935, u32 2927503052, u32 3205228454] : [u32; 8] + return v11 + } + "#; + assert_normalized_ssa_equals(ssa, expected_src); + } +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs index 48587cb4b7bc..6f26fef071e4 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs @@ -210,7 +210,7 @@ mod tests { // Regression test for https://github.com/noir-lang/noir/issues/7451 let src = " acir(inline) predicate_pure fn main f0 { - b0(v0: u8): + b0(v0: u8): v1 = and u8 255, v0 return v1 } @@ -220,7 +220,7 @@ mod tests { let expected = " acir(inline) fn main f0 { - b0(v0: u8): + b0(v0: u8): return v0 } "; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/basic_conditional.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/basic_conditional.rs new file mode 100644 index 000000000000..669a3dd77830 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/basic_conditional.rs @@ -0,0 +1,526 @@ +use std::collections::HashSet; + +use acvm::AcirField; +use fxhash::FxHashMap as HashMap; +use iter_extended::vecmap; + +use crate::ssa::{ + Ssa, + ir::{ + basic_block::BasicBlockId, + cfg::ControlFlowGraph, + dfg::DataFlowGraph, + function::{Function, FunctionId}, + function_inserter::FunctionInserter, + instruction::{BinaryOp, Instruction, TerminatorInstruction}, + post_order::PostOrder, + value::ValueId, + }, +}; + +use super::flatten_cfg::Context; +#[derive(Debug, Clone)] +struct BasicConditional { + block_entry: BasicBlockId, + block_then: Option, + block_else: Option, + block_exit: BasicBlockId, +} + +impl Ssa { + #[tracing::instrument(level = "trace", skip(self))] + /// This pass flatten simple IF-THEN-ELSE statements + /// This optimization pass identifies simple conditional control flow patterns in unconstrained code + /// and flattens them to reduce the number of basic blocks and improve performance. + /// + /// e.g: if c {a} else {b} would be flattened to c*(a-b)+b + /// A simple conditional pattern is defined as an IF-THEN (with optional ELSE) statement, with no nested conditional nor loop statements + /// Performance improvement is based on a simple execution cost metric + pub(crate) fn flatten_basic_conditionals(mut self) -> Ssa { + // Retrieve the 'no_predicates' attribute of the functions in a map, to avoid problems with borrowing + let mut no_predicates = HashMap::default(); + for function in self.functions.values() { + no_predicates.insert(function.id(), function.is_no_predicates()); + } + for function in self.functions.values_mut() { + flatten_function(function, &mut no_predicates); + } + self + } +} + +/// Returns the blocks of the simple conditional sub-graph whose input block is the entry. +/// Returns None if the input block is not the entry block of a simple conditional. +fn is_conditional( + block: BasicBlockId, + cfg: &ControlFlowGraph, + function: &Function, +) -> Option { + // jump overhead is the cost for doing the conditional and jump around the blocks + // We use 10 as a rough estimate, the real cost is less. + let jump_overhead = 10; + let mut successors = cfg.successors(block); + let mut result = None; + // a conditional must have 2 branches + if successors.len() != 2 { + return None; + } + let left = successors.next().unwrap(); + let right = successors.next().unwrap(); + let mut left_successors = cfg.successors(left); + let mut right_successors = cfg.successors(right); + let left_successors_len = left_successors.len(); + let right_successors_len = right_successors.len(); + let next_left = left_successors.next(); + let next_right = right_successors.next(); + if next_left == Some(block) || next_right == Some(block) { + // this is a loop, not a conditional + return None; + } + if left_successors_len == 1 && right_successors_len == 1 && next_left == next_right { + // The branches join on one block so it is a non-nested conditional + let cost_left = block_cost(left, &function.dfg); + let cost_right = block_cost(right, &function.dfg); + // For the flattening to be valuable, we compare the cost of the flattened code with the average cost of the 2 branches, + // including an overhead to take into account the jumps between the blocks. + let cost = cost_right.saturating_add(cost_left); + if cost < cost / 2 + jump_overhead { + if let Some(TerminatorInstruction::JmpIf { + condition: _, + then_destination, + else_destination, + call_stack: _, + }) = function.dfg[block].terminator() + { + result = Some(BasicConditional { + block_entry: block, + block_then: Some(*then_destination), + block_else: Some(*else_destination), + block_exit: next_left.unwrap(), + }); + } + } + } else if left_successors_len == 1 && next_left == Some(right) { + // Left branch joins the right branch, e.g if/then statement with no else + // This case may not happen (i.e not generated), but it is safer to handle it (e.g in case it happens due to some optimizations) + let cost = block_cost(left, &function.dfg); + if cost < cost / 2 + jump_overhead { + if let Some(TerminatorInstruction::JmpIf { + condition: _, + then_destination, + else_destination, + call_stack: _, + }) = function.dfg[block].terminator() + { + let (block_then, block_else) = if left == *then_destination { + (Some(left), None) + } else if left == *else_destination { + (None, Some(left)) + } else { + return None; + }; + + result = Some(BasicConditional { + block_entry: block, + block_then, + block_else, + block_exit: right, + }); + } + } + } else if right_successors_len == 1 && next_right == Some(left) { + // Right branch joins the left branch, e.g if/else statement with no then + // This case may not happen (i.e not generated), but it is safer to handle it (e.g in case it happens due to some optimizations) + let cost = block_cost(right, &function.dfg); + if cost < cost / 2 + jump_overhead { + if let Some(TerminatorInstruction::JmpIf { + condition: _, + then_destination, + else_destination, + call_stack: _, + }) = function.dfg[block].terminator() + { + let (block_then, block_else) = if right == *then_destination { + (Some(right), None) + } else if right == *else_destination { + (None, Some(right)) + } else { + return None; + }; + result = Some(BasicConditional { + block_entry: block, + block_then, + block_else, + block_exit: right, + }); + } + } + } + // A conditional exit would have exactly 2 predecessors + result.filter(|result| cfg.predecessors(result.block_exit).len() == 2) +} + +/// Computes a cost estimate of a basic block +/// returns u32::MAX if the block has side-effect instructions +/// WARNING: these are estimates of the runtime cost of each instruction, +/// 1 being the cost of the simplest instruction. These numbers can be improved. +fn block_cost(block: BasicBlockId, dfg: &DataFlowGraph) -> u32 { + let mut cost: u32 = 0; + for instruction in dfg[block].instructions() { + let instruction_cost = match &dfg[*instruction] { + Instruction::Binary(binary) => { + match binary.operator { + BinaryOp::Add { unchecked } + | BinaryOp::Sub { unchecked } + | BinaryOp::Mul { unchecked } => if unchecked { 3 } else { return u32::MAX }, + BinaryOp::Div + | BinaryOp::Mod => return u32::MAX, + BinaryOp::Eq => 1, + BinaryOp::Lt => 5, + BinaryOp::And + | BinaryOp::Or + | BinaryOp::Xor => 1, + BinaryOp::Shl + | BinaryOp::Shr => return u32::MAX, + } + }, + // A Cast can be either simplified, or lead to a truncate + Instruction::Cast(_, _) => 3, + Instruction::Not(_) => 1, + Instruction::Truncate { .. } => 7, + + Instruction::Constrain(_,_,_) + | Instruction::ConstrainNotEqual(_,_,_) + | Instruction::RangeCheck { .. } + // Calls with no-predicate set to true could be supported, but + // they are likely to be too costly anyways. Simple calls would + // have been inlined already. + | Instruction::Call { .. } + | Instruction::Load { .. } + | Instruction::Store { .. } + | Instruction::ArraySet { .. } => return u32::MAX, + + Instruction::ArrayGet { array, index } => { + // A get can fail because of out-of-bound index + let mut in_bound = false; + // check if index is in bound + if let (Some(index), Some(len)) = (dfg.get_numeric_constant(*index), dfg.try_get_array_length(*array)) { + // The index is in-bounds + if index.to_u128() < len as u128 { + in_bound = true; + } + } + if !in_bound { + return u32::MAX; + } + 1 + }, + // if less than 10 elements, it is translated into a store for each element + // if more than 10, it is a loop, so 20 should be a good estimate, worst case being 10 stores and ~10 index increments + Instruction::MakeArray { .. } => 20, + + Instruction::Allocate + | Instruction::EnableSideEffectsIf { .. } + | Instruction::IncrementRc { .. } + | Instruction::DecrementRc { .. } + | Instruction::Noop => 0, + Instruction::IfElse { .. } => 1, + }; + cost += instruction_cost; + } + cost +} + +/// Identifies all simple conditionals in the function and flattens them +fn flatten_function(function: &mut Function, no_predicates: &mut HashMap) { + // This pass is dedicated to brillig functions + if !function.runtime().is_brillig() { + return; + } + let cfg = ControlFlowGraph::with_function(function); + let mut stack = vec![function.entry_block()]; + let mut processed = HashSet::new(); + let mut conditionals = Vec::new(); + + // 1. Process all blocks of the cfg, starting from the root and following the successors + while let Some(block) = stack.pop() { + // Avoid cycles + if processed.contains(&block) { + continue; + } + processed.insert(block); + + // Identify the simple conditionals + if let Some(conditional) = is_conditional(block, &cfg, function) { + // no need to check the branches, process the join block directly + stack.push(conditional.block_exit); + conditionals.push(conditional); + } else { + stack.extend(cfg.successors(block)); + } + } + + // 2. Flatten all simple conditionals + // process basic conditionals in reverse order so that + // a conditional does not impact the previous ones + conditionals.reverse(); + flatten_multiple(&conditionals, function, no_predicates); +} + +fn flatten_multiple( + conditionals: &Vec, + function: &mut Function, + no_predicates: &mut HashMap, +) { + // 1. process each basic conditional, using a new context per conditional + let post_order = PostOrder::with_function(function); + + let mut mapping = HashMap::default(); + for conditional in conditionals { + let cfg = ControlFlowGraph::with_function(function); + let cfg_root = function.entry_block(); + let mut branch_ends = HashMap::default(); + branch_ends.insert(conditional.block_entry, conditional.block_exit); + let mut context = Context::new(function, cfg, branch_ends, cfg_root); + context.flatten_single_conditional(conditional, no_predicates); + // extract the mapping into 'mapping + context.inserter.extract_mapping(&mut mapping); + } + // 2. re-map the full program for values that may been simplified. + if !mapping.is_empty() { + for block in post_order.as_slice() { + Context::map_block_with_mapping(mapping.clone(), function, *block); + } + } +} + +impl Context<'_> { + fn flatten_single_conditional( + &mut self, + conditional: &BasicConditional, + no_predicates: &mut HashMap, + ) { + // Manually inline 'then', 'else' and 'exit' into the entry block + //0. initialize the context for flattening a 'single conditional' + let old_target = self.target_block; + let old_no_predicate = self.no_predicate; + let mut queue = vec![]; + self.target_block = conditional.block_entry; + self.no_predicate = true; + //1. process 'then' branch + self.inline_block(conditional.block_entry, no_predicates); + let to_process = self.handle_terminator(conditional.block_entry, &queue); + queue.extend(to_process); + if let Some(then) = conditional.block_then { + assert_eq!(queue.pop(), conditional.block_then); + self.inline_block(then, no_predicates); + let to_process = self.handle_terminator(then, &queue); + + for incoming_block in to_process { + if !queue.contains(&incoming_block) { + queue.push(incoming_block); + } + } + } + + //2. process 'else' branch, in case there is no 'then' + let next = queue.pop(); + if next == conditional.block_else { + let next = next.unwrap(); + self.inline_block(next, no_predicates); + let _ = self.handle_terminator(next, &queue); + } else { + assert_eq!(next, Some(conditional.block_exit)); + } + + //3. process 'exit' block + self.inline_block(conditional.block_exit, no_predicates); + // Manually set the terminator of the entry block to the one of the exit block + let terminator = + self.inserter.function.dfg[conditional.block_exit].terminator().unwrap().clone(); + let new_terminator = match terminator { + TerminatorInstruction::JmpIf { + condition, + then_destination, + else_destination, + call_stack, + } => { + let condition = self.inserter.resolve(condition); + TerminatorInstruction::JmpIf { + condition, + then_destination, + else_destination, + call_stack, + } + } + TerminatorInstruction::Jmp { destination, arguments, call_stack } => { + let arguments = vecmap(arguments, |value| self.inserter.resolve(value)); + TerminatorInstruction::Jmp { destination, arguments, call_stack } + } + TerminatorInstruction::Return { return_values, call_stack } => { + let return_values = vecmap(return_values, |value| self.inserter.resolve(value)); + TerminatorInstruction::Return { return_values, call_stack } + } + }; + self.inserter.function.dfg.set_block_terminator(conditional.block_entry, new_terminator); + self.inserter.map_data_bus_in_place(); + //4. restore the context, in case it is re-used. + self.target_block = old_target; + self.no_predicate = old_no_predicate; + } + + fn map_block_with_mapping( + mapping: HashMap, + func: &mut Function, + block: BasicBlockId, + ) { + // Map all instructions in the block + let mut inserter = FunctionInserter::new(func); + inserter.set_mapping(mapping); + let instructions = inserter.function.dfg[block].instructions().to_vec(); + for instruction in instructions { + inserter.map_instruction_in_place(instruction); + } + inserter.map_terminator_in_place(block); + } +} + +#[cfg(test)] +mod test { + use crate::ssa::{Ssa, opt::assert_normalized_ssa_equals}; + + #[test] + fn basic_jmpif() { + let src = " + brillig(inline) fn foo f0 { + b0(v0: u32): + v3 = eq v0, u32 0 + jmpif v3 then: b2, else: b1 + b1(): + jmp b3(u32 5) + b2(): + jmp b3(u32 3) + b3(v1: u32): + return v1 + } + "; + let ssa = Ssa::from_str(src).unwrap(); + assert_eq!(ssa.main().reachable_blocks().len(), 4); + + let expected = " + brillig(inline) fn foo f0 { + b0(v0: u32): + v2 = eq v0, u32 0 + v3 = not v2 + v4 = cast v2 as u32 + v5 = cast v3 as u32 + v7 = unchecked_mul v4, u32 3 + v9 = unchecked_mul v5, u32 5 + v10 = unchecked_add v7, v9 + return v10 + } + "; + + let ssa = ssa.flatten_basic_conditionals(); + assert_normalized_ssa_equals(ssa, expected); + } + + #[test] + fn array_jmpif() { + let src = r#" + brillig(inline) fn foo f0 { + b0(v0: u32): + v3 = eq v0, u32 5 + jmpif v3 then: b2, else: b1 + b1(): + v6 = make_array b"foo" + jmp b3(v6) + b2(): + v10 = make_array b"bar" + jmp b3(v10) + b3(v1: [u8; 3]): + return v1 + } + "#; + let ssa = Ssa::from_str(src).unwrap(); + assert_eq!(ssa.main().reachable_blocks().len(), 4); + let ssa = ssa.flatten_basic_conditionals(); + // make_array is not simplified + assert_normalized_ssa_equals(ssa, src); + } + + #[test] + fn nested_jmpifs() { + let src = " + brillig(inline) fn foo f0 { + b0(v0: u32): + v5 = eq v0, u32 5 + v6 = not v5 + jmpif v5 then: b5, else: b1 + b1(): + v8 = lt v0, u32 3 + jmpif v8 then: b3, else: b2 + b2(): + v9 = truncate v0 to 2 bits, max_bit_size: 32 + jmp b4(v9) + b3(): + v10 = truncate v0 to 1 bits, max_bit_size: 32 + jmp b4(v10) + b4(v1: u32): + jmp b9(v1) + b5(): + v12 = lt u32 2, v0 + jmpif v12 then: b7, else: b6 + b6(): + v13 = truncate v0 to 3 bits, max_bit_size: 32 + jmp b8(v13) + b7(): + v14 = and v0, u32 2 + jmp b8(v14) + b8(v2: u32): + jmp b9(v2) + b9(v3: u32): + return v3 + } + "; + let ssa = Ssa::from_str(src).unwrap(); + assert_eq!(ssa.main().reachable_blocks().len(), 10); + + let expected = " + brillig(inline) fn foo f0 { + b0(v0: u32): + v3 = eq v0, u32 5 + v4 = not v3 + jmpif v3 then: b2, else: b1 + b1(): + v6 = lt v0, u32 3 + v7 = truncate v0 to 1 bits, max_bit_size: 32 + v8 = not v6 + v9 = truncate v0 to 2 bits, max_bit_size: 32 + v10 = cast v6 as u32 + v11 = cast v8 as u32 + v12 = unchecked_mul v10, v7 + v13 = unchecked_mul v11, v9 + v14 = unchecked_add v12, v13 + jmp b3(v14) + b2(): + v16 = lt u32 2, v0 + v17 = and v0, u32 2 + v18 = not v16 + v19 = truncate v0 to 3 bits, max_bit_size: 32 + v20 = cast v16 as u32 + v21 = cast v18 as u32 + v22 = unchecked_mul v20, v17 + v23 = unchecked_mul v21, v19 + v24 = unchecked_add v22, v23 + jmp b3(v24) + b3(v1: u32): + return v1 + } + "; + + let ssa = ssa.flatten_basic_conditionals(); + assert_eq!(ssa.main().reachable_blocks().len(), 4); + assert_normalized_ssa_equals(ssa, expected); + } +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs index 380daabee6cf..a2844a599975 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/brillig_entry_points.rs @@ -13,7 +13,7 @@ //! generated for different entry points can conflict. //! //! To provide a more concrete example, let's take this program: -//! ``` +//! ```noir //! global ONE: Field = 1; //! global TWO: Field = 2; //! global THREE: Field = 3; @@ -40,7 +40,7 @@ //! } //! ``` //! The two entry points will have different global allocation maps: -//! ``` +//! ```noir //! GlobalInit(Id(1)): //! CONST M32835 = 1 //! CONST M32836 = 2 diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs index 51d3f0f01054..e742ad4aa5d8 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -135,9 +135,8 @@ struct Context { /// them just yet. flattened: bool, - // When tracking mutations we consider arrays with the same type as all being possibly mutated. - // This we consider to span all blocks of the functions. - mutated_array_types: HashSet, + /// Track IncrementRc instructions per block to determine whether they are useless. + rc_tracker: RcTracker, } impl Context { @@ -167,10 +166,8 @@ impl Context { let block = &function.dfg[block_id]; self.mark_terminator_values_as_used(function, block); - // Lend the shared array type to the tracker. - let mut mutated_array_types = std::mem::take(&mut self.mutated_array_types); - let mut rc_tracker = RcTracker::new(&mut mutated_array_types); - rc_tracker.mark_terminator_arrays_as_used(function, block); + self.rc_tracker.new_block(); + self.rc_tracker.mark_terminator_arrays_as_used(function, block); let instructions_len = block.instructions().len(); @@ -203,12 +200,11 @@ impl Context { } } - rc_tracker.track_inc_rcs_to_remove(*instruction_id, function); + self.rc_tracker.track_inc_rcs_to_remove(*instruction_id, function); } - self.instructions_to_remove.extend(rc_tracker.get_non_mutated_arrays(&function.dfg)); - self.instructions_to_remove.extend(rc_tracker.rc_pairs_to_remove); - + self.instructions_to_remove.extend(self.rc_tracker.get_non_mutated_arrays(&function.dfg)); + self.instructions_to_remove.extend(self.rc_tracker.rc_pairs_to_remove.drain()); // If there are some instructions that might trigger an out of bounds error, // first add constrain checks. Then run the DIE pass again, which will remove those // but leave the constrains (any any value needed by those constrains) @@ -228,9 +224,6 @@ impl Context { .instructions_mut() .retain(|instruction| !self.instructions_to_remove.contains(instruction)); - // Take the mutated array back. - self.mutated_array_types = mutated_array_types; - false } @@ -279,11 +272,15 @@ impl Context { let typ = typ.get_contained_array(); // Want to store the array type which is being referenced, // because it's the underlying array that the `inc_rc` is associated with. - self.mutated_array_types.insert(typ.clone()); + self.add_mutated_array_type(typ.clone()); } } } + fn add_mutated_array_type(&mut self, typ: Type) { + self.rc_tracker.mutated_array_types.insert(typ.get_contained_array().clone()); + } + /// Go through the RC instructions collected when we figured out which values were unused; /// for each RC that refers to an unused value, remove the RC as well. fn remove_rc_instructions(&self, dfg: &mut DataFlowGraph) { @@ -615,8 +612,9 @@ fn apply_side_effects( (lhs, rhs) } +#[derive(Default)] /// Per block RC tracker. -struct RcTracker<'a> { +struct RcTracker { // We can track IncrementRc instructions per block to determine whether they are useless. // IncrementRc and DecrementRc instructions are normally side effectual instructions, but we remove // them if their value is not used anywhere in the function. However, even when their value is used, their existence @@ -631,7 +629,8 @@ struct RcTracker<'a> { // If an array is the same type as one of those non-mutated array types, we can safely remove all IncrementRc instructions on that array. inc_rcs: HashMap>, // Mutated arrays shared across the blocks of the function. - mutated_array_types: &'a mut HashSet, + // When tracking mutations we consider arrays with the same type as all being possibly mutated. + mutated_array_types: HashSet, // The SSA often creates patterns where after simplifications we end up with repeat // IncrementRc instructions on the same value. We track whether the previous instruction was an IncrementRc, // and if the current instruction is also an IncrementRc on the same value we remove the current instruction. @@ -639,15 +638,12 @@ struct RcTracker<'a> { previous_inc_rc: Option, } -impl<'a> RcTracker<'a> { - fn new(mutated_array_types: &'a mut HashSet) -> Self { - Self { - rcs_with_possible_pairs: Default::default(), - rc_pairs_to_remove: Default::default(), - inc_rcs: Default::default(), - previous_inc_rc: Default::default(), - mutated_array_types, - } +impl RcTracker { + fn new_block(&mut self) { + self.rcs_with_possible_pairs.clear(); + self.rc_pairs_to_remove.clear(); + self.inc_rcs.clear(); + self.previous_inc_rc = Default::default(); } fn mark_terminator_arrays_as_used(&mut self, function: &Function, block: &BasicBlock) { @@ -1128,4 +1124,38 @@ mod test { "; assert_normalized_ssa_equals(ssa, expected); } + + #[test] + fn do_not_remove_inc_rc_if_mutated_in_other_block() { + let src = " + brillig(inline) fn main f0 { + b0(v0: &mut [Field; 3]): + v1 = load v0 -> [Field; 3] + inc_rc v1 + jmp b1() + b1(): + v2 = load v0 -> [Field; 3] + v3 = array_set v2, index u32 0, value u32 0 + store v3 at v0 + return + } + "; + let ssa = Ssa::from_str(src).unwrap(); + + let expected = " + brillig(inline) fn main f0 { + b0(v0: &mut [Field; 3]): + v1 = load v0 -> [Field; 3] + inc_rc v1 + jmp b1() + b1(): + v2 = load v0 -> [Field; 3] + v4 = array_set v2, index u32 0, value u32 0 + store v4 at v0 + return + } + "; + let ssa = ssa.dead_instruction_elimination(); + assert_normalized_ssa_equals(ssa, expected); + } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 2ffaa52f0eab..a25e3db2b080 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -176,12 +176,15 @@ impl Ssa { } } -struct Context<'f> { - inserter: FunctionInserter<'f>, +pub(crate) struct Context<'f> { + pub(crate) inserter: FunctionInserter<'f>, /// This ControlFlowGraph is the graph from before the function was modified by this flattening pass. cfg: ControlFlowGraph, + /// Target block of the flattening + pub(crate) target_block: BasicBlockId, + /// Maps start of branch -> end of branch branch_ends: HashMap, @@ -213,6 +216,10 @@ struct Context<'f> { /// us from unnecessarily inserting extra instructions, and keeps ids unique which /// helps simplifications. not_instructions: HashMap, + + /// Flag to tell the context to not issue 'enable_side_effect' instructions during flattening. + /// This should be set to true only by flatten_single(), when no instruction is known to fail. + pub(crate) no_predicate: bool, } #[derive(Clone)] @@ -249,6 +256,7 @@ fn flatten_function_cfg(function: &mut Function, no_predicates: &HashMap { - fn flatten(&mut self, no_predicates: &HashMap) { +impl<'f> Context<'f> { + //impl Context<'_> { + pub(crate) fn new( + function: &'f mut Function, + cfg: ControlFlowGraph, + branch_ends: HashMap, + target_block: BasicBlockId, + ) -> Self { + Context { + inserter: FunctionInserter::new(function), + cfg, + branch_ends, + condition_stack: Vec::new(), + arguments_stack: Vec::new(), + local_allocations: HashSet::default(), + not_instructions: HashMap::default(), + target_block, + no_predicate: false, + } + } + + pub(crate) fn flatten(&mut self, no_predicates: &HashMap) { // Flatten the CFG by inlining all instructions from the queued blocks // until all blocks have been flattened. // We follow the terminator of each block to determine which blocks to // process next - let mut queue = vec![self.inserter.function.entry_block()]; + let mut queue = vec![self.target_block]; while let Some(block) = queue.pop() { self.inline_block(block, no_predicates); let to_process = self.handle_terminator(block, &queue); @@ -318,10 +348,14 @@ impl Context<'_> { result } - // Inline all instructions from the given block into the entry block, and track slice capacities - fn inline_block(&mut self, block: BasicBlockId, no_predicates: &HashMap) { - if self.inserter.function.entry_block() == block { - // we do not inline the entry block into itself + // Inline all instructions from the given block into the target block, and track slice capacities + pub(crate) fn inline_block( + &mut self, + block: BasicBlockId, + no_predicates: &HashMap, + ) { + if self.target_block == block { + // we do not inline the target block into itself // for the outer block before we start inlining return; } @@ -354,7 +388,7 @@ impl Context<'_> { /// For a normal block, it would be its successor /// For blocks related to a conditional statement, we ensure to process /// the 'then-branch', then the 'else-branch' (if it exists), and finally the end block - fn handle_terminator( + pub(crate) fn handle_terminator( &mut self, block: BasicBlockId, work_list: &[BasicBlockId], @@ -388,9 +422,9 @@ impl Context<'_> { let return_values = vecmap(return_values.clone(), |value| self.inserter.resolve(value)); let new_return = TerminatorInstruction::Return { return_values, call_stack }; - let entry = self.inserter.function.entry_block(); + let target = self.target_block; - self.inserter.function.dfg.set_block_terminator(entry, new_return); + self.inserter.function.dfg.set_block_terminator(target, new_return); vec![] } } @@ -544,7 +578,7 @@ impl Context<'_> { } else { self.inserter.function.dfg.make_constant(FieldElement::zero(), NumericType::bool()) }; - let block = self.inserter.function.entry_block(); + let block = self.target_block; // Cannot include this in the previous vecmap since it requires exclusive access to self let args = vecmap(args, |(then_arg, else_arg)| { @@ -568,11 +602,11 @@ impl Context<'_> { destination } - /// Insert a new instruction into the function's entry block. + /// Insert a new instruction into the target block. /// Unlike push_instruction, this function will not map any ValueIds. /// within the given instruction, nor will it modify self.values in any way. fn insert_instruction(&mut self, instruction: Instruction, call_stack: CallStackId) -> ValueId { - let block = self.inserter.function.entry_block(); + let block = self.target_block; self.inserter .function .dfg @@ -580,7 +614,7 @@ impl Context<'_> { .first() } - /// Inserts a new instruction into the function's entry block, using the given + /// Inserts a new instruction into the target block, using the given /// control type variables to specify result types if needed. /// Unlike push_instruction, this function will not map any ValueIds. /// within the given instruction, nor will it modify self.values in any way. @@ -590,7 +624,7 @@ impl Context<'_> { ctrl_typevars: Option>, call_stack: CallStackId, ) -> InsertInstructionResult { - let block = self.inserter.function.entry_block(); + let block = self.target_block; self.inserter.function.dfg.insert_instruction_and_results( instruction, block, @@ -600,11 +634,14 @@ impl Context<'_> { } /// Checks the branch condition on the top of the stack and uses it to build and insert an - /// `EnableSideEffectsIf` instruction into the entry block. + /// `EnableSideEffectsIf` instruction into the target block. /// /// If the stack is empty, a "true" u1 constant is taken to be the active condition. This is /// necessary for re-enabling side-effects when re-emerging to a branch depth of 0. fn insert_current_side_effects_enabled(&mut self) { + if self.no_predicate { + return; + } let condition = match self.get_last_condition() { Some(cond) => cond, None => { @@ -616,7 +653,7 @@ impl Context<'_> { self.insert_instruction_with_typevars(enable_side_effects, None, call_stack); } - /// Push the given instruction to the end of the entry block of the current function. + /// Push the given instruction to the end of the target block of the current function. /// /// Note that each ValueId of the instruction will be mapped via self.inserter.resolve. /// As a result, the instruction that will be pushed will actually be a new instruction @@ -631,8 +668,8 @@ impl Context<'_> { let instruction = self.handle_instruction_side_effects(instruction, call_stack); let instruction_is_allocate = matches!(&instruction, Instruction::Allocate); - let entry = self.inserter.function.entry_block(); - let results = self.inserter.push_instruction_value(instruction, id, entry, call_stack); + let results = + self.inserter.push_instruction_value(instruction, id, self.target_block, call_stack); // Remember an allocate was created local to this branch so that we do not try to merge store // values across branches for it later. diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/hint.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/hint.rs index 638da8b7b6e5..911554e5d6dd 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/hint.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/hint.rs @@ -20,7 +20,7 @@ mod tests { emit_ssa: None, skip_underconstrained_check: true, enable_brillig_constraints_check_lookback: false, - enable_brillig_constraints_check: false, + skip_brillig_constraints_check: true, inliner_aggressiveness: 0, max_bytecode_increase_percent: None, }; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs index 15414e92eff6..161eea182d64 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs @@ -1274,29 +1274,29 @@ mod test { fn inline_simple_functions_with_zero_instructions() { let src = " acir(inline) fn main f0 { - b0(v0: Field): - v2 = call f1(v0) -> Field - v3 = call f1(v0) -> Field - v4 = add v2, v3 - return v4 + b0(v0: Field): + v2 = call f1(v0) -> Field + v3 = call f1(v0) -> Field + v4 = add v2, v3 + return v4 } acir(inline) fn foo f1 { - b0(v0: Field): - return v0 + b0(v0: Field): + return v0 } "; let ssa = Ssa::from_str(src).unwrap(); let expected = " acir(inline) fn main f0 { - b0(v0: Field): - v1 = add v0, v0 - return v1 + b0(v0: Field): + v1 = add v0, v0 + return v1 } acir(inline) fn foo f1 { - b0(v0: Field): - return v0 + b0(v0: Field): + return v0 } "; @@ -1308,33 +1308,33 @@ mod test { fn inline_simple_functions_with_one_instruction() { let src = " acir(inline) fn main f0 { - b0(v0: Field): - v2 = call f1(v0) -> Field - v3 = call f1(v0) -> Field - v4 = add v2, v3 - return v4 + b0(v0: Field): + v2 = call f1(v0) -> Field + v3 = call f1(v0) -> Field + v4 = add v2, v3 + return v4 } acir(inline) fn foo f1 { - b0(v0: Field): - v2 = add v0, Field 1 - return v2 + b0(v0: Field): + v2 = add v0, Field 1 + return v2 } "; let ssa = Ssa::from_str(src).unwrap(); let expected = " acir(inline) fn main f0 { - b0(v0: Field): - v2 = add v0, Field 1 - v3 = add v0, Field 1 - v4 = add v2, v3 - return v4 + b0(v0: Field): + v2 = add v0, Field 1 + v3 = add v0, Field 1 + v4 = add v2, v3 + return v4 } acir(inline) fn foo f1 { - b0(v0: Field): - v2 = add v0, Field 1 - return v2 + b0(v0: Field): + v2 = add v0, Field 1 + return v2 } "; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs index f68afc55efa0..9bbac1a4c0c8 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs @@ -840,15 +840,15 @@ mod test { let src = " brillig(inline) fn main f0 { b0(v0: u32, v1: u32): - jmp b1(u32 0) + jmp b1(u32 0) b1(v2: u32): - v5 = lt v2, u32 4 - jmpif v5 then: b3, else: b2 + v5 = lt v2, u32 4 + jmpif v5 then: b3, else: b2 b2(): - return + return b3(): - v7 = sub v2, u32 1 - jmp b1(v7) + v7 = sub v2, u32 1 + jmp b1(v7) } "; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mod.rs index 38004cdf1511..a9784d4c7cfb 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mod.rs @@ -7,6 +7,7 @@ mod array_set; mod as_slice_length; mod assert_constant; +mod basic_conditional; mod brillig_array_gets; pub(crate) mod brillig_entry_points; mod check_u128_mul_overflow; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs index 7f37f98b4fb9..b4427a1c91bf 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs @@ -167,6 +167,7 @@ impl Context<'_> { let lhs_typ = self.function.dfg.type_of_value(lhs).unwrap_numeric(); let base = self.field_constant(FieldElement::from(2_u128)); let pow = self.pow(base, rhs); + let pow = self.pow_or_max_for_bit_size(pow, rhs, bit_size, lhs_typ); let pow = self.insert_cast(pow, lhs_typ); if lhs_typ.is_unsigned() { // unsigned right bit shift is just a normal division @@ -205,6 +206,53 @@ impl Context<'_> { } } + /// Returns `pow` or the maximum value allowed for `typ` if 2^rhs is guaranteed to exceed that maximum. + fn pow_or_max_for_bit_size( + &mut self, + pow: ValueId, + rhs: ValueId, + bit_size: u32, + typ: NumericType, + ) -> ValueId { + let max = if typ.is_unsigned() { + if bit_size == 128 { u128::MAX } else { (1_u128 << bit_size) - 1 } + } else { + 1_u128 << (bit_size - 1) + }; + let max = self.field_constant(FieldElement::from(max)); + + // Here we check whether rhs is less than the bit_size: if it's not then it will overflow. + // Then we do: + // + // rhs_is_less_than_bit_size = lt rhs, bit_size + // rhs_is_not_less_than_bit_size = not rhs_is_less_than_bit_size + // pow_when_is_less_than_bit_size = rhs_is_less_than_bit_size * pow + // pow_when_is_not_less_than_bit_size = rhs_is_not_less_than_bit_size * max + // pow = add pow_when_is_less_than_bit_size, pow_when_is_not_less_than_bit_size + // + // All operations here are unchecked because they work on field types. + let rhs_typ = self.function.dfg.type_of_value(rhs).unwrap_numeric(); + let bit_size = self.numeric_constant(bit_size as u128, rhs_typ); + let rhs_is_less_than_bit_size = self.insert_binary(rhs, BinaryOp::Lt, bit_size); + let rhs_is_not_less_than_bit_size = self.insert_not(rhs_is_less_than_bit_size); + let rhs_is_less_than_bit_size = + self.insert_cast(rhs_is_less_than_bit_size, NumericType::NativeField); + let rhs_is_not_less_than_bit_size = + self.insert_cast(rhs_is_not_less_than_bit_size, NumericType::NativeField); + let pow_when_is_less_than_bit_size = + self.insert_binary(rhs_is_less_than_bit_size, BinaryOp::Mul { unchecked: true }, pow); + let pow_when_is_not_less_than_bit_size = self.insert_binary( + rhs_is_not_less_than_bit_size, + BinaryOp::Mul { unchecked: true }, + max, + ); + self.insert_binary( + pow_when_is_less_than_bit_size, + BinaryOp::Add { unchecked: true }, + pow_when_is_not_less_than_bit_size, + ) + } + /// Computes lhs^rhs via square&multiply, using the bits decomposition of rhs /// Pseudo-code of the computation: /// let mut r = 1; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs index 096e5cbad860..398e52676950 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs @@ -930,6 +930,13 @@ impl FunctionReturnType { FunctionReturnType::Ty(typ) => Cow::Borrowed(typ), } } + + pub fn location(&self) -> Location { + match self { + FunctionReturnType::Default(location) => *location, + FunctionReturnType::Ty(typ) => typ.location, + } + } } impl Display for FunctionReturnType { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/enums.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/enums.rs index 90849a750d56..c0bc86b51b0d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/enums.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/enums.rs @@ -1,8 +1,9 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use fxhash::FxHashMap as HashMap; -use iter_extended::{try_vecmap, vecmap}; +use iter_extended::{btree_map, try_vecmap, vecmap}; use noirc_errors::Location; +use rangemap::StepLite; use crate::{ DataType, Kind, Shared, Type, @@ -27,6 +28,80 @@ use crate::{ use super::Elaborator; +const WILDCARD_PATTERN: &str = "_"; + +struct MatchCompiler<'elab, 'ctx> { + elaborator: &'elab mut Elaborator<'ctx>, + has_missing_cases: bool, + // We iterate on this to issue errors later so it needs to be a BTreeMap (versus HashMap) to be + // deterministic. + unreachable_cases: BTreeMap, +} + +/// A Pattern is anything that can appear before the `=>` in a match rule. +#[derive(Debug, Clone)] +enum Pattern { + /// A pattern checking for a tag and possibly binding variables such as `Some(42)` + Constructor(Constructor, Vec), + /// An integer literal pattern such as `4`, `12345`, or `-56` + Int(SignedField), + /// A pattern binding a variable such as `a` or `_` + Binding(DefinitionId), + + /// Multiple patterns combined with `|` where we should match this pattern if any + /// constituent pattern matches. e.g. `Some(3) | None` or `Some(1) | Some(2) | None` + #[allow(unused)] + Or(Vec), + + /// An integer range pattern such as `1..20` which will match any integer n such that + /// 1 <= n < 20. + #[allow(unused)] + Range(SignedField, SignedField), + + /// An error occurred while translating this pattern. This Pattern kind always translates + /// to a Fail branch in the decision tree, although the compiler is expected to halt + /// with errors before execution. + Error, +} + +#[derive(Clone)] +struct Column { + variable_to_match: DefinitionId, + pattern: Pattern, +} + +impl Column { + fn new(variable_to_match: DefinitionId, pattern: Pattern) -> Self { + Column { variable_to_match, pattern } + } +} + +#[derive(Clone)] +pub(super) struct Row { + columns: Vec, + guard: Option, + body: RowBody, + original_body: RowBody, + location: Location, +} + +type RowBody = ExprId; + +impl Row { + fn new(columns: Vec, guard: Option, body: RowBody, location: Location) -> Row { + Row { columns, guard, body, original_body: body, location } + } +} + +impl Row { + fn remove_column(&mut self, variable: DefinitionId) -> Option { + self.columns + .iter() + .position(|c| c.variable_to_match == variable) + .map(|idx| self.columns.remove(idx)) + } +} + impl Elaborator<'_> { /// Defines the value of an enum variant that we resolve an enum /// variant expression to. E.g. `Foo::Bar` in `Foo::Bar(baz)`. @@ -273,6 +348,7 @@ impl Elaborator<'_> { let rows = vecmap(rules, |(pattern, branch)| { self.push_scope(); + let pattern_location = pattern.location; let pattern = self.expression_to_pattern(pattern, &expected_pattern_type, &mut Vec::new()); let columns = vec![Column::new(variable_to_match, pattern)]; @@ -288,7 +364,7 @@ impl Elaborator<'_> { }); self.pop_scope(); - Row::new(columns, guard, body) + Row::new(columns, guard, body, pattern_location) }); (rows, result_type) } @@ -433,7 +509,7 @@ impl Elaborator<'_> { if let Some(existing) = variables_defined.iter().find(|elem| *elem == &name) { // Allow redefinition of `_` only, to ignore variables - if name.0.contents != "_" { + if name.0.contents != WILDCARD_PATTERN { self.push_err(ResolverError::VariableAlreadyDefinedInPattern { existing: existing.clone(), new_location: name.location(), @@ -524,8 +600,7 @@ impl Elaborator<'_> { ), Err(error) => { self.push_err(error); - let id = self.fresh_match_variable(expected_type.clone(), location); - Pattern::Binding(id) + Pattern::Error } } } @@ -721,17 +796,49 @@ impl Elaborator<'_> { /// /// This is an adaptation of https://github.com/yorickpeterse/pattern-matching-in-rust/tree/main/jacobs2021 /// which is an implementation of https://julesjacobs.com/notes/patternmatching/patternmatching.pdf - pub(super) fn elaborate_match_rows(&mut self, rows: Vec) -> HirMatch { - self.compile_rows(rows).unwrap_or_else(|error| { - self.push_err(error); - HirMatch::Failure - }) + pub(super) fn elaborate_match_rows( + &mut self, + rows: Vec, + type_matched_on: &Type, + location: Location, + ) -> HirMatch { + MatchCompiler::run(self, rows, type_matched_on, location) + } +} + +impl<'elab, 'ctx> MatchCompiler<'elab, 'ctx> { + fn run( + elaborator: &'elab mut Elaborator<'ctx>, + rows: Vec, + type_matched_on: &Type, + location: Location, + ) -> HirMatch { + let mut compiler = Self { + elaborator, + has_missing_cases: false, + unreachable_cases: rows.iter().map(|row| (row.body, row.location)).collect(), + }; + + let hir_match = compiler.compile_rows(rows).unwrap_or_else(|error| { + compiler.elaborator.push_err(error); + HirMatch::Failure { missing_case: false } + }); + + if compiler.has_missing_cases { + compiler.issue_missing_cases_error(&hir_match, type_matched_on, location); + } + + if !compiler.unreachable_cases.is_empty() { + compiler.issue_unreachable_cases_warning(); + } + + hir_match } fn compile_rows(&mut self, mut rows: Vec) -> Result { if rows.is_empty() { - eprintln!("Warning: missing case"); - return Ok(HirMatch::Failure); + self.has_missing_cases = true; + return Ok(HirMatch::Failure { missing_case: true }); } self.push_tests_against_bare_variables(&mut rows); @@ -741,7 +848,10 @@ impl Elaborator<'_> { let row = rows.remove(0); return Ok(match row.guard { - None => HirMatch::Success(row.body), + None => { + self.unreachable_cases.remove(&row.original_body); + HirMatch::Success(row.body) + } Some(cond) => { let remaining = self.compile_rows(rows)?; HirMatch::Guard { cond, body: row.body, otherwise: Box::new(remaining) } @@ -750,9 +860,10 @@ impl Elaborator<'_> { } let branch_var = self.branch_variable(&rows); - let location = self.interner.definition(branch_var).location; + let location = self.elaborator.interner.definition(branch_var).location; - match self.interner.definition_type(branch_var).follow_bindings_shallow().into_owned() { + let definition_type = self.elaborator.interner.definition_type(branch_var); + match definition_type.follow_bindings_shallow().into_owned() { Type::FieldElement | Type::Integer(_, _) => { let (cases, fallback) = self.compile_int_cases(rows, branch_var)?; Ok(HirMatch::Switch(branch_var, cases, Some(fallback))) @@ -849,8 +960,8 @@ impl Elaborator<'_> { fn fresh_match_variable(&mut self, variable_type: Type, location: Location) -> DefinitionId { let name = "internal_match_variable".to_string(); let kind = DefinitionKind::Local(None); - let id = self.interner.push_definition(name, false, false, kind, location); - self.interner.push_definition_type(id, variable_type); + let id = self.elaborator.interner.push_definition(name, false, false, kind, location); + self.elaborator.interner.push_definition_type(id, variable_type); id } @@ -946,7 +1057,7 @@ impl Elaborator<'_> { cols.push(Column::new(*var, pat)); } - cases[idx].2.push(Row::new(cols, row.guard, row.body)); + cases[idx].2.push(Row::new(cols, row.guard, row.body, row.location)); } } else { for (_, _, rows) in &mut cases { @@ -1045,16 +1156,16 @@ impl Elaborator<'_> { /// Creates: /// `{ let = ; }` fn let_binding(&mut self, variable: DefinitionId, rhs: DefinitionId, body: ExprId) -> ExprId { - let location = self.interner.definition(rhs).location; + let location = self.elaborator.interner.definition(rhs).location; - let r#type = self.interner.definition_type(variable); - let rhs_type = self.interner.definition_type(rhs); + let r#type = self.elaborator.interner.definition_type(variable); + let rhs_type = self.elaborator.interner.definition_type(rhs); let variable = HirIdent::non_trait_method(variable, location); let rhs = HirExpression::Ident(HirIdent::non_trait_method(rhs, location), None); - let rhs = self.interner.push_expr(rhs); - self.interner.push_expr_type(rhs, rhs_type); - self.interner.push_expr_location(rhs, location); + let rhs = self.elaborator.interner.push_expr(rhs); + self.elaborator.interner.push_expr_type(rhs, rhs_type); + self.elaborator.interner.push_expr_location(rhs, location); let let_ = HirStatement::Let(HirLetStatement { pattern: HirPattern::Identifier(variable), @@ -1065,77 +1176,185 @@ impl Elaborator<'_> { is_global_let: false, }); - let body_type = self.interner.id_type(body); - let let_ = self.interner.push_stmt(let_); - let body = self.interner.push_stmt(HirStatement::Expression(body)); + let body_type = self.elaborator.interner.id_type(body); + let let_ = self.elaborator.interner.push_stmt(let_); + let body = self.elaborator.interner.push_stmt(HirStatement::Expression(body)); - self.interner.push_stmt_location(let_, location); - self.interner.push_stmt_location(body, location); + self.elaborator.interner.push_stmt_location(let_, location); + self.elaborator.interner.push_stmt_location(body, location); let block = HirExpression::Block(HirBlockExpression { statements: vec![let_, body] }); - let block = self.interner.push_expr(block); - self.interner.push_expr_type(block, body_type); - self.interner.push_expr_location(block, location); + let block = self.elaborator.interner.push_expr(block); + self.elaborator.interner.push_expr_type(block, body_type); + self.elaborator.interner.push_expr_location(block, location); block } -} -/// A Pattern is anything that can appear before the `=>` in a match rule. -#[derive(Debug, Clone)] -enum Pattern { - /// A pattern checking for a tag and possibly binding variables such as `Some(42)` - Constructor(Constructor, Vec), - /// An integer literal pattern such as `4`, `12345`, or `-56` - Int(SignedField), - /// A pattern binding a variable such as `a` or `_` - Binding(DefinitionId), + /// Any case that isn't branched to when the match is finished must be covered by another + /// case and is thus redundant. + fn issue_unreachable_cases_warning(&mut self) { + for location in self.unreachable_cases.values().copied() { + self.elaborator.push_err(TypeCheckError::UnreachableCase { location }); + } + } - /// Multiple patterns combined with `|` where we should match this pattern if any - /// constituent pattern matches. e.g. `Some(3) | None` or `Some(1) | Some(2) | None` - #[allow(unused)] - Or(Vec), + /// Traverse the resulting HirMatch to build counter-examples of values which would + /// not be covered by the match. + fn issue_missing_cases_error( + &mut self, + tree: &HirMatch, + type_matched_on: &Type, + location: Location, + ) { + let starting_id = match tree { + HirMatch::Switch(id, ..) => *id, + _ => return self.issue_missing_cases_error_for_type(type_matched_on, location), + }; - /// An integer range pattern such as `1..20` which will match any integer n such that - /// 1 <= n < 20. - #[allow(unused)] - Range(SignedField, SignedField), + let mut cases = BTreeSet::new(); + self.find_missing_values(tree, &mut Default::default(), &mut cases, starting_id); - /// An error occurred while translating this pattern. This Pattern kind always translates - /// to a Fail branch in the decision tree, although the compiler is expected to halt - /// with errors before execution. - Error, -} + // It's possible to trigger this matching on an empty enum like `enum Void {}` + if !cases.is_empty() { + self.elaborator.push_err(TypeCheckError::MissingCases { cases, location }); + } + } -#[derive(Clone)] -struct Column { - variable_to_match: DefinitionId, - pattern: Pattern, -} + /// Issue a missing cases error if necessary for the given type, assuming that no + /// case of the type is covered. This is the case for empty matches `match foo {}`. + /// Note that this is expected not to error if the given type is an enum with zero variants. + fn issue_missing_cases_error_for_type(&mut self, type_matched_on: &Type, location: Location) { + let typ = type_matched_on.follow_bindings_shallow(); + if let Type::DataType(shared, generics) = typ.as_ref() { + if let Some(variants) = shared.borrow().get_variants(generics) { + let cases: BTreeSet<_> = variants.into_iter().map(|(name, _)| name).collect(); + if !cases.is_empty() { + self.elaborator.push_err(TypeCheckError::MissingCases { cases, location }); + } + return; + } + } + let typ = typ.to_string(); + self.elaborator.push_err(TypeCheckError::MissingManyCases { typ, location }); + } -impl Column { - fn new(variable_to_match: DefinitionId, pattern: Pattern) -> Self { - Column { variable_to_match, pattern } + fn find_missing_values( + &self, + tree: &HirMatch, + env: &mut HashMap)>, + missing_cases: &mut BTreeSet, + starting_id: DefinitionId, + ) { + match tree { + HirMatch::Success(_) | HirMatch::Failure { missing_case: false } => (), + HirMatch::Guard { otherwise, .. } => { + self.find_missing_values(otherwise, env, missing_cases, starting_id); + } + HirMatch::Failure { missing_case: true } => { + let case = Self::construct_missing_case(starting_id, env); + missing_cases.insert(case); + } + HirMatch::Switch(definition_id, cases, else_case) => { + for case in cases { + let name = case.constructor.to_string(); + env.insert(*definition_id, (name, case.arguments.clone())); + self.find_missing_values(&case.body, env, missing_cases, starting_id); + } + + if let Some(else_case) = else_case { + let typ = self.elaborator.interner.definition_type(*definition_id); + + for case in self.missing_cases(cases, &typ) { + env.insert(*definition_id, case); + self.find_missing_values(else_case, env, missing_cases, starting_id); + } + } + + env.remove(definition_id); + } + } } -} -#[derive(Clone)] -pub(super) struct Row { - columns: Vec, - guard: Option, - body: ExprId, -} + fn missing_cases(&self, cases: &[Case], typ: &Type) -> Vec<(String, Vec)> { + // We expect `cases` to come from a `Switch` which should always have + // at least 2 cases, otherwise it should be a Success or Failure node. + let first = &cases[0]; -impl Row { - fn new(columns: Vec, guard: Option, body: ExprId) -> Row { - Row { columns, guard, body } + if matches!(&first.constructor, Constructor::Int(_) | Constructor::Range(..)) { + return self.missing_integer_cases(cases, typ); + } + + let all_constructors = first.constructor.all_constructors(); + let mut all_constructors = + btree_map(all_constructors, |(constructor, arg_count)| (constructor, arg_count)); + + for case in cases { + all_constructors.remove(&case.constructor); + } + + vecmap(all_constructors, |(constructor, arg_count)| { + // Safety: this id should only be used in `env` of `find_missing_values` which + // only uses it for display and defaults to "_" on unknown ids. + let args = vecmap(0..arg_count, |_| DefinitionId::dummy_id()); + (constructor.to_string(), args) + }) } -} -impl Row { - fn remove_column(&mut self, variable: DefinitionId) -> Option { - self.columns - .iter() - .position(|c| c.variable_to_match == variable) - .map(|idx| self.columns.remove(idx)) + fn missing_integer_cases( + &self, + cases: &[Case], + typ: &Type, + ) -> Vec<(String, Vec)> { + // We could give missed cases for field ranges of `0 .. field_modulus` but since the field + // used in Noir may change we recommend a match-all pattern instead. + // If the type is a type variable, we don't know exactly which integer type this may + // resolve to so also just suggest a catch-all in that case. + if typ.is_field() || typ.is_bindable() { + return vec![(WILDCARD_PATTERN.to_string(), Vec::new())]; + } + + let mut missing_cases = rangemap::RangeInclusiveSet::new(); + + let int_max = SignedField::positive(typ.integral_maximum_size().unwrap()); + let int_min = typ.integral_minimum_size().unwrap(); + missing_cases.insert(int_min..=int_max); + + for case in cases { + match &case.constructor { + Constructor::Int(signed_field) => { + missing_cases.remove(*signed_field..=*signed_field); + } + Constructor::Range(start, end) => { + // Our ranges are exclusive, so adjust for that + missing_cases.remove(*start..=end.sub_one()); + } + _ => unreachable!( + "missing_integer_cases should only be called with Int or Range constructors" + ), + } + } + + vecmap(missing_cases, |range| { + if range.start() == range.end() { + (format!("{}", range.start()), Vec::new()) + } else { + (format!("{}..={}", range.start(), range.end()), Vec::new()) + } + }) + } + + fn construct_missing_case( + starting_id: DefinitionId, + env: &HashMap)>, + ) -> String { + let Some((constructor, arguments)) = env.get(&starting_id) else { + return WILDCARD_PATTERN.to_string(); + }; + + let no_arguments = arguments.is_empty(); + + let args = vecmap(arguments, |arg| Self::construct_missing_case(*arg, env)).join(", "); + + if no_arguments { constructor.clone() } else { format!("{constructor}({args})") } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs index 3c4d3c513d69..2ac779d3e772 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -6,12 +6,12 @@ use rustc_hash::FxHashSet as HashSet; use crate::{ DataType, Kind, QuotedType, Shared, Type, ast::{ - ArrayLiteral, BinaryOpKind, BlockExpression, CallExpression, CastExpression, + ArrayLiteral, AsTraitPath, BinaryOpKind, BlockExpression, CallExpression, CastExpression, ConstrainExpression, ConstrainKind, ConstructorExpression, Expression, ExpressionKind, Ident, IfExpression, IndexExpression, InfixExpression, ItemVisibility, Lambda, Literal, MatchExpression, MemberAccessExpression, MethodCallExpression, Path, PathSegment, - PrefixExpression, StatementKind, UnaryOp, UnresolvedTypeData, UnresolvedTypeExpression, - UnsafeExpression, + PrefixExpression, StatementKind, TraitBound, UnaryOp, UnresolvedTraitConstraint, + UnresolvedTypeData, UnresolvedTypeExpression, UnsafeExpression, }, hir::{ comptime::{self, InterpreterError}, @@ -26,7 +26,8 @@ use crate::{ HirArrayLiteral, HirBinaryOp, HirBlockExpression, HirCallExpression, HirCastExpression, HirConstrainExpression, HirConstructorExpression, HirExpression, HirIdent, HirIfExpression, HirIndexExpression, HirInfixExpression, HirLambda, HirLiteral, - HirMemberAccess, HirMethodCallExpression, HirPrefixExpression, + HirMatch, HirMemberAccess, HirMethodCallExpression, HirPrefixExpression, ImplKind, + TraitMethod, }, stmt::{HirLetStatement, HirPattern, HirStatement}, traits::{ResolvedTraitBound, TraitConstraint}, @@ -94,11 +95,8 @@ impl Elaborator<'_> { self.push_err(ResolverError::UnquoteUsedOutsideQuote { location: expr.location }); (HirExpression::Error, Type::Error) } - ExpressionKind::AsTraitPath(_) => { - self.push_err(ResolverError::AsTraitPathNotYetImplemented { - location: expr.location, - }); - (HirExpression::Error, Type::Error) + ExpressionKind::AsTraitPath(path) => { + return self.elaborate_as_trait_path(path); } ExpressionKind::TypePath(path) => return self.elaborate_type_path(path), }; @@ -363,19 +361,25 @@ impl Elaborator<'_> { (expr_id, typ) } - fn check_can_mutate(&mut self, expr_id: ExprId, location: Location) { + pub(super) fn check_can_mutate(&mut self, expr_id: ExprId, location: Location) { let expr = self.interner.expression(&expr_id); match expr { HirExpression::Ident(hir_ident, _) => { if let Some(definition) = self.interner.try_definition(hir_ident.id) { + let name = definition.name.clone(); if !definition.mutable { self.push_err(TypeCheckError::CannotMutateImmutableVariable { - name: definition.name.clone(), + name, location, }); + } else { + self.check_can_mutate_lambda_capture(hir_ident.id, name, location); } } } + HirExpression::Index(_) => { + self.push_err(TypeCheckError::MutableReferenceToArrayElement { location }); + } HirExpression::MemberAccess(member_access) => { self.check_can_mutate(member_access.lhs, location); } @@ -383,6 +387,24 @@ impl Elaborator<'_> { } } + // We must check whether the mutable variable we are attempting to mutate + // comes from a lambda capture. All captures are immutable so we want to error + // if the user attempts to mutate a captured variable inside of a lambda without mutable references. + pub(super) fn check_can_mutate_lambda_capture( + &mut self, + id: DefinitionId, + name: String, + location: Location, + ) { + if let Some(lambda_context) = self.lambda_stack.last() { + let typ = self.interner.definition_type(id); + if !typ.is_mutable_ref() && lambda_context.captures.iter().any(|var| var.ident.id == id) + { + self.push_err(TypeCheckError::MutableCaptureWithoutRef { name, location }); + } + } + } + fn elaborate_index(&mut self, index_expr: IndexExpression) -> (HirExpression, Type) { let location = index_expr.index.location; @@ -1057,11 +1079,22 @@ impl Elaborator<'_> { ) -> (HirExpression, Type) { self.use_unstable_feature(super::UnstableFeature::Enums, location); + let expr_location = match_expr.expression.location; let (expression, typ) = self.elaborate_expression(match_expr.expression); - let (let_, variable) = self.wrap_in_let(expression, typ); + let (let_, variable) = self.wrap_in_let(expression, typ.clone()); + + let (errored, (rows, result_type)) = + self.errors_occurred_in(|this| this.elaborate_match_rules(variable, match_expr.rules)); + + // Avoid calling `elaborate_match_rows` if there were errors while constructing + // the match rows - it'll just lead to extra errors like `unreachable pattern` + // warnings on branches which previously had type errors. + let tree = HirExpression::Match(if !errored { + self.elaborate_match_rows(rows, &typ, expr_location) + } else { + HirMatch::Failure { missing_case: false } + }); - let (rows, result_type) = self.elaborate_match_rules(variable, match_expr.rules); - let tree = HirExpression::Match(self.elaborate_match_rows(rows)); let tree = self.interner.push_expr(tree); self.interner.push_expr_type(tree, result_type.clone()); self.interner.push_expr_location(tree, location); @@ -1319,4 +1352,55 @@ impl Elaborator<'_> { let (expr_id, typ) = self.inline_comptime_value(result, location); Some((self.interner.expression(&expr_id), typ)) } + + fn elaborate_as_trait_path(&mut self, path: AsTraitPath) -> (ExprId, Type) { + let location = path.typ.location.merge(path.trait_path.location); + + let constraint = UnresolvedTraitConstraint { + typ: path.typ, + trait_bound: TraitBound { + trait_path: path.trait_path, + trait_id: None, + trait_generics: path.trait_generics, + }, + }; + + let typ = self.resolve_type(constraint.typ.clone()); + let Some(trait_bound) = self.resolve_trait_bound(&constraint.trait_bound) else { + // resolve_trait_bound only returns None if it has already issued an error, so don't + // issue another here. + let error = self.interner.push_expr_full(HirExpression::Error, location, Type::Error); + return (error, Type::Error); + }; + + let constraint = TraitConstraint { typ, trait_bound }; + + let the_trait = self.interner.get_trait(constraint.trait_bound.trait_id); + let Some(method) = the_trait.find_method(&path.impl_item.0.contents) else { + let trait_name = the_trait.name.to_string(); + let method_name = path.impl_item.to_string(); + let location = path.impl_item.location(); + self.push_err(ResolverError::NoSuchMethodInTrait { trait_name, method_name, location }); + let error = self.interner.push_expr_full(HirExpression::Error, location, Type::Error); + return (error, Type::Error); + }; + + let trait_method = + TraitMethod { method_id: method, constraint: constraint.clone(), assumed: true }; + + let definition_id = self.interner.trait_method_id(trait_method.method_id); + + let ident = HirIdent { + location: path.impl_item.location(), + id: definition_id, + impl_kind: ImplKind::TraitMethod(trait_method), + }; + + let id = self.interner.push_expr(HirExpression::Ident(ident.clone(), None)); + self.interner.push_expr_location(id, location); + + let typ = self.type_check_variable(ident, id, None); + self.interner.push_expr_type(id, typ.clone()); + (id, typ) + } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs index 4ce797c6e077..f4c6c7919960 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs @@ -338,7 +338,7 @@ fn can_return_without_recursing_match( match match_expr { HirMatch::Success(expr) => check(*expr), - HirMatch::Failure => true, + HirMatch::Failure { .. } => true, HirMatch::Guard { cond: _, body, otherwise } => check(*body) && check_match(otherwise), HirMatch::Switch(_, cases, otherwise) => { cases.iter().all(|case| check_match(&case.body)) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs index ea784812aafb..23a9ec5246e8 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1264,9 +1264,13 @@ impl<'context> Elaborator<'context> { self.check_parent_traits_are_implemented(&trait_impl); self.remove_trait_impl_assumed_trait_implementations(trait_impl.impl_id); - for (module, function, _) in &trait_impl.methods.functions { + for (module, function, noir_function) in &trait_impl.methods.functions { self.local_module = *module; - let errors = check_trait_impl_method_matches_declaration(self.interner, *function); + let errors = check_trait_impl_method_matches_declaration( + self.interner, + *function, + noir_function, + ); self.push_errors(errors.into_iter().map(|error| error.into())); } @@ -2163,4 +2167,13 @@ impl<'context> Elaborator<'context> { self.push_err(ParserError::with_reason(reason, location)); } } + + /// Run the given function using the resolver and return true if any errors (not warnings) + /// occurred while running it. + pub fn errors_occurred_in(&mut self, f: impl FnOnce(&mut Self) -> T) -> (bool, T) { + let previous_errors = self.errors.len(); + let ret = f(self); + let errored = self.errors.iter().skip(previous_errors).any(|error| error.is_error()); + (errored, ret) + } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs index 5402a682cdb5..f00b2a87b1ed 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs @@ -91,6 +91,7 @@ impl Elaborator<'_> { let type_contains_unspecified = let_stmt.r#type.contains_unspecified(); let annotated_type = self.resolve_inferred_type(let_stmt.r#type); + let pattern_location = let_stmt.pattern.location(); let expr_location = let_stmt.expression.location; let (expression, expr_type) = self.elaborate_expression_with_target_type(let_stmt.expression, Some(&annotated_type)); @@ -98,8 +99,11 @@ impl Elaborator<'_> { // Require the top-level of a global's type to be fully-specified if type_contains_unspecified && global_id.is_some() { let expected_type = annotated_type.clone(); - let error = - ResolverError::UnspecifiedGlobalType { location: expr_location, expected_type }; + let error = ResolverError::UnspecifiedGlobalType { + pattern_location, + expr_location, + expected_type, + }; self.push_err(error); } @@ -151,8 +155,11 @@ impl Elaborator<'_> { let (lvalue, lvalue_type, mutable) = self.elaborate_lvalue(assign.lvalue); if !mutable { - let (name, location) = self.get_lvalue_name_and_location(&lvalue); + let (_, name, location) = self.get_lvalue_error_info(&lvalue); self.push_err(TypeCheckError::VariableMustBeMutable { name, location }); + } else { + let (id, name, location) = self.get_lvalue_error_info(&lvalue); + self.check_can_mutate_lambda_capture(id, name, location); } self.unify_with_coercions(&expr_type, &lvalue_type, expression, expr_location, || { @@ -202,11 +209,10 @@ impl Elaborator<'_> { ); // Check that start range and end range have the same types - let range_location = start_location.merge(end_location); self.unify(&start_range_type, &end_range_type, || TypeCheckError::TypeMismatch { expected_typ: start_range_type.to_string(), expr_typ: end_range_type.to_string(), - expr_location: range_location, + expr_location: end_location, }); let expected_type = self.polymorphic_integer(); @@ -214,7 +220,7 @@ impl Elaborator<'_> { self.unify(&start_range_type, &expected_type, || TypeCheckError::TypeCannotBeUsed { typ: start_range_type.clone(), place: "for loop", - location: range_location, + location: start_location, }); self.interner.push_definition_type(identifier.id, start_range_type); @@ -331,20 +337,20 @@ impl Elaborator<'_> { (expr, self.interner.next_type_variable()) } - fn get_lvalue_name_and_location(&self, lvalue: &HirLValue) -> (String, Location) { + fn get_lvalue_error_info(&self, lvalue: &HirLValue) -> (DefinitionId, String, Location) { match lvalue { HirLValue::Ident(name, _) => { let location = name.location; if let Some(definition) = self.interner.try_definition(name.id) { - (definition.name.clone(), location) + (name.id, definition.name.clone(), location) } else { - ("(undeclared variable)".into(), location) + (DefinitionId::dummy_id(), "(undeclared variable)".into(), location) } } - HirLValue::MemberAccess { object, .. } => self.get_lvalue_name_and_location(object), - HirLValue::Index { array, .. } => self.get_lvalue_name_and_location(array), - HirLValue::Dereference { lvalue, .. } => self.get_lvalue_name_and_location(lvalue), + HirLValue::MemberAccess { object, .. } => self.get_lvalue_error_info(object), + HirLValue::Index { array, .. } => self.get_lvalue_error_info(array), + HirLValue::Dereference { lvalue, .. } => self.get_lvalue_error_info(lvalue), } } @@ -446,8 +452,8 @@ impl Elaborator<'_> { Type::Slice(elem_type) => *elem_type, Type::Error => Type::Error, Type::String(_) => { - let (_lvalue_name, lvalue_location) = - self.get_lvalue_name_and_location(&lvalue); + let (_id, _lvalue_name, lvalue_location) = + self.get_lvalue_error_info(&lvalue); self.push_err(TypeCheckError::StringIndexAssign { location: lvalue_location, }); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs index 0ddef96c46a9..a931dde93de5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/traits.rs @@ -273,6 +273,7 @@ impl Elaborator<'_> { pub(crate) fn check_trait_impl_method_matches_declaration( interner: &mut NodeInterner, function: FuncId, + noir_function: &NoirFunction, ) -> Vec { let meta = interner.function_meta(&function); let modifiers = interner.function_modifiers(&function); @@ -349,6 +350,8 @@ pub(crate) fn check_trait_impl_method_matches_declaration( definition_type, method_name, &meta.parameters, + &meta.return_type, + noir_function, meta.name.location, &trait_info.name.0.contents, &mut errors, @@ -358,11 +361,14 @@ pub(crate) fn check_trait_impl_method_matches_declaration( errors } +#[allow(clippy::too_many_arguments)] fn check_function_type_matches_expected_type( expected: &Type, actual: &Type, method_name: &str, actual_parameters: &Parameters, + actual_return_type: &FunctionReturnType, + noir_function: &NoirFunction, location: Location, trait_name: &str, errors: &mut Vec, @@ -381,11 +387,16 @@ fn check_function_type_matches_expected_type( if params_a.len() == params_b.len() { for (i, (a, b)) in params_a.iter().zip(params_b.iter()).enumerate() { if a.try_unify(b, &mut bindings).is_err() { + let parameter_location = noir_function.def.parameters.get(i); + let parameter_location = parameter_location.map(|param| param.typ.location); + let parameter_location = + parameter_location.unwrap_or_else(|| actual_parameters.0[i].0.location()); + errors.push(TypeCheckError::TraitMethodParameterTypeMismatch { method_name: method_name.to_string(), expected_typ: a.to_string(), actual_typ: b.to_string(), - parameter_location: actual_parameters.0[i].0.location(), + parameter_location, parameter_index: i + 1, }); } @@ -395,7 +406,7 @@ fn check_function_type_matches_expected_type( errors.push(TypeCheckError::TypeMismatch { expected_typ: ret_a.to_string(), expr_typ: ret_b.to_string(), - expr_location: location, + expr_location: actual_return_type.location(), }); } } else { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs index 0610155a7981..6e2f92a1a098 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs @@ -31,8 +31,8 @@ use crate::{ traits::{NamedType, ResolvedTraitBound, Trait, TraitConstraint}, }, node_interner::{ - DependencyId, ExprId, FuncId, GlobalValue, ImplSearchErrorKind, NodeInterner, TraitId, - TraitImplKind, TraitMethodId, + DependencyId, ExprId, FuncId, GlobalValue, ImplSearchErrorKind, TraitId, TraitImplKind, + TraitMethodId, }, signed_field::SignedField, token::SecondaryAttribute, @@ -174,8 +174,8 @@ impl Elaborator<'_> { if !kind.unifies(&resolved_type.kind()) { let expected_typ_err = CompilationError::TypeError(TypeCheckError::TypeKindMismatch { - expected_kind: kind.to_string(), - expr_kind: resolved_type.kind().to_string(), + expected_kind: kind.clone(), + expr_kind: resolved_type.kind(), expr_location: location, }); self.push_err(expected_typ_err); @@ -523,8 +523,8 @@ impl Elaborator<'_> { (Type::Constant(lhs, lhs_kind), Type::Constant(rhs, rhs_kind)) => { if !lhs_kind.unifies(&rhs_kind) { self.push_err(TypeCheckError::TypeKindMismatch { - expected_kind: lhs_kind.to_string(), - expr_kind: rhs_kind.to_string(), + expected_kind: lhs_kind, + expr_kind: rhs_kind, expr_location: location, }); return Type::Error; @@ -557,8 +557,8 @@ impl Elaborator<'_> { fn check_kind(&mut self, typ: Type, expected_kind: &Kind, location: Location) -> Type { if !typ.kind().unifies(expected_kind) { self.push_err(TypeCheckError::TypeKindMismatch { - expected_kind: expected_kind.to_string(), - expr_kind: typ.kind().to_string(), + expected_kind: expected_kind.clone(), + expr_kind: typ.kind(), expr_location: location, }); return Type::Error; @@ -766,8 +766,7 @@ impl Elaborator<'_> { make_error: impl FnOnce() -> TypeCheckError, ) { if let Err(UnificationError) = actual.unify(expected) { - let error: CompilationError = make_error().into(); - self.push_err(error); + self.push_err(make_error()); } } @@ -1837,9 +1836,8 @@ impl Elaborator<'_> { if matches!(expected_object_type.follow_bindings(), Type::MutableReference(_)) { if !matches!(actual_type, Type::MutableReference(_)) { - if let Err(error) = verify_mutable_reference(self.interner, *object) { - self.push_err(TypeCheckError::ResolverError(error)); - } + let location = self.interner.id_location(*object); + self.check_can_mutate(*object, location); let new_type = Type::MutableReference(Box::new(actual_type)); *object_type = new_type.clone(); @@ -1850,8 +1848,6 @@ impl Elaborator<'_> { // If that didn't work, then wrap the whole expression in an `&mut` *object = new_object.unwrap_or_else(|| { - let location = self.interner.id_location(*object); - let new_object = self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { operator: UnaryOp::MutableReference, @@ -2134,30 +2130,3 @@ fn bind_generic(param: &ResolvedGeneric, arg: &Type, bindings: &mut TypeBindings bindings.insert(param.type_var.id(), (param.type_var.clone(), param.kind(), arg.clone())); } } - -/// Gives an error if a user tries to create a mutable reference -/// to an immutable variable. -fn verify_mutable_reference(interner: &NodeInterner, rhs: ExprId) -> Result<(), ResolverError> { - match interner.expression(&rhs) { - HirExpression::MemberAccess(member_access) => { - verify_mutable_reference(interner, member_access.lhs) - } - HirExpression::Index(_) => { - let location = interner.expr_location(&rhs); - Err(ResolverError::MutableReferenceToArrayElement { location }) - } - HirExpression::Ident(ident, _) => { - if let Some(definition) = interner.try_definition(ident.id) { - if !definition.mutable { - let location = interner.expr_location(&rhs); - let variable = definition.name.clone(); - let err = - ResolverError::MutableReferenceToImmutableVariable { location, variable }; - return Err(err); - } - } - Ok(()) - } - _ => Ok(()), - } -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs index dcc938faf2ac..4a4835c8bf73 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs @@ -233,7 +233,7 @@ impl HirMatch { fn to_display_ast(&self, interner: &NodeInterner, location: Location) -> ExpressionKind { match self { HirMatch::Success(expr) => expr.to_display_ast(interner).kind, - HirMatch::Failure => ExpressionKind::Error, + HirMatch::Failure { .. } => ExpressionKind::Error, HirMatch::Guard { cond, body, otherwise } => { let condition = cond.to_display_ast(interner); let consequence = body.to_display_ast(interner); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index b0c71f1ebe67..81b3f8424873 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -206,6 +206,13 @@ impl CompilationError { CompilationError::DebugComptimeScopeNotFound(_, location) => *location, } } + + pub(crate) fn is_error(&self) -> bool { + // This is a bit expensive but not all error types have a `is_warning` method + // and it'd lead to code duplication to add them. `CompilationError::is_error` + // also isn't expected to be called too often. + CustomDiagnostic::from(self).is_error() + } } impl std::fmt::Display for CompilationError { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs index 4e8ed6233c5f..7f17b1e30430 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs @@ -2,7 +2,6 @@ use crate::ast::{Ident, ItemVisibility, Path, UnsupportedNumericGenericType}; use crate::hir::resolution::import::PathResolutionError; use crate::hir::type_check::generics::TraitGenerics; -use noirc_errors::FileDiagnostic; use noirc_errors::{CustomDiagnostic as Diagnostic, Location}; use thiserror::Error; @@ -76,13 +75,9 @@ pub enum DefCollectorErrorKind { } impl DefCollectorErrorKind { - pub fn into_file_diagnostic(&self, file: fm::FileId) -> FileDiagnostic { - Diagnostic::from(self).in_file(file) - } - pub fn location(&self) -> Location { match self { - DefCollectorErrorKind::Duplicate { first_def: ident, .. } + DefCollectorErrorKind::Duplicate { second_def: ident, .. } | DefCollectorErrorKind::UnresolvedModuleDecl { mod_name: ident, .. } | DefCollectorErrorKind::CannotReexportItemWithLessVisibility { item_name: ident, @@ -165,10 +160,10 @@ impl<'a> From<&'a DefCollectorErrorKind> for Diagnostic { let second_location = second_def.0.location(); let mut diag = Diagnostic::simple_error( primary_message, - format!("First {} found here", &typ), - first_location, + format!("Second {} found here", &typ), + second_location, ); - diag.add_secondary(format!("Second {} found here", &typ), second_location); + diag.add_secondary(format!("First {} found here", &typ), first_location); diag } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs index 7bd5c79dc905..bc1c519ed5d8 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -1,6 +1,6 @@ use acvm::FieldElement; pub use noirc_errors::Span; -use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic, Location}; +use noirc_errors::{CustomDiagnostic as Diagnostic, Location}; use thiserror::Error; use crate::{ @@ -76,10 +76,6 @@ pub enum ResolverError { GenericsOnAssociatedType { location: Location }, #[error("{0}")] ParserError(Box), - #[error("Cannot create a mutable reference to {variable}, it was declared to be immutable")] - MutableReferenceToImmutableVariable { variable: String, location: Location }, - #[error("Mutable references to array indices are unsupported")] - MutableReferenceToArrayElement { location: Location }, #[error("Closure environment must be a tuple or unit type")] InvalidClosureEnvironment { typ: Type, location: Location }, #[error("Nested slices, i.e. slices within an array or slice, are not supported")] @@ -109,7 +105,11 @@ pub enum ResolverError { #[error("Only `comptime` globals can be mutable")] MutableGlobal { location: Location }, #[error("Globals must have a specified type")] - UnspecifiedGlobalType { location: Location, expected_type: Type }, + UnspecifiedGlobalType { + pattern_location: Location, + expr_location: Location, + expected_type: Type, + }, #[error("Global failed to evaluate")] UnevaluatedGlobalType { location: Location }, #[error("Globals used in a type position must be non-negative")] @@ -130,8 +130,6 @@ pub enum ResolverError { ArrayLengthInterpreter { error: InterpreterError }, #[error("The unquote operator '$' can only be used within a quote expression")] UnquoteUsedOutsideQuote { location: Location }, - #[error("\"as trait path\" not yet implemented")] - AsTraitPathNotYetImplemented { location: Location }, #[error("Invalid syntax in macro call")] InvalidSyntaxInMacroCall { location: Location }, #[error("Macros must be comptime functions")] @@ -170,7 +168,7 @@ pub enum ResolverError { AttributeFunctionIsNotAPath { function: String, location: Location }, #[error("Attribute function `{name}` is not in scope")] AttributeFunctionNotInScope { name: String, location: Location }, - #[error("The trait `{missing_trait}` is not implemented for `{type_missing_trait}")] + #[error("The trait `{missing_trait}` is not implemented for `{type_missing_trait}`")] TraitNotImplemented { impl_trait: String, missing_trait: String, @@ -192,16 +190,14 @@ pub enum ResolverError { TypeUnsupportedInMatch { typ: Type, location: Location }, #[error("Expected a struct, enum, or literal value in pattern, but found a {item}")] UnexpectedItemInPattern { location: Location, item: &'static str }, + #[error("Trait `{trait_name}` doesn't have a method named `{method_name}`")] + NoSuchMethodInTrait { trait_name: String, method_name: String, location: Location }, } impl ResolverError { - pub fn into_file_diagnostic(&self, file: fm::FileId) -> FileDiagnostic { - Diagnostic::from(self).in_file(file) - } - pub fn location(&self) -> Location { match self { - ResolverError::DuplicateDefinition { first_location: location, .. } + ResolverError::DuplicateDefinition { second_location: location, .. } | ResolverError::UnconditionalRecursion { location, .. } | ResolverError::PathIsNotIdent { location } | ResolverError::Expected { location, .. } @@ -224,8 +220,6 @@ impl ResolverError { | ResolverError::NonStructWithGenerics { location } | ResolverError::GenericsOnSelfType { location } | ResolverError::GenericsOnAssociatedType { location } - | ResolverError::MutableReferenceToImmutableVariable { location, .. } - | ResolverError::MutableReferenceToArrayElement { location } | ResolverError::InvalidClosureEnvironment { location, .. } | ResolverError::NestedSlices { location } | ResolverError::AbiAttributeOutsideContract { location } @@ -237,7 +231,7 @@ impl ResolverError { | ResolverError::WhileInConstrainedFn { location } | ResolverError::JumpOutsideLoop { location, .. } | ResolverError::MutableGlobal { location } - | ResolverError::UnspecifiedGlobalType { location, .. } + | ResolverError::UnspecifiedGlobalType { pattern_location: location, .. } | ResolverError::UnevaluatedGlobalType { location } | ResolverError::NegativeGlobalType { location, .. } | ResolverError::NonIntegralGlobalType { location, .. } @@ -245,7 +239,6 @@ impl ResolverError { | ResolverError::SelfReferentialType { location } | ResolverError::NumericGenericUsedForType { location, .. } | ResolverError::UnquoteUsedOutsideQuote { location } - | ResolverError::AsTraitPathNotYetImplemented { location } | ResolverError::InvalidSyntaxInMacroCall { location } | ResolverError::MacroIsNotComptime { location } | ResolverError::NonFunctionInAnnotation { location } @@ -261,6 +254,7 @@ impl ResolverError { | ResolverError::NonIntegerGlobalUsedInPattern { location, .. } | ResolverError::TypeUnsupportedInMatch { location, .. } | ResolverError::UnexpectedItemInPattern { location, .. } + | ResolverError::NoSuchMethodInTrait { location, .. } | ResolverError::VariableAlreadyDefinedInPattern { new_location: location, .. } => { *location } @@ -296,10 +290,10 @@ impl<'a> From<&'a ResolverError> for Diagnostic { ResolverError::DuplicateDefinition { name, first_location, second_location} => { let mut diag = Diagnostic::simple_error( format!("duplicate definitions of {name} found"), - "first definition found here".to_string(), - *first_location, + "second definition found here".to_string(), + *second_location, ); - diag.add_secondary("second definition found here".to_string(), *second_location); + diag.add_secondary("first definition found here".to_string(), *first_location); diag } ResolverError::UnusedVariable { ident } => { @@ -490,12 +484,6 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *location, ), ResolverError::ParserError(error) => error.as_ref().into(), - ResolverError::MutableReferenceToImmutableVariable { variable, location } => { - Diagnostic::simple_error(format!("Cannot mutably reference the immutable variable {variable}"), format!("{variable} is immutable"), *location) - }, - ResolverError::MutableReferenceToArrayElement { location } => { - Diagnostic::simple_error("Mutable references to array elements are currently unsupported".into(), "Try storing the element in a fresh variable first".into(), *location) - }, ResolverError::InvalidClosureEnvironment { location, typ } => Diagnostic::simple_error( format!("{typ} is not a valid closure environment type"), "Closure environment must be a tuple or unit type".to_string(), *location), @@ -577,12 +565,14 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *location, ) }, - ResolverError::UnspecifiedGlobalType { location, expected_type } => { - Diagnostic::simple_error( + ResolverError::UnspecifiedGlobalType { pattern_location, expr_location, expected_type } => { + let mut diagnostic = Diagnostic::simple_error( "Globals must have a specified type".to_string(), - format!("Inferred type is `{expected_type}`"), - *location, - ) + String::new(), + *pattern_location, + ); + diagnostic.add_secondary(format!("Inferred type is `{expected_type}`"), *expr_location); + diagnostic }, ResolverError::UnevaluatedGlobalType { location } => { Diagnostic::simple_error( @@ -658,13 +648,6 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *location, ) }, - ResolverError::AsTraitPathNotYetImplemented { location } => { - Diagnostic::simple_error( - "\"as trait path\" not yet implemented".into(), - "".into(), - *location, - ) - }, ResolverError::InvalidSyntaxInMacroCall { location } => { Diagnostic::simple_error( "Invalid syntax in macro call".into(), @@ -774,9 +757,9 @@ impl<'a> From<&'a ResolverError> for Diagnostic { ResolverError::TraitNotImplemented { impl_trait, missing_trait: the_trait, type_missing_trait: typ, location, missing_trait_location} => { let mut diagnostic = Diagnostic::simple_error( format!("The trait bound `{typ}: {the_trait}` is not satisfied"), - format!("The trait `{the_trait}` is not implemented for `{typ}") + format!("The trait `{the_trait}` is not implemented for `{typ}`") , *location); - diagnostic.add_secondary(format!("required by this bound in `{impl_trait}"), *missing_trait_location); + diagnostic.add_secondary(format!("required by this bound in `{impl_trait}`"), *missing_trait_location); diagnostic }, ResolverError::LoopNotYetSupported { location } => { @@ -822,6 +805,13 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *location, ) }, + ResolverError::NoSuchMethodInTrait { trait_name, method_name, location } => { + Diagnostic::simple_error( + format!("Trait `{trait_name}` has no method named `{method_name}`"), + String::new(), + *location, + ) + }, } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs index f3c2c635b041..e68e70253a36 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeSet; use std::rc::Rc; use acvm::FieldElement; @@ -15,6 +16,9 @@ use crate::hir_def::types::{BinaryTypeOperator, Kind, Type}; use crate::node_interner::NodeInterner; use crate::signed_field::SignedField; +/// Rust also only shows 3 maximum, even for short patterns. +pub const MAX_MISSING_CASES: usize = 3; + #[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum Source { #[error("Binary")] @@ -63,7 +67,7 @@ pub enum TypeCheckError { #[error("Expected type {expected} is not the same as {actual}")] TypeMismatchWithSource { expected: Type, actual: Type, location: Location, source: Source }, #[error("Expected type {expected_kind:?} is not the same as {expr_kind:?}")] - TypeKindMismatch { expected_kind: String, expr_kind: String, expr_location: Location }, + TypeKindMismatch { expected_kind: Kind, expr_kind: Kind, expr_location: Location }, #[error("Evaluating {to} resulted in {to_value}, but {from_value} was expected")] TypeCanonicalizationMismatch { to: Type, @@ -100,6 +104,10 @@ pub enum TypeCheckError { VariableMustBeMutable { name: String, location: Location }, #[error("Cannot mutate immutable variable `{name}`")] CannotMutateImmutableVariable { name: String, location: Location }, + #[error("Variable {name} captured in lambda must be a mutable reference")] + MutableCaptureWithoutRef { name: String, location: Location }, + #[error("Mutable references to array indices are unsupported")] + MutableReferenceToArrayElement { location: Location }, #[error("No method named '{method_name}' found for type '{object_type}'")] UnresolvedMethodCall { method_name: String, object_type: Type, location: Location }, #[error("Cannot invoke function field '{method_name}' on type '{object_type}' as a method")] @@ -235,6 +243,13 @@ pub enum TypeCheckError { UnnecessaryUnsafeBlock { location: Location }, #[error("Unnecessary `unsafe` block")] NestedUnsafeBlock { location: Location }, + #[error("Unreachable match case")] + UnreachableCase { location: Location }, + #[error("Missing cases")] + MissingCases { cases: BTreeSet, location: Location }, + /// This error is used for types like integers which have too many variants to enumerate + #[error("Missing cases: `{typ}` is non-empty")] + MissingManyCases { typ: String, location: Location }, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -279,6 +294,8 @@ impl TypeCheckError { | TypeCheckError::TupleIndexOutOfBounds { location, .. } | TypeCheckError::VariableMustBeMutable { location, .. } | TypeCheckError::CannotMutateImmutableVariable { location, .. } + | TypeCheckError::MutableCaptureWithoutRef { location, .. } + | TypeCheckError::MutableReferenceToArrayElement { location } | TypeCheckError::UnresolvedMethodCall { location, .. } | TypeCheckError::CannotInvokeStructFieldFunctionType { location, .. } | TypeCheckError::IntegerSignedness { location, .. } @@ -321,9 +338,14 @@ impl TypeCheckError { | TypeCheckError::CyclicType { location, .. } | TypeCheckError::TypeAnnotationsNeededForIndex { location } | TypeCheckError::UnnecessaryUnsafeBlock { location } + | TypeCheckError::UnreachableCase { location } + | TypeCheckError::MissingCases { location, .. } + | TypeCheckError::MissingManyCases { location, .. } | TypeCheckError::NestedUnsafeBlock { location } => *location, + TypeCheckError::DuplicateNamedTypeArg { name: ident, .. } | TypeCheckError::NoSuchNamedTypeArg { name: ident, .. } => ident.location(), + TypeCheckError::NoMatchingImplFound(no_matching_impl_found_error) => { no_matching_impl_found_error.location } @@ -369,11 +391,37 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { ) } TypeCheckError::TypeKindMismatch { expected_kind, expr_kind, expr_location } => { - Diagnostic::simple_error( - format!("Expected kind {expected_kind}, found kind {expr_kind}"), - String::new(), - *expr_location, - ) + // Try to improve the error message for some kind combinations + match (expected_kind, expr_kind) { + (Kind::Normal, Kind::Numeric(_)) => { + Diagnostic::simple_error( + "Expected type, found numeric generic".into(), + "not a type".into(), + *expr_location, + ) + } + (Kind::Numeric(typ), Kind::Normal) => { + Diagnostic::simple_error( + "Type provided when a numeric generic was expected".into(), + format!("the numeric generic is not of type `{typ}`"), + *expr_location, + ) + } + (Kind::Numeric(expected_type), Kind::Numeric(found_type)) => { + Diagnostic::simple_error( + format!("The numeric generic is not of type `{expected_type}`"), + format!("expected `{expected_type}`, found `{found_type}`"), + *expr_location, + ) + } + _ => { + Diagnostic::simple_error( + format!("Expected kind {expected_kind}, found kind {expr_kind}"), + String::new(), + *expr_location, + ) + } + } } TypeCheckError::TypeCanonicalizationMismatch { to, from, to_value, from_value, location } => { Diagnostic::simple_error( @@ -476,6 +524,14 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { | TypeCheckError::InvalidShiftSize { location } => { Diagnostic::simple_error(error.to_string(), String::new(), *location) } + TypeCheckError::MutableCaptureWithoutRef { name, location } => Diagnostic::simple_error( + format!("Mutable variable {name} captured in lambda must be a mutable reference"), + "Use '&mut' instead of 'mut' to capture a mutable variable.".to_string(), + *location, + ), + TypeCheckError::MutableReferenceToArrayElement { location } => { + Diagnostic::simple_error("Mutable references to array elements are currently unsupported".into(), "Try storing the element in a fresh variable first".into(), *location) + }, TypeCheckError::PublicReturnType { typ, location } => Diagnostic::simple_error( "Functions cannot declare a public return type".to_string(), format!("return type is {typ}"), @@ -641,6 +697,36 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { *location, ) }, + TypeCheckError::UnreachableCase { location } => { + Diagnostic::simple_warning( + "Unreachable match case".into(), + "This pattern is redundant with one or more prior patterns".into(), + *location, + ) + }, + TypeCheckError::MissingCases { cases, location } => { + let s = if cases.len() == 1 { "" } else { "s" }; + + let mut not_shown = String::new(); + let mut shown_cases = cases.iter() + .map(|s| format!("`{s}`")) + .take(MAX_MISSING_CASES) + .collect::>(); + + if cases.len() > MAX_MISSING_CASES { + shown_cases.truncate(MAX_MISSING_CASES); + not_shown = format!(", and {} more not shown", cases.len() - MAX_MISSING_CASES); + } + + let shown_cases = shown_cases.join(", "); + let msg = format!("Missing case{s}: {shown_cases}{not_shown}"); + Diagnostic::simple_error(msg, String::new(), *location) + }, + TypeCheckError::MissingManyCases { typ, location } => { + let msg = format!("Missing cases: `{typ}` is non-empty"); + let secondary = "Try adding a match-all pattern: `_`".to_string(); + Diagnostic::simple_error(msg, secondary, *location) + }, } } } @@ -648,15 +734,15 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { impl<'a> From<&'a NoMatchingImplFoundError> for Diagnostic { fn from(error: &'a NoMatchingImplFoundError) -> Self { let constraints = &error.constraints; - let span = error.location; + let location = error.location; assert!(!constraints.is_empty()); let msg = format!("No matching impl found for `{}: {}`", constraints[0].0, constraints[0].1); - let mut diagnostic = Diagnostic::from_message(&msg); + let mut diagnostic = Diagnostic::from_message(&msg, location.file); let secondary = format!("No impl for `{}: {}`", constraints[0].0, constraints[0].1); - diagnostic.add_secondary(secondary, span); + diagnostic.add_secondary(secondary, location); // These must be notes since secondaries are unordered for (typ, trait_name) in &constraints[1..] { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs index f45b68dd818c..c7a7890fcc01 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -2,4 +2,4 @@ mod errors; pub mod generics; pub use self::errors::Source; -pub use errors::{NoMatchingImplFoundError, TypeCheckError}; +pub use errors::{MAX_MISSING_CASES, NoMatchingImplFoundError, TypeCheckError}; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs index 0076cab8de5d..25e055188c2b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs @@ -1,4 +1,5 @@ use fm::FileId; +use iter_extended::vecmap; use noirc_errors::Location; use crate::Shared; @@ -358,15 +359,13 @@ pub enum HirMatch { /// Jump directly to ExprId Success(ExprId), - Failure, + /// A Failure node in the match. `missing_case` is true if this node is the result of a missing + /// case of the match for which we should later reconstruct an example of. + Failure { missing_case: bool }, /// Run `body` if the given expression is true. /// Otherwise continue with the given decision tree. - Guard { - cond: ExprId, - body: ExprId, - otherwise: Box, - }, + Guard { cond: ExprId, body: ExprId, otherwise: Box }, /// Switch on the given variable with the given cases to test. /// The final argument is an optional match-all case to take if @@ -387,7 +386,7 @@ impl Case { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum Constructor { True, False, @@ -437,6 +436,41 @@ impl Constructor { _ => false, } } + + /// Return all the constructors of this type from one constructor. Intended to be used + /// for error reporting in cases where there are at least 2 constructors. + pub(crate) fn all_constructors(&self) -> Vec<(Constructor, /*arg count:*/ usize)> { + match self { + Constructor::True | Constructor::False => { + vec![(Constructor::True, 0), (Constructor::False, 0)] + } + Constructor::Unit => vec![(Constructor::Unit, 0)], + Constructor::Tuple(args) => vec![(self.clone(), args.len())], + Constructor::Variant(typ, _) => { + let typ = typ.follow_bindings(); + let Type::DataType(def, generics) = &typ else { + unreachable!( + "Constructor::Variant should have a DataType type, but found {typ:?}" + ); + }; + + let def_ref = def.borrow(); + if let Some(variants) = def_ref.get_variants(generics) { + vecmap(variants.into_iter().enumerate(), |(i, (_, fields))| { + (Constructor::Variant(typ.clone(), i), fields.len()) + }) + } else + /* def is a struct */ + { + let field_count = def_ref.fields_raw().map(|fields| fields.len()).unwrap_or(0); + vec![(Constructor::Variant(typ.clone(), 0), field_count)] + } + } + + // Nothing great to return for these + Constructor::Int(_) | Constructor::Range(..) => Vec::new(), + } + } } impl std::fmt::Display for Constructor { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs index 4fd5b46657ec..1f4b21cb9a90 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs @@ -14,6 +14,7 @@ use crate::{ ast::{IntegerBitSize, ItemVisibility}, hir::type_check::{TypeCheckError, generics::TraitGenerics}, node_interner::{ExprId, NodeInterner, TraitId, TypeAliasId}, + signed_field::{AbsU128, SignedField}, }; use iter_extended::vecmap; use noirc_errors::Location; @@ -858,8 +859,8 @@ impl TypeVariable { ) -> Result<(), TypeCheckError> { if !binding.kind().unifies(kind) { return Err(TypeCheckError::TypeKindMismatch { - expected_kind: format!("{}", kind), - expr_kind: format!("{}", binding.kind()), + expected_kind: kind.clone(), + expr_kind: binding.kind(), expr_location: location, }); } @@ -1156,15 +1157,15 @@ impl Type { } pub fn is_field(&self) -> bool { - matches!(self.follow_bindings(), Type::FieldElement) + matches!(self.follow_bindings_shallow().as_ref(), Type::FieldElement) } pub fn is_bool(&self) -> bool { - matches!(self.follow_bindings(), Type::Bool) + matches!(self.follow_bindings_shallow().as_ref(), Type::Bool) } pub fn is_integer(&self) -> bool { - matches!(self.follow_bindings(), Type::Integer(_, _)) + matches!(self.follow_bindings_shallow().as_ref(), Type::Integer(_, _)) } /// If value_level, only check for Type::FieldElement, @@ -1242,6 +1243,10 @@ impl Type { } } + pub(crate) fn is_mutable_ref(&self) -> bool { + matches!(self.follow_bindings_shallow().as_ref(), Type::MutableReference(_)) + } + /// True if this type can be used as a parameter to `main` or a contract function. /// This is only false for unsized types like slices or slices that do not make sense /// as a program input such as named generics or mutable references. @@ -2144,8 +2149,8 @@ impl Type { kind.ensure_value_fits(x, location) } else { Err(TypeCheckError::TypeKindMismatch { - expected_kind: format!("{}", constant_kind), - expr_kind: format!("{}", kind), + expected_kind: constant_kind, + expr_kind: kind.clone(), expr_location: location, }) } @@ -2166,8 +2171,8 @@ impl Type { op.function(lhs_value, rhs_value, &infix_kind, location) } else { Err(TypeCheckError::TypeKindMismatch { - expected_kind: format!("{}", kind), - expr_kind: format!("{}", infix_kind), + expected_kind: kind.clone(), + expr_kind: infix_kind, expr_location: location, }) } @@ -2761,6 +2766,36 @@ impl Type { | Type::Error => None, } } + + pub(crate) fn integral_minimum_size(&self) -> Option { + match self.follow_bindings_shallow().as_ref() { + Type::FieldElement => None, + Type::Integer(sign, num_bits) => { + if *sign == Signedness::Unsigned { + return Some(SignedField::zero()); + } + + let max_bit_size = num_bits.bit_size() - 1; + Some(if max_bit_size == 128 { + SignedField::negative(i128::MIN.abs_u128()) + } else { + SignedField::negative(1u128 << max_bit_size) + }) + } + Type::Bool => Some(SignedField::zero()), + Type::TypeVariable(var) => { + let binding = &var.1; + match &*binding.borrow() { + TypeBinding::Unbound(_, type_var_kind) => match type_var_kind { + Kind::Any | Kind::Normal | Kind::Integer | Kind::IntegerOrField => None, + Kind::Numeric(typ) => typ.integral_minimum_size(), + }, + TypeBinding::Bound(typ) => typ.integral_minimum_size(), + } + } + _ => None, + } + } } /// Wraps a given `expression` in `expression.as_slice()` diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs index 32407f29cd04..93a12a46591b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs @@ -1,4 +1,4 @@ -use noirc_errors::{CustomDiagnostic, FileDiagnostic, Location}; +use noirc_errors::{CustomDiagnostic, Location}; use crate::{ Type, @@ -34,18 +34,9 @@ impl MonomorphizationError { } } -impl From for FileDiagnostic { - fn from(error: MonomorphizationError) -> FileDiagnostic { - let location = error.location(); - let call_stack = vec![location]; - let diagnostic = error.into_diagnostic(); - diagnostic.with_call_stack(call_stack).in_file(location.file) - } -} - -impl MonomorphizationError { - fn into_diagnostic(self) -> CustomDiagnostic { - let message = match &self { +impl From for CustomDiagnostic { + fn from(error: MonomorphizationError) -> CustomDiagnostic { + let message = match &error { MonomorphizationError::UnknownArrayLength { length, err, .. } => { format!("Could not determine array length `{length}`, encountered error: `{err}`") } @@ -78,7 +69,7 @@ impl MonomorphizationError { } }; - let location = self.location(); + let location = error.location(); CustomDiagnostic::simple_error(message, String::new(), location) } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs index 62ed1ef2e684..4b0c4b683454 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -1995,7 +1995,7 @@ impl<'interner> Monomorphizer<'interner> { ) -> Result { match match_expr { HirMatch::Success(id) => self.expr(id), - HirMatch::Failure => { + HirMatch::Failure { .. } => { let false_ = Box::new(ast::Expression::Literal(ast::Literal::Bool(false))); let msg = "match failure"; let msg_expr = ast::Expression::Literal(ast::Literal::Str(msg.to_string())); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs index e8b46b9db698..2430dca8fcad 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -725,6 +725,15 @@ impl NodeInterner { ExprId(self.nodes.insert(Node::Expression(expr))) } + /// Intern an expression with everything needed for it (location & Type) + /// instead of requiring they be pushed later. + pub fn push_expr_full(&mut self, expr: HirExpression, location: Location, typ: Type) -> ExprId { + let id = self.push_expr(expr); + self.push_expr_location(id, location); + self.push_expr_type(id, typ); + id + } + /// Stores the span for an interned expression. pub fn push_expr_location(&mut self, expr_id: ExprId, location: Location) { self.id_to_location.insert(expr_id.into(), location); @@ -756,7 +765,7 @@ impl NodeInterner { id: type_id, name: unresolved_trait.trait_def.name.clone(), crate_id: unresolved_trait.crate_id, - location: unresolved_trait.trait_def.location, + location: unresolved_trait.trait_def.name.location(), generics, visibility: ItemVisibility::Private, self_type_typevar: TypeVariable::unbound(self.next_type_variable_id(), Kind::Normal), @@ -1280,7 +1289,11 @@ impl NodeInterner { /// Returns the type of an item stored in the Interner or Error if it was not found. pub fn id_type(&self, index: impl Into) -> Type { - self.id_to_type.get(&index.into()).cloned().unwrap_or(Type::Error) + self.try_id_type(index).cloned().unwrap_or(Type::Error) + } + + pub fn try_id_type(&self, index: impl Into) -> Option<&Type> { + self.id_to_type.get(&index.into()) } /// Returns the type of the definition or `Type::Error` if it was not found. diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs index f0f707a2849d..a5ea2ea5fe9e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -208,6 +208,9 @@ impl<'a> Parser<'a> { match self.tokens.next() { Some(Ok(token)) => match token.token() { Token::LineComment(comment, None) | Token::BlockComment(comment, None) => { + if !last_comments.is_empty() { + last_comments.push('\n'); + } last_comments.push_str(comment); continue; } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs index 5c056c52cb43..d0f335414da4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs @@ -388,20 +388,23 @@ impl Parser<'_> { /// UnsafeExpression = 'unsafe' Block fn parse_unsafe_expr(&mut self) -> Option { let start_location = self.current_token_location; + let comments_before_unsafe = self.current_token_comments.clone(); if !self.eat_keyword(Keyword::Unsafe) { return None; } - if self.current_token_comments.is_empty() { - if let Some(statement_comments) = &mut self.statement_comments { - if !statement_comments.trim().to_lowercase().starts_with("safety:") { - self.push_error(ParserErrorReason::MissingSafetyComment, start_location); - } + let comments: &str = if comments_before_unsafe.is_empty() { + if let Some(statement_comments) = &self.statement_comments { + statement_comments } else { - self.push_error(ParserErrorReason::MissingSafetyComment, start_location); + "" } - } else if !self.current_token_comments.trim().to_lowercase().starts_with("safety:") { + } else { + &comments_before_unsafe + }; + + if !comments.lines().any(|line| line.trim().to_lowercase().starts_with("safety:")) { self.push_error(ParserErrorReason::MissingSafetyComment, start_location); } @@ -1079,7 +1082,9 @@ mod tests { let src = " // Safety: test unsafe { 1 }"; - let expr = parse_expression_no_errors(src); + let mut parser = Parser::for_str_with_dummy_file(src); + let expr = parser.parse_expression_or_error(); + assert!(parser.errors.is_empty()); let ExpressionKind::Unsafe(unsafe_expression) = expr.kind else { panic!("Expected unsafe expression"); }; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs index 0f152e64e9f5..f10b790e63f6 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs @@ -109,10 +109,24 @@ impl Parser<'_> { let visibility = self.parse_visibility(); (FunctionReturnType::Ty(self.parse_type_or_error()), visibility) } else { - ( - FunctionReturnType::Default(self.location_at_previous_token_end()), - Visibility::Private, - ) + // This will return the span between `)` and `{` + // + // fn foo() { } + // ^^^ + let mut location = self.previous_token_location.merge(self.current_token_location); + + // Here we change it to this (if there's space) + // + // fn foo() { } + // ^ + if location.span.end() - location.span.start() >= 3 { + location = Location::new( + Span::from(location.span.start() + 1..location.span.end() - 1), + location.file, + ); + } + + (FunctionReturnType::Default(location), Visibility::Private) }; let where_clause = self.parse_where_clause(); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs index 833a2e480c41..600ddec43c96 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs @@ -521,7 +521,9 @@ mod tests { fn parses_let_statement_with_unsafe() { let src = "// Safety: comment let x = unsafe { 1 };"; - let statement = parse_statement_no_errors(src); + let mut parser = Parser::for_str_with_dummy_file(src); + let statement = parser.parse_statement_or_error(); + assert!(parser.errors.is_empty()); let StatementKind::Let(let_statement) = statement.kind else { panic!("Expected let statement"); }; @@ -540,6 +542,20 @@ mod tests { assert_eq!(let_statement.pattern.to_string(), "x"); } + #[test] + fn parses_let_statement_with_unsafe_after_some_other_comment() { + let src = "// Top comment + // Safety: comment + let x = unsafe { 1 };"; + let mut parser = Parser::for_str_with_dummy_file(src); + let statement = parser.parse_statement_or_error(); + assert!(parser.errors.is_empty()); + let StatementKind::Let(let_statement) = statement.kind else { + panic!("Expected let statement"); + }; + assert_eq!(let_statement.pattern.to_string(), "x"); + } + #[test] fn parses_comptime_block() { let src = "comptime { 1 }"; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/resolve_locations.rs b/noir/noir-repo/compiler/noirc_frontend/src/resolve_locations.rs index 4daf088a2f11..652ce4aa557f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/resolve_locations.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/resolve_locations.rs @@ -32,9 +32,24 @@ impl NodeInterner { } /// Returns the Type of the expression that exists at the given location. - pub fn type_at_location(&self, location: Location) -> Option { - let index = self.find_location_index(location)?; - Some(self.id_type(index)) + pub fn type_at_location(&self, location: Location) -> Option<&Type> { + // This is similar to `find_location_index` except that we skip indexes for which there is no type + let mut location_candidate: Option<(&Index, &Location, &Type)> = None; + + for (index, interned_location) in self.id_to_location.iter() { + if interned_location.contains(&location) { + if let Some(typ) = self.try_id_type(*index) { + if let Some(current_location) = location_candidate { + if interned_location.span.is_smaller(¤t_location.1.span) { + location_candidate = Some((index, interned_location, typ)); + } + } else { + location_candidate = Some((index, interned_location, typ)); + } + } + } + } + location_candidate.map(|(_index, _location, typ)| typ) } /// Returns the [Location] of the definition of the given Ident found at [Span] of the given [FileId]. diff --git a/noir/noir-repo/compiler/noirc_frontend/src/signed_field.rs b/noir/noir-repo/compiler/noirc_frontend/src/signed_field.rs index dcddd52daa8f..925b128ea245 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/signed_field.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/signed_field.rs @@ -19,6 +19,14 @@ impl SignedField { Self { field: field.into(), is_negative: true } } + pub fn zero() -> SignedField { + Self { field: FieldElement::zero(), is_negative: false } + } + + pub fn one() -> SignedField { + Self { field: FieldElement::one(), is_negative: false } + } + /// Convert a signed integer to a SignedField, carefully handling /// INT_MIN in the process. Note that to convert an unsigned integer /// you can call `SignedField::positive`. @@ -125,6 +133,30 @@ impl std::fmt::Display for SignedField { } } +impl rangemap::StepLite for SignedField { + fn add_one(&self) -> Self { + if self.is_negative { + if self.field.is_one() { + Self::new(FieldElement::zero(), false) + } else { + Self::new(self.field - FieldElement::one(), self.is_negative) + } + } else { + Self::new(self.field + FieldElement::one(), self.is_negative) + } + } + + fn sub_one(&self) -> Self { + if self.is_negative { + Self::new(self.field + FieldElement::one(), self.is_negative) + } else if self.field.is_zero() { + Self::new(FieldElement::one(), true) + } else { + Self::new(self.field - FieldElement::one(), self.is_negative) + } + } +} + pub trait AbsU128 { /// Necessary to handle casting to unsigned generically without overflowing on INT_MIN. fn abs_u128(self) -> u128; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index 7bbe1f60873e..52d5f79c5e82 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -362,12 +362,12 @@ fn check_trait_implementation_duplicate_method() { impl Default for Foo { // Duplicate trait methods should not compile fn default(x: Field, y: Field) -> Field { - ^^^^^^^ Duplicate definitions of trait associated function with name default found ~~~~~~~ First trait associated function found here y + 2 * x } // Duplicate trait methods should not compile fn default(x: Field, y: Field) -> Field { + ^^^^^^^ Duplicate definitions of trait associated function with name default found ~~~~~~~ Second trait associated function found here x + 2 * y } @@ -392,7 +392,7 @@ fn check_trait_wrong_method_return_type() { impl Default for Foo { fn default() -> Field { - ^^^^^^^ Expected type Foo, found type Field + ^^^^^ Expected type Foo, found type Field 0 } } @@ -419,7 +419,7 @@ fn check_trait_wrong_method_return_type2() { impl Default for Foo { fn default(x: Field, _y: Field) -> Field { - ^^^^^^^ Expected type Foo, found type Field + ^^^^^ Expected type Foo, found type Field x } } @@ -430,6 +430,31 @@ fn check_trait_wrong_method_return_type2() { check_errors(src); } +#[test] +fn check_trait_wrong_method_return_type3() { + let src = " + trait Default { + fn default(x: Field, y: Field) -> Self; + } + + struct Foo { + bar: Field, + array: [Field; 2], + } + + impl Default for Foo { + fn default(_x: Field, _y: Field) { + ^ Expected type Foo, found type () + } + } + + fn main() { + let _ = Foo { bar: 1, array: [2, 3] }; // silence Foo never constructed warning + } + "; + check_errors(src); +} + #[test] fn check_trait_missing_implementation() { let src = " @@ -518,7 +543,7 @@ fn check_trait_wrong_parameter() { impl Default for Foo { fn default(x: u32) -> Self { - ^ Parameter #1 of method `default` must be of type Field, not u32 + ^^^ Parameter #1 of method `default` must be of type Field, not u32 Foo {bar: x} } } @@ -543,7 +568,7 @@ fn check_trait_wrong_parameter2() { impl Default for Foo { fn default(x: Field, y: Foo) -> Self { - ^ Parameter #2 of method `default` must be of type Field, not Foo + ^^^ Parameter #2 of method `default` must be of type Field, not Foo Self { bar: x, array: [x, y.bar] } } } @@ -644,7 +669,6 @@ fn check_impl_struct_not_trait() { fn check_trait_duplicate_declaration() { let src = " trait Default { - ^^^^^^^ Duplicate definitions of trait definition with name Default found ~~~~~~~ First trait definition found here fn default(x: Field, y: Field) -> Self; } @@ -661,6 +685,7 @@ fn check_trait_duplicate_declaration() { } trait Default { + ^^^^^^^ Duplicate definitions of trait definition with name Default found ~~~~~~~ Second trait definition found here fn default(x: Field) -> Self; } @@ -1505,9 +1530,10 @@ fn bool_generic_as_loop_bound() { ^ N has a type of bool. The only supported numeric generic types are `u1`, `u8`, `u16`, and `u32`. ~ Unsupported numeric generic type let mut fields = [0; N]; - ^ Expected kind numeric u32, found kind numeric bool + ^ The numeric generic is not of type `u32` + ~ expected `u32`, found `bool` for i in 0..N { - ^^^^ Expected type Field, found type bool + ^ Expected type Field, found type bool fields[i] = i + 1; } assert(fields[0] == 1); @@ -1516,6 +1542,19 @@ fn bool_generic_as_loop_bound() { check_errors(src); } +#[test] +fn wrong_type_in_for_range() { + let src = r#" + pub fn foo() { + for _ in true..false { + ^^^^ The type bool cannot be used in a for loop + + } + } + "#; + check_errors(src); +} + #[test] fn numeric_generic_in_function_signature() { let src = r#" @@ -1532,7 +1571,8 @@ fn numeric_generic_as_struct_field_type_fails() { pub struct Foo { a: Field, b: N, - ^ Expected kind normal, found kind numeric u32 + ^ Expected type, found numeric generic + ~ not a type } "#; check_errors(src); @@ -1545,7 +1585,8 @@ fn normal_generic_as_array_length() { pub struct Foo { a: Field, b: [Field; N], - ^^^^^^^^^^ Expected kind numeric u32, found kind normal + ^^^^^^^^^^ Type provided when a numeric generic was expected + ~~~~~~~~~~ the numeric generic is not of type `u32` } "#; check_errors(src); @@ -1556,10 +1597,15 @@ fn numeric_generic_as_param_type() { // TODO: improve the error message, see what Rust does let src = r#" pub fn foo(x: I) -> I { - ^ Expected kind normal, found kind numeric u32 - ^ Expected kind normal, found kind numeric u32 + ^ Expected type, found numeric generic + ~ not a type + ^ Expected type, found numeric generic + ~ not a type + + let _q: I = 5; - ^ Expected kind normal, found kind numeric u32 + ^ Expected type, found numeric generic + ~ not a type x } "#; @@ -1571,7 +1617,8 @@ fn numeric_generic_as_unused_param_type() { // TODO: improve the error message let src = r#" pub fn foo(_x: I) { } - ^ Expected kind normal, found kind numeric u32 + ^ Expected type, found numeric generic + ~ not a type "#; check_errors(src); } @@ -1584,7 +1631,8 @@ fn numeric_generic_as_unused_trait_fn_param_type() { ^^^ unused trait Foo ~~~ unused trait fn foo(_x: I) { } - ^ Expected kind normal, found kind numeric u32 + ^ Expected type, found numeric generic + ~ not a type } "#; check_errors(src); @@ -1600,7 +1648,8 @@ fn numeric_generic_as_return_type() { } fn foo(x: T) -> I where T: Zeroed { - ^ Expected kind normal, found kind numeric Field + ^ Expected type, found numeric generic + ~ not a type ^^^ unused function foo ~~~ unused function x.zeroed() @@ -1621,7 +1670,8 @@ fn numeric_generic_used_in_nested_type_fails() { } pub struct Bar { inner: N - ^ Expected kind normal, found kind numeric u32 + ^ Expected type, found numeric generic + ~ not a type } "#; check_errors(src); @@ -1634,7 +1684,8 @@ fn normal_generic_used_in_nested_array_length_fail() { pub struct Foo { a: Field, b: Bar, - ^ Expected kind numeric u32, found kind normal + ^ Type provided when a numeric generic was expected + ~ the numeric generic is not of type `u32` } pub struct Bar { inner: [Field; N] @@ -1755,7 +1806,7 @@ fn numeric_generic_used_in_turbofish() { // allow u16 to be used as an array size #[test] fn numeric_generic_u16_array_size() { - // TODO: improve the error location (and maybe the message) + // TODO: improve the error location let src = r#" fn len(_arr: [Field; N]) -> u32 { N @@ -1763,8 +1814,10 @@ fn numeric_generic_u16_array_size() { pub fn foo() -> u32 { let fields: [Field; N] = [0; N]; - ^^^^^^^^^^ Expected kind numeric u32, found kind numeric u16 - ^ Expected kind numeric u32, found kind numeric u16 + ^ The numeric generic is not of type `u32` + ~ expected `u32`, found `u16` + ^^^^^^^^^^ The numeric generic is not of type `u32` + ~~~~~~~~~~ expected `u32`, found `u16` len(fields) } "#; @@ -1864,7 +1917,8 @@ fn normal_generic_used_when_numeric_expected_in_where_clause() { } pub fn read() -> T where T: Deserialize { - ^ Expected kind numeric u32, found kind normal + ^ Type provided when a numeric generic was expected + ~ the numeric generic is not of type `u32` T::deserialize([0, 1]) } "#; @@ -1877,10 +1931,13 @@ fn normal_generic_used_when_numeric_expected_in_where_clause() { } pub fn read() -> T where T: Deserialize { - ^ Expected kind numeric u32, found kind normal + ^ Type provided when a numeric generic was expected + ~ the numeric generic is not of type `u32` let mut fields: [Field; N] = [0; N]; - ^ Expected kind numeric u32, found kind normal - ^^^^^^^^^^ Expected kind numeric u32, found kind normal + ^ Type provided when a numeric generic was expected + ~ the numeric generic is not of type `u32` + ^^^^^^^^^^ Type provided when a numeric generic was expected + ~~~~~~~~~~ the numeric generic is not of type `u32` for i in 0..N { ^ cannot find `N` in this scope ~ not found in this scope @@ -1903,7 +1960,8 @@ fn numeric_generics_type_kind_mismatch() { fn bar() -> u16 { foo::() - ^ Expected kind numeric u32, found kind numeric u16 + ^ The numeric generic is not of type `u32` + ~ expected `u32`, found `u16` } global M: u16 = 3; @@ -2425,9 +2483,9 @@ fn duplicate_struct_field() { let src = r#" pub struct Foo { x: i32, - ^ Duplicate definitions of struct field with name x found ~ First struct field found here x: i32, + ^ Duplicate definitions of struct field with name x found ~ Second struct field found here } @@ -2546,7 +2604,7 @@ fn uses_self_type_in_trait_where_clause() { } pub trait Foo where Self: Trait { - ~~~~~ required by this bound in `Foo + ~~~~~ required by this bound in `Foo` fn foo(self) -> bool { self.trait_func() ^^^^^^^^^^^^^^^^^ No method named 'trait_func' found for type 'Bar' @@ -2557,7 +2615,7 @@ fn uses_self_type_in_trait_where_clause() { impl Foo for Bar { ^^^ The trait bound `_: Trait` is not satisfied - ~~~ The trait `Trait` is not implemented for `_ + ~~~ The trait `Trait` is not implemented for `_` } @@ -2735,25 +2793,27 @@ fn as_trait_path_syntax_no_impl() { #[test] fn do_not_infer_globals_to_u32_from_type_use() { - // TODO: improve the error location (maybe it should be on the global name) let src = r#" global ARRAY_LEN = 3; - ^ Globals must have a specified type + ^^^^^^^^^ Globals must have a specified type ~ Inferred type is `Field` global STR_LEN: _ = 2; - ^ Globals must have a specified type + ^^^^^^^ Globals must have a specified type ~ Inferred type is `Field` global FMT_STR_LEN = 2; - ^ Globals must have a specified type + ^^^^^^^^^^^ Globals must have a specified type ~ Inferred type is `Field` fn main() { let _a: [u32; ARRAY_LEN] = [1, 2, 3]; - ^^^^^^^^^^^^^^^^ Expected kind numeric u32, found kind numeric Field + ^^^^^^^^^^^^^^^^ The numeric generic is not of type `u32` + ~~~~~~~~~~~~~~~~ expected `u32`, found `Field` let _b: str = "hi"; - ^^^^^^^^^^^^ Expected kind numeric u32, found kind numeric Field + ^^^^^^^^^^^^ The numeric generic is not of type `u32` + ~~~~~~~~~~~~ expected `u32`, found `Field` let _c: fmtstr = f"hi"; - ^^^^^^^^^^^^^^^^^^^^^^ Expected kind numeric u32, found kind numeric Field + ^^^^^^^^^^^^^^^^^^^^^^ The numeric generic is not of type `u32` + ~~~~~~~~~~~~~~~~~~~~~~ expected `u32`, found `Field` } "#; check_errors(src); @@ -2763,25 +2823,25 @@ fn do_not_infer_globals_to_u32_from_type_use() { fn do_not_infer_partial_global_types() { let src = r#" pub global ARRAY: [Field; _] = [0; 3]; - ^^^^^^ Globals must have a specified type + ^^^^^ Globals must have a specified type ~~~~~~ Inferred type is `[Field; 3]` pub global NESTED_ARRAY: [[Field; _]; 3] = [[]; 3]; - ^^^^^^^ Globals must have a specified type + ^^^^^^^^^^^^ Globals must have a specified type ~~~~~~~ Inferred type is `[[Field; 0]; 3]` pub global STR: str<_> = "hi"; - ^^^^ Globals must have a specified type + ^^^ Globals must have a specified type ~~~~ Inferred type is `str<2>` pub global NESTED_STR: [str<_>] = &["hi"]; - ^^^^^^^ Globals must have a specified type + ^^^^^^^^^^ Globals must have a specified type ~~~~~~~ Inferred type is `[str<2>]` pub global FORMATTED_VALUE: str<5> = "there"; pub global FMT_STR: fmtstr<_, _> = f"hi {FORMATTED_VALUE}"; - ^^^^^^^^^^^^^^^^^^^^^^^ Globals must have a specified type + ^^^^^^^ Globals must have a specified type ~~~~~~~~~~~~~~~~~~~~~~~ Inferred type is `fmtstr<20, (str<5>)>` pub global TUPLE_WITH_MULTIPLE: ([str<_>], [[Field; _]; 3]) = + ^^^^^^^^^^^^^^^^^^^ Globals must have a specified type (&["hi"], [[]; 3]); - ^^^^^^^^^^^^^^^^^^ Globals must have a specified type ~~~~~~~~~~~~~~~~~~ Inferred type is `([str<2>], [[Field; 0]; 3])` fn main() { } @@ -2839,7 +2899,8 @@ fn non_u32_as_array_length() { fn main() { let _a: [u32; ARRAY_LEN] = [1, 2, 3]; - ^^^^^^^^^^^^^^^^ Expected kind numeric u32, found kind numeric u8 + ^^^^^^^^^^^^^^^^ The numeric generic is not of type `u32` + ~~~~~~~~~~~~~~~~ expected `u32`, found `u8` } "#; check_errors(src); @@ -3850,15 +3911,14 @@ fn errors_if_while_body_type_is_not_unit() { #[test] fn check_impl_duplicate_method_without_self() { - // TODO: the primary error location should be n the second `foo` let src = " pub struct Foo {} impl Foo { fn foo() {} - ^^^ duplicate definitions of foo found ~~~ first definition found here fn foo() {} + ^^^ duplicate definitions of foo found ~~~ second definition found here } @@ -3896,3 +3956,174 @@ fn subtract_to_int_min() { let errors = get_program_errors(src); assert_eq!(errors.len(), 0); } + +#[test] +fn mutate_with_reference_in_lambda() { + let src = r#" + fn main() { + let x = &mut 3; + let f = || { + *x += 2; + }; + f(); + assert(*x == 5); + } + "#; + + assert_no_errors(src); +} + +#[test] +fn mutate_with_reference_marked_mutable_in_lambda() { + let src = r#" + fn main() { + let mut x = &mut 3; + let f = || { + *x += 2; + }; + f(); + assert(*x == 5); + } + "#; + assert_no_errors(src); +} + +#[test] +fn deny_capturing_mut_variable_without_reference_in_lambda() { + let src = r#" + fn main() { + let mut x = 3; + let f = || { + x += 2; + ^ Mutable variable x captured in lambda must be a mutable reference + ~ Use '&mut' instead of 'mut' to capture a mutable variable. + }; + f(); + assert(x == 5); + } + "#; + check_errors(src); +} + +#[test] +fn deny_capturing_mut_variable_without_reference_in_nested_lambda() { + let src = r#" + fn main() { + let mut x = 3; + let f = || { + let inner = || { + x += 2; + ^ Mutable variable x captured in lambda must be a mutable reference + ~ Use '&mut' instead of 'mut' to capture a mutable variable. + }; + inner(); + }; + f(); + assert(x == 5); + } + "#; + check_errors(src); +} + +#[test] +fn allow_capturing_mut_variable_only_used_immutably() { + let src = r#" + fn main() { + let mut x = 3; + let f = || x; + let _x2 = f(); + assert(x == 3); + } + "#; + assert_no_errors(src); +} + +#[test] +fn deny_capturing_mut_var_as_param_to_function() { + let src = r#" + fn main() { + let mut x = 3; + let f = || mutate(&mut x); + ^ Mutable variable x captured in lambda must be a mutable reference + ~ Use '&mut' instead of 'mut' to capture a mutable variable. + f(); + assert(x == 3); + } + + fn mutate(x: &mut Field) { + *x = 5; + } + "#; + check_errors(src); +} + +#[test] +fn deny_capturing_mut_var_as_param_to_function_in_nested_lambda() { + let src = r#" + fn main() { + let mut x = 3; + let f = || { + let inner = || mutate(&mut x); + ^ Mutable variable x captured in lambda must be a mutable reference + ~ Use '&mut' instead of 'mut' to capture a mutable variable. + inner(); + }; + f(); + assert(x == 3); + } + + fn mutate(x: &mut Field) { + *x = 5; + } + "#; + check_errors(src); +} + +#[test] +fn deny_capturing_mut_var_as_param_to_impl_method() { + let src = r#" + struct Foo { + value: Field, + } + + impl Foo { + fn mutate(&mut self) { + self.value = 2; + } + } + + fn main() { + let mut foo = Foo { value: 1 }; + let f = || foo.mutate(); + ^^^ Mutable variable foo captured in lambda must be a mutable reference + ~~~ Use '&mut' instead of 'mut' to capture a mutable variable. + f(); + assert(foo.value == 2); + } + "#; + check_errors(src); +} + +#[test] +fn deny_attaching_mut_ref_to_immutable_object() { + let src = r#" + struct Foo { + value: Field, + } + + impl Foo { + fn mutate(&mut self) { + self.value = 2; + } + } + + fn main() { + let foo = Foo { value: 1 }; + let f = || foo.mutate(); + ^^^ Cannot mutate immutable variable `foo` + f(); + assert(foo.value == 2); + } + "#; + check_errors(src); +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests/enums.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/enums.rs index 78f0442bc9fd..d9da717dc568 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/enums.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/enums.rs @@ -8,13 +8,12 @@ use super::{check_errors, check_errors_using_features}; #[test] fn error_with_duplicate_enum_variant() { - // TODO: the primary error should be on the second `Bar` let src = r#" pub enum Foo { Bar(i32), - ^^^ Duplicate definitions of enum variant with name Bar found ~~~ First enum variant found here Bar(u8), + ^^^ Duplicate definitions of enum variant with name Bar found ~~~ Second enum variant found here } @@ -200,3 +199,170 @@ fn constructor_arg_arity_mismatch_in_pattern() { "#; check_errors(src); } + +#[test] +fn unreachable_match_case() { + check_errors( + r#" + fn main() { + match Opt::Some(Opt::Some(3)) { + Opt::Some(_) => (), + Opt::None => (), + Opt::Some(Opt::Some(_)) => (), + ^^^^^^^^^^^^^^^^^^^^^^^ Unreachable match case + ~~~~~~~~~~~~~~~~~~~~~~~ This pattern is redundant with one or more prior patterns + } + } + + enum Opt { + None, + Some(T), + } + "#, + ); +} + +#[test] +fn match_reachability_errors_ignored_when_there_is_a_type_error() { + // No comment on the second `None` case. + // Type errors in general mess up reachability errors in match cases. + // If we naively change to catch this case (which is easy) we also end up + // erroring that the `3 => ()` case is unreachable as well, which is true + // but we don't want to annoy users with an extra obvious error. This + // behavior matches Rust as well. + check_errors( + " + fn main() { + match Opt::Some(3) { + Opt::None => (), + Opt::Some(_) => {}, + Opt::None => (), + 3 => (), + ^ Expected type Opt, found type Field + } + } + + enum Opt { + None, + Some(T), + } + ", + ); +} + +#[test] +fn missing_single_case() { + check_errors( + " + fn main() { + match Opt::Some(3) { + ^^^^^^^^^^^^ Missing case: `Some(_)` + Opt::None => (), + } + } + + enum Opt { + None, + Some(T), + } + ", + ); +} + +#[test] +fn missing_many_cases() { + check_errors( + " + fn main() { + match Abc::A { + ^^^^^^ Missing cases: `C`, `D`, `E`, and 21 more not shown + Abc::A => (), + Abc::B => (), + } + } + + enum Abc { + A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z + } + ", + ); +} + +#[test] +fn missing_int_ranges() { + check_errors( + " + fn main() { + let x: i8 = 3; + match Opt::Some(x) { + ^^^^^^^^^^^^ Missing cases: `None`, `Some(-128..=3)`, `Some(5)`, and 1 more not shown + Opt::Some(4) => (), + Opt::Some(6) => (), + } + } + + enum Opt { + None, + Some(T), + } + ", + ); +} + +#[test] +fn missing_int_ranges_with_negatives() { + check_errors( + " + fn main() { + let x: i32 = -4; + match x { + ^ Missing cases: `-2147483648..=-6`, `-4..=-1`, `1..=2`, and 1 more not shown + -5 => (), + 0 => (), + 3 => (), + } + } + ", + ); +} + +#[test] +fn missing_cases_with_empty_match() { + check_errors( + " + fn main() { + match Abc::A {} + ^^^^^^ Missing cases: `A`, `B`, `C`, and 23 more not shown + } + + enum Abc { + A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z + } + ", + ); +} + +#[test] +fn missing_integer_cases_with_empty_match() { + check_errors( + " + fn main() { + let x: i8 = 3; + match x {} + ^ Missing cases: `i8` is non-empty + ~ Try adding a match-all pattern: `_` + } + ", + ); +} + +#[test] +fn match_on_empty_enum() { + check_errors( + " + pub fn foo(v: Void) { + match v {} + } + pub enum Void {}", + ); +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/traits.rs index 5ba63bc6a29d..80ce9b87002d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/traits.rs @@ -105,8 +105,8 @@ fn trait_inheritance_dependency_cycle() { // TODO: maybe the error location should be just on Foo let src = r#" trait Foo: Bar {} - ^^^^^^^^^^^^^^^^^ Dependency cycle found - ~~~~~~~~~~~~~~~~~ 'Foo' recursively depends on itself: Foo -> Bar -> Foo + ^^^ Dependency cycle found + ~~~ 'Foo' recursively depends on itself: Foo -> Bar -> Foo trait Bar: Foo {} fn main() {} "#; @@ -120,13 +120,13 @@ fn trait_inheritance_missing_parent_implementation() { pub trait Foo {} pub trait Bar: Foo {} - ~~~ required by this bound in `Bar + ~~~ required by this bound in `Bar` pub struct Struct {} impl Bar for Struct {} ^^^^^^ The trait bound `Struct: Foo` is not satisfied - ~~~~~~ The trait `Foo` is not implemented for `Struct + ~~~~~~ The trait `Foo` is not implemented for `Struct` fn main() { let _ = Struct {}; // silence Struct never constructed warning @@ -214,7 +214,7 @@ fn errors_if_impl_trait_constraint_is_not_satisfied() { pub trait Foo where T: Greeter, - ~~~~~~~ required by this bound in `Foo + ~~~~~~~ required by this bound in `Foo` { fn greet(object: U) where @@ -230,7 +230,7 @@ fn errors_if_impl_trait_constraint_is_not_satisfied() { impl Foo for Bar {} ^^^ The trait bound `SomeGreeter: Greeter` is not satisfied - ~~~ The trait `Greeter` is not implemented for `SomeGreeter + ~~~ The trait `Greeter` is not implemented for `SomeGreeter` fn main() {} "#; @@ -1214,3 +1214,36 @@ fn calls_trait_method_using_struct_name_when_multiple_impls_exist_and_errors_tur "#; check_errors(src); } + +#[test] +fn as_trait_path_in_expression() { + let src = r#" + fn main() { + cursed::(); + } + + fn cursed() + where T: Foo + Foo2 + { + ::bar(1); + ::bar(()); + + // Use each function with different generic arguments + ::bar(()); + } + + trait Foo { fn bar(x: U); } + trait Foo2 { fn bar(x: U); } + + pub struct S {} + + impl Foo for S { + fn bar(_x: Z) {} + } + + impl Foo2 for S { + fn bar(_x: Z) {} + } + "#; + assert_no_errors(src); +} diff --git a/noir/noir-repo/compiler/wasm/src/compile.rs b/noir/noir-repo/compiler/wasm/src/compile.rs index 021462d9f46e..8c0359bbced0 100644 --- a/noir/noir-repo/compiler/wasm/src/compile.rs +++ b/noir/noir-repo/compiler/wasm/src/compile.rs @@ -130,7 +130,7 @@ pub(crate) struct DependencyGraph { pub(crate) root_dependencies: Vec, pub(crate) library_dependencies: BTreeMap>, } -/// This is map contains the paths of all of the files in the entry-point crate and +/// This map contains the paths of all of the files in the entry-point crate and /// the transitive dependencies of the entry-point crate. /// /// This is for all intents and purposes the file system that the compiler will use to resolve/compile @@ -176,7 +176,7 @@ pub fn compile_program( let compiled_program = noirc_driver::compile_main(&mut context, crate_id, &compile_options, None) .map_err(|errs| { - CompileError::with_file_diagnostics( + CompileError::with_custom_diagnostics( "Failed to compile program", errs, &context.file_manager, @@ -186,7 +186,7 @@ pub fn compile_program( let optimized_program = nargo::ops::transform_program(compiled_program, expression_width); nargo::ops::check_program(&optimized_program).map_err(|errs| { - CompileError::with_file_diagnostics( + CompileError::with_custom_diagnostics( "Compiled program is not solvable", errs, &context.file_manager, @@ -212,8 +212,8 @@ pub fn compile_contract( let compiled_contract = noirc_driver::compile_contract(&mut context, crate_id, &compile_options) - .map_err(|errs: Vec| { - CompileError::with_file_diagnostics( + .map_err(|errs: Vec| { + CompileError::with_custom_diagnostics( "Failed to compile contract", errs, &context.file_manager, diff --git a/noir/noir-repo/compiler/wasm/src/compile_new.rs b/noir/noir-repo/compiler/wasm/src/compile_new.rs index e7c2e94cd846..37065c8f8255 100644 --- a/noir/noir-repo/compiler/wasm/src/compile_new.rs +++ b/noir/noir-repo/compiler/wasm/src/compile_new.rs @@ -109,7 +109,7 @@ impl CompilerContext { let compiled_program = compile_main(&mut self.context, root_crate_id, &compile_options, None) .map_err(|errs| { - CompileError::with_file_diagnostics( + CompileError::with_custom_diagnostics( "Failed to compile program", errs, &self.context.file_manager, @@ -119,7 +119,7 @@ impl CompilerContext { let optimized_program = nargo::ops::transform_program(compiled_program, expression_width); nargo::ops::check_program(&optimized_program).map_err(|errs| { - CompileError::with_file_diagnostics( + CompileError::with_custom_diagnostics( "Compiled program is not solvable", errs, &self.context.file_manager, @@ -148,7 +148,7 @@ impl CompilerContext { let compiled_contract = compile_contract(&mut self.context, root_crate_id, &compile_options) .map_err(|errs| { - CompileError::with_file_diagnostics( + CompileError::with_custom_diagnostics( "Failed to compile contract", errs, &self.context.file_manager, diff --git a/noir/noir-repo/compiler/wasm/src/errors.rs b/noir/noir-repo/compiler/wasm/src/errors.rs index c2e51162d3ff..47927df10562 100644 --- a/noir/noir-repo/compiler/wasm/src/errors.rs +++ b/noir/noir-repo/compiler/wasm/src/errors.rs @@ -4,7 +4,7 @@ use serde::Serialize; use wasm_bindgen::prelude::*; use fm::FileManager; -use noirc_errors::FileDiagnostic; +use noirc_errors::CustomDiagnostic; #[wasm_bindgen(typescript_custom_section)] const DIAGNOSTICS: &'static str = r#" @@ -87,8 +87,7 @@ pub struct Diagnostic { } impl Diagnostic { - fn new(file_diagnostic: &FileDiagnostic, file: String) -> Diagnostic { - let diagnostic = &file_diagnostic.diagnostic; + fn new(diagnostic: &CustomDiagnostic, file: String) -> Diagnostic { let message = diagnostic.message.clone(); let secondaries = diagnostic @@ -116,16 +115,16 @@ impl CompileError { CompileError { message: message.to_string(), diagnostics: vec![] } } - pub fn with_file_diagnostics( + pub fn with_custom_diagnostics( message: &str, - file_diagnostics: Vec, + custom_diagnostics: Vec, file_manager: &FileManager, ) -> CompileError { - let diagnostics: Vec<_> = file_diagnostics + let diagnostics: Vec<_> = custom_diagnostics .iter() .map(|err| { let file_path = file_manager - .path(err.file_id) + .path(err.file) .expect("File must exist to have caused diagnostics"); Diagnostic::new(err, file_path.to_str().unwrap().to_string()) }) diff --git a/noir/noir-repo/docs/docs/how_to/merkle-proof.mdx b/noir/noir-repo/docs/docs/how_to/merkle-proof.mdx deleted file mode 100644 index 0a128adb2de6..000000000000 --- a/noir/noir-repo/docs/docs/how_to/merkle-proof.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Prove Merkle Tree Membership -description: - Learn how to use merkle membership proof in Noir to prove that a given leaf is a member of a - merkle tree with a specified root, at a given index. -keywords: - [merkle proof, merkle membership proof, Noir, rust, hash function, Pedersen, sha256, merkle tree] -sidebar_position: 4 ---- - -Let's walk through an example of a merkle membership proof in Noir that proves that a given leaf is -in a merkle tree. - -```rust - -fn main(message : [Field; 62], index : Field, hashpath : [Field; 40], root : Field) { - let leaf = std::hash::hash_to_field(message.as_slice()); - let merkle_root = std::merkle::compute_merkle_root(leaf, index, hashpath); - assert(merkle_root == root); -} - -``` - -The message is hashed using `hash_to_field`. The specific hash function that is being used is chosen -by the backend. The only requirement is that this hash function can heuristically be used as a -random oracle. If only collision resistance is needed, then one can call `std::hash::pedersen_hash` -instead. - -```rust -let leaf = std::hash::hash_to_field(message.as_slice()); -``` - -The leaf is then passed to a compute_merkle_root function with the root, index and hashpath. The returned root can then be asserted to be the same as the provided root. - -```rust -let merkle_root = std::merkle::compute_merkle_root(leaf, index, hashpath); -assert (merkle_root == root); -``` - -> **Note:** It is possible to re-implement the merkle tree implementation without standard library. -> However, for most usecases, it is enough. In general, the standard library will always opt to be -> as conservative as possible, while striking a balance with efficiency. - -An example, the merkle membership proof, only requires a hash function that has collision -resistance, hence a hash function like Pedersen is allowed, which in most cases is more efficient -than the even more conservative sha256. - -[View an example on the starter repo](https://github.com/noir-lang/noir-examples/blob/3ea09545cabfa464124ec2f3ea8e60c608abe6df/stealthdrop/circuits/src/main.nr#L20) diff --git a/noir/noir-repo/docs/docs/noir/concepts/data_types/integers.md b/noir/noir-repo/docs/docs/noir/concepts/data_types/integers.md index b8a5d4980296..ff3fafa1f905 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/data_types/integers.md +++ b/noir/noir-repo/docs/docs/noir/concepts/data_types/integers.md @@ -58,54 +58,6 @@ fn main(x: i16, y: i16) { Modulo operation is defined for negative integers thanks to integer division, so that the equality `x = (x/y)*y + (x%y)` holds. -## 128 bits Unsigned Integers - -The built-in structure `U128` allows you to use 128-bit unsigned integers almost like a native integer type. However, there are some differences to keep in mind: -- You cannot cast between a native integer and `U128` -- There is a higher performance cost when using `U128`, compared to a native type. - -Conversion between unsigned integer types and U128 are done through the use of `from_integer` and `to_integer` functions. `from_integer` also accepts the `Field` type as input. - -```rust -fn main() { - let x = U128::from_integer(23); - let y = U128::from_hex("0x7"); - let z = x + y; - assert(z.to_integer() == 30); -} -``` - -`U128` is implemented with two 64 bits limbs, representing the low and high bits, which explains the performance cost. You should expect `U128` to be twice more costly for addition and four times more costly for multiplication. -You can construct a U128 from its limbs: -```rust -fn main(x: u64, y: u64) { - let z = U128::from_u64s_be(x,y); - assert(z.hi == x as Field); - assert(z.lo == y as Field); -} -``` - -Note that the limbs are stored as Field elements in order to avoid unnecessary conversions. -Apart from this, most operations will work as usual: - -```rust -fn main(x: U128, y: U128) { - // multiplication - let c = x * y; - // addition and subtraction - let c = c - x + y; - // division - let c = x / y; - // bit operation; - let c = x & y | y; - // bit shift - let c = x << y; - // comparisons; - let c = x < y; - let c = x == y; -} -``` - ## Overflows Computations that exceed the type boundaries will result in overflow errors. This happens with both signed and unsigned integers. For example, attempting to prove: diff --git a/noir/noir-repo/docs/docs/noir/concepts/traits.md b/noir/noir-repo/docs/docs/noir/concepts/traits.md index 17cc04a97518..af5b396bfb82 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/traits.md +++ b/noir/noir-repo/docs/docs/noir/concepts/traits.md @@ -153,6 +153,37 @@ fn main() { } ``` +## As Trait Syntax + +Rarely to call a method it may not be sufficient to use the general method call syntax of `obj.method(args)`. +One case where this may happen is if there are two traits in scope which both define a method with the same name. +For example: + +```rust +trait Foo { fn bar(); } +trait Foo2 { fn bar(); } + +fn example() + where T: Foo + Foo2 +{ + // How to call Foo::bar and Foo2::bar? +} +``` + +In the above example we have both `Foo` and `Foo2` which define a `bar` method. The normal way to resolve +this would be to use the static method syntax `Foo::bar(object)` but there is no object in this case and +`Self` does not appear in the type signature of `bar` at all so we would not know which impl to choose. +For these situations there is the "as trait" syntax: `::method(object, args...)` + +```rust +fn example() + where T: Foo + Foo2 +{ + ::bar(); + ::bar(); +} +``` + ## Generic Implementations You can add generics to a trait implementation by adding the generic list after the `impl` keyword: diff --git a/noir/noir-repo/docs/docs/noir/modules_packages_crates/dependencies.md b/noir/noir-repo/docs/docs/noir/modules_packages_crates/dependencies.md index 22186b225988..cb8765323929 100644 --- a/noir/noir-repo/docs/docs/noir/modules_packages_crates/dependencies.md +++ b/noir/noir-repo/docs/docs/noir/modules_packages_crates/dependencies.md @@ -77,14 +77,14 @@ use lib_a; You can also import only the specific parts of dependency that you want to use, like so: ```rust -use std::hash::sha256; +use std::hash::blake3; use std::scalar_mul::fixed_base_embedded_curve; ``` Lastly, You can import multiple items in the same line by enclosing them in curly braces: ```rust -use std::hash::{keccak256, sha256}; +use std::hash::{blake2s, blake3}; ``` We don't have a way to consume libraries from inside a [workspace](./workspaces.md) as external dependencies right now. diff --git a/noir/noir-repo/docs/docs/noir/standard_library/black_box_fns.md b/noir/noir-repo/docs/docs/noir/standard_library/black_box_fns.md index e9392b20a92f..ed905ecb5c29 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/black_box_fns.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/black_box_fns.md @@ -23,7 +23,7 @@ Here is a list of the current black box functions: - AND - XOR - RANGE -- [Keccak256](./cryptographic_primitives/hashes.mdx#keccak256) +- [Keccakf1600](./cryptographic_primitives/hashes.mdx#keccakf1600) - [Recursive proof verification](./recursion.mdx) Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. diff --git a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx index b7518fa95c10..334873e68635 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx +++ b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -1,8 +1,7 @@ --- title: Hash methods description: - Learn about the cryptographic primitives ready to use for any Noir project, including sha256, - blake2s and pedersen + Learn about the cryptographic primitives ready to use for any Noir project keywords: [cryptographic primitives, Noir project, sha256, blake2s, pedersen, hash] sidebar_position: 0 @@ -10,23 +9,11 @@ sidebar_position: 0 import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; -## sha256 +## sha256 compression -Given an array of bytes, returns the resulting sha256 hash. -Specify a message_size to hash only the first `message_size` bytes of the input. - -#include_code sha256 noir_stdlib/src/hash/sha256.nr rust - -example: -#include_code sha256_var test_programs/execution_success/sha256/src/main.nr rust - -```rust -fn main() { - let x = [163, 117, 178, 149]; // some random bytes - let hash = std::sha256::sha256_var(x, 4); -} -``` +Performs a sha256 compression on an input and initial state, returning the resulting state. +#include_code sha256_compression noir_stdlib/src/hash/mod.nr rust @@ -88,17 +75,11 @@ example: -## keccak256 +## keccakf1600 -Given an array of bytes (`u8`), returns the resulting keccak hash as an array of -32 bytes (`[u8; 32]`). Specify a message_size to hash only the first -`message_size` bytes of the input. - -#include_code keccak256 noir_stdlib/src/hash/mod.nr rust - -example: +Given an initial `[u64; 25]` state, returns the state resulting from applying a keccakf1600 permutation (`[u64; 25]`). -#include_code keccak256 test_programs/execution_success/keccak256/src/main.nr rust +#include_code keccakf1600 noir_stdlib/src/hash/mod.nr rust diff --git a/noir/noir-repo/docs/docs/noir/standard_library/merkle_trees.md b/noir/noir-repo/docs/docs/noir/standard_library/merkle_trees.md deleted file mode 100644 index 6a9ebf72ada0..000000000000 --- a/noir/noir-repo/docs/docs/noir/standard_library/merkle_trees.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: Merkle Trees -description: Learn about Merkle Trees in Noir with this tutorial. Explore the basics of computing a merkle root using a proof, with examples. -keywords: - [ - Merkle trees in Noir, - Noir programming language, - check membership, - computing root from leaf, - Noir Merkle tree implementation, - Merkle tree tutorial, - Merkle tree code examples, - Noir libraries, - pedersen hash., - ] ---- - -## compute_merkle_root - -Returns the root of the tree from the provided leaf and its hash path, using a [Pedersen hash](./cryptographic_primitives/hashes.mdx#pedersen_hash). - -```rust -fn compute_merkle_root(leaf : Field, index : Field, hash_path: [Field]) -> Field -``` - -example: - -```rust -/** - // these values are for this example only - index = "0" - priv_key = "0x000000000000000000000000000000000000000000000000000000616c696365" - secret = "0x1929ea3ab8d9106a899386883d9428f8256cfedb3c4f6b66bf4aa4d28a79988f" - note_hash_path = [ - "0x1e61bdae0f027b1b2159e1f9d3f8d00fa668a952dddd822fda80dc745d6f65cc", - "0x0e4223f3925f98934393c74975142bd73079ab0621f4ee133cee050a3c194f1a", - "0x2fd7bb412155bf8693a3bd2a3e7581a679c95c68a052f835dddca85fa1569a40" - ] - */ -fn main(index: Field, priv_key: Field, secret: Field, note_hash_path: [Field; 3]) { - - let pubkey = std::scalar_mul::fixed_base_embedded_curve(priv_key); - let pubkey_x = pubkey[0]; - let pubkey_y = pubkey[1]; - let note_commitment = std::hash::pedersen(&[pubkey_x, pubkey_y, secret]); - - let root = std::merkle::compute_merkle_root(note_commitment[0], index, note_hash_path.as_slice()); - println(root); -} -``` - -To check merkle tree membership: - -1. Include a merkle root as a program input. -2. Compute the merkle root of a given leaf, index and hash path. -3. Assert the merkle roots are equal. - -For more info about merkle trees, see the Wikipedia [page](https://en.wikipedia.org/wiki/Merkle_tree). diff --git a/noir/noir-repo/docs/docs/noir/standard_library/traits.md b/noir/noir-repo/docs/docs/noir/standard_library/traits.md index e6f6f80ff032..ed923c0707a1 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/traits.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/traits.md @@ -71,7 +71,7 @@ As a general rule of thumb, `From` may be implemented in the [situations where i - The conversion is *infallible*: Noir does not provide an equivalent to Rust's `TryFrom`, if the conversion can fail then provide a named method instead. - The conversion is *lossless*: semantically, it should not lose or discard information. For example, `u32: From` can losslessly convert any `u16` into a valid `u32` such that the original `u16` can be recovered. On the other hand, `u16: From` should not be implemented as `2**16` is a `u32` which cannot be losslessly converted into a `u16`. - The conversion is *value-preserving*: the conceptual kind and meaning of the resulting value is the same, even though the Noir type and technical representation might be different. While it's possible to infallibly and losslessly convert a `u8` into a `str<2>` hex representation, `4u8` and `"04"` are too different for `str<2>: From` to be implemented. -- The conversion is *obvious*: it's the only reasonable conversion between the two types. If there's ambiguity on how to convert between them such that the same input could potentially map to two different values then a named method should be used. For instance rather than implementing `U128: From<[u8; 16]>`, the methods `U128::from_le_bytes` and `U128::from_be_bytes` are used as otherwise the endianness of the array would be ambiguous, resulting in two potential values of `U128` from the same byte array. +- The conversion is *obvious*: it's the only reasonable conversion between the two types. If there's ambiguity on how to convert between them such that the same input could potentially map to two different values then a named method should be used. For instance rather than implementing `u128: From<[u8; 16]>`, the methods `u128::from_le_bytes` and `u128::from_be_bytes` are used as otherwise the endianness of the array would be ambiguous, resulting in two potential values of `u128` from the same byte array. One additional recommendation specific to Noir is: - The conversion is *efficient*: it's relatively cheap to convert between the two types. Due to being a ZK DSL, it's more important to avoid unnecessary computation compared to Rust. If the implementation of `From` would encourage users to perform unnecessary conversion, resulting in additional proving time, then it may be preferable to expose functionality such that this conversion may be avoided. diff --git a/noir/noir-repo/docs/docs/tooling/security.md b/noir/noir-repo/docs/docs/tooling/security.md index e14481efc317..8a09d231a7da 100644 --- a/noir/noir-repo/docs/docs/tooling/security.md +++ b/noir/noir-repo/docs/docs/tooling/security.md @@ -39,7 +39,7 @@ Here, the results of `factor` are two elements of the returned array. The value This pass checks if the constraint coverage of Brillig calls is sufficient in these terms. -The check is at the moment disabled by default due to performance concerns and can be enabled by passing the `--enable-brillig-constraints-check` option to `nargo`. +The check is enabled by default and can be disabled by passing the `--skip-brillig-constraints-check` option to `nargo`. #### Lookback option diff --git a/noir/noir-repo/examples/oracle_transcript/Nargo.toml b/noir/noir-repo/examples/oracle_transcript/Nargo.toml new file mode 100644 index 000000000000..3f333c912b09 --- /dev/null +++ b/noir/noir-repo/examples/oracle_transcript/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "oracle_transcript" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] diff --git a/noir/noir-repo/examples/oracle_transcript/Oracle.jsonl b/noir/noir-repo/examples/oracle_transcript/Oracle.jsonl new file mode 100644 index 000000000000..570e95907617 --- /dev/null +++ b/noir/noir-repo/examples/oracle_transcript/Oracle.jsonl @@ -0,0 +1,5 @@ +{"call":{"function":"void_field","inputs":[]},"result":{"values":["000000000000000000000000000000000000000000000000000000000000000a"]}} +{"call":{"function":"void_field","inputs":[]},"result":{"values":["0000000000000000000000000000000000000000000000000000000000000014"]}} +{"call":{"function":"field_field","inputs":["0000000000000000000000000000000000000000000000000000000000000002"]},"result":{"values":["000000000000000000000000000000000000000000000000000000000000001e"]}} +{"call":{"function":"field_field","inputs":["0000000000000000000000000000000000000000000000000000000000000003"]},"result":{"values":["0000000000000000000000000000000000000000000000000000000000000028"]}} +{"call":{"function":"struct_field","inputs":["000000000000000000000000000000000000000000000000000000000000012c","0000000000000000000000000000000000000000000000000000000000000320",["000000000000000000000000000000000000000000000000000000000000000a","0000000000000000000000000000000000000000000000000000000000000014","000000000000000000000000000000000000000000000000000000000000001e","0000000000000000000000000000000000000000000000000000000000000028"]]},"result":{"values":["0000000000000000000000000000000000000000000000000000000000000064"]}} diff --git a/noir/noir-repo/examples/oracle_transcript/Oracle.test.jsonl b/noir/noir-repo/examples/oracle_transcript/Oracle.test.jsonl new file mode 100644 index 000000000000..cebe10d307fc --- /dev/null +++ b/noir/noir-repo/examples/oracle_transcript/Oracle.test.jsonl @@ -0,0 +1,20 @@ +{"call":{"function":"create_mock","inputs":[["0000000000000000000000000000000000000000000000000000000000000076","000000000000000000000000000000000000000000000000000000000000006f","0000000000000000000000000000000000000000000000000000000000000069","0000000000000000000000000000000000000000000000000000000000000064","000000000000000000000000000000000000000000000000000000000000005f","0000000000000000000000000000000000000000000000000000000000000066","0000000000000000000000000000000000000000000000000000000000000069","0000000000000000000000000000000000000000000000000000000000000065","000000000000000000000000000000000000000000000000000000000000006c","0000000000000000000000000000000000000000000000000000000000000064"]]},"result":{"values":["0000000000000000000000000000000000000000000000000000000000000000"]}} +{"call":{"function":"set_mock_returns","inputs":["0000000000000000000000000000000000000000000000000000000000000000","000000000000000000000000000000000000000000000000000000000000000a"]},"result":{"values":[]}} +{"call":{"function":"set_mock_times","inputs":["0000000000000000000000000000000000000000000000000000000000000000","0000000000000000000000000000000000000000000000000000000000000001"]},"result":{"values":[]}} +{"call":{"function":"create_mock","inputs":[["0000000000000000000000000000000000000000000000000000000000000076","000000000000000000000000000000000000000000000000000000000000006f","0000000000000000000000000000000000000000000000000000000000000069","0000000000000000000000000000000000000000000000000000000000000064","000000000000000000000000000000000000000000000000000000000000005f","0000000000000000000000000000000000000000000000000000000000000066","0000000000000000000000000000000000000000000000000000000000000069","0000000000000000000000000000000000000000000000000000000000000065","000000000000000000000000000000000000000000000000000000000000006c","0000000000000000000000000000000000000000000000000000000000000064"]]},"result":{"values":["0000000000000000000000000000000000000000000000000000000000000001"]}} +{"call":{"function":"set_mock_returns","inputs":["0000000000000000000000000000000000000000000000000000000000000001","0000000000000000000000000000000000000000000000000000000000000014"]},"result":{"values":[]}} +{"call":{"function":"set_mock_times","inputs":["0000000000000000000000000000000000000000000000000000000000000001","0000000000000000000000000000000000000000000000000000000000000001"]},"result":{"values":[]}} +{"call":{"function":"create_mock","inputs":[["0000000000000000000000000000000000000000000000000000000000000066","0000000000000000000000000000000000000000000000000000000000000069","0000000000000000000000000000000000000000000000000000000000000065","000000000000000000000000000000000000000000000000000000000000006c","0000000000000000000000000000000000000000000000000000000000000064","000000000000000000000000000000000000000000000000000000000000005f","0000000000000000000000000000000000000000000000000000000000000066","0000000000000000000000000000000000000000000000000000000000000069","0000000000000000000000000000000000000000000000000000000000000065","000000000000000000000000000000000000000000000000000000000000006c","0000000000000000000000000000000000000000000000000000000000000064"]]},"result":{"values":["0000000000000000000000000000000000000000000000000000000000000002"]}} +{"call":{"function":"set_mock_params","inputs":["0000000000000000000000000000000000000000000000000000000000000002","0000000000000000000000000000000000000000000000000000000000000002"]},"result":{"values":[]}} +{"call":{"function":"set_mock_returns","inputs":["0000000000000000000000000000000000000000000000000000000000000002","000000000000000000000000000000000000000000000000000000000000001e"]},"result":{"values":[]}} +{"call":{"function":"create_mock","inputs":[["0000000000000000000000000000000000000000000000000000000000000066","0000000000000000000000000000000000000000000000000000000000000069","0000000000000000000000000000000000000000000000000000000000000065","000000000000000000000000000000000000000000000000000000000000006c","0000000000000000000000000000000000000000000000000000000000000064","000000000000000000000000000000000000000000000000000000000000005f","0000000000000000000000000000000000000000000000000000000000000066","0000000000000000000000000000000000000000000000000000000000000069","0000000000000000000000000000000000000000000000000000000000000065","000000000000000000000000000000000000000000000000000000000000006c","0000000000000000000000000000000000000000000000000000000000000064"]]},"result":{"values":["0000000000000000000000000000000000000000000000000000000000000003"]}} +{"call":{"function":"set_mock_params","inputs":["0000000000000000000000000000000000000000000000000000000000000003","0000000000000000000000000000000000000000000000000000000000000003"]},"result":{"values":[]}} +{"call":{"function":"set_mock_returns","inputs":["0000000000000000000000000000000000000000000000000000000000000003","0000000000000000000000000000000000000000000000000000000000000028"]},"result":{"values":[]}} +{"call":{"function":"create_mock","inputs":[["0000000000000000000000000000000000000000000000000000000000000073","0000000000000000000000000000000000000000000000000000000000000074","0000000000000000000000000000000000000000000000000000000000000072","0000000000000000000000000000000000000000000000000000000000000075","0000000000000000000000000000000000000000000000000000000000000063","0000000000000000000000000000000000000000000000000000000000000074","000000000000000000000000000000000000000000000000000000000000005f","0000000000000000000000000000000000000000000000000000000000000066","0000000000000000000000000000000000000000000000000000000000000069","0000000000000000000000000000000000000000000000000000000000000065","000000000000000000000000000000000000000000000000000000000000006c","0000000000000000000000000000000000000000000000000000000000000064"]]},"result":{"values":["0000000000000000000000000000000000000000000000000000000000000004"]}} +{"call":{"function":"set_mock_params","inputs":["0000000000000000000000000000000000000000000000000000000000000004","000000000000000000000000000000000000000000000000000000000000012c","0000000000000000000000000000000000000000000000000000000000000320",["000000000000000000000000000000000000000000000000000000000000000a","0000000000000000000000000000000000000000000000000000000000000014","000000000000000000000000000000000000000000000000000000000000001e","0000000000000000000000000000000000000000000000000000000000000028"]]},"result":{"values":[]}} +{"call":{"function":"set_mock_returns","inputs":["0000000000000000000000000000000000000000000000000000000000000004","0000000000000000000000000000000000000000000000000000000000000064"]},"result":{"values":[]}} +{"call":{"function":"void_field","inputs":[]},"result":{"values":["000000000000000000000000000000000000000000000000000000000000000a"]}} +{"call":{"function":"void_field","inputs":[]},"result":{"values":["0000000000000000000000000000000000000000000000000000000000000014"]}} +{"call":{"function":"field_field","inputs":["0000000000000000000000000000000000000000000000000000000000000002"]},"result":{"values":["000000000000000000000000000000000000000000000000000000000000001e"]}} +{"call":{"function":"field_field","inputs":["0000000000000000000000000000000000000000000000000000000000000003"]},"result":{"values":["0000000000000000000000000000000000000000000000000000000000000028"]}} +{"call":{"function":"struct_field","inputs":["000000000000000000000000000000000000000000000000000000000000012c","0000000000000000000000000000000000000000000000000000000000000320",["000000000000000000000000000000000000000000000000000000000000000a","0000000000000000000000000000000000000000000000000000000000000014","000000000000000000000000000000000000000000000000000000000000001e","0000000000000000000000000000000000000000000000000000000000000028"]]},"result":{"values":["0000000000000000000000000000000000000000000000000000000000000064"]}} diff --git a/noir/noir-repo/examples/oracle_transcript/Prover.toml b/noir/noir-repo/examples/oracle_transcript/Prover.toml new file mode 100644 index 000000000000..eb8504c2b0c3 --- /dev/null +++ b/noir/noir-repo/examples/oracle_transcript/Prover.toml @@ -0,0 +1,6 @@ + +[input] +x = 2 +y = 3 + +return = 100 diff --git a/noir/noir-repo/examples/oracle_transcript/log_and_exec_transcript.sh b/noir/noir-repo/examples/oracle_transcript/log_and_exec_transcript.sh new file mode 100755 index 000000000000..c6e5066f158a --- /dev/null +++ b/noir/noir-repo/examples/oracle_transcript/log_and_exec_transcript.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -eu + +cd $(dirname $0) + +# Execute the test to capture oracle calls. +NARGO_TEST_FOREIGN_CALL_LOG=Oracle.test.jsonl nargo test + +# Get rid of the mock setup calls +cat Oracle.test.jsonl \ + | jq --slurp -r -c '.[] | select(.call.function | contains("mock") | not)' \ + > Oracle.jsonl + +# Execute `main` with the Prover.toml and Oracle.jsonl files. +nargo execute --skip-underconstrained-check --oracle-file Oracle.jsonl + +# Also execute through `noir-execute` +noir-execute \ + --artifact-path target/oracle_transcript.json \ + --oracle-file Oracle.jsonl \ + --prover-file Prover.toml \ + --output-dir target diff --git a/noir/noir-repo/examples/oracle_transcript/src/main.nr b/noir/noir-repo/examples/oracle_transcript/src/main.nr new file mode 100644 index 000000000000..585ff2af2b2d --- /dev/null +++ b/noir/noir-repo/examples/oracle_transcript/src/main.nr @@ -0,0 +1,63 @@ +use std::test::OracleMock; + +struct Point { + x: Field, + y: Field, +} + +impl Eq for Point { + fn eq(self, other: Point) -> bool { + (self.x == other.x) & (self.y == other.y) + } +} + +#[oracle(void_field)] +unconstrained fn void_field_oracle() -> Field {} + +unconstrained fn void_field() -> Field { + void_field_oracle() +} + +#[oracle(field_field)] +unconstrained fn field_field_oracle(_x: Field) -> Field {} + +unconstrained fn field_field(x: Field) -> Field { + field_field_oracle(x) +} + +#[oracle(struct_field)] +unconstrained fn struct_field_oracle(_point: Point, _array: [Field; 4]) -> Field {} + +unconstrained fn struct_field(point: Point, array: [Field; 4]) -> Field { + struct_field_oracle(point, array) +} + +fn main(input: Point) -> pub Field { + // Safety: testing context + unsafe { + let a = void_field(); + let b = void_field(); + let c = field_field(input.x); + let d = field_field(input.y); + let p = Point { x: a * c, y: b * d }; + struct_field(p, [a, b, c, d]) + } +} + +/// This test is used to capture an oracle transcript, which can then be replayed +/// during execution. +#[test] +fn test_main() { + // Safety: testing context + unsafe { + let _ = OracleMock::mock("void_field").returns(10).times(1); + let _ = OracleMock::mock("void_field").returns(20).times(1); + let _ = OracleMock::mock("field_field").with_params((2,)).returns(30); + let _ = OracleMock::mock("field_field").with_params((3,)).returns(40); + let _ = OracleMock::mock("struct_field") + .with_params((Point { x: 300, y: 800 }, [10, 20, 30, 40])) + .returns(100); + } + let output = main(Point { x: 2, y: 3 }); + assert_eq(output, 100) +} diff --git a/noir/noir-repo/examples/oracle_transcript/test.sh b/noir/noir-repo/examples/oracle_transcript/test.sh new file mode 100755 index 000000000000..8f43c3b8bb9b --- /dev/null +++ b/noir/noir-repo/examples/oracle_transcript/test.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -eu + +cd $(dirname $0) + +# This file is used for Noir CI and is not required. + +rm -f ./Oracle.* + +./log_and_exec_transcript.sh diff --git a/noir/noir-repo/noir_stdlib/src/hash/keccak.nr b/noir/noir-repo/noir_stdlib/src/hash/keccak.nr deleted file mode 100644 index 75be7982e667..000000000000 --- a/noir/noir-repo/noir_stdlib/src/hash/keccak.nr +++ /dev/null @@ -1,155 +0,0 @@ -use crate::runtime::is_unconstrained; - -global BLOCK_SIZE_IN_BYTES: u32 = 136; //(1600 - BITS * 2) / WORD_SIZE; -global WORD_SIZE: u32 = 8; // Limbs are made up of u64s so 8 bytes each. -global LIMBS_PER_BLOCK: u32 = BLOCK_SIZE_IN_BYTES / WORD_SIZE; -global NUM_KECCAK_LANES: u32 = 25; - -#[foreign(keccakf1600)] -pub fn keccakf1600(input: [u64; 25]) -> [u64; 25] {} - -#[no_predicates] -#[deprecated("keccak256 is being deprecated from the stdlib, use https://github.com/noir-lang/keccak256 instead")] -pub(crate) fn keccak256(input: [u8; N], message_size: u32) -> [u8; 32] { - assert(N >= message_size); - - // Copy input to block bytes. For that we'll need at least input bytes (N) - // but we want it to be padded to a multiple of BLOCK_SIZE_IN_BYTES. - let mut block_bytes = [0; ((N / BLOCK_SIZE_IN_BYTES) + 1) * BLOCK_SIZE_IN_BYTES]; - if is_unconstrained() { - for i in 0..message_size { - block_bytes[i] = input[i]; - } - } else { - for i in 0..N { - if i < message_size { - block_bytes[i] = input[i]; - } - } - } - - //1. format_input_lanes - let max_blocks = (N + BLOCK_SIZE_IN_BYTES) / BLOCK_SIZE_IN_BYTES; - //maximum number of bytes to hash - let real_max_blocks = (message_size + BLOCK_SIZE_IN_BYTES) / BLOCK_SIZE_IN_BYTES; - let real_blocks_bytes = real_max_blocks * BLOCK_SIZE_IN_BYTES; - - block_bytes[message_size] = 1; - block_bytes[real_blocks_bytes - 1] = 0x80; - - // populate a vector of 64-bit limbs from our byte array - let mut sliced_buffer = - [0; (((N / BLOCK_SIZE_IN_BYTES) + 1) * BLOCK_SIZE_IN_BYTES) / WORD_SIZE]; - for i in 0..sliced_buffer.len() { - let limb_start = WORD_SIZE * i; - - let mut sliced = 0; - let mut v = 1; - for k in 0..WORD_SIZE { - sliced += v * (block_bytes[limb_start + k] as Field); - v *= 256; - } - - sliced_buffer[i] = sliced as u64; - } - - //2. sponge_absorb - let mut state: [u64; NUM_KECCAK_LANES] = [0; NUM_KECCAK_LANES]; - // When in an unconstrained runtime we can take advantage of runtime loop bounds, - // thus allowing us to simplify the loop body. - if is_unconstrained() { - for i in 0..real_max_blocks { - if (i == 0) { - for j in 0..LIMBS_PER_BLOCK { - state[j] = sliced_buffer[j]; - } - } else { - for j in 0..LIMBS_PER_BLOCK { - state[j] = state[j] ^ sliced_buffer[i * LIMBS_PER_BLOCK + j]; - } - } - state = keccakf1600(state); - } - } else { - // `real_max_blocks` is guaranteed to at least be `1` - // We peel out the first block as to avoid a conditional inside of the loop. - // Otherwise, a dynamic predicate can cause a blowup in a constrained runtime. - for j in 0..LIMBS_PER_BLOCK { - state[j] = sliced_buffer[j]; - } - state = keccakf1600(state); - for i in 1..max_blocks { - if i < real_max_blocks { - for j in 0..LIMBS_PER_BLOCK { - state[j] = state[j] ^ sliced_buffer[i * LIMBS_PER_BLOCK + j]; - } - state = keccakf1600(state); - } - } - } - - //3. sponge_squeeze - let mut result = [0; 32]; - for i in 0..4 { - let lane = state[i] as Field; - let lane_le: [u8; 8] = lane.to_le_bytes(); - for j in 0..8 { - result[8 * i + j] = lane_le[j]; - } - } - result -} - -mod tests { - use super::keccak256; - - #[test] - fn smoke_test() { - let input = [0xbd]; - let result = [ - 0x5a, 0x50, 0x2f, 0x9f, 0xca, 0x46, 0x7b, 0x26, 0x6d, 0x5b, 0x78, 0x33, 0x65, 0x19, - 0x37, 0xe8, 0x05, 0x27, 0x0c, 0xa3, 0xf3, 0xaf, 0x1c, 0x0d, 0xd2, 0x46, 0x2d, 0xca, - 0x4b, 0x3b, 0x1a, 0xbf, - ]; - assert_eq(keccak256(input, input.len()), result); - } - - #[test] - fn hash_hello_world() { - let input = "Hello world!".as_bytes(); - let result = [ - 0xec, 0xd0, 0xe1, 0x8, 0xa9, 0x8e, 0x19, 0x2a, 0xf1, 0xd2, 0xc2, 0x50, 0x55, 0xf4, 0xe3, - 0xbe, 0xd7, 0x84, 0xb5, 0xc8, 0x77, 0x20, 0x4e, 0x73, 0x21, 0x9a, 0x52, 0x3, 0x25, 0x1f, - 0xea, 0xab, - ]; - assert_eq(keccak256(input, input.len()), result); - } - - #[test] - fn var_size_hash() { - let input = [ - 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, - 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, - 223, - ]; - let result = [ - 226, 37, 115, 94, 94, 196, 72, 116, 194, 105, 79, 233, 65, 12, 30, 94, 181, 131, 170, - 219, 171, 166, 236, 88, 143, 67, 255, 160, 248, 214, 39, 129, - ]; - assert_eq(keccak256(input, 13), result); - } - - #[test] - fn hash_longer_than_136_bytes() { - let input = "123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789" - .as_bytes(); - assert(input.len() > 136); - - let result = [ - 0x1d, 0xca, 0xeb, 0xdf, 0xd9, 0xd6, 0x24, 0x67, 0x1c, 0x18, 0x16, 0xda, 0xd, 0x8a, 0xeb, - 0xa8, 0x75, 0x71, 0x2c, 0xc, 0x89, 0xe0, 0x25, 0x2, 0xe8, 0xb6, 0x5e, 0x16, 0x5, 0x55, - 0xe4, 0x40, - ]; - assert_eq(keccak256(input, input.len()), result); - } -} diff --git a/noir/noir-repo/noir_stdlib/src/hash/mod.nr b/noir/noir-repo/noir_stdlib/src/hash/mod.nr index 1ded89ec80d5..7a492d373cc9 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/mod.nr @@ -1,18 +1,28 @@ pub mod poseidon; pub mod poseidon2; -pub mod keccak; -pub mod sha256; -pub mod sha512; use crate::default::Default; use crate::embedded_curve_ops::{ EmbeddedCurvePoint, EmbeddedCurveScalar, multi_scalar_mul, multi_scalar_mul_array_return, }; use crate::meta::derive_via; -use crate::uint128::U128; -// Kept for backwards compatibility -pub use sha256::{digest, sha256, sha256_compression, sha256_var}; +#[foreign(sha256_compression)] +// docs:start:sha256_compression +pub fn sha256_compression(input: [u32; 16], state: [u32; 8]) -> [u32; 8] {} +// docs:end:sha256_compression + +#[foreign(keccakf1600)] +// docs:start:keccakf1600 +pub fn keccakf1600(input: [u64; 25]) -> [u64; 25] {} +// docs:end:keccakf1600 + +pub mod keccak { + #[deprecated("This function has been moved to std::hash::keccakf1600")] + pub fn keccakf1600(input: [u64; 25]) -> [u64; 25] { + super::keccakf1600(input) + } +} #[foreign(blake2s)] // docs:start:blake2s @@ -114,13 +124,6 @@ pub fn hash_to_field(inputs: [Field]) -> Field { sum } -// docs:start:keccak256 -pub fn keccak256(input: [u8; N], message_size: u32) -> [u8; 32] -// docs:end:keccak256 -{ - crate::hash::keccak::keccak256(input, message_size) -} - #[foreign(poseidon2_permutation)] pub fn poseidon2_permutation(_input: [Field; N], _state_length: u32) -> [Field; N] {} @@ -302,16 +305,6 @@ impl Hash for () { {} } -impl Hash for U128 { - fn hash(self, state: &mut H) - where - H: Hasher, - { - H::write(state, self.lo as Field); - H::write(state, self.hi as Field); - } -} - impl Hash for [T; N] where T: Hash, diff --git a/noir/noir-repo/noir_stdlib/src/hash/sha256.nr b/noir/noir-repo/noir_stdlib/src/hash/sha256.nr deleted file mode 100644 index a8bd71a21111..000000000000 --- a/noir/noir-repo/noir_stdlib/src/hash/sha256.nr +++ /dev/null @@ -1,845 +0,0 @@ -use crate::runtime::is_unconstrained; - -// Implementation of SHA-256 mapping a byte array of variable length to -// 32 bytes. - -// A message block is up to 64 bytes taken from the input. -global BLOCK_SIZE: u32 = 64; - -// The first index in the block where the 8 byte message size will be written. -global MSG_SIZE_PTR: u32 = 56; - -// Size of the message block when packed as 4-byte integer array. -global INT_BLOCK_SIZE: u32 = 16; - -// A `u32` integer consists of 4 bytes. -global INT_SIZE: u32 = 4; - -// Index of the integer in the `INT_BLOCK` where the length is written. -global INT_SIZE_PTR: u32 = MSG_SIZE_PTR / INT_SIZE; - -// Magic numbers for bit shifting. -// Works with actual bit shifting as well as the compiler turns them into * and / -// but circuit execution appears to be 10% faster this way. -global TWO_POW_8: u32 = 256; -global TWO_POW_16: u32 = TWO_POW_8 * 256; -global TWO_POW_24: u32 = TWO_POW_16 * 256; -global TWO_POW_32: u64 = TWO_POW_24 as u64 * 256; - -// Index of a byte in a 64 byte block; ie. 0..=63 -type BLOCK_BYTE_PTR = u32; - -// The foreign function to compress blocks works on 16 pieces of 4-byte integers, instead of 64 bytes. -type INT_BLOCK = [u32; INT_BLOCK_SIZE]; - -// A message block is a slice of the original message of a fixed size, -// potentially padded with zeros, with neighbouring 4 bytes packed into integers. -type MSG_BLOCK = INT_BLOCK; - -// The hash is 32 bytes. -type HASH = [u8; 32]; - -// The state accumulates the blocks. -// Its overall size is the same as the `HASH`. -type STATE = [u32; 8]; - -// docs:start:sha256 -#[deprecated("sha256 is being deprecated from the stdlib, use https://github.com/noir-lang/sha256 instead")] -pub fn sha256(input: [u8; N]) -> HASH -// docs:end:sha256 -{ - digest(input) -} - -#[foreign(sha256_compression)] -pub fn sha256_compression(_input: INT_BLOCK, _state: STATE) -> STATE {} - -// SHA-256 hash function -#[no_predicates] -#[deprecated("sha256 is being deprecated from the stdlib, use https://github.com/noir-lang/sha256 instead")] -pub fn digest(msg: [u8; N]) -> HASH { - sha256_var(msg, N as u64) -} - -// Variable size SHA-256 hash -#[deprecated("sha256 is being deprecated from the stdlib, use https://github.com/noir-lang/sha256 instead")] -pub fn sha256_var(msg: [u8; N], message_size: u64) -> HASH { - let message_size = message_size as u32; - let num_blocks = N / BLOCK_SIZE; - let mut msg_block: MSG_BLOCK = [0; INT_BLOCK_SIZE]; - // Intermediate hash, starting with the canonical initial value - let mut h: STATE = [ - 1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, - 1541459225, - ]; - // Pointer into msg_block on a 64 byte scale - let mut msg_byte_ptr = 0; - for i in 0..num_blocks { - let msg_start = BLOCK_SIZE * i; - // Safety: the msg_block is checked below in verify_msg_block - let (new_msg_block, new_msg_byte_ptr) = - unsafe { build_msg_block(msg, message_size, msg_start) }; - - if msg_start < message_size { - msg_block = new_msg_block; - } - - if !is_unconstrained() { - // Verify the block we are compressing was appropriately constructed - let new_msg_byte_ptr = verify_msg_block(msg, message_size, msg_block, msg_start); - if msg_start < message_size { - msg_byte_ptr = new_msg_byte_ptr; - } - } else if msg_start < message_size { - msg_byte_ptr = new_msg_byte_ptr; - } - - // If the block is filled, compress it. - // An un-filled block is handled after this loop. - if (msg_start < message_size) & (msg_byte_ptr == BLOCK_SIZE) { - h = sha256_compression(msg_block, h); - } - } - - let modulo = N % BLOCK_SIZE; - // Handle setup of the final msg block. - // This case is only hit if the msg is less than the block size, - // or our message cannot be evenly split into blocks. - if modulo != 0 { - let msg_start = BLOCK_SIZE * num_blocks; - // Safety: the msg_block is checked below in verify_msg_block - let (new_msg_block, new_msg_byte_ptr) = - unsafe { build_msg_block(msg, message_size, msg_start) }; - - if msg_start < message_size { - msg_block = new_msg_block; - } - - if !is_unconstrained() { - let new_msg_byte_ptr = verify_msg_block(msg, message_size, msg_block, msg_start); - if msg_start < message_size { - msg_byte_ptr = new_msg_byte_ptr; - verify_msg_block_padding(msg_block, msg_byte_ptr); - } - } else if msg_start < message_size { - msg_byte_ptr = new_msg_byte_ptr; - } - } - - // If we had modulo == 0 then it means the last block was full, - // and we can reset the pointer to zero to overwrite it. - if msg_byte_ptr == BLOCK_SIZE { - msg_byte_ptr = 0; - } - - // Pad the rest such that we have a [u32; 2] block at the end representing the length - // of the message, and a block of 1 0 ... 0 following the message (i.e. [1 << 7, 0, ..., 0]). - // Here we rely on the fact that everything beyond the available input is set to 0. - msg_block = update_block_item( - msg_block, - msg_byte_ptr, - |msg_item| set_item_byte_then_zeros(msg_item, msg_byte_ptr, 1 << 7), - ); - msg_byte_ptr = msg_byte_ptr + 1; - let last_block = msg_block; - - // If we don't have room to write the size, compress the block and reset it. - if msg_byte_ptr > MSG_SIZE_PTR { - h = sha256_compression(msg_block, h); - // `attach_len_to_msg_block` will zero out everything after the `msg_byte_ptr`. - msg_byte_ptr = 0; - } - - // Safety: the msg_len is checked below in verify_msg_len - msg_block = unsafe { attach_len_to_msg_block(msg_block, msg_byte_ptr, message_size) }; - - if !is_unconstrained() { - verify_msg_len(msg_block, last_block, msg_byte_ptr, message_size); - } - - hash_final_block(msg_block, h) -} - -// Take `BLOCK_SIZE` number of bytes from `msg` starting at `msg_start`. -// Returns the block and the length that has been copied rather than padded with zeros. -unconstrained fn build_msg_block( - msg: [u8; N], - message_size: u32, - msg_start: u32, -) -> (MSG_BLOCK, BLOCK_BYTE_PTR) { - let mut msg_block: MSG_BLOCK = [0; INT_BLOCK_SIZE]; - - // We insert `BLOCK_SIZE` bytes (or up to the end of the message) - let block_input = if msg_start + BLOCK_SIZE > message_size { - if message_size < msg_start { - // This function is sometimes called with `msg_start` past the end of the message. - // In this case we return an empty block and zero pointer to signal that the result should be ignored. - 0 - } else { - message_size - msg_start - } - } else { - BLOCK_SIZE - }; - - // Figure out the number of items in the int array that we have to pack. - // e.g. if the input is [0,1,2,3,4,5] then we need to pack it as 2 items: [0123, 4500] - let mut int_input = block_input / INT_SIZE; - if block_input % INT_SIZE != 0 { - int_input = int_input + 1; - }; - - for i in 0..int_input { - let mut msg_item: u32 = 0; - // Always construct the integer as 4 bytes, even if it means going beyond the input. - for j in 0..INT_SIZE { - let k = i * INT_SIZE + j; - let msg_byte = if k < block_input { - msg[msg_start + k] - } else { - 0 - }; - msg_item = lshift8(msg_item, 1) + msg_byte as u32; - } - msg_block[i] = msg_item; - } - - // Returning the index as if it was a 64 byte array. - // We have to project it down to 16 items and bit shifting to get a byte back if we need it. - (msg_block, block_input) -} - -// Verify the block we are compressing was appropriately constructed by `build_msg_block` -// and matches the input data. Returns the index of the first unset item. -// If `message_size` is less than `msg_start` then this is called with the old non-empty block; -// in that case we can skip verification, ie. no need to check that everything is zero. -fn verify_msg_block( - msg: [u8; N], - message_size: u32, - msg_block: MSG_BLOCK, - msg_start: u32, -) -> BLOCK_BYTE_PTR { - let mut msg_byte_ptr = 0; - let mut msg_end = msg_start + BLOCK_SIZE; - if msg_end > N { - msg_end = N; - } - // We might have to go beyond the input to pad the fields. - if msg_end % INT_SIZE != 0 { - msg_end = msg_end + INT_SIZE - msg_end % INT_SIZE; - } - - // Reconstructed packed item. - let mut msg_item: u32 = 0; - - // Inclusive at the end so that we can compare the last item. - let mut i: u32 = 0; - for k in msg_start..=msg_end { - if k % INT_SIZE == 0 { - // If we consumed some input we can compare against the block. - if (msg_start < message_size) & (k > msg_start) { - assert_eq(msg_block[i], msg_item as u32); - i = i + 1; - msg_item = 0; - } - } - // Shift the accumulator - msg_item = lshift8(msg_item, 1); - // If we have input to consume, add it at the rightmost position. - if k < message_size & k < msg_end { - msg_item = msg_item + msg[k] as u32; - msg_byte_ptr = msg_byte_ptr + 1; - } - } - - msg_byte_ptr -} - -// Verify the block we are compressing was appropriately padded with zeros by `build_msg_block`. -// This is only relevant for the last, potentially partially filled block. -fn verify_msg_block_padding(msg_block: MSG_BLOCK, msg_byte_ptr: BLOCK_BYTE_PTR) { - // Check all the way to the end of the block. - verify_msg_block_zeros(msg_block, msg_byte_ptr, INT_BLOCK_SIZE); -} - -// Verify that a region of ints in the message block are (partially) zeroed, -// up to an (exclusive) maximum which can either be the end of the block -// or just where the size is to be written. -fn verify_msg_block_zeros( - msg_block: MSG_BLOCK, - mut msg_byte_ptr: BLOCK_BYTE_PTR, - max_int_byte_ptr: u32, -) { - // This variable is used to get around the compiler under-constrained check giving a warning. - // We want to check against a constant zero, but if it does not come from the circuit inputs - // or return values the compiler check will issue a warning. - let zero = msg_block[0] - msg_block[0]; - - // First integer which is supposed to be (partially) zero. - let mut int_byte_ptr = msg_byte_ptr / INT_SIZE; - - // Check partial zeros. - let modulo = msg_byte_ptr % INT_SIZE; - if modulo != 0 { - let zeros = INT_SIZE - modulo; - let mask = if zeros == 3 { - TWO_POW_24 - } else if zeros == 2 { - TWO_POW_16 - } else { - TWO_POW_8 - }; - assert_eq(msg_block[int_byte_ptr] % mask, zero); - int_byte_ptr = int_byte_ptr + 1; - } - - // Check the rest of the items. - for i in 0..max_int_byte_ptr { - if i >= int_byte_ptr { - assert_eq(msg_block[i], zero); - } - } -} - -// Verify that up to the byte pointer the two blocks are equal. -// At the byte pointer the new block can be partially zeroed. -fn verify_msg_block_equals_last( - msg_block: MSG_BLOCK, - last_block: MSG_BLOCK, - mut msg_byte_ptr: BLOCK_BYTE_PTR, -) { - // msg_byte_ptr is the position at which they are no longer have to be the same. - // First integer which is supposed to be (partially) zero contains that pointer. - let mut int_byte_ptr = msg_byte_ptr / INT_SIZE; - - // Check partial zeros. - let modulo = msg_byte_ptr % INT_SIZE; - if modulo != 0 { - // Reconstruct the partially zero item from the last block. - let last_field = last_block[int_byte_ptr]; - let mut msg_item: u32 = 0; - // Reset to where they are still equal. - msg_byte_ptr = msg_byte_ptr - modulo; - for i in 0..INT_SIZE { - msg_item = lshift8(msg_item, 1); - if i < modulo { - msg_item = msg_item + get_item_byte(last_field, msg_byte_ptr) as u32; - msg_byte_ptr = msg_byte_ptr + 1; - } - } - assert_eq(msg_block[int_byte_ptr], msg_item); - } - - for i in 0..INT_SIZE_PTR { - if i < int_byte_ptr { - assert_eq(msg_block[i], last_block[i]); - } - } -} - -// Apply a function on the block item which the pointer indicates. -fn update_block_item( - mut msg_block: MSG_BLOCK, - msg_byte_ptr: BLOCK_BYTE_PTR, - f: fn[Env](u32) -> u32, -) -> MSG_BLOCK { - let i = msg_byte_ptr / INT_SIZE; - msg_block[i] = f(msg_block[i]); - msg_block -} - -// Set the rightmost `zeros` number of bytes to 0. -fn set_item_zeros(item: u32, zeros: u8) -> u32 { - lshift8(rshift8(item, zeros), zeros) -} - -// Replace one byte in the item with a value, and set everything after it to zero. -fn set_item_byte_then_zeros(msg_item: u32, msg_byte_ptr: BLOCK_BYTE_PTR, msg_byte: u8) -> u32 { - let zeros = INT_SIZE - msg_byte_ptr % INT_SIZE; - let zeroed_item = set_item_zeros(msg_item, zeros as u8); - let new_item = byte_into_item(msg_byte, msg_byte_ptr); - zeroed_item + new_item -} - -// Get a byte of a message item according to its overall position in the `BLOCK_SIZE` space. -fn get_item_byte(mut msg_item: u32, msg_byte_ptr: BLOCK_BYTE_PTR) -> u8 { - // How many times do we have to shift to the right to get to the position we want? - let max_shifts = INT_SIZE - 1; - let shifts = max_shifts - msg_byte_ptr % INT_SIZE; - msg_item = rshift8(msg_item, shifts as u8); - // At this point the byte we want is in the rightmost position. - msg_item as u8 -} - -// Project a byte into a position in a field based on the overall block pointer. -// For example putting 1 into pointer 5 would be 100, because overall we would -// have [____, 0100] with indexes [0123,4567]. -fn byte_into_item(msg_byte: u8, msg_byte_ptr: BLOCK_BYTE_PTR) -> u32 { - let mut msg_item = msg_byte as u32; - // How many times do we have to shift to the left to get to the position we want? - let max_shifts = INT_SIZE - 1; - let shifts = max_shifts - msg_byte_ptr % INT_SIZE; - lshift8(msg_item, shifts as u8) -} - -// Construct a field out of 4 bytes. -fn make_item(b0: u8, b1: u8, b2: u8, b3: u8) -> u32 { - let mut item = b0 as u32; - item = lshift8(item, 1) + b1 as u32; - item = lshift8(item, 1) + b2 as u32; - item = lshift8(item, 1) + b3 as u32; - item -} - -// Shift by 8 bits to the left between 0 and 4 times. -// Checks `is_unconstrained()` to just use a bitshift if we're running in an unconstrained context, -// otherwise multiplies by 256. -fn lshift8(item: u32, shifts: u8) -> u32 { - if is_unconstrained() { - if item == 0 { - 0 - } else { - // Brillig wouldn't shift 0<<4 without overflow. - item << (8 * shifts) - } - } else { - // We can do a for loop up to INT_SIZE or an if-else. - if shifts == 0 { - item - } else if shifts == 1 { - item * TWO_POW_8 - } else if shifts == 2 { - item * TWO_POW_16 - } else if shifts == 3 { - item * TWO_POW_24 - } else { - // Doesn't make sense, but it's most likely called on 0 anyway. - 0 - } - } -} - -// Shift by 8 bits to the right between 0 and 4 times. -// Checks `is_unconstrained()` to just use a bitshift if we're running in an unconstrained context, -// otherwise divides by 256. -fn rshift8(item: u32, shifts: u8) -> u32 { - if is_unconstrained() { - item >> (8 * shifts) - } else { - // Division wouldn't work on `Field`. - if shifts == 0 { - item - } else if shifts == 1 { - item / TWO_POW_8 - } else if shifts == 2 { - item / TWO_POW_16 - } else if shifts == 3 { - item / TWO_POW_24 - } else { - 0 - } - } -} - -// Zero out all bytes between the end of the message and where the length is appended, -// then write the length into the last 8 bytes of the block. -unconstrained fn attach_len_to_msg_block( - mut msg_block: MSG_BLOCK, - mut msg_byte_ptr: BLOCK_BYTE_PTR, - message_size: u32, -) -> MSG_BLOCK { - // We assume that `msg_byte_ptr` is less than 57 because if not then it is reset to zero before calling this function. - // In any case, fill blocks up with zeros until the last 64 bits (i.e. until msg_byte_ptr = 56). - // There can be one item which has to be partially zeroed. - let modulo = msg_byte_ptr % INT_SIZE; - if modulo != 0 { - // Index of the block in which we find the item we need to partially zero. - let i = msg_byte_ptr / INT_SIZE; - let zeros = INT_SIZE - modulo; - msg_block[i] = set_item_zeros(msg_block[i], zeros as u8); - msg_byte_ptr = msg_byte_ptr + zeros; - } - - // The rest can be zeroed without bit shifting anything. - for i in (msg_byte_ptr / INT_SIZE)..INT_SIZE_PTR { - msg_block[i] = 0; - } - - // Set the last two 4 byte ints as the first/second half of the 8 bytes of the length. - let len = 8 * message_size; - let len_bytes: [u8; 8] = (len as Field).to_be_bytes(); - for i in 0..=1 { - let shift = i * 4; - msg_block[INT_SIZE_PTR + i] = make_item( - len_bytes[shift], - len_bytes[shift + 1], - len_bytes[shift + 2], - len_bytes[shift + 3], - ); - } - msg_block -} - -// Verify that the message length was correctly written by `attach_len_to_msg_block`, -// and that everything between the byte pointer and the size pointer was zeroed, -// and that everything before the byte pointer was untouched. -fn verify_msg_len( - msg_block: MSG_BLOCK, - last_block: MSG_BLOCK, - msg_byte_ptr: BLOCK_BYTE_PTR, - message_size: u32, -) { - // Check zeros up to the size pointer. - verify_msg_block_zeros(msg_block, msg_byte_ptr, INT_SIZE_PTR); - - // Check that up to the pointer we match the last block. - verify_msg_block_equals_last(msg_block, last_block, msg_byte_ptr); - - // We verify the message length was inserted correctly by reversing the byte decomposition. - let mut reconstructed_len: u64 = 0; - for i in INT_SIZE_PTR..INT_BLOCK_SIZE { - reconstructed_len = reconstructed_len * TWO_POW_32; - reconstructed_len = reconstructed_len + msg_block[i] as u64; - } - let len = 8 * message_size as u64; - assert_eq(reconstructed_len, len); -} - -// Perform the final compression, then transform the `STATE` into `HASH`. -fn hash_final_block(msg_block: MSG_BLOCK, mut state: STATE) -> HASH { - let mut out_h: HASH = [0; 32]; // Digest as sequence of bytes - // Hash final padded block - state = sha256_compression(msg_block, state); - - // Return final hash as byte array - for j in 0..8 { - let h_bytes: [u8; 4] = (state[j] as Field).to_be_bytes(); - for k in 0..4 { - out_h[4 * j + k] = h_bytes[k]; - } - } - - out_h -} - -mod tests { - use super::{ - attach_len_to_msg_block, build_msg_block, byte_into_item, get_item_byte, make_item, - set_item_byte_then_zeros, set_item_zeros, - }; - use super::INT_BLOCK; - use super::sha256_var; - - #[test] - fn smoke_test() { - let input = [0xbd]; - let result = [ - 0x68, 0x32, 0x57, 0x20, 0xaa, 0xbd, 0x7c, 0x82, 0xf3, 0x0f, 0x55, 0x4b, 0x31, 0x3d, - 0x05, 0x70, 0xc9, 0x5a, 0xcc, 0xbb, 0x7d, 0xc4, 0xb5, 0xaa, 0xe1, 0x12, 0x04, 0xc0, - 0x8f, 0xfe, 0x73, 0x2b, - ]; - assert_eq(sha256_var(input, input.len() as u64), result); - } - - #[test] - fn msg_just_over_block() { - let input = [ - 102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, - 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, - 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, - 108, 97, 105, 110, 59, 32, 99, 104, 97, 114, 115, 101, 116, - ]; - let result = [ - 91, 122, 146, 93, 52, 109, 133, 148, 171, 61, 156, 70, 189, 238, 153, 7, 222, 184, 94, - 24, 65, 114, 192, 244, 207, 199, 87, 232, 192, 224, 171, 207, - ]; - assert_eq(sha256_var(input, input.len() as u64), result); - } - - #[test] - fn msg_multiple_over_block() { - let input = [ - 102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, - 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, - 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, - 108, 97, 105, 110, 59, 32, 99, 104, 97, 114, 115, 101, 116, 61, 117, 115, 45, 97, 115, - 99, 105, 105, 13, 10, 109, 105, 109, 101, 45, 118, 101, 114, 115, 105, 111, 110, 58, 49, - 46, 48, 32, 40, 77, 97, 99, 32, 79, 83, 32, 88, 32, 77, 97, 105, 108, 32, 49, 54, 46, - 48, 32, 92, 40, 51, 55, 51, 49, 46, 53, 48, 48, 46, 50, 51, 49, 92, 41, 41, 13, 10, 115, - 117, 98, 106, 101, 99, 116, 58, 72, 101, 108, 108, 111, 13, 10, 109, 101, 115, 115, 97, - 103, 101, 45, 105, 100, 58, 60, 56, 70, 56, 49, 57, 68, 51, 50, 45, 66, 54, 65, 67, 45, - 52, 56, 57, 68, 45, 57, 55, 55, 70, 45, 52, 51, 56, 66, 66, 67, 52, 67, 65, 66, 50, 55, - 64, 109, 101, 46, 99, 111, 109, 62, 13, 10, 100, 97, 116, 101, 58, 83, 97, 116, 44, 32, - 50, 54, 32, 65, 117, 103, 32, 50, 48, 50, 51, 32, 49, 50, 58, 50, 53, 58, 50, 50, 32, - 43, 48, 52, 48, 48, 13, 10, 116, 111, 58, 122, 107, 101, 119, 116, 101, 115, 116, 64, - 103, 109, 97, 105, 108, 46, 99, 111, 109, 13, 10, 100, 107, 105, 109, 45, 115, 105, 103, - 110, 97, 116, 117, 114, 101, 58, 118, 61, 49, 59, 32, 97, 61, 114, 115, 97, 45, 115, - 104, 97, 50, 53, 54, 59, 32, 99, 61, 114, 101, 108, 97, 120, 101, 100, 47, 114, 101, - 108, 97, 120, 101, 100, 59, 32, 100, 61, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, - 59, 32, 115, 61, 49, 97, 49, 104, 97, 105, 59, 32, 116, 61, 49, 54, 57, 51, 48, 51, 56, - 51, 51, 55, 59, 32, 98, 104, 61, 55, 120, 81, 77, 68, 117, 111, 86, 86, 85, 52, 109, 48, - 87, 48, 87, 82, 86, 83, 114, 86, 88, 77, 101, 71, 83, 73, 65, 83, 115, 110, 117, 99, 75, - 57, 100, 74, 115, 114, 99, 43, 118, 85, 61, 59, 32, 104, 61, 102, 114, 111, 109, 58, 67, - 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 77, 105, 109, 101, 45, 86, 101, - 114, 115, 105, 111, 110, 58, 83, 117, 98, 106, 101, 99, - ]; - let result = [ - 116, 90, 151, 31, 78, 22, 138, 180, 211, 189, 69, 76, 227, 200, 155, 29, 59, 123, 154, - 60, 47, 153, 203, 129, 157, 251, 48, 2, 79, 11, 65, 47, - ]; - assert_eq(sha256_var(input, input.len() as u64), result); - } - - #[test] - fn msg_just_under_block() { - let input = [ - 102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, - 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, - 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, - 108, 97, 105, 110, 59, - ]; - let result = [ - 143, 140, 76, 173, 222, 123, 102, 68, 70, 149, 207, 43, 39, 61, 34, 79, 216, 252, 213, - 165, 74, 16, 110, 74, 29, 64, 138, 167, 30, 1, 9, 119, - ]; - assert_eq(sha256_var(input, input.len() as u64), result); - } - - #[test] - fn msg_big_not_block_multiple() { - let input = [ - 102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, - 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, - 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, - 108, 97, 105, 110, 59, 32, 99, 104, 97, 114, 115, 101, 116, 61, 117, 115, 45, 97, 115, - 99, 105, 105, 13, 10, 109, 105, 109, 101, 45, 118, 101, 114, 115, 105, 111, 110, 58, 49, - 46, 48, 32, 40, 77, 97, 99, 32, 79, 83, 32, 88, 32, 77, 97, 105, 108, 32, 49, 54, 46, - 48, 32, 92, 40, 51, 55, 51, 49, 46, 53, 48, 48, 46, 50, 51, 49, 92, 41, 41, 13, 10, 115, - 117, 98, 106, 101, 99, 116, 58, 72, 101, 108, 108, 111, 13, 10, 109, 101, 115, 115, 97, - 103, 101, 45, 105, 100, 58, 60, 56, 70, 56, 49, 57, 68, 51, 50, 45, 66, 54, 65, 67, 45, - 52, 56, 57, 68, 45, 57, 55, 55, 70, 45, 52, 51, 56, 66, 66, 67, 52, 67, 65, 66, 50, 55, - 64, 109, 101, 46, 99, 111, 109, 62, 13, 10, 100, 97, 116, 101, 58, 83, 97, 116, 44, 32, - 50, 54, 32, 65, 117, 103, 32, 50, 48, 50, 51, 32, 49, 50, 58, 50, 53, 58, 50, 50, 32, - 43, 48, 52, 48, 48, 13, 10, 116, 111, 58, 122, 107, 101, 119, 116, 101, 115, 116, 64, - 103, 109, 97, 105, 108, 46, 99, 111, 109, 13, 10, 100, 107, 105, 109, 45, 115, 105, 103, - 110, 97, 116, 117, 114, 101, 58, 118, 61, 49, 59, 32, 97, 61, 114, 115, 97, 45, 115, - 104, 97, 50, 53, 54, 59, 32, 99, 61, 114, 101, 108, 97, 120, 101, 100, 47, 114, 101, - 108, 97, 120, 101, 100, 59, 32, 100, 61, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, - 59, 32, 115, 61, 49, 97, 49, 104, 97, 105, 59, 32, 116, 61, 49, 54, 57, 51, 48, 51, 56, - 51, 51, 55, 59, 32, 98, 104, 61, 55, 120, 81, 77, 68, 117, 111, 86, 86, 85, 52, 109, 48, - 87, 48, 87, 82, 86, 83, 114, 86, 88, 77, 101, 71, 83, 73, 65, 83, 115, 110, 117, 99, 75, - 57, 100, 74, 115, 114, 99, 43, 118, 85, 61, 59, 32, 104, 61, 102, 114, 111, 109, 58, 67, - 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 77, 105, 109, 101, 45, 86, 101, - 114, 115, 105, 111, 110, 58, 83, 117, 98, 106, 101, 99, 116, 58, 77, 101, 115, 115, 97, - 103, 101, 45, 73, 100, 58, 68, 97, 116, 101, 58, 116, 111, 59, 32, 98, 61, - ]; - let result = [ - 112, 144, 73, 182, 208, 98, 9, 238, 54, 229, 61, 145, 222, 17, 72, 62, 148, 222, 186, - 55, 192, 82, 220, 35, 66, 47, 193, 200, 22, 38, 26, 186, - ]; - assert_eq(sha256_var(input, input.len() as u64), result); - } - - #[test] - fn msg_big_with_padding() { - let input = [ - 48, 130, 1, 37, 2, 1, 0, 48, 11, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, 48, 130, 1, 17, - 48, 37, 2, 1, 1, 4, 32, 176, 223, 31, 133, 108, 84, 158, 102, 70, 11, 165, 175, 196, 12, - 201, 130, 25, 131, 46, 125, 156, 194, 28, 23, 55, 133, 157, 164, 135, 136, 220, 78, 48, - 37, 2, 1, 2, 4, 32, 190, 82, 180, 235, 222, 33, 79, 50, 152, 136, 142, 35, 116, 224, 6, - 242, 156, 141, 128, 248, 10, 61, 98, 86, 248, 45, 207, 210, 90, 232, 175, 38, 48, 37, 2, - 1, 3, 4, 32, 0, 194, 104, 108, 237, 246, 97, 230, 116, 198, 69, 110, 26, 87, 17, 89, - 110, 199, 108, 250, 36, 21, 39, 87, 110, 102, 250, 213, 174, 131, 171, 174, 48, 37, 2, - 1, 11, 4, 32, 136, 155, 87, 144, 111, 15, 152, 127, 85, 25, 154, 81, 20, 58, 51, 75, - 193, 116, 234, 0, 60, 30, 29, 30, 183, 141, 72, 247, 255, 203, 100, 124, 48, 37, 2, 1, - 12, 4, 32, 41, 234, 106, 78, 31, 11, 114, 137, 237, 17, 92, 71, 134, 47, 62, 78, 189, - 233, 201, 214, 53, 4, 47, 189, 201, 133, 6, 121, 34, 131, 64, 142, 48, 37, 2, 1, 13, 4, - 32, 91, 222, 210, 193, 62, 222, 104, 82, 36, 41, 138, 253, 70, 15, 148, 208, 156, 45, - 105, 171, 241, 195, 185, 43, 217, 162, 146, 201, 222, 89, 238, 38, 48, 37, 2, 1, 14, 4, - 32, 76, 123, 216, 13, 51, 227, 72, 245, 59, 193, 238, 166, 103, 49, 23, 164, 171, 188, - 194, 197, 156, 187, 249, 28, 198, 95, 69, 15, 182, 56, 54, 38, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]; - let result = [ - 32, 85, 108, 174, 127, 112, 178, 182, 8, 43, 134, 123, 192, 211, 131, 66, 184, 240, 212, - 181, 240, 180, 106, 195, 24, 117, 54, 129, 19, 10, 250, 53, - ]; - let message_size = 297; - assert_eq(sha256_var(input, message_size), result); - } - - #[test] - fn msg_big_no_padding() { - let input = [ - 48, 130, 1, 37, 2, 1, 0, 48, 11, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, 48, 130, 1, 17, - 48, 37, 2, 1, 1, 4, 32, 176, 223, 31, 133, 108, 84, 158, 102, 70, 11, 165, 175, 196, 12, - 201, 130, 25, 131, 46, 125, 156, 194, 28, 23, 55, 133, 157, 164, 135, 136, 220, 78, 48, - 37, 2, 1, 2, 4, 32, 190, 82, 180, 235, 222, 33, 79, 50, 152, 136, 142, 35, 116, 224, 6, - 242, 156, 141, 128, 248, 10, 61, 98, 86, 248, 45, 207, 210, 90, 232, 175, 38, 48, 37, 2, - 1, 3, 4, 32, 0, 194, 104, 108, 237, 246, 97, 230, 116, 198, 69, 110, 26, 87, 17, 89, - 110, 199, 108, 250, 36, 21, 39, 87, 110, 102, 250, 213, 174, 131, 171, 174, 48, 37, 2, - 1, 11, 4, 32, 136, 155, 87, 144, 111, 15, 152, 127, 85, 25, 154, 81, 20, 58, 51, 75, - 193, 116, 234, 0, 60, 30, 29, 30, 183, 141, 72, 247, 255, 203, 100, 124, 48, 37, 2, 1, - 12, 4, 32, 41, 234, 106, 78, 31, 11, 114, 137, 237, 17, 92, 71, 134, 47, 62, 78, 189, - 233, 201, 214, 53, 4, 47, 189, 201, 133, 6, 121, 34, 131, 64, 142, 48, 37, 2, 1, 13, 4, - 32, 91, 222, 210, 193, 62, 222, 104, 82, 36, 41, 138, 253, 70, 15, 148, 208, 156, 45, - 105, 171, 241, 195, 185, 43, 217, 162, 146, 201, 222, 89, 238, 38, 48, 37, 2, 1, 14, 4, - 32, 76, 123, 216, 13, 51, 227, 72, 245, 59, 193, 238, 166, 103, 49, 23, 164, 171, 188, - 194, 197, 156, 187, 249, 28, 198, 95, 69, 15, 182, 56, 54, 38, - ]; - let result = [ - 32, 85, 108, 174, 127, 112, 178, 182, 8, 43, 134, 123, 192, 211, 131, 66, 184, 240, 212, - 181, 240, 180, 106, 195, 24, 117, 54, 129, 19, 10, 250, 53, - ]; - assert_eq(sha256_var(input, input.len() as u64), result); - } - - #[test] - fn same_msg_len_variable_padding() { - let input = [ - 29, 81, 165, 84, 243, 114, 101, 37, 242, 146, 127, 99, 69, 145, 39, 72, 213, 39, 253, - 179, 218, 37, 217, 201, 172, 93, 198, 50, 249, 70, 15, 30, 162, 112, 187, 40, 140, 9, - 236, 53, 32, 44, 38, 163, 113, 254, 192, 197, 44, 89, 71, 130, 169, 242, 17, 211, 214, - 72, 19, 178, 186, 168, 147, 127, 99, 101, 252, 227, 8, 147, 150, 85, 97, 158, 17, 107, - 218, 244, 82, 113, 247, 91, 208, 214, 60, 244, 87, 137, 173, 201, 130, 18, 66, 56, 198, - 149, 207, 189, 175, 120, 123, 224, 177, 167, 251, 159, 143, 110, 68, 183, 189, 70, 126, - 32, 35, 164, 44, 30, 44, 12, 65, 18, 62, 239, 242, 2, 248, 104, 2, 178, 64, 28, 126, 36, - 137, 24, 14, 116, 91, 98, 90, 159, 218, 102, 45, 11, 110, 223, 245, 184, 52, 99, 59, - 245, 136, 175, 3, 72, 164, 146, 145, 116, 22, 66, 24, 49, 193, 121, 3, 60, 37, 41, 97, - 3, 190, 66, 195, 225, 63, 46, 3, 118, 4, 208, 15, 1, 40, 254, 235, 151, 123, 70, 180, - 170, 44, 172, 90, 4, 254, 53, 239, 116, 246, 67, 56, 129, 61, 22, 169, 213, 65, 27, 216, - 116, 162, 239, 214, 207, 126, 177, 20, 100, 25, 48, 143, 84, 215, 70, 197, 53, 65, 70, - 86, 172, 61, 62, 9, 212, 167, 169, 133, 41, 126, 213, 196, 33, 192, 238, 0, 63, 246, - 215, 58, 128, 110, 101, 92, 3, 170, 214, 130, 149, 52, 81, 125, 118, 233, 3, 118, 193, - 104, 207, 120, 115, 77, 253, 191, 122, 0, 107, 164, 207, 113, 81, 169, 36, 201, 228, 74, - 134, 131, 218, 178, 35, 30, 216, 101, 2, 103, 174, 87, 95, 50, 50, 215, 157, 5, 210, - 188, 54, 211, 78, 45, 199, 96, 121, 241, 241, 176, 226, 194, 134, 130, 89, 217, 210, - 186, 32, 140, 39, 91, 103, 212, 26, 87, 32, 72, 144, 228, 230, 117, 99, 188, 50, 15, 69, - 79, 179, 50, 12, 106, 86, 218, 101, 73, 142, 243, 29, 250, 122, 228, 233, 29, 255, 22, - 121, 114, 125, 103, 41, 250, 241, 179, 126, 158, 198, 116, 209, 65, 94, 98, 228, 175, - 169, 96, 3, 9, 233, 133, 214, 55, 161, 164, 103, 80, 85, 24, 186, 64, 167, 92, 131, 53, - 101, 202, 47, 25, 104, 118, 155, 14, 12, 12, 25, 116, 45, 221, 249, 28, 246, 212, 200, - 157, 167, 169, 56, 197, 181, 4, 245, 146, 1, 140, 234, 191, 212, 228, 125, 87, 81, 86, - 119, 30, 63, 129, 143, 32, 96, - ]; - - // Prepare inputs of different lengths - let mut input_511 = [0; 511]; - let mut input_512 = [0; 512]; // Next block - let mut input_575 = [0; 575]; - let mut input_576 = [0; 576]; // Next block - for i in 0..input.len() { - input_511[i] = input[i]; - input_512[i] = input[i]; - input_575[i] = input[i]; - input_576[i] = input[i]; - } - - // Compute hashes of all inputs (with same message length) - let fixed_length_hash = super::sha256(input); - let var_full_length_hash = sha256_var(input, input.len() as u64); - let var_length_hash_511 = sha256_var(input_511, input.len() as u64); - let var_length_hash_512 = sha256_var(input_512, input.len() as u64); - let var_length_hash_575 = sha256_var(input_575, input.len() as u64); - let var_length_hash_576 = sha256_var(input_576, input.len() as u64); - - // All of the above should have produced the same hash - assert_eq(var_full_length_hash, fixed_length_hash); - assert_eq(var_length_hash_511, fixed_length_hash); - assert_eq(var_length_hash_512, fixed_length_hash); - assert_eq(var_length_hash_575, fixed_length_hash); - assert_eq(var_length_hash_576, fixed_length_hash); - } - - #[test] - fn test_get_item_byte() { - let fld = make_item(10, 20, 30, 40); - assert_eq(fld, 0x0a141e28); - assert_eq(get_item_byte(fld, 0), 10); - assert_eq(get_item_byte(fld, 4), 10); - assert_eq(get_item_byte(fld, 6), 30); - } - - #[test] - fn test_byte_into_item() { - let fld = make_item(0, 20, 0, 0); - assert_eq(byte_into_item(20, 1), fld); - assert_eq(byte_into_item(20, 5), fld); - } - - #[test] - fn test_set_item_zeros() { - let fld0 = make_item(10, 20, 30, 40); - let fld1 = make_item(10, 0, 0, 0); - assert_eq(set_item_zeros(fld0, 3), fld1); - assert_eq(set_item_zeros(fld0, 4), 0); - assert_eq(set_item_zeros(0, 4), 0); - } - - #[test] - fn test_set_item_byte_then_zeros() { - let fld0 = make_item(10, 20, 30, 40); - let fld1 = make_item(10, 50, 0, 0); - assert_eq(set_item_byte_then_zeros(fld0, 1, 50), fld1); - } - - #[test] - fn test_build_msg_block_start_0() { - let input = [ - 102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, - 101, 115, 46, 48, - ]; - assert_eq(input.len(), 22); - - // Safety: testing context - let (msg_block, msg_byte_ptr) = unsafe { build_msg_block(input, input.len(), 0) }; - assert_eq(msg_byte_ptr, input.len()); - assert_eq(msg_block[0], make_item(input[0], input[1], input[2], input[3])); - assert_eq(msg_block[1], make_item(input[4], input[5], input[6], input[7])); - assert_eq(msg_block[5], make_item(input[20], input[21], 0, 0)); - assert_eq(msg_block[6], 0); - } - - #[test] - fn test_build_msg_block_start_1() { - let input = [ - 102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, - 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, - 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, - 108, 97, 105, 110, 59, 32, 99, 104, 97, 114, 115, 101, 116, - ]; - assert_eq(input.len(), 68); - // Safety: test context - let (msg_block, msg_byte_ptr) = unsafe { build_msg_block(input, input.len(), 64) }; - assert_eq(msg_byte_ptr, 4); - assert_eq(msg_block[0], make_item(input[64], input[65], input[66], input[67])); - assert_eq(msg_block[1], 0); - } - - #[test] - fn test_attach_len_to_msg_block() { - let input: INT_BLOCK = [ - 2152555847, 1397309779, 1936618851, 1262052426, 1936876331, 1985297723, 543702374, - 1919905082, 1131376244, 1701737517, 1417244773, 978151789, 1697470053, 1920166255, - 1849316213, 1651139939, - ]; - // Safety: testing context - let msg_block = unsafe { attach_len_to_msg_block(input, 1, 448) }; - assert_eq(msg_block[0], ((1 << 7) as u32) * 256 * 256 * 256); - assert_eq(msg_block[1], 0); - assert_eq(msg_block[15], 3584); - } -} diff --git a/noir/noir-repo/noir_stdlib/src/hash/sha512.nr b/noir/noir-repo/noir_stdlib/src/hash/sha512.nr deleted file mode 100644 index 5630139c1f12..000000000000 --- a/noir/noir-repo/noir_stdlib/src/hash/sha512.nr +++ /dev/null @@ -1,165 +0,0 @@ -// Implementation of SHA-512 mapping a byte array of variable length to -// 64 bytes. -// Internal functions act on 64-bit unsigned integers for simplicity. -// Auxiliary mappings; names as in FIPS PUB 180-4 -fn rotr64(a: u64, b: u8) -> u64 // 64-bit right rotation -{ - // None of the bits overlap between `(a >> b)` and `(a << (64 - b))` - // Addition is then equivalent to OR, with fewer constraints. - (a >> b) + (a << (64 - b)) -} - -fn sha_ch(x: u64, y: u64, z: u64) -> u64 { - (x & y) ^ (!x & z) -} - -fn sha_maj(x: u64, y: u64, z: u64) -> u64 { - (x & y) ^ (x & z) ^ (y & z) -} - -fn sha_bigma0(x: u64) -> u64 { - rotr64(x, 28) ^ rotr64(x, 34) ^ rotr64(x, 39) -} - -fn sha_bigma1(x: u64) -> u64 { - rotr64(x, 14) ^ rotr64(x, 18) ^ rotr64(x, 41) -} - -fn sha_sigma0(x: u64) -> u64 { - rotr64(x, 1) ^ rotr64(x, 8) ^ (x >> 7) -} - -fn sha_sigma1(x: u64) -> u64 { - rotr64(x, 19) ^ rotr64(x, 61) ^ (x >> 6) -} - -fn sha_w(msg: [u64; 16]) -> [u64; 80] // Expanded message blocks -{ - let mut w: [u64; 80] = [0; 80]; - - for j in 0..16 { - w[j] = msg[j]; - } - - for j in 16..80 { - w[j] = crate::wrapping_add( - crate::wrapping_add(sha_sigma1(w[j - 2]), w[j - 7]), - crate::wrapping_add(sha_sigma0(w[j - 15]), w[j - 16]), - ); - } - w -} - -// SHA-512 compression function -#[no_predicates] -fn sha_c(msg: [u64; 16], hash: [u64; 8]) -> [u64; 8] { - // noir-fmt:ignore - let K: [u64; 80] = [4794697086780616226, 8158064640168781261, 13096744586834688815, 16840607885511220156, 4131703408338449720, 6480981068601479193, 10538285296894168987, 12329834152419229976, 15566598209576043074, 1334009975649890238, 2608012711638119052, 6128411473006802146, 8268148722764581231, 9286055187155687089, 11230858885718282805, 13951009754708518548, 16472876342353939154, 17275323862435702243, 1135362057144423861, 2597628984639134821, 3308224258029322869, 5365058923640841347, 6679025012923562964, 8573033837759648693, 10970295158949994411, 12119686244451234320, 12683024718118986047, 13788192230050041572, 14330467153632333762, 15395433587784984357, 489312712824947311, 1452737877330783856, 2861767655752347644, 3322285676063803686, 5560940570517711597, 5996557281743188959, 7280758554555802590, 8532644243296465576, 9350256976987008742, 10552545826968843579, 11727347734174303076, 12113106623233404929, 14000437183269869457, 14369950271660146224, 15101387698204529176, 15463397548674623760, 17586052441742319658, 1182934255886127544, 1847814050463011016, 2177327727835720531, 2830643537854262169, 3796741975233480872, 4115178125766777443, 5681478168544905931, 6601373596472566643, 7507060721942968483, 8399075790359081724, 8693463985226723168, 9568029438360202098, 10144078919501101548, 10430055236837252648, 11840083180663258601, 13761210420658862357, 14299343276471374635, 14566680578165727644, 15097957966210449927, 16922976911328602910, 17689382322260857208, 500013540394364858, 748580250866718886, 1242879168328830382, 1977374033974150939, 2944078676154940804, 3659926193048069267, 4368137639120453308, 4836135668995329356, 5532061633213252278, 6448918945643986474, 6902733635092675308, 7801388544844847127]; // first 64 bits of fractional parts of cube roots of first 80 primes - let mut out_h: [u64; 8] = hash; - let w = sha_w(msg); - for j in 0..80 { - let out1 = crate::wrapping_add(out_h[7], sha_bigma1(out_h[4])); - let out2 = crate::wrapping_add(out1, sha_ch(out_h[4], out_h[5], out_h[6])); - let t1 = crate::wrapping_add(crate::wrapping_add(out2, K[j]), w[j]); - let t2 = crate::wrapping_add(sha_bigma0(out_h[0]), sha_maj(out_h[0], out_h[1], out_h[2])); - out_h[7] = out_h[6]; - out_h[6] = out_h[5]; - out_h[5] = out_h[4]; - out_h[4] = crate::wrapping_add(out_h[3], t1); - out_h[3] = out_h[2]; - out_h[2] = out_h[1]; - out_h[1] = out_h[0]; - out_h[0] = crate::wrapping_add(t1, t2); - } - - out_h -} -// Convert 128-byte array to array of 16 u64s -fn msg_u8_to_u64(msg: [u8; 128]) -> [u64; 16] { - let mut msg64: [u64; 16] = [0; 16]; - - for i in 0..16 { - let mut msg_field: Field = 0; - for j in 0..8 { - msg_field = msg_field * 256 + msg[128 - 8 * (i + 1) + j] as Field; - } - msg64[15 - i] = msg_field as u64; - } - - msg64 -} -// SHA-512 hash function -pub fn digest(msg: [u8; N]) -> [u8; 64] { - let mut msg_block: [u8; 128] = [0; 128]; - // noir-fmt:ignore - let mut h: [u64; 8] = [7640891576956012808, 13503953896175478587, 4354685564936845355, 11912009170470909681, 5840696475078001361, 11170449401992604703, 2270897969802886507, 6620516959819538809]; // Intermediate hash, starting with the canonical initial value - let mut c: [u64; 8] = [0; 8]; // Compression of current message block as sequence of u64 - let mut out_h: [u8; 64] = [0; 64]; // Digest as sequence of bytes - let mut i: u64 = 0; // Message byte pointer - for k in 0..msg.len() { - // Populate msg_block - msg_block[i] = msg[k]; - i = i + 1; - if i == 128 { - // Enough to hash block - c = sha_c(msg_u8_to_u64(msg_block), h); - for j in 0..8 { - h[j] = crate::wrapping_add(h[j], c[j]); - } - - i = 0; - } - } - // Pad the rest such that we have a [u64; 2] block at the end representing the length - // of the message, and a block of 1 0 ... 0 following the message (i.e. [1 << 7, 0, ..., 0]). - msg_block[i] = 1 << 7; - i += 1; - // If i >= 113, there aren't enough bits in the current message block to accomplish this, so - // the 1 and 0s fill up the current block, which we then compress accordingly. - if i >= 113 { - // Not enough bits (128) to store length. Fill up with zeros. - if i < 128 { - for _i in 113..128 { - if i <= 127 { - msg_block[i] = 0; - i += 1; - } - } - } - c = sha_c(msg_u8_to_u64(msg_block), h); - for j in 0..8 { - h[j] = crate::wrapping_add(h[j], c[j]); - } - - i = 0; - } - - let len = 8 * msg.len(); - let len_bytes: [u8; 16] = (len as Field).to_le_bytes(); - for _i in 0..128 { - // In any case, fill blocks up with zeros until the last 128 (i.e. until i = 112). - if i < 112 { - msg_block[i] = 0; - i += 1; - } else if i < 128 { - for j in 0..16 { - msg_block[127 - j] = len_bytes[j]; - } - i += 16; // Done. - } - } - // Hash final padded block - c = sha_c(msg_u8_to_u64(msg_block), h); - for j in 0..8 { - h[j] = crate::wrapping_add(h[j], c[j]); - } - // Return final hash as byte array - for j in 0..8 { - let h_bytes: [u8; 8] = (h[7 - j] as Field).to_le_bytes(); - for k in 0..8 { - out_h[63 - 8 * j - k] = h_bytes[k]; - } - } - - out_h -} diff --git a/noir/noir-repo/noir_stdlib/src/lib.nr b/noir/noir-repo/noir_stdlib/src/lib.nr index d5c360792d91..cd54162a5045 100644 --- a/noir/noir-repo/noir_stdlib/src/lib.nr +++ b/noir/noir-repo/noir_stdlib/src/lib.nr @@ -2,12 +2,9 @@ pub mod hash; pub mod aes128; pub mod array; pub mod slice; -pub mod merkle; pub mod ecdsa_secp256k1; pub mod ecdsa_secp256r1; pub mod embedded_curve_ops; -pub mod sha256; -pub mod sha512; pub mod field; pub mod collections; pub mod compat; @@ -19,7 +16,6 @@ pub mod cmp; pub mod ops; pub mod default; pub mod prelude; -pub mod uint128; pub mod runtime; pub mod meta; pub mod append; @@ -121,8 +117,46 @@ where pub fn as_witness(x: Field) {} mod tests { + use super::wrapping_mul; + #[test(should_fail_with = "custom message")] fn test_static_assert_custom_message() { super::static_assert(1 == 2, "custom message"); } + + #[test(should_fail)] + fn test_wrapping_mul() { + // This currently fails. + // See: https://github.com/noir-lang/noir/issues/7528 + let zero: u128 = 0; + let one: u128 = 1; + let two_pow_64: u128 = 0x10000000000000000; + let u128_max: u128 = 0xffffffffffffffffffffffffffffffff; + + // 1*0==0 + assert_eq(zero, wrapping_mul(zero, one)); + + // 0*1==0 + assert_eq(zero, wrapping_mul(one, zero)); + + // 1*1==1 + assert_eq(one, wrapping_mul(one, one)); + + // 0 * ( 1 << 64 ) == 0 + assert_eq(zero, wrapping_mul(zero, two_pow_64)); + + // ( 1 << 64 ) * 0 == 0 + assert_eq(zero, wrapping_mul(two_pow_64, zero)); + + // 1 * ( 1 << 64 ) == 1 << 64 + assert_eq(two_pow_64, wrapping_mul(two_pow_64, one)); + + // ( 1 << 64 ) * 1 == 1 << 64 + assert_eq(two_pow_64, wrapping_mul(one, two_pow_64)); + + // ( 1 << 64 ) * ( 1 << 64 ) == 1 << 64 + assert_eq(zero, wrapping_mul(two_pow_64, two_pow_64)); + // -1 * -1 == 1 + assert_eq(one, wrapping_mul(u128_max, u128_max)); + } } diff --git a/noir/noir-repo/noir_stdlib/src/merkle.nr b/noir/noir-repo/noir_stdlib/src/merkle.nr deleted file mode 100644 index 34cfcdb17877..000000000000 --- a/noir/noir-repo/noir_stdlib/src/merkle.nr +++ /dev/null @@ -1,19 +0,0 @@ -// Regular merkle tree means a append-only merkle tree (Explain why this is the only way to have privacy and alternatives if you don't want it) -// Currently we assume that it is a binary tree, so depth k implies a width of 2^k -// XXX: In the future we can add an arity parameter -// Returns the merkle root of the tree from the provided leaf, its hashpath, using a pedersen hash function. -#[deprecated("This function will be removed from the stdlib in version 1.0.0-beta.4")] -pub fn compute_merkle_root(leaf: Field, index: Field, hash_path: [Field; N]) -> Field { - let index_bits: [u1; N] = index.to_le_bits(); - let mut current = leaf; - for i in 0..N { - let path_bit = index_bits[i] as bool; - let (hash_left, hash_right) = if path_bit { - (hash_path[i], current) - } else { - (current, hash_path[i]) - }; - current = crate::hash::pedersen_hash([hash_left, hash_right]); - } - current -} diff --git a/noir/noir-repo/noir_stdlib/src/prelude.nr b/noir/noir-repo/noir_stdlib/src/prelude.nr index a4a6c35b615e..7aa60456b6dd 100644 --- a/noir/noir-repo/noir_stdlib/src/prelude.nr +++ b/noir/noir-repo/noir_stdlib/src/prelude.nr @@ -7,4 +7,3 @@ pub use crate::default::Default; pub use crate::meta::{derive, derive_via}; pub use crate::option::Option; pub use crate::panic::panic; -pub use crate::uint128::U128; diff --git a/noir/noir-repo/noir_stdlib/src/sha256.nr b/noir/noir-repo/noir_stdlib/src/sha256.nr deleted file mode 100644 index 534c954d3dc1..000000000000 --- a/noir/noir-repo/noir_stdlib/src/sha256.nr +++ /dev/null @@ -1,10 +0,0 @@ -// This file is kept for backwards compatibility. -#[deprecated("sha256 is being deprecated from the stdlib, use https://github.com/noir-lang/sha256 instead")] -pub fn digest(msg: [u8; N]) -> [u8; 32] { - crate::hash::sha256::digest(msg) -} - -#[deprecated("sha256 is being deprecated from the stdlib, use https://github.com/noir-lang/sha256 instead")] -pub fn sha256_var(msg: [u8; N], message_size: u64) -> [u8; 32] { - crate::hash::sha256::sha256_var(msg, message_size) -} diff --git a/noir/noir-repo/noir_stdlib/src/sha512.nr b/noir/noir-repo/noir_stdlib/src/sha512.nr deleted file mode 100644 index 27b53f4395f6..000000000000 --- a/noir/noir-repo/noir_stdlib/src/sha512.nr +++ /dev/null @@ -1,5 +0,0 @@ -// This file is kept for backwards compatibility. -#[deprecated] -pub fn digest(msg: [u8; N]) -> [u8; 64] { - crate::hash::sha512::digest(msg) -} diff --git a/noir/noir-repo/noir_stdlib/src/uint128.nr b/noir/noir-repo/noir_stdlib/src/uint128.nr deleted file mode 100644 index f41958e0e306..000000000000 --- a/noir/noir-repo/noir_stdlib/src/uint128.nr +++ /dev/null @@ -1,572 +0,0 @@ -use crate::cmp::{Eq, Ord, Ordering}; -use crate::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Not, Rem, Shl, Shr, Sub}; -use crate::static_assert; -use super::{convert::AsPrimitive, default::Default}; - -global pow64: Field = 18446744073709551616; //2^64; -global pow63: Field = 9223372036854775808; // 2^63; -pub struct U128 { - pub(crate) lo: Field, - pub(crate) hi: Field, -} - -impl U128 { - - pub fn from_u64s_le(lo: u64, hi: u64) -> U128 { - // in order to handle multiplication, we need to represent the product of two u64 without overflow - assert(crate::field::modulus_num_bits() as u32 > 128); - U128 { lo: lo as Field, hi: hi as Field } - } - - pub fn from_u64s_be(hi: u64, lo: u64) -> U128 { - U128::from_u64s_le(lo, hi) - } - - pub fn zero() -> U128 { - U128 { lo: 0, hi: 0 } - } - - pub fn one() -> U128 { - U128 { lo: 1, hi: 0 } - } - pub fn from_le_bytes(bytes: [u8; 16]) -> U128 { - let mut lo = 0; - let mut base = 1; - for i in 0..8 { - lo += (bytes[i] as Field) * base; - base *= 256; - } - let mut hi = 0; - base = 1; - for i in 8..16 { - hi += (bytes[i] as Field) * base; - base *= 256; - } - U128 { lo, hi } - } - - pub fn to_be_bytes(self: Self) -> [u8; 16] { - let lo: [u8; 8] = self.lo.to_be_bytes(); - let hi: [u8; 8] = self.hi.to_be_bytes(); - let mut bytes = [0; 16]; - for i in 0..8 { - bytes[i] = hi[i]; - bytes[i + 8] = lo[i]; - } - bytes - } - - pub fn to_le_bytes(self: Self) -> [u8; 16] { - let lo: [u8; 8] = self.lo.to_le_bytes(); - let hi: [u8; 8] = self.hi.to_le_bytes(); - let mut bytes = [0; 16]; - for i in 0..8 { - bytes[i] = lo[i]; - bytes[i + 8] = hi[i]; - } - bytes - } - - pub fn from_hex(hex: str) -> U128 { - let bytes = hex.as_bytes(); - // string must starts with "0x" - assert((bytes[0] == 48) & (bytes[1] == 120), "Invalid hexadecimal string"); - static_assert(N < 35, "Input does not fit into a U128"); - - let mut lo = 0; - let mut hi = 0; - let mut base = 1; - if N <= 18 { - for i in 0..N - 2 { - lo += U128::decode_ascii(bytes[N - i - 1]) * base; - base = base * 16; - } - } else { - for i in 0..16 { - lo += U128::decode_ascii(bytes[N - i - 1]) * base; - base = base * 16; - } - base = 1; - for i in 17..N - 1 { - hi += U128::decode_ascii(bytes[N - i]) * base; - base = base * 16; - } - } - U128 { lo: lo as Field, hi: hi as Field } - } - - unconstrained fn unconstrained_check_is_upper_ascii(ascii: u8) -> bool { - ((ascii >= 65) & (ascii <= 90)) // Between 'A' and 'Z' - } - - pub(crate) fn decode_ascii(ascii: u8) -> Field { - ( - if ascii < 58 { - ascii - 48 - } else { - // Safety: optionally adds 32 and then check (below) the result is in 'a..f' range - let ascii = - ascii + 32 * (unsafe { U128::unconstrained_check_is_upper_ascii(ascii) as u8 }); - assert(ascii >= 97); // enforce >= 'a' - assert(ascii <= 102); // enforce <= 'f' - ascii - 87 - } - ) as Field - } - - // TODO: Replace with a faster version. - // A circuit that uses this function can be slow to compute - // (we're doing up to 127 calls to compute the quotient) - unconstrained fn unconstrained_div(self: Self, b: U128) -> (U128, U128) { - if b == U128::zero() { - // Return 0,0 to avoid eternal loop - (U128::zero(), U128::zero()) - } else if self < b { - (U128::zero(), self) - } else if self == b { - (U128::one(), U128::zero()) - } else { - let (q, r) = if b.hi as u64 >= pow63 as u64 { - // The result of multiplication by 2 would overflow - (U128::zero(), self) - } else { - self.unconstrained_div(b * U128::from_u64s_le(2, 0)) - }; - let q_mul_2 = q * U128::from_u64s_le(2, 0); - if r < b { - (q_mul_2, r) - } else { - (q_mul_2 + U128::one(), r - b) - } - } - } - - pub fn from_integer(i: T) -> U128 - where - T: AsPrimitive, - { - let f = i.as_(); - // Reject values which would overflow a u128 - f.assert_max_bit_size::<128>(); - let lo = f as u64 as Field; - let hi = (f - lo) / pow64; - U128 { lo, hi } - } - - pub fn to_integer(self) -> T - where - Field: AsPrimitive, - { - AsPrimitive::as_(self.lo + self.hi * pow64) - } - - fn wrapping_mul(self: Self, b: U128) -> U128 { - let low = self.lo * b.lo; - let lo = low as u64 as Field; - let carry = (low - lo) / pow64; - let high = self.lo * b.hi + self.hi * b.lo + carry; - let hi = high as u64 as Field; - U128 { lo, hi } - } -} - -impl Add for U128 { - fn add(self: Self, b: U128) -> U128 { - let low = self.lo + b.lo; - let lo = low as u64 as Field; - let carry = (low - lo) / pow64; - let high = self.hi + b.hi + carry; - let hi = high as u64 as Field; - assert(hi == high, "attempt to add with overflow"); - U128 { lo, hi } - } -} - -impl Sub for U128 { - fn sub(self: Self, b: U128) -> U128 { - let low = pow64 + self.lo - b.lo; - let lo = low as u64 as Field; - let borrow = (low == lo) as Field; - let high = self.hi - b.hi - borrow; - let hi = high as u64 as Field; - assert(hi == high, "attempt to subtract with underflow"); - U128 { lo, hi } - } -} - -impl Mul for U128 { - fn mul(self: Self, b: U128) -> U128 { - assert(self.hi * b.hi == 0, "attempt to multiply with overflow"); - let low = self.lo * b.lo; - let lo = low as u64 as Field; - let carry = (low - lo) / pow64; - let high = if crate::field::modulus_num_bits() as u32 > 196 { - (self.lo + self.hi) * (b.lo + b.hi) - low + carry - } else { - self.lo * b.hi + self.hi * b.lo + carry - }; - let hi = high as u64 as Field; - assert(hi == high, "attempt to multiply with overflow"); - U128 { lo, hi } - } -} - -impl Div for U128 { - fn div(self: Self, b: U128) -> U128 { - // Safety: euclidian division is asserted to be correct: assert(a == b * q + r); and assert(r < b); - // Furthermore, U128 addition and multiplication ensures that b * q + r does not overflow - unsafe { - let (q, r) = self.unconstrained_div(b); - let a = b * q + r; - assert_eq(self, a); - assert(r < b); - q - } - } -} - -impl Rem for U128 { - fn rem(self: Self, b: U128) -> U128 { - // Safety: cf div() above - unsafe { - let (q, r) = self.unconstrained_div(b); - let a = b * q + r; - assert_eq(self, a); - assert(r < b); - - r - } - } -} - -impl Eq for U128 { - fn eq(self: Self, b: U128) -> bool { - (self.lo == b.lo) & (self.hi == b.hi) - } -} - -impl Ord for U128 { - fn cmp(self, other: Self) -> Ordering { - let hi_ordering = (self.hi as u64).cmp((other.hi as u64)); - let lo_ordering = (self.lo as u64).cmp((other.lo as u64)); - - if hi_ordering == Ordering::equal() { - lo_ordering - } else { - hi_ordering - } - } -} - -impl Not for U128 { - fn not(self) -> U128 { - U128 { lo: (!(self.lo as u64)) as Field, hi: (!(self.hi as u64)) as Field } - } -} - -impl BitOr for U128 { - fn bitor(self, other: U128) -> U128 { - U128 { - lo: ((self.lo as u64) | (other.lo as u64)) as Field, - hi: ((self.hi as u64) | (other.hi as u64)) as Field, - } - } -} - -impl BitAnd for U128 { - fn bitand(self, other: U128) -> U128 { - U128 { - lo: ((self.lo as u64) & (other.lo as u64)) as Field, - hi: ((self.hi as u64) & (other.hi as u64)) as Field, - } - } -} - -impl BitXor for U128 { - fn bitxor(self, other: U128) -> U128 { - U128 { - lo: ((self.lo as u64) ^ (other.lo as u64)) as Field, - hi: ((self.hi as u64) ^ (other.hi as u64)) as Field, - } - } -} - -impl Shl for U128 { - fn shl(self, other: u8) -> U128 { - assert(other < 128, "attempt to shift left with overflow"); - let exp_bits: [u1; 7] = (other as Field).to_be_bits(); - - let mut r: Field = 2; - let mut y: Field = 1; - for i in 1..8 { - let bit = exp_bits[7 - i] as Field; - y = bit * (r * y) + (1 - bit) * y; - r *= r; - } - self.wrapping_mul(U128::from_integer(y)) - } -} - -impl Shr for U128 { - fn shr(self, other: u8) -> U128 { - assert(other < 128, "attempt to shift right with overflow"); - let exp_bits: [u1; 7] = (other as Field).to_be_bits(); - - let mut r: Field = 2; - let mut y: Field = 1; - for i in 1..8 { - let bit = exp_bits[7 - i] as Field; - y = bit * (r * y) + (1 - bit) * y; - r *= r; - } - self / U128::from_integer(y) - } -} - -impl Default for U128 { - fn default() -> Self { - U128::zero() - } -} - -mod tests { - use crate::default::Default; - use crate::ops::Not; - use crate::uint128::{pow63, pow64, U128}; - - #[test] - fn test_not(lo: u64, hi: u64) { - let num = U128::from_u64s_le(lo, hi); - let not_num = num.not(); - - assert_eq(not_num.hi, (hi.not() as Field)); - assert_eq(not_num.lo, (lo.not() as Field)); - - let not_not_num = not_num.not(); - assert_eq(num, not_not_num); - } - #[test] - fn test_construction() { - // Check little-endian u64 is inversed with big-endian u64 construction - let a = U128::from_u64s_le(2, 1); - let b = U128::from_u64s_be(1, 2); - assert_eq(a, b); - // Check byte construction is equivalent - let c = U128::from_le_bytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); - let d = U128::from_u64s_le(0x0706050403020100, 0x0f0e0d0c0b0a0908); - assert_eq(c, d); - } - #[test] - fn test_byte_decomposition() { - let a = U128::from_u64s_le(0x0706050403020100, 0x0f0e0d0c0b0a0908); - // Get big-endian and little-endian byte decompostions - let le_bytes_a = a.to_le_bytes(); - let be_bytes_a = a.to_be_bytes(); - - // Check equivalence - for i in 0..16 { - assert_eq(le_bytes_a[i], be_bytes_a[15 - i]); - } - // Reconstruct U128 from byte decomposition - let b = U128::from_le_bytes(le_bytes_a); - // Check that it's the same element - assert_eq(a, b); - } - #[test] - fn test_hex_constuction() { - let a = U128::from_u64s_le(0x1, 0x2); - let b = U128::from_hex("0x20000000000000001"); - assert_eq(a, b); - - let c = U128::from_hex("0xffffffffffffffffffffffffffffffff"); - let d = U128::from_u64s_le(0xffffffffffffffff, 0xffffffffffffffff); - assert_eq(c, d); - - let e = U128::from_hex("0x00000000000000000000000000000000"); - let f = U128::from_u64s_le(0, 0); - assert_eq(e, f); - } - - // Ascii decode tests - - #[test] - fn test_ascii_decode_correct_range() { - // '0'..'9' range - for i in 0..10 { - let decoded = U128::decode_ascii(48 + i); - assert_eq(decoded, i as Field); - } - // 'A'..'F' range - for i in 0..6 { - let decoded = U128::decode_ascii(65 + i); - assert_eq(decoded, (i + 10) as Field); - } - // 'a'..'f' range - for i in 0..6 { - let decoded = U128::decode_ascii(97 + i); - assert_eq(decoded, (i + 10) as Field); - } - } - - #[test(should_fail)] - fn test_ascii_decode_range_less_than_48_fails_0() { - crate::println(U128::decode_ascii(0)); - } - #[test(should_fail)] - fn test_ascii_decode_range_less_than_48_fails_1() { - crate::println(U128::decode_ascii(47)); - } - - #[test(should_fail)] - fn test_ascii_decode_range_58_64_fails_0() { - let _ = U128::decode_ascii(58); - } - #[test(should_fail)] - fn test_ascii_decode_range_58_64_fails_1() { - let _ = U128::decode_ascii(64); - } - #[test(should_fail)] - fn test_ascii_decode_range_71_96_fails_0() { - let _ = U128::decode_ascii(71); - } - #[test(should_fail)] - fn test_ascii_decode_range_71_96_fails_1() { - let _ = U128::decode_ascii(96); - } - #[test(should_fail)] - fn test_ascii_decode_range_greater_than_102_fails() { - let _ = U128::decode_ascii(103); - } - - #[test(should_fail)] - fn test_ascii_decode_regression() { - // This code will actually fail because of ascii_decode, - // but in the past it was possible to create a value > (1<<128) - let a = U128::from_hex("0x~fffffffffffffffffffffffffffffff"); - let b: Field = a.to_integer(); - let c: [u8; 17] = b.to_le_bytes(); - assert(c[16] != 0); - } - - #[test] - fn test_unconstrained_div() { - // Test the potential overflow case - let a = U128::from_u64s_le(0x0, 0xffffffffffffffff); - let b = U128::from_u64s_le(0x0, 0xfffffffffffffffe); - let c = U128::one(); - let d = U128::from_u64s_le(0x0, 0x1); - // Safety: testing context - unsafe { - let (q, r) = a.unconstrained_div(b); - assert_eq(q, c); - assert_eq(r, d); - } - - let a = U128::from_u64s_le(2, 0); - let b = U128::one(); - // Check the case where a is a multiple of b - // Safety: testing context - unsafe { - let (c, d) = a.unconstrained_div(b); - assert_eq((c, d), (a, U128::zero())); - } - - // Check where b is a multiple of a - // Safety: testing context - unsafe { - let (c, d) = b.unconstrained_div(a); - assert_eq((c, d), (U128::zero(), b)); - } - - // Dividing by zero returns 0,0 - let a = U128::from_u64s_le(0x1, 0x0); - let b = U128::zero(); - // Safety: testing context - unsafe { - let (c, d) = a.unconstrained_div(b); - assert_eq((c, d), (U128::zero(), U128::zero())); - } - // Dividing 1<<127 by 1<<127 (special case) - let a = U128::from_u64s_le(0x0, pow63 as u64); - let b = U128::from_u64s_le(0x0, pow63 as u64); - // Safety: testing context - unsafe { - let (c, d) = a.unconstrained_div(b); - assert_eq((c, d), (U128::one(), U128::zero())); - } - } - - #[test] - fn integer_conversions() { - // Maximum - let start: Field = 0xffffffffffffffffffffffffffffffff; - let a = U128::from_integer(start); - let end = a.to_integer(); - assert_eq(start, end); - - // Minimum - let start: Field = 0x0; - let a = U128::from_integer(start); - let end = a.to_integer(); - assert_eq(start, end); - - // Low limb - let start: Field = 0xffffffffffffffff; - let a = U128::from_integer(start); - let end = a.to_integer(); - assert_eq(start, end); - - // High limb - let start: Field = 0xffffffffffffffff0000000000000000; - let a = U128::from_integer(start); - let end = a.to_integer(); - assert_eq(start, end); - } - - #[test] - fn integer_conversions_fuzz(lo: u64, hi: u64) { - let start: Field = (lo as Field) + pow64 * (hi as Field); - let a = U128::from_integer(start); - let end = a.to_integer(); - assert_eq(start, end); - } - - #[test] - fn test_wrapping_mul() { - // 1*0==0 - assert_eq(U128::zero(), U128::zero().wrapping_mul(U128::one())); - - // 0*1==0 - assert_eq(U128::zero(), U128::one().wrapping_mul(U128::zero())); - - // 1*1==1 - assert_eq(U128::one(), U128::one().wrapping_mul(U128::one())); - - // 0 * ( 1 << 64 ) == 0 - assert_eq(U128::zero(), U128::zero().wrapping_mul(U128::from_u64s_le(0, 1))); - - // ( 1 << 64 ) * 0 == 0 - assert_eq(U128::zero(), U128::from_u64s_le(0, 1).wrapping_mul(U128::zero())); - - // 1 * ( 1 << 64 ) == 1 << 64 - assert_eq(U128::from_u64s_le(0, 1), U128::from_u64s_le(0, 1).wrapping_mul(U128::one())); - - // ( 1 << 64 ) * 1 == 1 << 64 - assert_eq(U128::from_u64s_le(0, 1), U128::one().wrapping_mul(U128::from_u64s_le(0, 1))); - - // ( 1 << 64 ) * ( 1 << 64 ) == 1 << 64 - assert_eq(U128::zero(), U128::from_u64s_le(0, 1).wrapping_mul(U128::from_u64s_le(0, 1))); - // -1 * -1 == 1 - assert_eq( - U128::one(), - U128::from_u64s_le(0xffffffffffffffff, 0xffffffffffffffff).wrapping_mul( - U128::from_u64s_le(0xffffffffffffffff, 0xffffffffffffffff), - ), - ); - } - - #[test] - fn test_default() { - assert_eq(U128::default(), U128::zero()); - } -} diff --git a/noir/noir-repo/scripts/install_bb.sh b/noir/noir-repo/scripts/install_bb.sh index 72170af78d8d..e95bd50a0f08 100755 --- a/noir/noir-repo/scripts/install_bb.sh +++ b/noir/noir-repo/scripts/install_bb.sh @@ -1,11 +1,11 @@ #!/bin/bash -VERSION="0.72.1" +VERSION="0.77.1" BBUP_PATH=~/.bb/bbup if ! [ -f $BBUP_PATH ]; then - curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash + curl -L https://bbup.aztec.network | bash fi $BBUP_PATH -v $VERSION diff --git a/noir/noir-repo/test_programs/benchmarks/bench_sha256/Nargo.toml b/noir/noir-repo/test_programs/benchmarks/bench_sha256/Nargo.toml deleted file mode 100644 index 488b94ca8586..000000000000 --- a/noir/noir-repo/test_programs/benchmarks/bench_sha256/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "bench_sha256" -version = "0.1.0" -type = "bin" -authors = [""] - -[dependencies] diff --git a/noir/noir-repo/test_programs/benchmarks/bench_sha256/Prover.toml b/noir/noir-repo/test_programs/benchmarks/bench_sha256/Prover.toml deleted file mode 100644 index 66779dea9d7b..000000000000 --- a/noir/noir-repo/test_programs/benchmarks/bench_sha256/Prover.toml +++ /dev/null @@ -1 +0,0 @@ -input = [1,2] diff --git a/noir/noir-repo/test_programs/benchmarks/bench_sha256/src/main.nr b/noir/noir-repo/test_programs/benchmarks/bench_sha256/src/main.nr deleted file mode 100644 index c94d359239dd..000000000000 --- a/noir/noir-repo/test_programs/benchmarks/bench_sha256/src/main.nr +++ /dev/null @@ -1,4 +0,0 @@ - -fn main(input: [u8; 2]) -> pub [u8; 32] { - std::hash::sha256(input) -} \ No newline at end of file diff --git a/noir/noir-repo/test_programs/benchmarks/bench_sha256_100/Nargo.toml b/noir/noir-repo/test_programs/benchmarks/bench_sha256_100/Nargo.toml deleted file mode 100644 index d0c90d75088e..000000000000 --- a/noir/noir-repo/test_programs/benchmarks/bench_sha256_100/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "bench_sha256_100" -version = "0.1.0" -type = "bin" -authors = [""] - -[dependencies] diff --git a/noir/noir-repo/test_programs/benchmarks/bench_sha256_100/Prover.toml b/noir/noir-repo/test_programs/benchmarks/bench_sha256_100/Prover.toml deleted file mode 100644 index 542b4a08dd6f..000000000000 --- a/noir/noir-repo/test_programs/benchmarks/bench_sha256_100/Prover.toml +++ /dev/null @@ -1,102 +0,0 @@ -input = [ - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], -] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/benchmarks/bench_sha256_100/src/main.nr b/noir/noir-repo/test_programs/benchmarks/bench_sha256_100/src/main.nr deleted file mode 100644 index 6e4bfc27c8f2..000000000000 --- a/noir/noir-repo/test_programs/benchmarks/bench_sha256_100/src/main.nr +++ /dev/null @@ -1,10 +0,0 @@ -global SIZE: u32 = 100; - -fn main(input: [[u8; 2]; SIZE]) -> pub [[u8; 32]; SIZE] { - let mut results: [[u8; 32]; SIZE] = [[0; 32]; SIZE]; - for i in 0..SIZE { - results[i] = std::hash::sha256(input[i]); - } - - results -} diff --git a/noir/noir-repo/test_programs/benchmarks/bench_sha256_30/Nargo.toml b/noir/noir-repo/test_programs/benchmarks/bench_sha256_30/Nargo.toml deleted file mode 100644 index c1dc76df3942..000000000000 --- a/noir/noir-repo/test_programs/benchmarks/bench_sha256_30/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "bench_sha256_30" -version = "0.1.0" -type = "bin" -authors = [""] - -[dependencies] diff --git a/noir/noir-repo/test_programs/benchmarks/bench_sha256_30/Prover.toml b/noir/noir-repo/test_programs/benchmarks/bench_sha256_30/Prover.toml deleted file mode 100644 index 7792a9ab8e36..000000000000 --- a/noir/noir-repo/test_programs/benchmarks/bench_sha256_30/Prover.toml +++ /dev/null @@ -1,32 +0,0 @@ -input = [ - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], - [1,2], -] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/benchmarks/bench_sha256_30/src/main.nr b/noir/noir-repo/test_programs/benchmarks/bench_sha256_30/src/main.nr deleted file mode 100644 index 0a4288114e34..000000000000 --- a/noir/noir-repo/test_programs/benchmarks/bench_sha256_30/src/main.nr +++ /dev/null @@ -1,10 +0,0 @@ -global SIZE: u32 = 30; - -fn main(input: [[u8; 2]; SIZE]) -> pub [[u8; 32]; SIZE] { - let mut results: [[u8; 32]; SIZE] = [[0; 32]; SIZE]; - for i in 0..SIZE { - results[i] = std::hash::sha256(input[i]); - } - - results -} diff --git a/noir/noir-repo/test_programs/benchmarks/bench_sha256_long/Nargo.toml b/noir/noir-repo/test_programs/benchmarks/bench_sha256_long/Nargo.toml deleted file mode 100644 index ae66d7ed5a60..000000000000 --- a/noir/noir-repo/test_programs/benchmarks/bench_sha256_long/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "bench_sha256_long" -version = "0.1.0" -type = "bin" -authors = [""] - -[dependencies] diff --git a/noir/noir-repo/test_programs/benchmarks/bench_sha256_long/Prover.toml b/noir/noir-repo/test_programs/benchmarks/bench_sha256_long/Prover.toml deleted file mode 100644 index ba4bbc1540dc..000000000000 --- a/noir/noir-repo/test_programs/benchmarks/bench_sha256_long/Prover.toml +++ /dev/null @@ -1,191 +0,0 @@ -# 2*64+60=188 bytes -input = [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 97, - 98, - 99, - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, -] diff --git a/noir/noir-repo/test_programs/benchmarks/bench_sha256_long/src/main.nr b/noir/noir-repo/test_programs/benchmarks/bench_sha256_long/src/main.nr deleted file mode 100644 index c47bdc2a5610..000000000000 --- a/noir/noir-repo/test_programs/benchmarks/bench_sha256_long/src/main.nr +++ /dev/null @@ -1,7 +0,0 @@ -// Input size long enough that we have to compress a few times -// and then pad the last block out. -global INPUT_SIZE: u32 = 2 * 64 + 60; - -fn main(input: [u8; INPUT_SIZE]) -> pub [u8; 32] { - std::hash::sha256(input) -} diff --git a/noir/noir-repo/test_programs/compilation_report.sh b/noir/noir-repo/test_programs/compilation_report.sh index 6f7ef254477b..aa1bbef39dec 100755 --- a/noir/noir-repo/test_programs/compilation_report.sh +++ b/noir/noir-repo/test_programs/compilation_report.sh @@ -6,7 +6,7 @@ current_dir=$(pwd) base_path="$current_dir/execution_success" # Tests to be profiled for compilation report -tests_to_profile=("sha256_regression" "regression_4709" "ram_blowup_regression" "global_var_regression_entry_points") +tests_to_profile=("regression_4709" "ram_blowup_regression" "global_var_regression_entry_points") echo "[ " > $current_dir/compilation_report.json diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_as_field/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_as_field/src/main.nr deleted file mode 100644 index f5871bbed81c..000000000000 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_as_field/src/main.nr +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - comptime { - let _: U128 = U128::from_integer(1); - } -} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_as_field/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_as_primitive/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/compile_success_empty/comptime_as_field/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/comptime_as_primitive/Nargo.toml diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_as_primitive/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_as_primitive/src/main.nr new file mode 100644 index 000000000000..392ccf2ebfc6 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_as_primitive/src/main.nr @@ -0,0 +1,7 @@ +fn main() { + comptime { + let x: u64 = 1; + let y = x as Field; + let _ = y as u128; + } +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_from_field/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_from_field/Nargo.toml deleted file mode 100644 index 38a46ba0dbec..000000000000 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_from_field/Nargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "comptime_from_field" -type = "bin" -authors = [""] - -[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_from_field/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_from_field/src/main.nr deleted file mode 100644 index 028722b94b23..000000000000 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_from_field/src/main.nr +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - comptime { - let _: Field = U128::from_hex("0x0").to_integer(); - } -} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_keccak/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_keccak/Nargo.toml deleted file mode 100644 index 47c8654804d6..000000000000 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_keccak/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "comptime_keccak" -type = "bin" -authors = [""] -compiler_version = ">=0.33.0" - -[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_keccak/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_keccak/src/main.nr deleted file mode 100644 index dc4e88b7ab2d..000000000000 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_keccak/src/main.nr +++ /dev/null @@ -1,32 +0,0 @@ -// Tests a very simple program. -// -// The features being tested is keccak256 in brillig -fn main() { - comptime { - let x = 0xbd; - let result = [ - 0x5a, 0x50, 0x2f, 0x9f, 0xca, 0x46, 0x7b, 0x26, 0x6d, 0x5b, 0x78, 0x33, 0x65, 0x19, - 0x37, 0xe8, 0x05, 0x27, 0x0c, 0xa3, 0xf3, 0xaf, 0x1c, 0x0d, 0xd2, 0x46, 0x2d, 0xca, - 0x4b, 0x3b, 0x1a, 0xbf, - ]; - // We use the `as` keyword here to denote the fact that we want to take just the first byte from the x Field - // The padding is taken care of by the program - let digest = keccak256([x as u8], 1); - assert(digest == result); - //#1399: variable message size - let message_size = 4; - let hash_a = keccak256([1, 2, 3, 4], message_size); - let hash_b = keccak256([1, 2, 3, 4, 0, 0, 0, 0], message_size); - - assert(hash_a == hash_b); - - let message_size_big = 8; - let hash_c = keccak256([1, 2, 3, 4, 0, 0, 0, 0], message_size_big); - - assert(hash_a != hash_c); - } -} - -comptime fn keccak256(data: [u8; N], msg_len: u32) -> [u8; 32] { - std::hash::keccak256(data, msg_len) -} diff --git a/noir/noir-repo/test_programs/execution_report.sh b/noir/noir-repo/test_programs/execution_report.sh index 5c916ef6bd75..c7b12d996811 100755 --- a/noir/noir-repo/test_programs/execution_report.sh +++ b/noir/noir-repo/test_programs/execution_report.sh @@ -6,7 +6,7 @@ current_dir=$(pwd) base_path="$current_dir/execution_success" # Tests to be profiled for execution report -tests_to_profile=("sha256_regression" "regression_4709" "ram_blowup_regression" "global_var_regression_entry_points") +tests_to_profile=("regression_4709" "ram_blowup_regression" "global_var_regression_entry_points") echo "[" > $current_dir/execution_report.json diff --git a/noir/noir-repo/test_programs/execution_success/6/Prover.toml b/noir/noir-repo/test_programs/execution_success/6/Prover.toml index 1c52aef063c9..d370032bd530 100644 --- a/noir/noir-repo/test_programs/execution_success/6/Prover.toml +++ b/noir/noir-repo/test_programs/execution_success/6/Prover.toml @@ -3,37 +3,4 @@ # used : https://emn178.github.io/online-tools/sha256.html x = [104, 101, 108, 108, 111] -result = [ - 0x2c, - 0xf2, - 0x4d, - 0xba, - 0x5f, - 0xb0, - 0xa3, - 0x0e, - 0x26, - 0xe8, - 0x3b, - 0x2a, - 0xc5, - 0xb9, - 0xe2, - 0x9e, - 0x1b, - 0x16, - 0x1e, - 0x5c, - 0x1f, - 0xa7, - 0x42, - 0x5e, - 0x73, - 0x04, - 0x33, - 0x62, - 0x93, - 0x8b, - 0x98, - 0x24, -] +result = [234, 143, 22, 61, 179, 134, 130, 146, 94, 68, 145, 197, 229, 141, 75, 179, 80, 110, 248, 193, 78, 183, 138, 134, 233, 8, 197, 98, 74, 103, 32, 15] diff --git a/noir/noir-repo/test_programs/execution_success/6/src/main.nr b/noir/noir-repo/test_programs/execution_success/6/src/main.nr index 5b71174614f5..e950e309df2d 100644 --- a/noir/noir-repo/test_programs/execution_success/6/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/6/src/main.nr @@ -1,11 +1,11 @@ -// Sha256 circuit where the input is 5 bytes -// not five field elements since sha256 operates over +// blake3 circuit where the input is 5 bytes +// not five field elements since blake3 operates over // bytes. // // If you do not cast, it will take all the bytes from the field element! fn main(x: [u8; 5], result: pub [u8; 32]) { - let mut digest = std::hash::sha256(x); + let mut digest = std::hash::blake3(x); digest[0] = 5 as u8; - digest = std::hash::sha256(x); + digest = std::hash::blake3(x); assert(digest == result); } diff --git a/noir/noir-repo/test_programs/execution_success/array_dynamic_blackbox_input/Prover.toml b/noir/noir-repo/test_programs/execution_success/array_dynamic_blackbox_input/Prover.toml index cc60eb8a8baa..f852a79a1033 100644 --- a/noir/noir-repo/test_programs/execution_success/array_dynamic_blackbox_input/Prover.toml +++ b/noir/noir-repo/test_programs/execution_success/array_dynamic_blackbox_input/Prover.toml @@ -1,4 +1,4 @@ index = "1" leaf = ["51", "109", "224", "175", "60", "42", "79", "222", "117", "255", "174", "79", "126", "242", "74", "34", "100", "35", "20", "200", "109", "89", "191", "219", "41", "10", "118", "217", "165", "224", "215", "109"] path = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63"] -root = [79, 230, 126, 184, 98, 125, 226, 58, 117, 45, 140, 15, 72, 118, 89, 173, 117, 161, 166, 0, 214, 125, 13, 16, 113, 81, 173, 156, 97, 15, 57, 216] +root = [186, 47, 168, 70, 152, 149, 203, 90, 138, 188, 96, 15, 111, 179, 82, 106, 198, 166, 172, 38, 110, 187, 182, 64, 29, 101, 171, 221, 89, 105, 243, 22] diff --git a/noir/noir-repo/test_programs/execution_success/array_dynamic_blackbox_input/src/main.nr b/noir/noir-repo/test_programs/execution_success/array_dynamic_blackbox_input/src/main.nr index 260d609928b8..603ae704d704 100644 --- a/noir/noir-repo/test_programs/execution_success/array_dynamic_blackbox_input/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/array_dynamic_blackbox_input/src/main.nr @@ -18,7 +18,7 @@ fn compute_root(leaf: [u8; 32], path: [u8; 64], _index: u32, root: [u8; 32]) { hash_input[j + b] = path[offset + j]; } - current = std::hash::sha256(hash_input); + current = std::hash::blake3(hash_input); index = index >> 1; } diff --git a/noir/noir-repo/test_programs/execution_success/array_dynamic_nested_blackbox_input/Prover.toml b/noir/noir-repo/test_programs/execution_success/array_dynamic_nested_blackbox_input/Prover.toml index 1f2915324144..85b480415b1b 100644 --- a/noir/noir-repo/test_programs/execution_success/array_dynamic_nested_blackbox_input/Prover.toml +++ b/noir/noir-repo/test_programs/execution_success/array_dynamic_nested_blackbox_input/Prover.toml @@ -1,5 +1,5 @@ y = "3" -hash_result = [50, 53, 90, 252, 105, 236, 223, 30, 135, 229, 193, 172, 51, 139, 8, 32, 188, 104, 151, 115, 129, 168, 27, 71, 203, 47, 40, 228, 89, 177, 129, 100] +hash_result = [77, 43, 36, 42, 132, 232, 186, 191, 119, 43, 192, 121, 66, 137, 143, 205, 13, 23, 80, 25, 162, 45, 100, 31, 178, 150, 138, 4, 72, 73, 120, 70] [[x]] a = "1" @@ -20,4 +20,4 @@ a = "7" b = ["8", "9", "22"] [x.bar] -inner = ["106", "107", "108"] \ No newline at end of file +inner = ["106", "107", "108"] diff --git a/noir/noir-repo/test_programs/execution_success/array_dynamic_nested_blackbox_input/src/main.nr b/noir/noir-repo/test_programs/execution_success/array_dynamic_nested_blackbox_input/src/main.nr index 15a2747eaa9d..14f110a23a0b 100644 --- a/noir/noir-repo/test_programs/execution_success/array_dynamic_nested_blackbox_input/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/array_dynamic_nested_blackbox_input/src/main.nr @@ -15,6 +15,6 @@ fn main(mut x: [Foo; 3], y: pub Field, hash_result: pub [u8; 32]) { // Make sure that we are passing a dynamic array to the black box function call // by setting the array using a dynamic index here hash_input[y - 1] = 0; - let hash = std::hash::sha256(hash_input); + let hash = std::hash::blake3(hash_input); assert_eq(hash, hash_result); } diff --git a/noir/noir-repo/test_programs/execution_success/brillig_cow_regression/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_cow_regression/src/main.nr index 69273bc3dcac..4b70f2961b66 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_cow_regression/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_cow_regression/src/main.nr @@ -172,6 +172,6 @@ unconstrained fn main(kernel_data: DataToHash) -> pub [Field; NUM_FIELDS_PER_SHA } } - let sha_digest = std::hash::sha256(hash_input_flattened); - U256::from_bytes32(sha_digest).to_u128_limbs() + let blake3_digest = std::hash::blake3(hash_input_flattened); + U256::from_bytes32(blake3_digest).to_u128_limbs() } diff --git a/noir/noir-repo/test_programs/execution_success/conditional_1/Prover.toml b/noir/noir-repo/test_programs/execution_success/conditional_1/Prover.toml index baad8be126a5..b06d750fda0b 100644 --- a/noir/noir-repo/test_programs/execution_success/conditional_1/Prover.toml +++ b/noir/noir-repo/test_programs/execution_success/conditional_1/Prover.toml @@ -3,7 +3,7 @@ a=0 x = [104, 101, 108, 108, 111] result = [ - 0x2c, + 234, 0xf2, 0x4d, 0xba, diff --git a/noir/noir-repo/test_programs/execution_success/conditional_1/src/main.nr b/noir/noir-repo/test_programs/execution_success/conditional_1/src/main.nr index eedb8a697d1d..e0292c733bd1 100644 --- a/noir/noir-repo/test_programs/execution_success/conditional_1/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/conditional_1/src/main.nr @@ -53,7 +53,7 @@ fn main(a: u32, mut c: [u32; 4], x: [u8; 5], result: pub [u8; 32]) { let mut y = 0; if a == 0 { - let digest = std::hash::sha256(x); + let digest = std::hash::blake3(x); y = digest[0]; } else { y = 5; diff --git a/noir/noir-repo/test_programs/execution_success/conditional_regression_short_circuit/Prover.toml b/noir/noir-repo/test_programs/execution_success/conditional_regression_short_circuit/Prover.toml index baad8be126a5..5f098ae39d81 100644 --- a/noir/noir-repo/test_programs/execution_success/conditional_regression_short_circuit/Prover.toml +++ b/noir/noir-repo/test_programs/execution_success/conditional_regression_short_circuit/Prover.toml @@ -2,37 +2,4 @@ c=[2, 4, 3, 0, ] a=0 x = [104, 101, 108, 108, 111] -result = [ - 0x2c, - 0xf2, - 0x4d, - 0xba, - 0x5f, - 0xb0, - 0xa3, - 0x0e, - 0x26, - 0xe8, - 0x3b, - 0x2a, - 0xc5, - 0xb9, - 0xe2, - 0x9e, - 0x1b, - 0x16, - 0x1e, - 0x5c, - 0x1f, - 0xa7, - 0x42, - 0x5e, - 0x73, - 0x04, - 0x33, - 0x62, - 0x93, - 0x8b, - 0x98, - 0x24, -] +result = [234, 143, 22, 61, 179, 134, 130, 146, 94, 68, 145, 197, 229, 141, 75, 179, 80, 110, 248, 193, 78, 183, 138, 134, 233, 8, 197, 98, 74, 103, 32, 15] diff --git a/noir/noir-repo/test_programs/execution_success/conditional_regression_short_circuit/src/main.nr b/noir/noir-repo/test_programs/execution_success/conditional_regression_short_circuit/src/main.nr index de5ad20a6423..9312655c8f36 100644 --- a/noir/noir-repo/test_programs/execution_success/conditional_regression_short_circuit/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/conditional_regression_short_circuit/src/main.nr @@ -24,9 +24,9 @@ fn bar(x: Field) { } fn call_intrinsic(x: [u8; 5], result: [u8; 32]) { - let mut digest = std::hash::sha256(x); + let mut digest = std::hash::blake3(x); digest[0] = 5 as u8; - digest = std::hash::sha256(x); + digest = std::hash::blake3(x); assert(digest == result); } diff --git a/noir/noir-repo/test_programs/execution_success/ecdsa_secp256k1/Prover.toml b/noir/noir-repo/test_programs/execution_success/ecdsa_secp256k1/Prover.toml index 412c7b36e4c2..e78fc19cb71b 100644 --- a/noir/noir-repo/test_programs/execution_success/ecdsa_secp256k1/Prover.toml +++ b/noir/noir-repo/test_programs/execution_success/ecdsa_secp256k1/Prover.toml @@ -33,46 +33,6 @@ hashed_message = [ 0xc1, 0xe2, ] -message = [ - 0x49, - 0x6e, - 0x73, - 0x74, - 0x72, - 0x75, - 0x63, - 0x74, - 0x69, - 0x6f, - 0x6e, - 0x73, - 0x20, - 0x75, - 0x6e, - 0x63, - 0x6c, - 0x65, - 0x61, - 0x72, - 0x2c, - 0x20, - 0x61, - 0x73, - 0x6b, - 0x20, - 0x61, - 0x67, - 0x61, - 0x69, - 0x6e, - 0x20, - 0x6c, - 0x61, - 0x74, - 0x65, - 0x72, - 0x2e, -] pub_key_x = [ 0xa0, 0x43, diff --git a/noir/noir-repo/test_programs/execution_success/ecdsa_secp256k1/src/main.nr b/noir/noir-repo/test_programs/execution_success/ecdsa_secp256k1/src/main.nr index 00d420089fc9..1c94bf8961e5 100644 --- a/noir/noir-repo/test_programs/execution_success/ecdsa_secp256k1/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/ecdsa_secp256k1/src/main.nr @@ -1,14 +1,4 @@ -fn main( - message: [u8; 38], - hashed_message: [u8; 32], - pub_key_x: [u8; 32], - pub_key_y: [u8; 32], - signature: [u8; 64], -) { - // Hash the message, since secp256k1 expects a hashed_message - let expected = std::hash::sha256(message); - assert(hashed_message == expected); - +fn main(hashed_message: [u8; 32], pub_key_x: [u8; 32], pub_key_y: [u8; 32], signature: [u8; 64]) { let valid_signature = std::ecdsa_secp256k1::verify_signature(pub_key_x, pub_key_y, signature, hashed_message); assert(valid_signature); diff --git a/noir/noir-repo/test_programs/execution_success/keccak256/Nargo.toml b/noir/noir-repo/test_programs/execution_success/keccak256/Nargo.toml deleted file mode 100644 index 7e48c3b342cb..000000000000 --- a/noir/noir-repo/test_programs/execution_success/keccak256/Nargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "keccak256" -type = "bin" -authors = [""] - -[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/keccak256/Prover.toml b/noir/noir-repo/test_programs/execution_success/keccak256/Prover.toml deleted file mode 100644 index d65c4011d3f8..000000000000 --- a/noir/noir-repo/test_programs/execution_success/keccak256/Prover.toml +++ /dev/null @@ -1,35 +0,0 @@ -x = 0xbd -result = [ - 0x5a, - 0x50, - 0x2f, - 0x9f, - 0xca, - 0x46, - 0x7b, - 0x26, - 0x6d, - 0x5b, - 0x78, - 0x33, - 0x65, - 0x19, - 0x37, - 0xe8, - 0x05, - 0x27, - 0x0c, - 0xa3, - 0xf3, - 0xaf, - 0x1c, - 0x0d, - 0xd2, - 0x46, - 0x2d, - 0xca, - 0x4b, - 0x3b, - 0x1a, - 0xbf, -] diff --git a/noir/noir-repo/test_programs/execution_success/keccak256/src/main.nr b/noir/noir-repo/test_programs/execution_success/keccak256/src/main.nr deleted file mode 100644 index 1e13fa028b79..000000000000 --- a/noir/noir-repo/test_programs/execution_success/keccak256/src/main.nr +++ /dev/null @@ -1,20 +0,0 @@ -// docs:start:keccak256 -fn main(x: Field, result: [u8; 32]) { - // We use the `as` keyword here to denote the fact that we want to take just the first byte from the x Field - // The padding is taken care of by the program - let digest = std::hash::keccak256([x as u8], 1); - assert(digest == result); - - //#1399: variable message size - let message_size = 4; - let hash_a = std::hash::keccak256([1, 2, 3, 4], message_size); - let hash_b = std::hash::keccak256([1, 2, 3, 4, 0, 0, 0, 0], message_size); - - assert(hash_a == hash_b); - - let message_size_big = 8; - let hash_c = std::hash::keccak256([1, 2, 3, 4, 0, 0, 0, 0], message_size_big); - - assert(hash_a != hash_c); -} -// docs:end:keccak256 diff --git a/noir/noir-repo/test_programs/execution_success/merkle_insert/src/main.nr b/noir/noir-repo/test_programs/execution_success/merkle_insert/src/main.nr index 25a455c90b8e..42a261ae6c6a 100644 --- a/noir/noir-repo/test_programs/execution_success/merkle_insert/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/merkle_insert/src/main.nr @@ -6,8 +6,23 @@ fn main( leaf: Field, index: Field, ) { - assert(old_root == std::merkle::compute_merkle_root(old_leaf, index, old_hash_path)); + assert(old_root == compute_merkle_root(old_leaf, index, old_hash_path)); - let calculated_root = std::merkle::compute_merkle_root(leaf, index, old_hash_path); + let calculated_root = compute_merkle_root(leaf, index, old_hash_path); assert(new_root == calculated_root); } + +fn compute_merkle_root(leaf: Field, index: Field, hash_path: [Field; N]) -> Field { + let index_bits: [u1; N] = index.to_le_bits(); + let mut current = leaf; + for i in 0..N { + let path_bit = index_bits[i] as bool; + let (hash_left, hash_right) = if path_bit { + (hash_path[i], current) + } else { + (current, hash_path[i]) + }; + current = std::hash::pedersen_hash([hash_left, hash_right]); + } + current +} diff --git a/noir/noir-repo/test_programs/execution_success/ram_blowup_regression/src/main.nr b/noir/noir-repo/test_programs/execution_success/ram_blowup_regression/src/main.nr index 6deb54dd21d7..5f63bf03e558 100644 --- a/noir/noir-repo/test_programs/execution_success/ram_blowup_regression/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/ram_blowup_regression/src/main.nr @@ -20,9 +20,9 @@ pub fn field_from_bytes_32_trunc(bytes32: [u8; 32]) -> Field { low + high * v } -pub fn sha256_to_field(bytes_to_hash: [u8; N]) -> Field { - let sha256_hashed = std::hash::sha256(bytes_to_hash); - let hash_in_a_field = field_from_bytes_32_trunc(sha256_hashed); +pub fn blake3_to_field(bytes_to_hash: [u8; N]) -> Field { + let blake3_hashed = std::hash::blake3(bytes_to_hash); + let hash_in_a_field = field_from_bytes_32_trunc(blake3_hashed); hash_in_a_field } @@ -36,6 +36,6 @@ fn main(tx_effects_hash_input: [Field; TX_EFFECTS_HASH_INPUT_FIELDS]) -> pub Fie } } - let sha_digest = sha256_to_field(hash_input_flattened); - sha_digest + let blake3_digest = blake3_to_field(hash_input_flattened); + blake3_digest } diff --git a/noir/noir-repo/test_programs/execution_success/regression_4449/Prover.toml b/noir/noir-repo/test_programs/execution_success/regression_4449/Prover.toml index 81af476bcc98..12b95c0dbfa1 100644 --- a/noir/noir-repo/test_programs/execution_success/regression_4449/Prover.toml +++ b/noir/noir-repo/test_programs/execution_success/regression_4449/Prover.toml @@ -1,3 +1,3 @@ x = 0xbd -result = [204, 59, 83, 197, 18, 1, 128, 43, 247, 28, 104, 225, 106, 13, 20, 187, 42, 26, 67, 150, 48, 75, 238, 168, 121, 247, 142, 160, 71, 222, 97, 188] \ No newline at end of file +result = [3, 128, 126, 121, 21, 242, 202, 74, 58, 183, 180, 171, 169, 186, 245, 81, 206, 26, 69, 29, 25, 207, 152, 152, 52, 33, 40, 106, 200, 237, 90, 156] diff --git a/noir/noir-repo/test_programs/execution_success/regression_4449/src/main.nr b/noir/noir-repo/test_programs/execution_success/regression_4449/src/main.nr index 3fda39bd8741..88b80dabc7f5 100644 --- a/noir/noir-repo/test_programs/execution_success/regression_4449/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/regression_4449/src/main.nr @@ -5,7 +5,7 @@ fn main(x: u8, result: [u8; 32]) { for i in 0..70 { let y = x + i; let a = [y, x, 32, 0, y + 1, y - 1, y - 2, 5]; - digest = std::sha256::digest(a); + digest = std::hash::blake3(a); } assert(digest == result); diff --git a/noir/noir-repo/test_programs/execution_success/sha256/Nargo.toml b/noir/noir-repo/test_programs/execution_success/sha256/Nargo.toml deleted file mode 100644 index 255d2156ef67..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256/Nargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "sha256" -type = "bin" -authors = [""] - -[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/sha256/Prover.toml b/noir/noir-repo/test_programs/execution_success/sha256/Prover.toml deleted file mode 100644 index b4bf41623709..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256/Prover.toml +++ /dev/null @@ -1,38 +0,0 @@ - -x = 0xbd -result = [ - 0x68, - 0x32, - 0x57, - 0x20, - 0xaa, - 0xbd, - 0x7c, - 0x82, - 0xf3, - 0x0f, - 0x55, - 0x4b, - 0x31, - 0x3d, - 0x05, - 0x70, - 0xc9, - 0x5a, - 0xcc, - 0xbb, - 0x7d, - 0xc4, - 0xb5, - 0xaa, - 0xe1, - 0x12, - 0x04, - 0xc0, - 0x8f, - 0xfe, - 0x73, - 0x2b, -] -input = [0, 0] -toggle = false \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/sha256/src/main.nr b/noir/noir-repo/test_programs/execution_success/sha256/src/main.nr deleted file mode 100644 index 8e5e46b9837e..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256/src/main.nr +++ /dev/null @@ -1,27 +0,0 @@ -// Sha256 example -// -// Calls Sha256 from the standard library. -// -// The Compiler sees this special function and creates an ACIR gate -// -// The ACIR SHA256 gate is passed to PLONK who should -// know how to create the necessary constraints. -// -// Not yet here: For R1CS, it is more about manipulating arithmetic gates to get performance -// This can be done in ACIR! -fn main(x: Field, result: [u8; 32], input: [u8; 2], toggle: bool) { - // We use the `as` keyword here to denote the fact that we want to take just the first byte from the x Field - // The padding is taken care of by the program - // docs:start:sha256_var - let digest = std::hash::sha256_var([x as u8], 1); - // docs:end:sha256_var - assert(digest == result); - - let digest = std::hash::sha256([x as u8]); - assert(digest == result); - - // variable size - let size: Field = 1 + toggle as Field; - let var_sha = std::hash::sha256_var(input, size as u64); - assert(var_sha == std::hash::sha256_var(input, 1)); -} diff --git a/noir/noir-repo/test_programs/execution_success/sha256_brillig_performance_regression/Nargo.toml b/noir/noir-repo/test_programs/execution_success/sha256_brillig_performance_regression/Nargo.toml deleted file mode 100644 index f7076311e1dc..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_brillig_performance_regression/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "sha256_brillig_performance_regression" -type = "bin" -authors = [""] -compiler_version = ">=0.33.0" - -[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/sha256_brillig_performance_regression/Prover.toml b/noir/noir-repo/test_programs/execution_success/sha256_brillig_performance_regression/Prover.toml deleted file mode 100644 index 5bb7f3542573..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_brillig_performance_regression/Prover.toml +++ /dev/null @@ -1,16 +0,0 @@ -input_amount = "1" -minimum_output_amount = "2" -secret_hash_for_L1_to_l2_message = "3" -uniswap_fee_tier = "4" - -[aztec_recipient] -inner = "5" - -[caller_on_L1] -inner = "6" - -[input_asset_bridge_portal_address] -inner = "7" - -[output_asset_bridge_portal_address] -inner = "8" diff --git a/noir/noir-repo/test_programs/execution_success/sha256_brillig_performance_regression/src/main.nr b/noir/noir-repo/test_programs/execution_success/sha256_brillig_performance_regression/src/main.nr deleted file mode 100644 index 42cc6d4ff3bc..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_brillig_performance_regression/src/main.nr +++ /dev/null @@ -1,104 +0,0 @@ -// Performance regression extracted from an aztec protocol contract. - -unconstrained fn main( - input_asset_bridge_portal_address: EthAddress, - input_amount: Field, - uniswap_fee_tier: Field, - output_asset_bridge_portal_address: EthAddress, - minimum_output_amount: Field, - aztec_recipient: AztecAddress, - secret_hash_for_L1_to_l2_message: Field, - caller_on_L1: EthAddress, -) -> pub Field { - let mut hash_bytes = [0; 260]; // 8 fields of 32 bytes each + 4 bytes fn selector - let input_token_portal_bytes: [u8; 32] = - input_asset_bridge_portal_address.to_field().to_be_bytes(); - let in_amount_bytes: [u8; 32] = input_amount.to_be_bytes(); - let uniswap_fee_tier_bytes: [u8; 32] = uniswap_fee_tier.to_be_bytes(); - let output_token_portal_bytes: [u8; 32] = - output_asset_bridge_portal_address.to_field().to_be_bytes(); - let amount_out_min_bytes: [u8; 32] = minimum_output_amount.to_be_bytes(); - let aztec_recipient_bytes: [u8; 32] = aztec_recipient.to_field().to_be_bytes(); - let secret_hash_for_L1_to_l2_message_bytes: [u8; 32] = - secret_hash_for_L1_to_l2_message.to_be_bytes(); - let caller_on_L1_bytes: [u8; 32] = caller_on_L1.to_field().to_be_bytes(); - - // The purpose of including the following selector is to make the message unique to that specific call. Note that - // it has nothing to do with calling the function. - let selector = comptime { - std::hash::keccak256( - "swap_public(address,uint256,uint24,address,uint256,bytes32,bytes32,address)".as_bytes(), - 75, - ) - }; - - hash_bytes[0] = selector[0]; - hash_bytes[1] = selector[1]; - hash_bytes[2] = selector[2]; - hash_bytes[3] = selector[3]; - - for i in 0..32 { - hash_bytes[i + 4] = input_token_portal_bytes[i]; - hash_bytes[i + 36] = in_amount_bytes[i]; - hash_bytes[i + 68] = uniswap_fee_tier_bytes[i]; - hash_bytes[i + 100] = output_token_portal_bytes[i]; - hash_bytes[i + 132] = amount_out_min_bytes[i]; - hash_bytes[i + 164] = aztec_recipient_bytes[i]; - hash_bytes[i + 196] = secret_hash_for_L1_to_l2_message_bytes[i]; - hash_bytes[i + 228] = caller_on_L1_bytes[i]; - } - - let content_hash = sha256_to_field(hash_bytes); - content_hash -} - -// Convert a 32 byte array to a field element by truncating the final byte -pub fn field_from_bytes_32_trunc(bytes32: [u8; 32]) -> Field { - // Convert it to a field element - let mut v = 1; - let mut high = 0 as Field; - let mut low = 0 as Field; - - for i in 0..15 { - // covers bytes 16..30 (31 is truncated and ignored) - low = low + (bytes32[15 + 15 - i] as Field) * v; - v = v * 256; - // covers bytes 0..14 - high = high + (bytes32[14 - i] as Field) * v; - } - // covers byte 15 - low = low + (bytes32[15] as Field) * v; - - low + high * v -} - -pub fn sha256_to_field(bytes_to_hash: [u8; N]) -> Field { - let sha256_hashed = std::hash::sha256(bytes_to_hash); - let hash_in_a_field = field_from_bytes_32_trunc(sha256_hashed); - - hash_in_a_field -} - -pub trait ToField { - fn to_field(self) -> Field; -} - -pub struct EthAddress { - inner: Field, -} - -impl ToField for EthAddress { - fn to_field(self) -> Field { - self.inner - } -} - -pub struct AztecAddress { - pub inner: Field, -} - -impl ToField for AztecAddress { - fn to_field(self) -> Field { - self.inner - } -} diff --git a/noir/noir-repo/test_programs/execution_success/sha256_regression/Nargo.toml b/noir/noir-repo/test_programs/execution_success/sha256_regression/Nargo.toml deleted file mode 100644 index ce98d000bcb7..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_regression/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "sha256_regression" -type = "bin" -authors = [""] -compiler_version = ">=0.33.0" - -[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/sha256_regression/Prover.toml b/noir/noir-repo/test_programs/execution_success/sha256_regression/Prover.toml deleted file mode 100644 index ea0a0f2e8a71..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_regression/Prover.toml +++ /dev/null @@ -1,14 +0,0 @@ -msg_just_over_block = [102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, 108, 97, 105, 110, 59, 32, 99, 104, 97, 114, 115, 101, 116] -msg_multiple_of_block = [102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, 108, 97, 105, 110, 59, 32, 99, 104, 97, 114, 115, 101, 116, 61, 117, 115, 45, 97, 115, 99, 105, 105, 13, 10, 109, 105, 109, 101, 45, 118, 101, 114, 115, 105, 111, 110, 58, 49, 46, 48, 32, 40, 77, 97, 99, 32, 79, 83, 32, 88, 32, 77, 97, 105, 108, 32, 49, 54, 46, 48, 32, 92, 40, 51, 55, 51, 49, 46, 53, 48, 48, 46, 50, 51, 49, 92, 41, 41, 13, 10, 115, 117, 98, 106, 101, 99, 116, 58, 72, 101, 108, 108, 111, 13, 10, 109, 101, 115, 115, 97, 103, 101, 45, 105, 100, 58, 60, 56, 70, 56, 49, 57, 68, 51, 50, 45, 66, 54, 65, 67, 45, 52, 56, 57, 68, 45, 57, 55, 55, 70, 45, 52, 51, 56, 66, 66, 67, 52, 67, 65, 66, 50, 55, 64, 109, 101, 46, 99, 111, 109, 62, 13, 10, 100, 97, 116, 101, 58, 83, 97, 116, 44, 32, 50, 54, 32, 65, 117, 103, 32, 50, 48, 50, 51, 32, 49, 50, 58, 50, 53, 58, 50, 50, 32, 43, 48, 52, 48, 48, 13, 10, 116, 111, 58, 122, 107, 101, 119, 116, 101, 115, 116, 64, 103, 109, 97, 105, 108, 46, 99, 111, 109, 13, 10, 100, 107, 105, 109, 45, 115, 105, 103, 110, 97, 116, 117, 114, 101, 58, 118, 61, 49, 59, 32, 97, 61, 114, 115, 97, 45, 115, 104, 97, 50, 53, 54, 59, 32, 99, 61, 114, 101, 108, 97, 120, 101, 100, 47, 114, 101, 108, 97, 120, 101, 100, 59, 32, 100, 61, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 59, 32, 115, 61, 49, 97, 49, 104, 97, 105, 59, 32, 116, 61, 49, 54, 57, 51, 48, 51, 56, 51, 51, 55, 59, 32, 98, 104, 61, 55, 120, 81, 77, 68, 117, 111, 86, 86, 85, 52, 109, 48, 87, 48, 87, 82, 86, 83, 114, 86, 88, 77, 101, 71, 83, 73, 65, 83, 115, 110, 117, 99, 75, 57, 100, 74, 115, 114, 99, 43, 118, 85, 61, 59, 32, 104, 61, 102, 114, 111, 109, 58, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 77, 105, 109, 101, 45, 86, 101, 114, 115, 105, 111, 110, 58, 83, 117, 98, 106, 101, 99] -msg_just_under_block = [102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, 108, 97, 105, 110, 59] -msg_big_not_block_multiple = [102, 114, 111, 109, 58, 114, 117, 110, 110, 105, 101, 114, 46, 108, 101, 97, 103, 117, 101, 115, 46, 48, 106, 64, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 13, 10, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 116, 101, 120, 116, 47, 112, 108, 97, 105, 110, 59, 32, 99, 104, 97, 114, 115, 101, 116, 61, 117, 115, 45, 97, 115, 99, 105, 105, 13, 10, 109, 105, 109, 101, 45, 118, 101, 114, 115, 105, 111, 110, 58, 49, 46, 48, 32, 40, 77, 97, 99, 32, 79, 83, 32, 88, 32, 77, 97, 105, 108, 32, 49, 54, 46, 48, 32, 92, 40, 51, 55, 51, 49, 46, 53, 48, 48, 46, 50, 51, 49, 92, 41, 41, 13, 10, 115, 117, 98, 106, 101, 99, 116, 58, 72, 101, 108, 108, 111, 13, 10, 109, 101, 115, 115, 97, 103, 101, 45, 105, 100, 58, 60, 56, 70, 56, 49, 57, 68, 51, 50, 45, 66, 54, 65, 67, 45, 52, 56, 57, 68, 45, 57, 55, 55, 70, 45, 52, 51, 56, 66, 66, 67, 52, 67, 65, 66, 50, 55, 64, 109, 101, 46, 99, 111, 109, 62, 13, 10, 100, 97, 116, 101, 58, 83, 97, 116, 44, 32, 50, 54, 32, 65, 117, 103, 32, 50, 48, 50, 51, 32, 49, 50, 58, 50, 53, 58, 50, 50, 32, 43, 48, 52, 48, 48, 13, 10, 116, 111, 58, 122, 107, 101, 119, 116, 101, 115, 116, 64, 103, 109, 97, 105, 108, 46, 99, 111, 109, 13, 10, 100, 107, 105, 109, 45, 115, 105, 103, 110, 97, 116, 117, 114, 101, 58, 118, 61, 49, 59, 32, 97, 61, 114, 115, 97, 45, 115, 104, 97, 50, 53, 54, 59, 32, 99, 61, 114, 101, 108, 97, 120, 101, 100, 47, 114, 101, 108, 97, 120, 101, 100, 59, 32, 100, 61, 105, 99, 108, 111, 117, 100, 46, 99, 111, 109, 59, 32, 115, 61, 49, 97, 49, 104, 97, 105, 59, 32, 116, 61, 49, 54, 57, 51, 48, 51, 56, 51, 51, 55, 59, 32, 98, 104, 61, 55, 120, 81, 77, 68, 117, 111, 86, 86, 85, 52, 109, 48, 87, 48, 87, 82, 86, 83, 114, 86, 88, 77, 101, 71, 83, 73, 65, 83, 115, 110, 117, 99, 75, 57, 100, 74, 115, 114, 99, 43, 118, 85, 61, 59, 32, 104, 61, 102, 114, 111, 109, 58, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 77, 105, 109, 101, 45, 86, 101, 114, 115, 105, 111, 110, 58, 83, 117, 98, 106, 101, 99, 116, 58, 77, 101, 115, 115, 97, 103, 101, 45, 73, 100, 58, 68, 97, 116, 101, 58, 116, 111, 59, 32, 98, 61] -msg_big_with_padding = [48, 130, 1, 37, 2, 1, 0, 48, 11, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, 48, 130, 1, 17, 48, 37, 2, 1, 1, 4, 32, 176, 223, 31, 133, 108, 84, 158, 102, 70, 11, 165, 175, 196, 12, 201, 130, 25, 131, 46, 125, 156, 194, 28, 23, 55, 133, 157, 164, 135, 136, 220, 78, 48, 37, 2, 1, 2, 4, 32, 190, 82, 180, 235, 222, 33, 79, 50, 152, 136, 142, 35, 116, 224, 6, 242, 156, 141, 128, 248, 10, 61, 98, 86, 248, 45, 207, 210, 90, 232, 175, 38, 48, 37, 2, 1, 3, 4, 32, 0, 194, 104, 108, 237, 246, 97, 230, 116, 198, 69, 110, 26, 87, 17, 89, 110, 199, 108, 250, 36, 21, 39, 87, 110, 102, 250, 213, 174, 131, 171, 174, 48, 37, 2, 1, 11, 4, 32, 136, 155, 87, 144, 111, 15, 152, 127, 85, 25, 154, 81, 20, 58, 51, 75, 193, 116, 234, 0, 60, 30, 29, 30, 183, 141, 72, 247, 255, 203, 100, 124, 48, 37, 2, 1, 12, 4, 32, 41, 234, 106, 78, 31, 11, 114, 137, 237, 17, 92, 71, 134, 47, 62, 78, 189, 233, 201, 214, 53, 4, 47, 189, 201, 133, 6, 121, 34, 131, 64, 142, 48, 37, 2, 1, 13, 4, 32, 91, 222, 210, 193, 62, 222, 104, 82, 36, 41, 138, 253, 70, 15, 148, 208, 156, 45, 105, 171, 241, 195, 185, 43, 217, 162, 146, 201, 222, 89, 238, 38, 48, 37, 2, 1, 14, 4, 32, 76, 123, 216, 13, 51, 227, 72, 245, 59, 193, 238, 166, 103, 49, 23, 164, 171, 188, 194, 197, 156, 187, 249, 28, 198, 95, 69, 15, 182, 56, 54, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] -msg_big_no_padding = [48, 130, 1, 37, 2, 1, 0, 48, 11, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, 48, 130, 1, 17, 48, 37, 2, 1, 1, 4, 32, 176, 223, 31, 133, 108, 84, 158, 102, 70, 11, 165, 175, 196, 12, 201, 130, 25, 131, 46, 125, 156, 194, 28, 23, 55, 133, 157, 164, 135, 136, 220, 78, 48, 37, 2, 1, 2, 4, 32, 190, 82, 180, 235, 222, 33, 79, 50, 152, 136, 142, 35, 116, 224, 6, 242, 156, 141, 128, 248, 10, 61, 98, 86, 248, 45, 207, 210, 90, 232, 175, 38, 48, 37, 2, 1, 3, 4, 32, 0, 194, 104, 108, 237, 246, 97, 230, 116, 198, 69, 110, 26, 87, 17, 89, 110, 199, 108, 250, 36, 21, 39, 87, 110, 102, 250, 213, 174, 131, 171, 174, 48, 37, 2, 1, 11, 4, 32, 136, 155, 87, 144, 111, 15, 152, 127, 85, 25, 154, 81, 20, 58, 51, 75, 193, 116, 234, 0, 60, 30, 29, 30, 183, 141, 72, 247, 255, 203, 100, 124, 48, 37, 2, 1, 12, 4, 32, 41, 234, 106, 78, 31, 11, 114, 137, 237, 17, 92, 71, 134, 47, 62, 78, 189, 233, 201, 214, 53, 4, 47, 189, 201, 133, 6, 121, 34, 131, 64, 142, 48, 37, 2, 1, 13, 4, 32, 91, 222, 210, 193, 62, 222, 104, 82, 36, 41, 138, 253, 70, 15, 148, 208, 156, 45, 105, 171, 241, 195, 185, 43, 217, 162, 146, 201, 222, 89, 238, 38, 48, 37, 2, 1, 14, 4, 32, 76, 123, 216, 13, 51, 227, 72, 245, 59, 193, 238, 166, 103, 49, 23, 164, 171, 188, 194, 197, 156, 187, 249, 28, 198, 95, 69, 15, 182, 56, 54, 38] -message_size = 297 - -# Results matched against ethers library -result_just_over_block = [91, 122, 146, 93, 52, 109, 133, 148, 171, 61, 156, 70, 189, 238, 153, 7, 222, 184, 94, 24, 65, 114, 192, 244, 207, 199, 87, 232, 192, 224, 171, 207] -result_multiple_of_block = [116, 90, 151, 31, 78, 22, 138, 180, 211, 189, 69, 76, 227, 200, 155, 29, 59, 123, 154, 60, 47, 153, 203, 129, 157, 251, 48, 2, 79, 11, 65, 47] -result_just_under_block = [143, 140, 76, 173, 222, 123, 102, 68, 70, 149, 207, 43, 39, 61, 34, 79, 216, 252, 213, 165, 74, 16, 110, 74, 29, 64, 138, 167, 30, 1, 9, 119] -result_big = [112, 144, 73, 182, 208, 98, 9, 238, 54, 229, 61, 145, 222, 17, 72, 62, 148, 222, 186, 55, 192, 82, 220, 35, 66, 47, 193, 200, 22, 38, 26, 186] -result_big_with_padding = [32, 85, 108, 174, 127, 112, 178, 182, 8, 43, 134, 123, 192, 211, 131, 66, 184, 240, 212, 181, 240, 180, 106, 195, 24, 117, 54, 129, 19, 10, 250, 53] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/sha256_regression/src/main.nr b/noir/noir-repo/test_programs/execution_success/sha256_regression/src/main.nr deleted file mode 100644 index dbbcc07e501e..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_regression/src/main.nr +++ /dev/null @@ -1,39 +0,0 @@ -// A bunch of different test cases for sha256_var in the stdlib -fn main( - msg_just_over_block: [u8; 68], - result_just_over_block: pub [u8; 32], - msg_multiple_of_block: [u8; 448], - result_multiple_of_block: pub [u8; 32], - // We want to make sure we are testing a message with a size >= 57 but < 64 - msg_just_under_block: [u8; 60], - result_just_under_block: pub [u8; 32], - msg_big_not_block_multiple: [u8; 472], - result_big: pub [u8; 32], - // This message is only 297 elements and we want to hash only a variable amount - msg_big_with_padding: [u8; 700], - // This is the same as `msg_big_with_padding` but with no padding - msg_big_no_padding: [u8; 297], - message_size: u64, - result_big_with_padding: pub [u8; 32], -) { - let hash = std::hash::sha256_var(msg_just_over_block, msg_just_over_block.len() as u64); - assert_eq(hash, result_just_over_block); - - let hash = std::hash::sha256_var(msg_multiple_of_block, msg_multiple_of_block.len() as u64); - assert_eq(hash, result_multiple_of_block); - - let hash = std::hash::sha256_var(msg_just_under_block, msg_just_under_block.len() as u64); - assert_eq(hash, result_just_under_block); - - let hash = std::hash::sha256_var( - msg_big_not_block_multiple, - msg_big_not_block_multiple.len() as u64, - ); - assert_eq(hash, result_big); - - let hash_padding = std::hash::sha256_var(msg_big_with_padding, message_size); - assert_eq(hash_padding, result_big_with_padding); - - let hash_no_padding = std::hash::sha256_var(msg_big_no_padding, message_size); - assert_eq(hash_no_padding, result_big_with_padding); -} diff --git a/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/Nargo.toml b/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/Nargo.toml deleted file mode 100644 index a80677c585d7..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "sha256_var_padding_regression" -type = "bin" -authors = [""] -compiler_version = ">=0.34.0" - -[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/Prover.toml b/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/Prover.toml deleted file mode 100644 index 7b20e8701281..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/Prover.toml +++ /dev/null @@ -1,2 +0,0 @@ -preimage = [29, 81, 165, 84, 243, 114, 101, 37, 242, 146, 127, 99, 69, 145, 39, 72, 213, 39, 253, 179, 218, 37, 217, 201, 172, 93, 198, 50, 249, 70, 15, 30, 162, 112, 187, 40, 140, 9, 236, 53, 32, 44, 38, 163, 113, 254, 192, 197, 44, 89, 71, 130, 169, 242, 17, 211, 214, 72, 19, 178, 186, 168, 147, 127, 99, 101, 252, 227, 8, 147, 150, 85, 97, 158, 17, 107, 218, 244, 82, 113, 247, 91, 208, 214, 60, 244, 87, 137, 173, 201, 130, 18, 66, 56, 198, 149, 207, 189, 175, 120, 123, 224, 177, 167, 251, 159, 143, 110, 68, 183, 189, 70, 126, 32, 35, 164, 44, 30, 44, 12, 65, 18, 62, 239, 242, 2, 248, 104, 2, 178, 64, 28, 126, 36, 137, 24, 14, 116, 91, 98, 90, 159, 218, 102, 45, 11, 110, 223, 245, 184, 52, 99, 59, 245, 136, 175, 3, 72, 164, 146, 145, 116, 22, 66, 24, 49, 193, 121, 3, 60, 37, 41, 97, 3, 190, 66, 195, 225, 63, 46, 3, 118, 4, 208, 15, 1, 40, 254, 235, 151, 123, 70, 180, 170, 44, 172, 90, 4, 254, 53, 239, 116, 246, 67, 56, 129, 61, 22, 169, 213, 65, 27, 216, 116, 162, 239, 214, 207, 126, 177, 20, 100, 25, 48, 143, 84, 215, 70, 197, 53, 65, 70, 86, 172, 61, 62, 9, 212, 167, 169, 133, 41, 126, 213, 196, 33, 192, 238, 0, 63, 246, 215, 58, 128, 110, 101, 92, 3, 170, 214, 130, 149, 52, 81, 125, 118, 233, 3, 118, 193, 104, 207, 120, 115, 77, 253, 191, 122, 0, 107, 164, 207, 113, 81, 169, 36, 201, 228, 74, 134, 131, 218, 178, 35, 30, 216, 101, 2, 103, 174, 87, 95, 50, 50, 215, 157, 5, 210, 188, 54, 211, 78, 45, 199, 96, 121, 241, 241, 176, 226, 194, 134, 130, 89, 217, 210, 186, 32, 140, 39, 91, 103, 212, 26, 87, 32, 72, 144, 228, 230, 117, 99, 188, 50, 15, 69, 79, 179, 50, 12, 106, 86, 218, 101, 73, 142, 243, 29, 250, 122, 228, 233, 29, 255, 22, 121, 114, 125, 103, 41, 250, 241, 179, 126, 158, 198, 116, 209, 65, 94, 98, 228, 175, 169, 96, 3, 9, 233, 133, 214, 55, 161, 164, 103, 80, 85, 24, 186, 64, 167, 92, 131, 53, 101, 202, 47, 25, 104, 118, 155, 14, 12, 12, 25, 116, 45, 221, 249, 28, 246, 212, 200, 157, 167, 169, 56, 197, 181, 4, 245, 146, 1, 140, 234, 191, 212, 228, 125, 87, 81, 86, 119, 30, 63, 129, 143, 32, 96] -result = [205, 74, 73, 134, 202, 93, 199, 152, 171, 244, 133, 193, 132, 40, 42, 9, 248, 11, 99, 200, 135, 58, 220, 227, 45, 253, 183, 241, 69, 69, 80, 219] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/src/main.nr b/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/src/main.nr deleted file mode 100644 index 13f87a0efc51..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_var_padding_regression/src/main.nr +++ /dev/null @@ -1,29 +0,0 @@ -// Test to check sha256_var produces same results irrespective of number of padding bytes after message.length -// Ref: https://github.com/noir-lang/noir/issues/6163, https://gist.github.com/jp4g/d5953faae9eadb2909357474f7901e58 -fn main(preimage: [u8; 448], result: [u8; 32]) { - // Construct arrays of different lengths - let mut preimage_511 = [0; 511]; - let mut preimage_512 = [0; 512]; // Next block - let mut preimage_575 = [0; 575]; - let mut preimage_576 = [0; 576]; // Next block - for i in 0..preimage.len() { - preimage_511[i] = preimage[i]; - preimage_512[i] = preimage[i]; - preimage_575[i] = preimage[i]; - preimage_576[i] = preimage[i]; - } - let fixed_length_hash = std::hash::sha256::digest(preimage); - let var_full_length_hash = std::hash::sha256::sha256_var(preimage, preimage.len() as u64); - let var_length_hash_511 = std::hash::sha256::sha256_var(preimage_511, preimage.len() as u64); - let var_length_hash_512 = std::hash::sha256::sha256_var(preimage_512, preimage.len() as u64); - let var_length_hash_575 = std::hash::sha256::sha256_var(preimage_575, preimage.len() as u64); - let var_length_hash_576 = std::hash::sha256::sha256_var(preimage_576, preimage.len() as u64); - - // All of the above should have produced the same hash (result) - assert(fixed_length_hash == result); - assert(var_full_length_hash == result); - assert(var_length_hash_511 == result); - assert(var_length_hash_512 == result); - assert(var_length_hash_575 == result); - assert(var_length_hash_576 == result); -} diff --git a/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/Nargo.toml b/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/Nargo.toml deleted file mode 100644 index 3e141ee5d5f6..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "sha256_var_size_regression" -type = "bin" -authors = [""] -compiler_version = ">=0.33.0" - -[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/Prover.toml b/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/Prover.toml deleted file mode 100644 index df632a428584..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/Prover.toml +++ /dev/null @@ -1,3 +0,0 @@ -enable = [true, false] -foo = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] -toggle = false diff --git a/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/src/main.nr b/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/src/main.nr deleted file mode 100644 index 4278cdda8a30..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_var_size_regression/src/main.nr +++ /dev/null @@ -1,17 +0,0 @@ -global NUM_HASHES: u32 = 2; - -fn main(foo: [u8; 95], toggle: bool, enable: [bool; NUM_HASHES]) { - let mut result = [[0; 32]; NUM_HASHES]; - let mut const_result = [[0; 32]; NUM_HASHES]; - let size: Field = 93 + toggle as Field * 2; - for i in 0..NUM_HASHES { - if enable[i] { - result[i] = std::sha256::sha256_var(foo, size as u64); - const_result[i] = std::sha256::sha256_var(foo, 93); - } - } - - for i in 0..NUM_HASHES { - assert_eq(result[i], const_result[i]); - } -} diff --git a/noir/noir-repo/test_programs/execution_success/sha256_var_witness_const_regression/Nargo.toml b/noir/noir-repo/test_programs/execution_success/sha256_var_witness_const_regression/Nargo.toml deleted file mode 100644 index e8f3e6bbe643..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_var_witness_const_regression/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "sha256_var_witness_const_regression" -type = "bin" -authors = [""] -compiler_version = ">=0.33.0" - -[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/sha256_var_witness_const_regression/Prover.toml b/noir/noir-repo/test_programs/execution_success/sha256_var_witness_const_regression/Prover.toml deleted file mode 100644 index 7b91051c1a0c..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_var_witness_const_regression/Prover.toml +++ /dev/null @@ -1,2 +0,0 @@ -input = [0, 0] -toggle = false \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/sha256_var_witness_const_regression/src/main.nr b/noir/noir-repo/test_programs/execution_success/sha256_var_witness_const_regression/src/main.nr deleted file mode 100644 index 97c4435d41d2..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha256_var_witness_const_regression/src/main.nr +++ /dev/null @@ -1,9 +0,0 @@ -fn main(input: [u8; 2], toggle: bool) { - let size: Field = 1 + toggle as Field; - assert(!toggle); - - let variable_sha = std::sha256::sha256_var(input, size as u64); - let constant_sha = std::sha256::sha256_var(input, 1); - - assert_eq(variable_sha, constant_sha); -} diff --git a/noir/noir-repo/test_programs/execution_success/sha2_byte/Prover.toml b/noir/noir-repo/test_programs/execution_success/sha2_byte/Prover.toml deleted file mode 100644 index 2f82f14a58d4..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha2_byte/Prover.toml +++ /dev/null @@ -1,5 +0,0 @@ -# Values obtainable from https://emn178.github.io/online-tools/sha256.html and https://emn178.github.io/online-tools/sha512.html -x = 0xbd -result256 = [0x68, 0x32, 0x57, 0x20, 0xaa, 0xbd, 0x7c, 0x82, 0xf3, 0x0f, 0x55, 0x4b, 0x31, 0x3d, 0x05, 0x70, 0xc9, 0x5a, 0xcc, 0xbb, 0x7d, 0xc4, 0xb5, 0xaa, 0xe1, 0x12, 0x04, 0xc0, 0x8f, 0xfe, 0x73, 0x2b] -result512 = [0x29, 0x6e, 0x22, 0x67, 0xd7, 0x4c, 0x27, 0x8d, 0xaa, 0xaa, 0x94, 0x0d, 0x17, 0xb0, 0xcf, 0xb7, 0x4a, 0x50, 0x83, 0xf8, 0xe0, 0x69, 0x72, 0x6d, 0x8c, 0x84, 0x1c, 0xbe, 0x59, 0x6e, 0x04, 0x31, 0xcb, 0x77, 0x41, 0xa5, 0xb5, 0x0f, 0x71, 0x66, 0x6c, 0xfd, 0x54, 0xba, 0xcb, 0x7b, 0x00, 0xae, 0xa8, 0x91, 0x49, 0x9c, 0xf4, 0xef, 0x6a, 0x03, 0xc8, 0xa8, 0x3f, 0xe3, 0x7c, 0x3f, 0x7b, 0xaf] - diff --git a/noir/noir-repo/test_programs/execution_success/sha2_byte/src/main.nr b/noir/noir-repo/test_programs/execution_success/sha2_byte/src/main.nr deleted file mode 100644 index a1663642c69b..000000000000 --- a/noir/noir-repo/test_programs/execution_success/sha2_byte/src/main.nr +++ /dev/null @@ -1,8 +0,0 @@ -// Test Noir implementations of SHA256 and SHA512 on a one-byte message. -fn main(x: Field, result256: [u8; 32], result512: [u8; 64]) { - let digest256 = std::hash::sha256([x as u8]); - assert(digest256 == result256); - - let digest512 = std::hash::sha512::digest([x as u8]); - assert(digest512 == result512); -} diff --git a/noir/noir-repo/test_programs/execution_success/sha2_byte/Nargo.toml b/noir/noir-repo/test_programs/execution_success/shift_right_overflow/Nargo.toml similarity index 63% rename from noir/noir-repo/test_programs/execution_success/sha2_byte/Nargo.toml rename to noir/noir-repo/test_programs/execution_success/shift_right_overflow/Nargo.toml index efd691fce585..a883299fbc26 100644 --- a/noir/noir-repo/test_programs/execution_success/sha2_byte/Nargo.toml +++ b/noir/noir-repo/test_programs/execution_success/shift_right_overflow/Nargo.toml @@ -1,6 +1,5 @@ [package] -name = "sha2_byte" +name = "shift_right_overflow" type = "bin" authors = [""] - [dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/shift_right_overflow/Prover.toml b/noir/noir-repo/test_programs/execution_success/shift_right_overflow/Prover.toml new file mode 100644 index 000000000000..57cb8b2eac81 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/shift_right_overflow/Prover.toml @@ -0,0 +1 @@ +x = 9 diff --git a/noir/noir-repo/test_programs/execution_success/shift_right_overflow/src/main.nr b/noir/noir-repo/test_programs/execution_success/shift_right_overflow/src/main.nr new file mode 100644 index 000000000000..c5d23ab5cd97 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/shift_right_overflow/src/main.nr @@ -0,0 +1,5 @@ +fn main(x: u8) { + // This would previously overflow in ACIR. Now it returns zero. + let value = 1 >> x; + assert_eq(value, 0); +} diff --git a/noir/noir-repo/test_programs/execution_success/simple_shield/src/main.nr b/noir/noir-repo/test_programs/execution_success/simple_shield/src/main.nr index 35b50150986d..82b53dd2cc9e 100644 --- a/noir/noir-repo/test_programs/execution_success/simple_shield/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/simple_shield/src/main.nr @@ -20,7 +20,22 @@ fn main( // Compute output note nullifier let receiver_note_commitment = std::hash::pedersen_commitment([to_pubkey_x, to_pubkey_y]); // Check that the input note nullifier is in the root - assert(note_root == std::merkle::compute_merkle_root(note_commitment.x, index, note_hash_path)); + assert(note_root == compute_merkle_root(note_commitment.x, index, note_hash_path)); [nullifier.x, receiver_note_commitment.x] } + +fn compute_merkle_root(leaf: Field, index: Field, hash_path: [Field; N]) -> Field { + let index_bits: [u1; N] = index.to_le_bits(); + let mut current = leaf; + for i in 0..N { + let path_bit = index_bits[i] as bool; + let (hash_left, hash_right) = if path_bit { + (hash_path[i], current) + } else { + (current, hash_path[i]) + }; + current = std::hash::pedersen_hash([hash_left, hash_right]); + } + current +} diff --git a/noir/noir-repo/test_programs/execution_success/u128/Nargo.toml b/noir/noir-repo/test_programs/execution_success/u128/Nargo.toml deleted file mode 100644 index c1dcd84db046..000000000000 --- a/noir/noir-repo/test_programs/execution_success/u128/Nargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "u128" -type = "bin" -authors = [""] - -[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/u128/Prover.toml b/noir/noir-repo/test_programs/execution_success/u128/Prover.toml deleted file mode 100644 index 961db9825a70..000000000000 --- a/noir/noir-repo/test_programs/execution_success/u128/Prover.toml +++ /dev/null @@ -1,7 +0,0 @@ -x = "3" -y = "4" -z = "7" -hexa ="0x1f03a" -[big_int] -lo = 1 -hi = 2 \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/u128/src/main.nr b/noir/noir-repo/test_programs/execution_success/u128/src/main.nr deleted file mode 100644 index 56ac5b995d9b..000000000000 --- a/noir/noir-repo/test_programs/execution_success/u128/src/main.nr +++ /dev/null @@ -1,41 +0,0 @@ -fn main(mut x: u32, y: u32, z: u32, big_int: U128, hexa: str<7>) { - let a = U128::from_u64s_le(x as u64, x as u64); - let b = U128::from_u64s_le(y as u64, x as u64); - let c = a + b; - assert(c.lo == z as Field); - assert(c.hi == 2 * x as Field); - assert(U128::from_hex(hexa).lo == 0x1f03a); - let t1 = U128::from_hex("0x9d9c7a87771f03a23783f9d9c7a8777"); - let t2 = U128::from_hex("0x45a26c708BFCF39041"); - let t = t1 + t2; - assert(t.lo == 0xc5e4b029996e17b8); - assert(t.hi == 0x09d9c7a87771f07f); - let t3 = U128::from_le_bytes(t.to_le_bytes()); - assert(t == t3); - - let t4 = t - t2; - assert(t4 == t1); - - let t5 = U128::from_u64s_le(0, 1); - let t6 = U128::from_u64s_le(1, 0); - assert((t5 - t6).hi == 0); - - assert( - (U128::from_hex("0x71f03a23783f9d9c7a8777") * U128::from_hex("0x8BFCF39041")).hi - == U128::from_hex("0x3e4e0471b873470e247c824e61445537").hi, - ); - let q = U128::from_hex("0x3e4e0471b873470e247c824e61445537") / U128::from_hex("0x8BFCF39041"); - assert(q == U128::from_hex("0x71f03a23783f9d9c7a8777")); - - assert(big_int.hi == 2); - - let mut small_int = U128::from_integer(x); - assert(small_int.lo == x as Field); - assert(x == small_int.to_integer()); - let shift = small_int << (x as u8); - assert(shift == U128::from_integer(x << (x as u8))); - assert(shift >> (x as u8) == small_int); - assert(shift >> 127 == U128::from_integer(0)); - assert(shift << 127 == U128::from_integer(0)); - assert(U128::from_integer(3).to_integer() == 3); -} diff --git a/noir/noir-repo/test_programs/memory_report.sh b/noir/noir-repo/test_programs/memory_report.sh index eb83004affdb..ff64449fcf5e 100755 --- a/noir/noir-repo/test_programs/memory_report.sh +++ b/noir/noir-repo/test_programs/memory_report.sh @@ -8,7 +8,7 @@ PARSE_MEMORY=$(realpath "$(dirname "$0")/parse_memory.sh") # Tests to be profiled for memory report -tests_to_profile=("keccak256" "workspace" "regression_4709" "ram_blowup_regression" "global_var_regression_entry_points") +tests_to_profile=("workspace" "regression_4709" "ram_blowup_regression" "global_var_regression_entry_points") current_dir=$(pwd) base_path="$current_dir/execution_success" diff --git a/noir/noir-repo/test_programs/noir_test_success/comptime_blackbox/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/comptime_blackbox/src/main.nr index 446692c485bb..3b1f2963b9f9 100644 --- a/noir/noir-repo/test_programs/noir_test_success/comptime_blackbox/src/main.nr +++ b/noir/noir-repo/test_programs/noir_test_success/comptime_blackbox/src/main.nr @@ -117,13 +117,16 @@ fn test_ecdsa_secp256r1() { /// Test that sha256_compression is implemented. #[test] -fn test_sha256() { +fn test_sha256_compression() { + let input: [u32; 16] = [0xbd; 16]; + let state: [u32; 8] = [0; 8]; + let hash = comptime { - let input: [u8; 1] = [0xbd]; - std::hash::sha256(input) + let input: [u32; 16] = [0xbd; 16]; + let state: [u32; 8] = [0; 8]; + std::hash::sha256_compression(input, state) }; - assert_eq(hash[0], 0x68); - assert_eq(hash[31], 0x2b); + assert_eq(hash, std::hash::sha256_compression(input, state)); } /// Test that `embedded_curve_add` and `multi_scalar_mul` are implemented. diff --git a/noir/noir-repo/test_programs/noir_test_success/global_eval/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/global_eval/src/main.nr index 87a2d50a9162..da962bf52032 100644 --- a/noir/noir-repo/test_programs/noir_test_success/global_eval/src/main.nr +++ b/noir/noir-repo/test_programs/noir_test_success/global_eval/src/main.nr @@ -1,20 +1,9 @@ -use std::uint128::U128; - // These definitions require `to_be_bits` and `to_le_bits` to be supported at comptime. global BITS_BE_13: [u1; 4] = (13 as Field).to_be_bits(); global BITS_LE_13: [u1; 4] = (13 as Field).to_le_bits(); -// Examples from #6691 which use the above behind the scenes. -global POW64_A: Field = 2.pow_32(64); -global POW64_B: Field = (U128::one() << 64).to_integer(); - #[test] fn test_be_and_le_bits() { assert_eq(BITS_BE_13, [1, 1, 0, 1]); assert_eq(BITS_LE_13, [1, 0, 1, 1]); } - -#[test] -fn test_pow64() { - assert_eq(POW64_A, POW64_B); -} diff --git a/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs b/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs index 703d272310bf..c1d4533230e7 100644 --- a/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs +++ b/noir/noir-repo/tooling/acvm_cli/src/cli/execute_cmd.rs @@ -8,7 +8,7 @@ use bn254_blackbox_solver::Bn254BlackBoxSolver; use clap::Args; use nargo::PrintOutput; -use nargo::{foreign_calls::DefaultForeignCallBuilder, ops::execute_program}; +use nargo::foreign_calls::DefaultForeignCallBuilder; use noir_artifact_cli::errors::CliError; use noir_artifact_cli::fs::artifact::read_bytecode_from_file; use noir_artifact_cli::fs::witness::save_witness_to_dir; @@ -56,7 +56,7 @@ fn run_command(args: ExecuteCommand) -> Result { )?; if args.output_witness.is_some() { save_witness_to_dir( - output_witness, + &output_witness, &args.output_witness.unwrap(), &args.working_directory, )?; @@ -80,7 +80,8 @@ pub(crate) fn execute_program_from_witness( ) -> Result, CliError> { let program: Program = Program::deserialize_program(bytecode).map_err(CliError::CircuitDeserializationError)?; - execute_program( + + nargo::ops::execute_program( &program, inputs_map, &Bn254BlackBoxSolver(pedantic_solving), diff --git a/noir/noir-repo/tooling/acvm_cli/src/main.rs b/noir/noir-repo/tooling/acvm_cli/src/main.rs index 8f9edc48f32c..2ac8a96eaba7 100644 --- a/noir/noir-repo/tooling/acvm_cli/src/main.rs +++ b/noir/noir-repo/tooling/acvm_cli/src/main.rs @@ -20,18 +20,19 @@ fn main() { .with_span_events(FmtSpan::ACTIVE) .with_writer(debug_file) .with_ansi(false) - .with_env_filter(EnvFilter::from_default_env()) + .with_env_filter(EnvFilter::from_env("NOIR_LOG")) .init(); } else { tracing_subscriber::fmt() .with_span_events(FmtSpan::ACTIVE) + .with_writer(std::io::stderr) .with_ansi(true) .with_env_filter(EnvFilter::from_env("NOIR_LOG")) .init(); } if let Err(report) = cli::start_cli() { - eprintln!("{report}"); + eprintln!("{report:#}"); std::process::exit(1); } } diff --git a/noir/noir-repo/tooling/artifact_cli/src/bin/execute.rs b/noir/noir-repo/tooling/artifact_cli/src/bin/execute.rs index 8bca901e4852..5d67ce0a958d 100644 --- a/noir/noir-repo/tooling/artifact_cli/src/bin/execute.rs +++ b/noir/noir-repo/tooling/artifact_cli/src/bin/execute.rs @@ -35,6 +35,7 @@ pub fn start_cli() -> eyre::Result<()> { fn main() { tracing_subscriber::fmt() .with_span_events(FmtSpan::ACTIVE) + .with_writer(std::io::stderr) .with_ansi(true) .with_env_filter(EnvFilter::from_env("NOIR_LOG")) .init(); diff --git a/noir/noir-repo/tooling/artifact_cli/src/commands/execute_cmd.rs b/noir/noir-repo/tooling/artifact_cli/src/commands/execute_cmd.rs index f863428149ba..6ce966282696 100644 --- a/noir/noir-repo/tooling/artifact_cli/src/commands/execute_cmd.rs +++ b/noir/noir-repo/tooling/artifact_cli/src/commands/execute_cmd.rs @@ -1,18 +1,18 @@ -use std::{collections::BTreeMap, path::PathBuf}; +use std::path::PathBuf; -use acir::{FieldElement, circuit::Program, native_types::WitnessStack}; use bn254_blackbox_solver::Bn254BlackBoxSolver; use clap::Args; -use color_eyre::eyre::{self, bail}; use crate::{ Artifact, errors::CliError, - fs::{inputs::read_inputs_from_file, witness::save_witness_to_dir}, + execution::{self, ExecutionResults}, }; -use nargo::{NargoError, PrintOutput, foreign_calls::DefaultForeignCallBuilder}; -use noirc_abi::{Abi, input_parser::InputValue}; -use noirc_artifacts::debug::DebugArtifact; +use nargo::{ + PrintOutput, + foreign_calls::{DefaultForeignCallBuilder, layers, transcript::ReplayForeignCallExecutor}, +}; +use noirc_driver::CompiledProgram; use super::parse_and_normalize_path; @@ -21,106 +21,106 @@ use super::parse_and_normalize_path; pub struct ExecuteCommand { /// Path to the JSON build artifact (either a program or a contract). #[clap(long, short, value_parser = parse_and_normalize_path)] - artifact_path: PathBuf, + pub artifact_path: PathBuf, /// Path to the Prover.toml file which contains the inputs and the /// optional return value in ABI format. #[clap(long, short, value_parser = parse_and_normalize_path)] - prover_file: PathBuf, + pub prover_file: PathBuf, /// Path to the directory where the output witness should be saved. /// If empty then the results are discarded. #[clap(long, short, value_parser = parse_and_normalize_path)] - output_dir: Option, + pub output_dir: Option, /// Write the execution witness to named file /// /// Defaults to the name of the circuit being executed. #[clap(long, short)] - witness_name: Option, + pub witness_name: Option, /// Name of the function to execute, if the artifact is a contract. #[clap(long)] - contract_fn: Option, + pub contract_fn: Option, + + /// Path to the oracle transcript that is to be replayed during the + /// execution in response to foreign calls. The format is expected + /// to be JSON Lines, with each request/response on a separate line. + /// + /// Note that a transcript might be invalid if the inputs change and + /// the circuit takes a different path during execution. + #[clap(long, conflicts_with = "oracle_resolver")] + pub oracle_file: Option, /// JSON RPC url to solve oracle calls. + #[clap(long, conflicts_with = "oracle_file")] + pub oracle_resolver: Option, + + /// Root directory for the RPC oracle resolver. + #[clap(long, value_parser = parse_and_normalize_path)] + pub oracle_root_dir: Option, + + /// Package name for the RPC oracle resolver #[clap(long)] - oracle_resolver: Option, + pub oracle_package_name: Option, /// Use pedantic ACVM solving, i.e. double-check some black-box function assumptions when solving. #[clap(long, default_value_t = false)] - pedantic_solving: bool, + pub pedantic_solving: bool, } -pub fn run(args: ExecuteCommand) -> eyre::Result<()> { +pub fn run(args: ExecuteCommand) -> Result<(), CliError> { let artifact = Artifact::read_from_file(&args.artifact_path)?; + let artifact_name = args.artifact_path.file_stem().and_then(|s| s.to_str()).unwrap_or_default(); - let circuit = match artifact { - Artifact::Program(program) => Circuit { - name: None, - abi: program.abi, - bytecode: program.bytecode, - debug_symbols: program.debug_symbols, - file_map: program.file_map, - }, + let (circuit, circuit_name): (CompiledProgram, String) = match artifact { + Artifact::Program(program) => (program.into(), artifact_name.to_string()), Artifact::Contract(contract) => { - let names = - contract.functions.iter().map(|f| f.name.clone()).collect::>().join(","); + let names = || contract.functions.iter().map(|f| f.name.clone()).collect::>(); let Some(ref name) = args.contract_fn else { - bail!("--contract-fn missing; options: [{names}]"); + return Err(CliError::MissingContractFn { names: names() }); }; - let Some(function) = contract.functions.into_iter().find(|f| f.name == *name) else { - bail!("unknown --contract-fn '{name}'; options: [{names}]"); + let Some(program) = contract.function_as_compiled_program(name) else { + return Err(CliError::UnknownContractFn { name: name.clone(), names: names() }); }; - Circuit { - name: Some(name.clone()), - abi: function.abi, - bytecode: function.bytecode, - debug_symbols: function.debug_symbols, - file_map: contract.file_map, - } + (program, format!("{artifact_name}::{name}")) } }; match execute(&circuit, &args) { - Ok(solved) => { - save_witness(circuit, args, solved)?; - } - Err(CliError::CircuitExecutionError(err)) => { - show_diagnostic(circuit, err); + Ok(results) => { + execution::save_and_check_witness( + &circuit, + results, + &circuit_name, + args.output_dir.as_deref(), + args.witness_name.as_deref(), + )?; } Err(e) => { - bail!("failed to execute the circuit: {e}"); + if let CliError::CircuitExecutionError(ref err) = e { + execution::show_diagnostic(&circuit, err); + } + // Still returning the error to facilitate command forwarding, to indicate that the command failed. + return Err(e); } } Ok(()) } -/// Parameters necessary to execute a circuit, display execution failures, etc. -struct Circuit { - name: Option, - abi: Abi, - bytecode: Program, - debug_symbols: noirc_errors::debug_info::ProgramDebugInfo, - file_map: BTreeMap, -} - -struct SolvedWitnesses { - expected_return: Option, - actual_return: Option, - witness_stack: WitnessStack, -} - /// Execute a circuit and return the output witnesses. -fn execute(circuit: &Circuit, args: &ExecuteCommand) -> Result { - let (input_map, expected_return) = read_inputs_from_file(&args.prover_file, &circuit.abi)?; - - let initial_witness = circuit.abi.encode(&input_map, None)?; +fn execute(circuit: &CompiledProgram, args: &ExecuteCommand) -> Result { + // Build a custom foreign call executor that replays the Oracle transcript, + // and use it as a base for the default executor. Using it as the innermost rather + // than top layer so that any extra `print` added for debugging is handled by the + // default, rather than trying to match it to the transcript. + let transcript_executor = match args.oracle_file { + Some(ref path) => layers::Either::Left(ReplayForeignCallExecutor::from_file(path)?), + None => layers::Either::Right(layers::Empty), + }; - // TODO: Build a custom foreign call executor that reads from the Oracle transcript, - // and use it as a base for the default executor; see `DefaultForeignCallBuilder::build_with_base` let mut foreign_call_executor = DefaultForeignCallBuilder { output: PrintOutput::Stdout, enable_mocks: false, @@ -128,82 +128,9 @@ fn execute(circuit: &Circuit, args: &ExecuteCommand) -> Result) { - if let Some(diagnostic) = nargo::errors::try_to_diagnose_runtime_error( - &err, - &circuit.abi, - &circuit.debug_symbols.debug_infos, - ) { - let debug_artifact = DebugArtifact { - debug_symbols: circuit.debug_symbols.debug_infos, - file_map: circuit.file_map, - }; - diagnostic.report(&debug_artifact, false); - } -} - -/// Print information about the witness and compare to expectations, -/// returning errors if something isn't right. -fn save_witness( - circuit: Circuit, - args: ExecuteCommand, - solved: SolvedWitnesses, -) -> eyre::Result<()> { - let artifact = args.artifact_path.file_stem().and_then(|s| s.to_str()).unwrap_or_default(); - let name = circuit - .name - .as_ref() - .map(|name| format!("{artifact}.{name}")) - .unwrap_or_else(|| artifact.to_string()); - - println!("[{}] Circuit witness successfully solved", name); - - if let Some(ref witness_dir) = args.output_dir { - let witness_path = save_witness_to_dir( - solved.witness_stack, - &args.witness_name.unwrap_or_else(|| name.clone()), - witness_dir, - )?; - println!("[{}] Witness saved to {}", name, witness_path.display()); - } + .build_with_base(transcript_executor); - // Check that the circuit returned a non-empty result if the ABI expects a return value. - if let Some(ref expected) = circuit.abi.return_type { - if solved.actual_return.is_none() { - bail!("Missing return witness; expected a value of type {expected:?}"); - } - } - - // Check that if the prover file contained a `return` entry then that's what we got. - if let Some(expected) = solved.expected_return { - match solved.actual_return { - None => { - bail!("Missing return witness;\nexpected:\n{expected:?}"); - } - Some(actual) if actual != expected => { - bail!("Unexpected return witness;\nexpected:\n{expected:?}\ngot:\n{actual:?}"); - } - _ => {} - } - } + let blackbox_solver = Bn254BlackBoxSolver(args.pedantic_solving); - Ok(()) + execution::execute(circuit, &blackbox_solver, &mut foreign_call_executor, &args.prover_file) } diff --git a/noir/noir-repo/tooling/artifact_cli/src/commands/mod.rs b/noir/noir-repo/tooling/artifact_cli/src/commands/mod.rs index 78f3b19292f2..9049b3695b7a 100644 --- a/noir/noir-repo/tooling/artifact_cli/src/commands/mod.rs +++ b/noir/noir-repo/tooling/artifact_cli/src/commands/mod.rs @@ -1,3 +1,4 @@ +//! This module is for commands that we might want to invoke from `nargo` as-is. use std::path::PathBuf; use color_eyre::eyre; diff --git a/noir/noir-repo/tooling/artifact_cli/src/errors.rs b/noir/noir-repo/tooling/artifact_cli/src/errors.rs index 5f302f78695a..1dfaac850b03 100644 --- a/noir/noir-repo/tooling/artifact_cli/src/errors.rs +++ b/noir/noir-repo/tooling/artifact_cli/src/errors.rs @@ -1,6 +1,10 @@ use acir::FieldElement; -use nargo::NargoError; -use noirc_abi::errors::{AbiError, InputParserError}; +use nargo::{NargoError, foreign_calls::transcript::TranscriptError}; +use noirc_abi::{ + AbiReturnType, + errors::{AbiError, InputParserError}, + input_parser::InputValue, +}; use std::path::PathBuf; use thiserror::Error; @@ -35,6 +39,10 @@ pub enum CliError { #[error("Failed to deserialize inputs")] InputDeserializationError(#[from] InputParserError), + /// Error related to oracle transcript deserialization + #[error(transparent)] + TranscriptError(#[from] TranscriptError), + /// Error related to ABI encoding #[error(transparent)] AbiError(#[from] AbiError), @@ -61,4 +69,16 @@ pub enum CliError { #[error("Failed to serialize output witness: {0}")] OutputWitnessSerializationFailed(#[from] toml::ser::Error), + + #[error("Unexpected return value: expected {expected:?}; got {actual:?}")] + UnexpectedReturn { expected: InputValue, actual: Option }, + + #[error("Missing return witnesses; expected {expected:?}")] + MissingReturn { expected: AbiReturnType }, + + #[error("Missing contract function name; options: {names:?}")] + MissingContractFn { names: Vec }, + + #[error("Unknown contract function '{name}'; options: {names:?}")] + UnknownContractFn { name: String, names: Vec }, } diff --git a/noir/noir-repo/tooling/artifact_cli/src/execution.rs b/noir/noir-repo/tooling/artifact_cli/src/execution.rs new file mode 100644 index 000000000000..2e19ab551613 --- /dev/null +++ b/noir/noir-repo/tooling/artifact_cli/src/execution.rs @@ -0,0 +1,135 @@ +use std::path::Path; + +use acir::{FieldElement, native_types::WitnessStack}; +use acvm::BlackBoxFunctionSolver; +use nargo::{NargoError, foreign_calls::ForeignCallExecutor}; +use noirc_abi::input_parser::InputValue; +use noirc_artifacts::debug::DebugArtifact; +use noirc_driver::CompiledProgram; + +use crate::{ + errors::CliError, + fs::{inputs::read_inputs_from_file, witness::save_witness_to_dir}, +}; + +/// Results of a circuit execution. +#[derive(Clone, Debug)] +pub struct ExecutionResults { + pub witness_stack: WitnessStack, + pub return_values: ReturnValues, +} + +/// The decoded `return` witnesses. +#[derive(Clone, Debug)] +pub struct ReturnValues { + /// The `return` value from the `Prover.toml` file, if present. + pub expected_return: Option, + /// The `return` value from the circuit execution. + pub actual_return: Option, +} + +/// Execute a circuit and return the output witnesses. +pub fn execute( + circuit: &CompiledProgram, + blackbox_solver: &B, + foreign_call_executor: &mut E, + prover_file: &Path, +) -> Result +where + B: BlackBoxFunctionSolver, + E: ForeignCallExecutor, +{ + let (input_map, expected_return) = read_inputs_from_file(prover_file, &circuit.abi)?; + + let initial_witness = circuit.abi.encode(&input_map, None)?; + + let witness_stack = nargo::ops::execute_program( + &circuit.program, + initial_witness, + blackbox_solver, + foreign_call_executor, + )?; + + let main_witness = + &witness_stack.peek().expect("Should have at least one witness on the stack").witness; + + let (_, actual_return) = circuit.abi.decode(main_witness)?; + + Ok(ExecutionResults { + witness_stack, + return_values: ReturnValues { actual_return, expected_return }, + }) +} + +/// Print an error stack trace, if possible. +pub fn show_diagnostic(circuit: &CompiledProgram, err: &NargoError) { + if let Some(diagnostic) = + nargo::errors::try_to_diagnose_runtime_error(err, &circuit.abi, &circuit.debug) + { + let debug_artifact = DebugArtifact { + debug_symbols: circuit.debug.clone(), + file_map: circuit.file_map.clone(), + }; + + diagnostic.report(&debug_artifact, false); + } +} + +/// Print some information and save the witness if an output directory is specified, +/// then checks if the expected return values were the ones we expected. +pub fn save_and_check_witness( + circuit: &CompiledProgram, + results: ExecutionResults, + circuit_name: &str, + witness_dir: Option<&Path>, + witness_name: Option<&str>, +) -> Result<(), CliError> { + println!("[{}] Circuit witness successfully solved", circuit_name); + // Save first, so that we can potentially look at the output if the expectations fail. + if let Some(witness_dir) = witness_dir { + save_witness(&results.witness_stack, circuit_name, witness_dir, witness_name)?; + } + check_witness(circuit, results.return_values) +} + +/// Save the witness stack to a file. +pub fn save_witness( + witness_stack: &WitnessStack, + circuit_name: &str, + witness_dir: &Path, + witness_name: Option<&str>, +) -> Result<(), CliError> { + let witness_name = witness_name.unwrap_or(circuit_name); + let witness_path = save_witness_to_dir(witness_stack, witness_name, witness_dir)?; + println!("[{}] Witness saved to {}", circuit_name, witness_path.display()); + Ok(()) +} + +/// Compare return values to expectations, returning errors if something unexpected was returned. +pub fn check_witness( + circuit: &CompiledProgram, + return_values: ReturnValues, +) -> Result<(), CliError> { + // Check that the circuit returned a non-empty result if the ABI expects a return value. + if let Some(ref expected) = circuit.abi.return_type { + if return_values.actual_return.is_none() { + return Err(CliError::MissingReturn { expected: expected.clone() }); + } + } + + // Check that if the prover file contained a `return` entry then that's what we got. + if let Some(expected) = return_values.expected_return { + match return_values.actual_return { + None => { + return Err(CliError::UnexpectedReturn { expected, actual: None }); + } + Some(actual) => { + if actual != expected { + return Err(CliError::UnexpectedReturn { expected, actual: Some(actual) }); + } + } + } + } + + Ok(()) +} diff --git a/noir/noir-repo/tooling/artifact_cli/src/fs/witness.rs b/noir/noir-repo/tooling/artifact_cli/src/fs/witness.rs index cf1fe75aad75..fee31ec1f224 100644 --- a/noir/noir-repo/tooling/artifact_cli/src/fs/witness.rs +++ b/noir/noir-repo/tooling/artifact_cli/src/fs/witness.rs @@ -7,7 +7,7 @@ use crate::errors::{CliError, FilesystemError}; /// Write `witness.gz` to the output directory. pub fn save_witness_to_dir( - witnesses: WitnessStack, + witnesses: &WitnessStack, witness_name: &str, witness_dir: &Path, ) -> Result { diff --git a/noir/noir-repo/tooling/artifact_cli/src/lib.rs b/noir/noir-repo/tooling/artifact_cli/src/lib.rs index 2cd2341b7b70..ac4316f5801a 100644 --- a/noir/noir-repo/tooling/artifact_cli/src/lib.rs +++ b/noir/noir-repo/tooling/artifact_cli/src/lib.rs @@ -2,6 +2,7 @@ use noirc_artifacts::{contract::ContractArtifact, program::ProgramArtifact}; pub mod commands; pub mod errors; +pub mod execution; pub mod fs; /// A parsed JSON build artifact. diff --git a/noir/noir-repo/tooling/inspector/src/cli/info_cmd.rs b/noir/noir-repo/tooling/inspector/src/cli/info_cmd.rs index b221042c79ff..34107ebea3a9 100644 --- a/noir/noir-repo/tooling/inspector/src/cli/info_cmd.rs +++ b/noir/noir-repo/tooling/inspector/src/cli/info_cmd.rs @@ -3,7 +3,6 @@ use std::path::PathBuf; use clap::Args; use color_eyre::eyre; use noir_artifact_cli::Artifact; -use noirc_artifacts::program::ProgramArtifact; use noirc_artifacts_info::{InfoReport, count_opcodes_and_gates_in_program, show_info_report}; #[derive(Debug, Clone, Args)] @@ -39,19 +38,12 @@ pub(crate) fn run(args: InfoCommand) -> eyre::Result<()> { .into_iter() .filter(|f| args.contract_fn.as_ref().map(|n| *n == f.name).unwrap_or(true)) .map(|f| { - // We have to cheat to be able to call `count_opcodes_and_gates_in_program`. let package_name = format!("{}::{}", contract.name, f.name); - let program = ProgramArtifact { - noir_version: contract.noir_version.clone(), - hash: f.hash, - abi: f.abi, - bytecode: f.bytecode, - debug_symbols: f.debug_symbols, - file_map: contract.file_map.clone(), - names: f.names, - brillig_names: f.brillig_names, - }; - count_opcodes_and_gates_in_program(program, package_name, None) + let program = f.into_compiled_program( + contract.noir_version.clone(), + contract.file_map.clone(), + ); + count_opcodes_and_gates_in_program(program.into(), package_name, None) }) .collect::>(), }; diff --git a/noir/noir-repo/tooling/inspector/src/main.rs b/noir/noir-repo/tooling/inspector/src/main.rs index 8270fedbf2c0..4d6513b8394f 100644 --- a/noir/noir-repo/tooling/inspector/src/main.rs +++ b/noir/noir-repo/tooling/inspector/src/main.rs @@ -2,7 +2,7 @@ mod cli; fn main() { if let Err(report) = cli::start_cli() { - eprintln!("{report:?}"); + eprintln!("{report:#}"); std::process::exit(1); } } diff --git a/noir/noir-repo/tooling/lsp/src/notifications/mod.rs b/noir/noir-repo/tooling/lsp/src/notifications/mod.rs index dd3315705fc5..b7ba8cd47615 100644 --- a/noir/noir-repo/tooling/lsp/src/notifications/mod.rs +++ b/noir/noir-repo/tooling/lsp/src/notifications/mod.rs @@ -11,7 +11,7 @@ use fxhash::FxHashMap as HashMap; use lsp_types::{DiagnosticRelatedInformation, DiagnosticTag, Url}; use noirc_driver::check_crate; use noirc_errors::reporter::CustomLabel; -use noirc_errors::{DiagnosticKind, FileDiagnostic, Location}; +use noirc_errors::{CustomDiagnostic, DiagnosticKind, Location}; use crate::types::{ Diagnostic, DiagnosticSeverity, DidChangeConfigurationParams, DidChangeTextDocumentParams, @@ -191,16 +191,16 @@ fn publish_diagnostics( package_root_dir: &PathBuf, files: &FileMap, fm: &FileManager, - file_diagnostics: Vec, + custom_diagnostics: Vec, ) { let mut diagnostics_per_url: HashMap> = HashMap::default(); - for file_diagnostic in file_diagnostics.into_iter() { - let file_id = file_diagnostic.file_id; - let path = fm.path(file_id).expect("file must exist to have emitted diagnostic"); + for custom_diagnostic in custom_diagnostics.into_iter() { + let file = custom_diagnostic.file; + let path = fm.path(file).expect("file must exist to have emitted diagnostic"); if let Ok(uri) = Url::from_file_path(path) { if let Some(diagnostic) = - file_diagnostic_to_diagnostic(file_diagnostic, files, fm, uri.clone()) + custom_diagnostic_to_diagnostic(custom_diagnostic, files, fm, uri.clone()) { diagnostics_per_url.entry(uri).or_default().push(diagnostic); } @@ -232,21 +232,18 @@ fn publish_diagnostics( state.files_with_errors.insert(package_root_dir.clone(), new_files_with_errors); } -fn file_diagnostic_to_diagnostic( - file_diagnostic: FileDiagnostic, +fn custom_diagnostic_to_diagnostic( + diagnostic: CustomDiagnostic, files: &FileMap, fm: &FileManager, uri: Url, ) -> Option { - let file_id = file_diagnostic.file_id; - let diagnostic = file_diagnostic.diagnostic; - if diagnostic.secondaries.is_empty() { return None; } let span = diagnostic.secondaries.first().unwrap().location.span; - let range = byte_span_to_range(files, file_id, span.into())?; + let range = byte_span_to_range(files, diagnostic.file, span.into())?; let severity = match diagnostic.kind { DiagnosticKind::Error => DiagnosticSeverity::ERROR, diff --git a/noir/noir-repo/tooling/lsp/src/requests/code_action/import_trait.rs b/noir/noir-repo/tooling/lsp/src/requests/code_action/import_trait.rs index 379b627f8785..e29558c617b9 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/code_action/import_trait.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/code_action/import_trait.rs @@ -42,7 +42,7 @@ impl CodeActionFinder<'_> { }; let trait_methods = - self.interner.lookup_trait_methods(&typ, &method_call.method_name.0.contents, true); + self.interner.lookup_trait_methods(typ, &method_call.method_name.0.contents, true); let trait_ids: HashSet<_> = trait_methods.iter().map(|(_, trait_id)| *trait_id).collect(); for trait_id in trait_ids { diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion.rs b/noir/noir-repo/tooling/lsp/src/requests/completion.rs index eab8522693f8..9d11aba95349 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion.rs @@ -1448,7 +1448,7 @@ impl Visitor for NodeFinder<'_> { let prefix = ""; let self_prefix = false; self.complete_type_fields_and_methods( - &typ, + typ, prefix, FunctionCompletionKind::Name, self_prefix, @@ -1481,7 +1481,7 @@ impl Visitor for NodeFinder<'_> { let prefix = prefix[0..offset].to_string(); let self_prefix = false; self.complete_type_fields_and_methods( - &typ, + typ, &prefix, FunctionCompletionKind::Name, self_prefix, @@ -1658,7 +1658,7 @@ impl Visitor for NodeFinder<'_> { let prefix = ""; let self_prefix = false; self.complete_type_fields_and_methods( - &typ, + typ, prefix, FunctionCompletionKind::NameAndParameters, self_prefix, @@ -1731,7 +1731,7 @@ impl Visitor for NodeFinder<'_> { let prefix = ident.to_string().to_case(Case::Snake); let self_prefix = false; self.complete_type_fields_and_methods( - &typ, + typ, &prefix, FunctionCompletionKind::NameAndParameters, self_prefix, diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs index 3958b92deb08..98bb2aae6e6f 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs @@ -2115,17 +2115,17 @@ fn main() { async fn test_auto_import_from_std() { let src = r#" fn main() { - compute_merkle_roo>|< + zeroe>|< } "#; let items = get_completions(src).await; assert_eq!(items.len(), 1); let item = &items[0]; - assert_eq!(item.label, "compute_merkle_root(…)"); + assert_eq!(item.label, "zeroed()"); assert_eq!( item.label_details.as_ref().unwrap().detail, - Some("(use std::merkle::compute_merkle_root)".to_string()), + Some("(use std::mem::zeroed)".to_string()), ); } diff --git a/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs b/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs index 1d36aabdbe74..4827d8827afb 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs @@ -660,7 +660,7 @@ mod document_symbol_tests { deprecated: None, range: Range { start: Position { line: 15, character: 7 }, - end: Position { line: 15, character: 24 }, + end: Position { line: 15, character: 25 }, }, selection_range: Range { start: Position { line: 15, character: 7 }, diff --git a/noir/noir-repo/tooling/lsp/src/requests/hover/from_visitor.rs b/noir/noir-repo/tooling/lsp/src/requests/hover/from_visitor.rs index 97ead183cd21..2b58a31e012f 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/hover/from_visitor.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/hover/from_visitor.rs @@ -70,7 +70,7 @@ impl Visitor for HoverFinder<'_> { } } -fn format_integer(typ: Type, value: SignedField) -> String { +fn format_integer(typ: &Type, value: SignedField) -> String { let value_base_10 = value.field.to_string(); // For simplicity we parse the value as a BigInt to convert it to hex @@ -98,7 +98,7 @@ mod tests { let typ = Type::FieldElement; let value = SignedField::positive(0_u128); let expected = " Field\n---\nvalue of literal: `0 (0x00)`"; - assert_eq!(format_integer(typ, value), expected); + assert_eq!(format_integer(&typ, value), expected); } #[test] @@ -106,7 +106,7 @@ mod tests { let typ = Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo); let value = SignedField::positive(123456_u128); let expected = " u32\n---\nvalue of literal: `123456 (0x1e240)`"; - assert_eq!(format_integer(typ, value), expected); + assert_eq!(format_integer(&typ, value), expected); } #[test] @@ -114,6 +114,6 @@ mod tests { let typ = Type::Integer(Signedness::Signed, IntegerBitSize::SixtyFour); let value = SignedField::new(987654_u128.into(), true); let expected = " i64\n---\nvalue of literal: `-987654 (-0xf1206)`"; - assert_eq!(format_integer(typ, value), expected); + assert_eq!(format_integer(&typ, value), expected); } } diff --git a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs index be7722e744d2..8c794d288686 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/inlay_hint.rs @@ -255,7 +255,7 @@ impl<'a> InlayHintCollector<'a> { self.push_type_hint( object_lsp_location, - &typ, + typ, false, // not editable false, // don't include colon ); diff --git a/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs b/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs index 1709106e1218..7563dc50c987 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/signature_help.rs @@ -98,8 +98,8 @@ impl<'a> SignatureFinder<'a> { } // Otherwise, the call must be a reference to an fn type - if let Some(mut typ) = self.interner.type_at_location(location) { - typ = typ.follow_bindings(); + if let Some(typ) = self.interner.type_at_location(location) { + let mut typ = typ.follow_bindings(); if let Type::Forall(_, forall_typ) = typ { typ = *forall_typ; } diff --git a/noir/noir-repo/tooling/lsp/src/requests/test_run.rs b/noir/noir-repo/tooling/lsp/src/requests/test_run.rs index f0d49c4d8640..6b7a53e4596e 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/test_run.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/test_run.rs @@ -119,7 +119,7 @@ fn on_test_run_request_inner( TestStatus::CompileError(diag) => NargoTestRunResult { id: params.id.clone(), result: "error".to_string(), - message: Some(diag.diagnostic.message), + message: Some(diag.message), }, }; Ok(result) diff --git a/noir/noir-repo/tooling/nargo/src/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index cdf11a0e9e71..f1743af79ca6 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -9,9 +9,7 @@ use acvm::{ pwg::{ErrorLocation, OpcodeResolutionError}, }; use noirc_abi::{Abi, AbiErrorType, display_abi_error}; -use noirc_errors::{ - CustomDiagnostic, FileDiagnostic, debug_info::DebugInfo, reporter::ReportedErrors, -}; +use noirc_errors::{CustomDiagnostic, debug_info::DebugInfo, reporter::ReportedErrors}; pub use noirc_errors::Location; @@ -230,7 +228,7 @@ pub fn try_to_diagnose_runtime_error( nargo_err: &NargoError, abi: &Abi, debug: &[DebugInfo], -) -> Option { +) -> Option { let source_locations = match nargo_err { NargoError::ExecutionError(execution_error) => { extract_locations_from_error(execution_error, debug)? @@ -242,5 +240,5 @@ pub fn try_to_diagnose_runtime_error( let location = *source_locations.last()?; let message = extract_message_from_error(&abi.error_types, nargo_err); let error = CustomDiagnostic::simple_error(message, String::new(), location); - Some(error.with_call_stack(source_locations).in_file(location.file)) + Some(error.with_call_stack(source_locations)) } diff --git a/noir/noir-repo/tooling/nargo/src/foreign_calls/mod.rs b/noir/noir-repo/tooling/nargo/src/foreign_calls/mod.rs index f17a97cecd49..8d33b07dd089 100644 --- a/noir/noir-repo/tooling/nargo/src/foreign_calls/mod.rs +++ b/noir/noir-repo/tooling/nargo/src/foreign_calls/mod.rs @@ -4,6 +4,7 @@ use thiserror::Error; pub mod layers; pub mod mocker; pub mod print; +pub mod transcript; pub mod default; #[cfg(feature = "rpc")] @@ -83,4 +84,7 @@ pub enum ForeignCallError { #[error("Assert message resolved after an unsatisfied constrain. {0}")] ResolvedAssertMessage(String), + + #[error("Failed to replay oracle transcript: {0}")] + TranscriptError(String), } diff --git a/noir/noir-repo/tooling/nargo/src/foreign_calls/print.rs b/noir/noir-repo/tooling/nargo/src/foreign_calls/print.rs index a225fe31dcbe..96db9232d0d4 100644 --- a/noir/noir-repo/tooling/nargo/src/foreign_calls/print.rs +++ b/noir/noir-repo/tooling/nargo/src/foreign_calls/print.rs @@ -16,6 +16,7 @@ pub enum PrintOutput<'a> { String(&'a mut String), } +/// Handle `println` calls. #[derive(Debug, Default)] pub struct PrintForeignCallExecutor<'a> { output: PrintOutput<'a>, diff --git a/noir/noir-repo/tooling/nargo/src/foreign_calls/transcript.rs b/noir/noir-repo/tooling/nargo/src/foreign_calls/transcript.rs new file mode 100644 index 000000000000..155835c3fc17 --- /dev/null +++ b/noir/noir-repo/tooling/nargo/src/foreign_calls/transcript.rs @@ -0,0 +1,156 @@ +use std::{ + collections::VecDeque, + path::{Path, PathBuf}, +}; + +use acvm::{AcirField, acir::brillig::ForeignCallResult, pwg::ForeignCallWaitInfo}; +use serde::{Deserialize, Serialize}; +use serde_json::json; + +use crate::PrintOutput; + +use super::{ForeignCallError, ForeignCallExecutor}; + +#[derive(Debug, thiserror::Error)] +pub enum TranscriptError { + #[error(transparent)] + IoError(#[from] std::io::Error), + + #[error(transparent)] + DeserializationError(#[from] serde_json::Error), +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +struct LogItem { + call: ForeignCallWaitInfo, + result: ForeignCallResult, +} + +/// Log foreign calls during the execution, for testing purposes. +pub struct LoggingForeignCallExecutor<'a, E> { + pub executor: E, + pub output: PrintOutput<'a>, +} + +impl<'a, E> LoggingForeignCallExecutor<'a, E> { + pub fn new(executor: E, output: PrintOutput<'a>) -> Self { + Self { executor, output } + } +} + +impl ForeignCallExecutor for LoggingForeignCallExecutor<'_, E> +where + F: AcirField + Serialize, + E: ForeignCallExecutor, +{ + fn execute( + &mut self, + foreign_call: &ForeignCallWaitInfo, + ) -> Result, ForeignCallError> { + let result = self.executor.execute(foreign_call); + if let Ok(ref result) = result { + let log_item = || { + // Match the JSON structure of `LogItem` without having to clone. + let json = json!({"call": foreign_call, "result": result}); + serde_json::to_string(&json).expect("failed to serialize foreign call") + }; + match &mut self.output { + PrintOutput::None => (), + PrintOutput::Stdout => println!("{}", log_item()), + PrintOutput::String(s) => { + s.push_str(&log_item()); + s.push('\n'); + } + } + } + result + } +} + +/// Log foreign calls to stdout as soon as they are made, or buffer them and write to a file at the end. +pub enum ForeignCallLog { + None, + Stdout, + File(PathBuf, String), +} + +impl ForeignCallLog { + /// Instantiate based on an env var. + pub fn from_env(key: &str) -> Self { + match std::env::var(key) { + Err(_) => Self::None, + Ok(s) if s == "stdout" => Self::Stdout, + Ok(s) => Self::File(PathBuf::from(s), String::new()), + } + } + + /// Create a [PrintOutput] based on the log setting. + pub fn print_output(&mut self) -> PrintOutput { + match self { + ForeignCallLog::None => PrintOutput::None, + ForeignCallLog::Stdout => PrintOutput::Stdout, + ForeignCallLog::File(_, s) => PrintOutput::String(s), + } + } + + /// Any final logging. + pub fn write_log(self) -> std::io::Result<()> { + if let ForeignCallLog::File(path, contents) = self { + std::fs::write(path, contents)?; + } + Ok(()) + } +} + +/// Replay an oracle transcript which was logged with [LoggingForeignCallExecutor]. +/// +/// This is expected to be the last executor in the stack, e.g. prints can be handled above it. +pub struct ReplayForeignCallExecutor { + transcript: VecDeque>, +} + +impl Deserialize<'a>> ReplayForeignCallExecutor { + pub fn from_file(path: &Path) -> Result { + let contents = std::fs::read_to_string(path)?; + + let transcript = + contents.lines().map(serde_json::from_str).collect::, _>>()?; + + Ok(Self { transcript }) + } +} + +impl ForeignCallExecutor for ReplayForeignCallExecutor +where + F: AcirField, +{ + fn execute( + &mut self, + foreign_call: &ForeignCallWaitInfo, + ) -> Result, ForeignCallError> { + let error = |msg| Err(ForeignCallError::TranscriptError(msg)); + // Verify without popping. + if let Some(next) = self.transcript.front() { + if next.call.function != foreign_call.function { + let msg = format!( + "unexpected foreign call; expected '{}', got '{}'", + next.call.function, foreign_call.function + ); + return error(msg); + } + if next.call.inputs != foreign_call.inputs { + let msg = format!( + "unexpected foreign call inputs to '{}'; expected {:?}, got {:?}", + next.call.function, next.call.inputs, foreign_call.inputs + ); + return error(msg); + } + } + // Consume the next call. + if let Some(next) = self.transcript.pop_front() { + Ok(next.result) + } else { + error("unexpected foreign call; no more calls in transcript".to_string()) + } + } +} diff --git a/noir/noir-repo/tooling/nargo/src/ops/check.rs b/noir/noir-repo/tooling/nargo/src/ops/check.rs index f22def8bd91b..129aa1bd7885 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/check.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/check.rs @@ -1,6 +1,6 @@ use acvm::compiler::CircuitSimulator; use noirc_driver::{CompiledProgram, ErrorsAndWarnings}; -use noirc_errors::{CustomDiagnostic, FileDiagnostic}; +use noirc_errors::CustomDiagnostic; /// Run each function through a circuit simulator to check that they are solvable. #[tracing::instrument(level = "trace", skip_all)] @@ -8,13 +8,10 @@ pub fn check_program(compiled_program: &CompiledProgram) -> Result<(), ErrorsAnd for (i, circuit) in compiled_program.program.functions.iter().enumerate() { let mut simulator = CircuitSimulator::default(); if !simulator.check_circuit(circuit) { - let diag = FileDiagnostic { - file_id: fm::FileId::dummy(), - diagnostic: CustomDiagnostic::from_message(&format!( - "Circuit \"{}\" is not solvable", - compiled_program.names[i] - )), - }; + let diag = CustomDiagnostic::from_message( + &format!("Circuit \"{}\" is not solvable", compiled_program.names[i]), + fm::FileId::dummy(), + ); return Err(vec![diag]); } } diff --git a/noir/noir-repo/tooling/nargo/src/ops/test.rs b/noir/noir-repo/tooling/nargo/src/ops/test.rs index 8eba3000f3d5..c4adaa5cfaab 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/test.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/test.rs @@ -8,13 +8,17 @@ use acvm::{ }; use noirc_abi::Abi; use noirc_driver::{CompileError, CompileOptions, DEFAULT_EXPRESSION_WIDTH, compile_no_check}; -use noirc_errors::{FileDiagnostic, debug_info::DebugInfo}; +use noirc_errors::{CustomDiagnostic, debug_info::DebugInfo}; use noirc_frontend::hir::{Context, def_map::TestFunction}; use crate::{ NargoError, errors::try_to_diagnose_runtime_error, - foreign_calls::{ForeignCallError, ForeignCallExecutor, layers, print::PrintOutput}, + foreign_calls::{ + ForeignCallError, ForeignCallExecutor, layers, + print::PrintOutput, + transcript::{ForeignCallLog, LoggingForeignCallExecutor}, + }, }; use super::execute_program; @@ -22,9 +26,9 @@ use super::execute_program; #[derive(Debug)] pub enum TestStatus { Pass, - Fail { message: String, error_diagnostic: Option }, + Fail { message: String, error_diagnostic: Option }, Skipped, - CompileError(FileDiagnostic), + CompileError(CustomDiagnostic), } impl TestStatus { @@ -60,11 +64,21 @@ where let compiled_program = crate::ops::transform_program(compiled_program, target_width); if test_function_has_no_arguments { + let ignore_foreign_call_failures = + std::env::var("NARGO_IGNORE_TEST_FAILURES_FROM_FOREIGN_CALLS") + .is_ok_and(|var| &var == "true"); + + let mut foreign_call_log = ForeignCallLog::from_env("NARGO_TEST_FOREIGN_CALL_LOG"); + // Run the backend to ensure the PWG evaluates functions like std::hash::pedersen, // otherwise constraints involving these expressions will not error. // Use a base layer that doesn't handle anything, which we handle in the `execute` below. - let inner_executor = build_foreign_call_executor(output, layers::Unhandled); - let mut foreign_call_executor = TestForeignCallExecutor::new(inner_executor); + let foreign_call_executor = build_foreign_call_executor(output, layers::Unhandled); + let foreign_call_executor = TestForeignCallExecutor::new(foreign_call_executor); + let mut foreign_call_executor = LoggingForeignCallExecutor::new( + foreign_call_executor, + foreign_call_log.print_output(), + ); let circuit_execution = execute_program( &compiled_program.program, @@ -80,9 +94,8 @@ where &circuit_execution, ); - let ignore_foreign_call_failures = - std::env::var("NARGO_IGNORE_TEST_FAILURES_FROM_FOREIGN_CALLS") - .is_ok_and(|var| &var == "true"); + let foreign_call_executor = foreign_call_executor.executor; + foreign_call_log.write_log().expect("failed to write foreign call log"); if let TestStatus::Fail { .. } = status { if ignore_foreign_call_failures @@ -218,7 +231,7 @@ fn test_status_program_compile_pass( fn check_expected_failure_message( test_function: &TestFunction, failed_assertion: Option, - error_diagnostic: Option, + error_diagnostic: Option, ) -> TestStatus { // Extract the expected failure message, if there was one // @@ -235,9 +248,7 @@ fn check_expected_failure_message( // expected_failure_message let expected_failure_message_matches = failed_assertion .as_ref() - .or_else(|| { - error_diagnostic.as_ref().map(|file_diagnostic| &file_diagnostic.diagnostic.message) - }) + .or_else(|| error_diagnostic.as_ref().map(|file_diagnostic| &file_diagnostic.message)) .map(|message| message.contains(expected_failure_message)) .unwrap_or(false); if expected_failure_message_matches { diff --git a/noir/noir-repo/tooling/nargo_cli/Cargo.toml b/noir/noir-repo/tooling/nargo_cli/Cargo.toml index 5aa37cffcda0..a80828356ffe 100644 --- a/noir/noir-repo/tooling/nargo_cli/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_cli/Cargo.toml @@ -99,12 +99,7 @@ sha3.workspace = true iai = "0.1.1" test-case.workspace = true lazy_static.workspace = true -light-poseidon = "0.2.0" - -ark-bn254-v04 = { package = "ark-bn254", version = "^0.4.0", default-features = false, features = [ - "curve", -] } -ark-ff-v04 = { package = "ark-ff", version = "^0.4.0", default-features = false } +light-poseidon = "0.3.0" [[bench]] name = "criterion" diff --git a/noir/noir-repo/tooling/nargo_cli/build.rs b/noir/noir-repo/tooling/nargo_cli/build.rs index 904e404d7c55..b9faf018dfda 100644 --- a/noir/noir-repo/tooling/nargo_cli/build.rs +++ b/noir/noir-repo/tooling/nargo_cli/build.rs @@ -438,7 +438,6 @@ fn generate_compile_success_no_bug_tests(test_file: &mut File, test_data_dir: &P &test_dir, "compile", r#" - nargo.arg("--enable-brillig-constraints-check"); nargo.assert().success().stderr(predicate::str::contains("bug:").not()); "#, &MatrixConfig::default(), @@ -468,7 +467,6 @@ fn generate_compile_success_with_bug_tests(test_file: &mut File, test_data_dir: &test_dir, "compile", r#" - nargo.arg("--enable-brillig-constraints-check"); nargo.assert().success().stderr(predicate::str::contains("bug:")); "#, &MatrixConfig::default(), diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs index f12e83297e66..f9303180fc0d 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs @@ -215,7 +215,7 @@ fn run_async( if let Some(witness_name) = witness_name { let witness_path = - save_witness_to_dir(solved_witness_stack, witness_name, target_dir)?; + save_witness_to_dir(&solved_witness_stack, witness_name, target_dir)?; println!("[{}] Witness saved to {}", package.name, witness_path.display()); } diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs index c7c3227795de..7ba95a16eebd 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs @@ -1,24 +1,11 @@ use std::path::PathBuf; -use acvm::FieldElement; -use acvm::acir::native_types::WitnessStack; -use bn254_blackbox_solver::Bn254BlackBoxSolver; use clap::Args; -use nargo::PrintOutput; use nargo::constants::PROVER_INPUT_FILE; -use nargo::errors::try_to_diagnose_runtime_error; -use nargo::foreign_calls::DefaultForeignCallBuilder; -use nargo::package::Package; use nargo::workspace::Workspace; use nargo_toml::PackageSelection; -use noir_artifact_cli::fs::artifact::read_program_from_file; -use noir_artifact_cli::fs::inputs::read_inputs_from_file; -use noir_artifact_cli::fs::witness::save_witness_to_dir; -use noirc_abi::InputMap; -use noirc_abi::input_parser::InputValue; -use noirc_artifacts::debug::DebugArtifact; -use noirc_driver::{CompileOptions, CompiledProgram}; +use noirc_driver::CompileOptions; use super::compile_cmd::compile_workspace_full; use super::{LockType, PackageOptions, WorkspaceCommand}; @@ -44,8 +31,12 @@ pub(crate) struct ExecuteCommand { compile_options: CompileOptions, /// JSON RPC url to solve oracle calls - #[clap(long)] + #[clap(long, conflicts_with = "oracle_file")] oracle_resolver: Option, + + /// Path to the oracle transcript. + #[clap(long, conflicts_with = "oracle_resolver")] + oracle_file: Option, } impl WorkspaceCommand for ExecuteCommand { @@ -60,127 +51,30 @@ impl WorkspaceCommand for ExecuteCommand { } pub(crate) fn run(args: ExecuteCommand, workspace: Workspace) -> Result<(), CliError> { - let target_dir = &workspace.target_directory_path(); - // Compile the full workspace in order to generate any build artifacts. compile_workspace_full(&workspace, &args.compile_options)?; let binary_packages = workspace.into_iter().filter(|package| package.is_binary()); for package in binary_packages { let program_artifact_path = workspace.package_build_path(package); - let program: CompiledProgram = read_program_from_file(&program_artifact_path)?.into(); - let abi = program.abi.clone(); - - let results = execute_program_and_decode( - program, - package, - &args.prover_name, - args.oracle_resolver.as_deref(), - Some(workspace.root_dir.clone()), - Some(package.name.to_string()), - args.compile_options.pedantic_solving, - )?; - - println!("[{}] Circuit witness successfully solved", package.name); - if let Some(ref return_value) = results.actual_return { - println!("[{}] Circuit output: {return_value:?}", package.name); - } - - let package_name = package.name.clone().into(); - let witness_name = args.witness_name.as_ref().unwrap_or(&package_name); - let witness_path = save_witness_to_dir(results.witness_stack, witness_name, target_dir)?; - println!("[{}] Witness saved to {}", package.name, witness_path.display()); - - // Sanity checks on the return value after the witness has been saved, so it can be inspected if necessary. - if let Some(expected) = results.expected_return { - if results.actual_return.as_ref() != Some(&expected) { - return Err(CliError::UnexpectedReturn { expected, actual: results.actual_return }); - } - } - // We can expect that if the circuit returns something, it should be non-empty after execution. - if let Some(ref expected) = abi.return_type { - if results.actual_return.is_none() { - return Err(CliError::MissingReturn { expected: expected.clone() }); - } - } + let prover_file = package.root_dir.join(&args.prover_name).with_extension("toml"); + + let cmd = noir_artifact_cli::commands::execute_cmd::ExecuteCommand { + artifact_path: program_artifact_path, + prover_file, + output_dir: Some(workspace.target_directory_path()), + witness_name: Some( + args.witness_name.clone().unwrap_or_else(|| package.name.to_string()), + ), + contract_fn: None, + oracle_file: args.oracle_file.clone(), + oracle_resolver: args.oracle_resolver.clone(), + oracle_root_dir: Some(workspace.root_dir.clone()), + oracle_package_name: Some(package.name.to_string()), + pedantic_solving: args.compile_options.pedantic_solving, + }; + + noir_artifact_cli::commands::execute_cmd::run(cmd)?; } Ok(()) } - -fn execute_program_and_decode( - program: CompiledProgram, - package: &Package, - prover_name: &str, - foreign_call_resolver_url: Option<&str>, - root_path: Option, - package_name: Option, - pedantic_solving: bool, -) -> Result { - // Parse the initial witness values from Prover.toml - let (inputs_map, expected_return) = read_inputs_from_file( - &package.root_dir.join(prover_name).with_extension("toml"), - &program.abi, - )?; - let witness_stack = execute_program( - &program, - &inputs_map, - foreign_call_resolver_url, - root_path, - package_name, - pedantic_solving, - )?; - // Get the entry point witness for the ABI - let main_witness = - &witness_stack.peek().expect("Should have at least one witness on the stack").witness; - let (_, actual_return) = program.abi.decode(main_witness)?; - - Ok(ExecutionResults { expected_return, actual_return, witness_stack }) -} - -struct ExecutionResults { - expected_return: Option, - actual_return: Option, - witness_stack: WitnessStack, -} - -pub(crate) fn execute_program( - compiled_program: &CompiledProgram, - inputs_map: &InputMap, - foreign_call_resolver_url: Option<&str>, - root_path: Option, - package_name: Option, - pedantic_solving: bool, -) -> Result, CliError> { - let initial_witness = compiled_program.abi.encode(inputs_map, None)?; - - let solved_witness_stack_err = nargo::ops::execute_program( - &compiled_program.program, - initial_witness, - &Bn254BlackBoxSolver(pedantic_solving), - &mut DefaultForeignCallBuilder { - output: PrintOutput::Stdout, - enable_mocks: false, - resolver_url: foreign_call_resolver_url.map(|s| s.to_string()), - root_path, - package_name, - } - .build(), - ); - match solved_witness_stack_err { - Ok(solved_witness_stack) => Ok(solved_witness_stack), - Err(err) => { - let debug_artifact = DebugArtifact { - debug_symbols: compiled_program.debug.clone(), - file_map: compiled_program.file_map.clone(), - }; - - if let Some(diagnostic) = - try_to_diagnose_runtime_error(&err, &compiled_program.abi, &compiled_program.debug) - { - diagnostic.report(&debug_artifact, false); - } - - Err(CliError::NargoError(err)) - } - } -} diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs index 9fe682bef7ff..373dfce86a91 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/export_cmd.rs @@ -1,7 +1,7 @@ use nargo::errors::CompileError; use nargo::ops::report_errors; use noir_artifact_cli::fs::artifact::save_program_to_file; -use noirc_errors::FileDiagnostic; +use noirc_errors::CustomDiagnostic; use noirc_frontend::hir::ParsedFiles; use rayon::prelude::*; @@ -82,7 +82,7 @@ fn compile_exported_functions( |(function_name, function_id)| -> Result<(String, CompiledProgram), CompileError> { // TODO: We should to refactor how to deal with compilation errors to avoid this. let program = compile_no_check(&mut context, compile_options, function_id, None, false) - .map_err(|error| vec![FileDiagnostic::from(error)]); + .map_err(|error| vec![CustomDiagnostic::from(error)]); let program = report_errors( program.map(|program| (program, Vec::new())), diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/fmt_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/fmt_cmd.rs index b16ce9d1f7de..53551c139508 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/fmt_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/fmt_cmd.rs @@ -56,11 +56,8 @@ pub(crate) fn run(args: FormatCommand, workspace: Workspace) -> Result<(), CliEr let is_all_warnings = errors.iter().all(ParserError::is_warning); if !is_all_warnings { let errors = errors - .into_iter() - .map(|error| { - let error = CustomDiagnostic::from(&error); - error.in_file(file_id) - }) + .iter() + .map(CustomDiagnostic::from) .collect(); let _ = report_errors::<()>( diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd/formatters.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd/formatters.rs index b62e2e2ad9c7..686281292458 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd/formatters.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd/formatters.rs @@ -2,7 +2,7 @@ use std::{io::Write, panic::RefUnwindSafe, time::Duration}; use fm::FileManager; use nargo::ops::TestStatus; -use noirc_errors::{FileDiagnostic, reporter::stack_trace}; +use noirc_errors::{CustomDiagnostic, reporter::stack_trace}; use serde_json::{Map, json}; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, StandardStreamLock, WriteColor}; @@ -438,7 +438,7 @@ impl Formatter for JsonFormatter { stdout.push_str(message.trim()); if let Some(diagnostic) = error_diagnostic { - if !(diagnostic.diagnostic.is_warning() && silence_warnings) { + if !(diagnostic.is_warning() && silence_warnings) { stdout.push('\n'); stdout.push_str(&diagnostic_to_string(diagnostic, file_manager)); } @@ -450,7 +450,7 @@ impl Formatter for JsonFormatter { TestStatus::CompileError(diagnostic) => { json.insert("event".to_string(), json!("failed")); - if !(diagnostic.diagnostic.is_warning() && silence_warnings) { + if !(diagnostic.is_warning() && silence_warnings) { if !stdout.is_empty() { stdout.push('\n'); } @@ -515,12 +515,11 @@ fn package_start(package_name: &str, test_count: usize) -> std::io::Result<()> { } pub(crate) fn diagnostic_to_string( - file_diagnostic: &FileDiagnostic, + custom_diagnostic: &CustomDiagnostic, file_manager: &FileManager, ) -> String { let file_map = file_manager.as_file_map(); - let custom_diagnostic = &file_diagnostic.diagnostic; let mut message = String::new(); message.push_str(custom_diagnostic.message.trim()); @@ -529,7 +528,7 @@ pub(crate) fn diagnostic_to_string( message.push_str(note.trim()); } - if let Ok(name) = file_map.get_name(file_diagnostic.file_id) { + if let Ok(name) = file_map.get_name(custom_diagnostic.file) { message.push('\n'); message.push_str(&format!("at {name}")); } diff --git a/noir/noir-repo/tooling/nargo_cli/src/errors.rs b/noir/noir-repo/tooling/nargo_cli/src/errors.rs index 74ede00f0d07..fb241bcbd293 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/errors.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/errors.rs @@ -2,7 +2,7 @@ use acvm::FieldElement; use nargo::{NargoError, errors::CompileError}; use nargo_toml::ManifestError; use noir_debugger::errors::DapError; -use noirc_abi::{AbiReturnType, errors::AbiError, input_parser::InputValue}; +use noirc_abi::errors::AbiError; use std::path::PathBuf; use thiserror::Error; @@ -42,10 +42,4 @@ pub(crate) enum CliError { /// Error from the compilation pipeline #[error(transparent)] CompileError(#[from] CompileError), - - #[error("Unexpected return value: expected {expected:?}; got {actual:?}")] - UnexpectedReturn { expected: InputValue, actual: Option }, - - #[error("Missing return witnesses; expected {expected:?}")] - MissingReturn { expected: AbiReturnType }, } diff --git a/noir/noir-repo/tooling/nargo_cli/src/main.rs b/noir/noir-repo/tooling/nargo_cli/src/main.rs index f4cc74447bc6..33e18ce6f941 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/main.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/main.rs @@ -17,6 +17,9 @@ use color_eyre::config::HookBuilder; use tracing_appender::rolling; use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan}; +// TODO: Currently only used by benches. +use noir_artifact_cli as _; + const PANIC_MESSAGE: &str = "This is a bug. We may have already fixed this in newer versions of Nargo so try searching for similar issues at https://github.com/noir-lang/noir/issues/.\nIf there isn't an open issue for this bug, consider opening one at https://github.com/noir-lang/noir/issues/new?labels=bug&template=bug_report.yml"; fn main() { @@ -28,7 +31,7 @@ fn main() { panic_hook.install(); if let Err(report) = cli::start_cli() { - eprintln!("{report}"); + eprintln!("{report:#}"); std::process::exit(1); } } @@ -42,6 +45,6 @@ fn setup_tracing() { let debug_file = rolling::daily(log_dir, "nargo-log"); subscriber.with_writer(debug_file).with_ansi(false).json().init(); } else { - subscriber.with_ansi(true).init(); + subscriber.with_writer(std::io::stderr).with_ansi(true).init(); } } diff --git a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs index 2e153b85ba89..7c3794d03ab0 100644 --- a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs +++ b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs @@ -10,7 +10,6 @@ use noirc_driver::{ }; use noirc_frontend::hir::Context; use proptest::prelude::*; -use sha3::Digest; /// Inputs and expected output of a snippet encoded in ABI format. #[derive(Debug)] @@ -78,8 +77,8 @@ fn run_snippet_proptest( Err(e) => panic!("failed to compile program; brillig = {force_brillig}:\n{source}\n{e:?}"), }; - let pedandic_solving = true; - let blackbox_solver = bn254_blackbox_solver::Bn254BlackBoxSolver(pedandic_solving); + let pedantic_solving = true; + let blackbox_solver = bn254_blackbox_solver::Bn254BlackBoxSolver(pedantic_solving); let foreign_call_executor = RefCell::new(DefaultForeignCallBuilder::default().build()); // Generate multiple input/output @@ -105,64 +104,6 @@ fn run_snippet_proptest( }); } -/// Run property tests on a code snippet which is assumed to execute a hashing function with the following signature: -/// -/// ```ignore -/// fn main(input: [u8; {max_len}], message_size: u32) -> pub [u8; 32] -/// ``` -/// -/// The calls are executed with and without forcing brillig, because it seems common for hash functions to run different -/// code paths based on `runtime::is_unconstrained()`. -fn run_hash_proptest( - // Different generic maximum input sizes to try. - max_lengths: &[usize], - // Some hash functions allow inputs which are less than the generic parameters, others don't. - variable_length: bool, - // Make the source code specialized for a given expected input size. - source: impl Fn(usize) -> String, - // Rust implementation of the hash function. - hash: fn(&[u8]) -> [u8; N], -) { - for max_len in max_lengths { - let max_len = *max_len; - // The maximum length is used to pick the generic version of the method. - let source = source(max_len); - // Hash functions runs differently depending on whether the code is unconstrained or not. - for force_brillig in [false, true] { - let length_strategy = - if variable_length { (0..=max_len).boxed() } else { Just(max_len).boxed() }; - // The actual input length can be up to the maximum. - let strategy = length_strategy - .prop_flat_map(|len| prop::collection::vec(any::(), len)) - .prop_map(move |mut msg| { - // The output is the hash of the data as it is. - let output = hash(&msg); - - // The input has to be padded to the maximum length. - let msg_size = msg.len(); - msg.resize(max_len, 0u8); - - let mut inputs = vec![("input", bytes_input(&msg))]; - - // Omit the `message_size` if the hash function doesn't support it. - if variable_length { - inputs.push(( - "message_size", - InputValue::Field(FieldElement::from(msg_size)), - )); - } - - SnippetInputOutput::new(inputs, bytes_input(&output)).with_description(format!( - "force_brillig = {force_brillig}, max_len = {max_len}" - )) - }) - .boxed(); - - run_snippet_proptest(source.clone(), force_brillig, strategy); - } - } -} - /// This is just a simple test to check that property testing works. #[test] fn fuzz_basic() { @@ -187,72 +128,6 @@ fn fuzz_basic() { run_snippet_proptest(program.to_string(), false, strategy); } -#[test] -fn fuzz_keccak256_equivalence() { - run_hash_proptest( - // XXX: Currently it fails with inputs >= 135 bytes - &[0, 1, 100, 134], - true, - |max_len| { - format!( - "fn main(input: [u8; {max_len}], message_size: u32) -> pub [u8; 32] {{ - std::hash::keccak256(input, message_size) - }}" - ) - }, - |data| sha3::Keccak256::digest(data).into(), - ); -} - -#[test] -#[should_panic] // Remove once fixed -fn fuzz_keccak256_equivalence_over_135() { - run_hash_proptest( - &[135, 150], - true, - |max_len| { - format!( - "fn main(input: [u8; {max_len}], message_size: u32) -> pub [u8; 32] {{ - std::hash::keccak256(input, message_size) - }}" - ) - }, - |data| sha3::Keccak256::digest(data).into(), - ); -} - -#[test] -fn fuzz_sha256_equivalence() { - run_hash_proptest( - &[0, 1, 200, 511, 512], - true, - |max_len| { - format!( - "fn main(input: [u8; {max_len}], message_size: u64) -> pub [u8; 32] {{ - std::hash::sha256_var(input, message_size) - }}" - ) - }, - |data| sha2::Sha256::digest(data).into(), - ); -} - -#[test] -fn fuzz_sha512_equivalence() { - run_hash_proptest( - &[0, 1, 200], - false, - |max_len| { - format!( - "fn main(input: [u8; {max_len}]) -> pub [u8; 64] {{ - std::hash::sha512::digest(input) - }}" - ) - }, - |data| sha2::Sha512::digest(data).into(), - ); -} - #[test] fn fuzz_poseidon2_equivalence() { use bn254_blackbox_solver::poseidon_hash; @@ -290,17 +165,13 @@ fn fuzz_poseidon2_equivalence() { #[test] fn fuzz_poseidon_equivalence() { - use ark_ff_v04::{BigInteger, PrimeField}; use light_poseidon::{Poseidon, PoseidonHasher}; let poseidon_hash = |inputs: &[FieldElement]| { - let mut poseidon = Poseidon::::new_circom(inputs.len()).unwrap(); - let frs: Vec = inputs - .iter() - .map(|f| ark_bn254_v04::Fr::from_be_bytes_mod_order(&f.to_be_bytes())) - .collect::>(); - let hash = poseidon.hash(&frs).expect("failed to hash"); - FieldElement::from_be_bytes_reduce(&hash.into_bigint().to_bytes_be()) + let mut poseidon = Poseidon::::new_circom(inputs.len()).unwrap(); + let frs: Vec = inputs.iter().map(|f| f.into_repr()).collect::>(); + let hash: ark_bn254::Fr = poseidon.hash(&frs).expect("failed to hash"); + FieldElement::from_repr(hash) }; // Noir has hashes up to length 16, but the reference library won't work with more than 12. @@ -332,12 +203,6 @@ fn fuzz_poseidon_equivalence() { } } -fn bytes_input(bytes: &[u8]) -> InputValue { - InputValue::Vec( - bytes.iter().map(|b| InputValue::Field(FieldElement::from(*b as u32))).collect(), - ) -} - fn field_vec_strategy(len: usize) -> impl Strategy> { // Generate Field elements from random 32 byte vectors. let field = prop::collection::vec(any::(), 32) diff --git a/noir/noir-repo/tooling/noirc_artifacts/src/contract.rs b/noir/noir-repo/tooling/noirc_artifacts/src/contract.rs index c2e44e542664..9f8f7019ff13 100644 --- a/noir/noir-repo/tooling/noirc_artifacts/src/contract.rs +++ b/noir/noir-repo/tooling/noirc_artifacts/src/contract.rs @@ -1,6 +1,6 @@ use acvm::{FieldElement, acir::circuit::Program}; use noirc_abi::{Abi, AbiType, AbiValue}; -use noirc_driver::{CompiledContract, CompiledContractOutputs, ContractFunction}; +use noirc_driver::{CompiledContract, CompiledContractOutputs, CompiledProgram, ContractFunction}; use serde::{Deserialize, Serialize}; use noirc_driver::DebugFile; @@ -49,6 +49,14 @@ impl From for ContractArtifact { } } +impl ContractArtifact { + pub fn function_as_compiled_program(&self, function_name: &str) -> Option { + self.functions.iter().find(|f| f.name == function_name).map(|f| { + f.clone().into_compiled_program(self.noir_version.clone(), self.file_map.clone()) + }) + } +} + /// Each function in the contract will be compiled as a separate noir program. /// /// A contract function unlike a regular Noir program however can have additional properties. @@ -85,6 +93,26 @@ pub struct ContractFunctionArtifact { pub brillig_names: Vec, } +impl ContractFunctionArtifact { + pub fn into_compiled_program( + self, + noir_version: String, + file_map: BTreeMap, + ) -> CompiledProgram { + CompiledProgram { + noir_version, + hash: self.hash, + program: self.bytecode, + abi: self.abi, + debug: self.debug_symbols.debug_infos, + file_map, + warnings: Vec::new(), + names: self.names, + brillig_names: self.brillig_names, + } + } +} + impl From for ContractFunctionArtifact { fn from(func: ContractFunction) -> Self { ContractFunctionArtifact { diff --git a/noir/noir-repo/tooling/profiler/src/cli/execution_flamegraph_cmd.rs b/noir/noir-repo/tooling/profiler/src/cli/execution_flamegraph_cmd.rs index c9d7eca50f59..8aaf1c0ad851 100644 --- a/noir/noir-repo/tooling/profiler/src/cli/execution_flamegraph_cmd.rs +++ b/noir/noir-repo/tooling/profiler/src/cli/execution_flamegraph_cmd.rs @@ -30,13 +30,22 @@ pub(crate) struct ExecutionFlamegraphCommand { /// The output folder for the flamegraph svg files #[clap(long, short)] - output: PathBuf, + output: Option, /// Use pedantic ACVM solving, i.e. double-check some black-box function /// assumptions when solving. /// This is disabled by default. #[clap(long, default_value = "false")] pedantic_solving: bool, + + /// A single number representing the total opcodes executed. + /// Outputs to stdout and skips generating a flamegraph. + #[clap(long, default_value = "false")] + sample_count: bool, + + /// Enables additional logging + #[clap(long, default_value = "false")] + verbose: bool, } pub(crate) fn run(args: ExecutionFlamegraphCommand) -> eyre::Result<()> { @@ -46,6 +55,8 @@ pub(crate) fn run(args: ExecutionFlamegraphCommand) -> eyre::Result<()> { &InfernoFlamegraphGenerator { count_name: "samples".to_string() }, &args.output, args.pedantic_solving, + args.sample_count, + args.verbose, ) } @@ -53,20 +64,29 @@ fn run_with_generator( artifact_path: &Path, prover_toml_path: &Path, flamegraph_generator: &impl FlamegraphGenerator, - output_path: &Path, + output_path: &Option, pedantic_solving: bool, + print_sample_count: bool, + verbose: bool, ) -> eyre::Result<()> { let program = read_program_from_file(artifact_path).context("Error reading program from file")?; ensure_brillig_entry_point(&program)?; + if !print_sample_count && output_path.is_none() { + return report_error("Missing --output argument for when building a flamegraph") + .map_err(Into::into); + } + let (inputs_map, _) = read_inputs_from_file(&prover_toml_path.with_extension("toml"), &program.abi)?; let initial_witness = program.abi.encode(&inputs_map, None)?; - println!("Executing..."); + if verbose { + println!("Executing..."); + } let solved_witness_stack_err = nargo::ops::execute_program_with_profiling( &program.bytecode, @@ -94,9 +114,21 @@ fn run_with_generator( } }; - println!("Executed"); + if verbose { + println!("Executed"); + } + + if print_sample_count { + println!("{}", profiling_samples.len()); + return Ok(()); + } - println!("Collecting {} samples", profiling_samples.len()); + // We place this logging output before the transforming and collection of the samples. + // This is done because large traces can take some time, and can make it look + // as if the profiler has stalled. + if verbose { + println!("Generating flamegraph for {} samples...", profiling_samples.len()); + } let profiling_samples: Vec = profiling_samples .iter_mut() @@ -118,24 +150,28 @@ fn run_with_generator( }) .collect(); - let debug_artifact: DebugArtifact = program.into(); - - println!("Generating flamegraph with {} samples", profiling_samples.len()); + let output_path = + output_path.as_ref().expect("Should have already checked for the output path"); + let debug_artifact: DebugArtifact = program.into(); flamegraph_generator.generate_flamegraph( profiling_samples, &debug_artifact.debug_symbols[0], &debug_artifact, artifact_path.to_str().unwrap(), "main", - &Path::new(&output_path).join(Path::new(&format!("{}_brillig_trace.svg", "main"))), + &Path::new(output_path).join(Path::new(&format!("{}_brillig_trace.svg", "main"))), )?; + if verbose { + println!("Generated flamegraph"); + } + Ok(()) } fn ensure_brillig_entry_point(artifact: &ProgramArtifact) -> Result<(), CliError> { - let err_msg = "Command only supports fully unconstrained Noir programs e.g. `unconstrained fn main() { .. }".to_owned(); + let err_msg = "Command only supports fully unconstrained Noir programs e.g. `unconstrained fn main() { .. }"; let program = &artifact.bytecode; if program.functions.len() != 1 || program.unconstrained_functions.len() != 1 { return report_error(err_msg); @@ -152,3 +188,86 @@ fn ensure_brillig_entry_point(artifact: &ProgramArtifact) -> Result<(), CliError Ok(()) } + +#[cfg(test)] +mod tests { + use acir::circuit::{Circuit, Program, brillig::BrilligBytecode}; + use color_eyre::eyre; + use fm::codespan_files::Files; + use noirc_artifacts::program::ProgramArtifact; + use noirc_driver::CrateName; + use noirc_errors::debug_info::{DebugInfo, ProgramDebugInfo}; + use std::{collections::BTreeMap, path::Path, str::FromStr}; + + use crate::flamegraph::Sample; + + #[derive(Default)] + struct TestFlamegraphGenerator {} + + impl super::FlamegraphGenerator for TestFlamegraphGenerator { + fn generate_flamegraph<'files, S: Sample>( + &self, + _samples: Vec, + _debug_symbols: &DebugInfo, + _files: &'files impl Files<'files, FileId = fm::FileId>, + _artifact_name: &str, + _function_name: &str, + output_path: &Path, + ) -> eyre::Result<()> { + let output_file = std::fs::File::create(output_path).unwrap(); + std::io::Write::write_all(&mut std::io::BufWriter::new(output_file), b"success") + .unwrap(); + + Ok(()) + } + } + + #[test] + fn error_reporter_smoke_test() { + // This test purposefully uses an artifact that does not represent a Brillig entry point. + // The goal is to see that our program fails gracefully and does not panic. + let temp_dir = tempfile::tempdir().unwrap(); + + let prover_toml_path = temp_dir.path().join("Prover.toml"); + + let artifact = ProgramArtifact { + noir_version: "0.0.0".to_string(), + hash: 27, + abi: noirc_abi::Abi::default(), + bytecode: Program { + functions: vec![Circuit::default()], + unconstrained_functions: vec![ + BrilligBytecode::default(), + BrilligBytecode::default(), + ], + }, + debug_symbols: ProgramDebugInfo { debug_infos: vec![DebugInfo::default()] }, + file_map: BTreeMap::default(), + names: vec!["main".to_string()], + brillig_names: Vec::new(), + }; + + // Write the artifact to a file + let artifact_path = noir_artifact_cli::fs::artifact::save_program_to_file( + &artifact, + &CrateName::from_str("test").unwrap(), + temp_dir.path(), + ) + .unwrap(); + + let flamegraph_generator = TestFlamegraphGenerator::default(); + + assert!( + super::run_with_generator( + &artifact_path, + &prover_toml_path, + &flamegraph_generator, + &Some(temp_dir.into_path()), + false, + false, + false + ) + .is_err() + ); + } +} diff --git a/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs b/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs index a82e5ea3e2a6..736cddf7cf4a 100644 --- a/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs +++ b/noir/noir-repo/tooling/profiler/src/cli/gates_flamegraph_cmd.rs @@ -125,7 +125,7 @@ fn run_with_provider( #[cfg(test)] mod tests { use acir::circuit::{Circuit, Program}; - use color_eyre::eyre::{self}; + use color_eyre::eyre; use fm::codespan_files::Files; use noirc_artifacts::program::ProgramArtifact; use noirc_errors::debug_info::{DebugInfo, ProgramDebugInfo}; diff --git a/noir/noir-repo/tooling/profiler/src/cli/mod.rs b/noir/noir-repo/tooling/profiler/src/cli/mod.rs index b91dd6990aa0..0b4e0a92b271 100644 --- a/noir/noir-repo/tooling/profiler/src/cli/mod.rs +++ b/noir/noir-repo/tooling/profiler/src/cli/mod.rs @@ -1,5 +1,5 @@ use clap::{Parser, Subcommand}; -use color_eyre::eyre; +use color_eyre::eyre::{self}; use const_format::formatcp; mod execution_flamegraph_cmd; diff --git a/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs b/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs index 649331891a29..8ce9ba1de398 100644 --- a/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs +++ b/noir/noir-repo/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs @@ -160,7 +160,7 @@ mod tests { brillig::{BrilligBytecode, BrilligFunctionId}, }, }; - use color_eyre::eyre::{self}; + use color_eyre::eyre; use fm::codespan_files::Files; use noirc_artifacts::program::ProgramArtifact; use noirc_errors::debug_info::{DebugInfo, ProgramDebugInfo}; diff --git a/noir/noir-repo/tooling/profiler/src/errors.rs b/noir/noir-repo/tooling/profiler/src/errors.rs index 6a028931f5e4..951199436aa3 100644 --- a/noir/noir-repo/tooling/profiler/src/errors.rs +++ b/noir/noir-repo/tooling/profiler/src/errors.rs @@ -1,5 +1,5 @@ -use fm::FileMap; -use noirc_errors::{CustomDiagnostic, Location}; +use fm::{FileId, FileMap}; +use noirc_errors::CustomDiagnostic; use thiserror::Error; #[derive(Debug, Error)] @@ -9,8 +9,8 @@ pub(crate) enum CliError { } /// Report an error from the CLI that is not reliant on a stack trace. -pub(crate) fn report_error(message: String) -> Result<(), CliError> { - let error = CustomDiagnostic::simple_error(message.clone(), String::new(), Location::dummy()); +pub(crate) fn report_error(message: &str) -> Result<(), CliError> { + let error = CustomDiagnostic::from_message(message, FileId::dummy()); noirc_errors::reporter::report(&FileMap::default(), &error, false); Err(CliError::Generic) } diff --git a/noir/noir-repo/tooling/profiler/src/flamegraph.rs b/noir/noir-repo/tooling/profiler/src/flamegraph.rs index b56e8a43312b..16857eb2454c 100644 --- a/noir/noir-repo/tooling/profiler/src/flamegraph.rs +++ b/noir/noir-repo/tooling/profiler/src/flamegraph.rs @@ -3,7 +3,7 @@ use std::{collections::BTreeMap, io::BufWriter}; use acir::circuit::OpcodeLocation; use acir::circuit::brillig::BrilligFunctionId; -use color_eyre::eyre::{self}; +use color_eyre::eyre; use fm::codespan_files::Files; use fxhash::FxHashMap as HashMap; use inferno::flamegraph::{Options, TextTruncateDirection, from_lines}; diff --git a/noir/noir-repo/tooling/profiler/src/gates_provider.rs b/noir/noir-repo/tooling/profiler/src/gates_provider.rs index 3f07f3e4be6f..044e2a3642cd 100644 --- a/noir/noir-repo/tooling/profiler/src/gates_provider.rs +++ b/noir/noir-repo/tooling/profiler/src/gates_provider.rs @@ -1,7 +1,7 @@ use std::path::{Path, PathBuf}; use std::process::Command; -use color_eyre::eyre::{self}; +use color_eyre::eyre; use serde::{Deserialize, Serialize}; pub(crate) trait GatesProvider { diff --git a/noir/noir-repo/tooling/profiler/src/main.rs b/noir/noir-repo/tooling/profiler/src/main.rs index e4a5bc153d2e..1ac17617825c 100644 --- a/noir/noir-repo/tooling/profiler/src/main.rs +++ b/noir/noir-repo/tooling/profiler/src/main.rs @@ -22,18 +22,19 @@ fn main() { .with_span_events(FmtSpan::ACTIVE) .with_writer(debug_file) .with_ansi(false) - .with_env_filter(EnvFilter::from_default_env()) + .with_env_filter(EnvFilter::from_env("NOIR_LOG")) .init(); } else { tracing_subscriber::fmt() .with_span_events(FmtSpan::ACTIVE) + .with_writer(std::io::stderr) .with_ansi(true) .with_env_filter(EnvFilter::from_env("NOIR_LOG")) .init(); } if let Err(report) = cli::start_cli() { - eprintln!("{report}"); + eprintln!("{report:#}"); std::process::exit(1); } }