diff --git a/.eslintrc.js b/.eslintrc.js index b1346a8792f..f47f171561c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,19 +1,19 @@ module.exports = { root: true, - parser: "@typescript-eslint/parser", - plugins: ["@typescript-eslint", "prettier"], - extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint', 'prettier'], + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], rules: { - "comma-spacing": ["error", { before: false, after: true }], - "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": [ - "warn", // or "error" + 'comma-spacing': ['error', { before: false, after: true }], + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [ + 'warn', // or "error" { - argsIgnorePattern: "^_", - varsIgnorePattern: "^_", - caughtErrorsIgnorePattern: "^_", + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', }, ], - "prettier/prettier": "error", + 'prettier/prettier': 'error', }, }; diff --git a/.github/NIGHTLY_TEST_FAILURE.md b/.github/NIGHTLY_TEST_FAILURE.md index e86c01b25b7..05772d82a51 100644 --- a/.github/NIGHTLY_TEST_FAILURE.md +++ b/.github/NIGHTLY_TEST_FAILURE.md @@ -1,6 +1,6 @@ --- title: "nightly test-integration failed" -assignees: kobyhallx, phated, tomafrench, jonybur +assignees: kobyhallx, tomafrench, jonybur labels: bug --- diff --git a/.github/workflows/publish-abi_wasm.yml b/.github/workflows/publish-abi_wasm.yml index 1769b4d771c..feca0e58ff8 100644 --- a/.github/workflows/publish-abi_wasm.yml +++ b/.github/workflows/publish-abi_wasm.yml @@ -46,6 +46,6 @@ jobs: - name: Publish to npm working-directory: ./temp_publish_dir run: | - npm publish --tag latest + yarn npm publish --tag latest env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/publish-acvm-js.yml b/.github/workflows/publish-acvm-js.yml new file mode 100644 index 00000000000..b2145085a02 --- /dev/null +++ b/.github/workflows/publish-acvm-js.yml @@ -0,0 +1,54 @@ +name: Publish acvm_js + +on: + workflow_dispatch: + inputs: + acvm-ref: + description: The acvm reference to checkout + required: true + +jobs: + publish-acvm-js-package: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + ref: ${{ inputs.acvm-ref }} + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + registry-url: "https://registry.npmjs.org" + node-version: 18.15 + + - uses: cachix/install-nix-action@v22 + with: + nix_path: nixpkgs=channel:nixos-23.05 + github_access_token: ${{ secrets.GITHUB_TOKEN }} + + - uses: cachix/cachix-action@v12 + with: + name: barretenberg + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + + - name: Build acvm-js + working-directory: acvm-repo + run: | + nix build .# + + - name: Discover Build Output Path + working-directory: acvm-repo + run: echo "BUILD_OUTPUT_PATH=$(readlink -f ./result)" >> $GITHUB_ENV + + - name: Copy Build Output to Temporary Directory + working-directory: acvm-repo + run: | + mkdir temp_publish_dir + cp -r ${{ env.BUILD_OUTPUT_PATH }}/* temp_publish_dir/ + + - name: Publish to NPM + working-directory: ./acvm-repo/temp_publish_dir + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/publish-acvm.yml b/.github/workflows/publish-acvm.yml new file mode 100644 index 00000000000..3f4641a690c --- /dev/null +++ b/.github/workflows/publish-acvm.yml @@ -0,0 +1,72 @@ +name: Publish ACVM crates + +on: + workflow_dispatch: + inputs: + acvm-ref: + description: The acvm reference to checkout + required: true + +jobs: + publish: + name: Publish in order + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + ref: ${{ inputs.acvm-ref }} + + - name: Setup toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.66.0 + + # These steps are in a specific order so crate dependencies are updated first + - name: Publish acir_field + run: | + cargo publish --package acir_field + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.ACVM_CRATES_IO_TOKEN }} + + - name: Publish brillig + run: | + cargo publish --package brillig + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.ACVM_CRATES_IO_TOKEN }} + + - name: Publish acir + run: | + cargo publish --package acir + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.ACVM_CRATES_IO_TOKEN }} + + - name: Publish acvm_blackbox_solver + run: | + cargo publish --package acvm_blackbox_solver + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.ACVM_CRATES_IO_TOKEN }} + + - name: Publish barretenberg_blackbox_solver + run: | + cargo publish --package barretenberg_blackbox_solver + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.ACVM_CRATES_IO_TOKEN }} + + - name: Publish acvm_stdlib + run: | + cargo publish --package acvm_stdlib + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.ACVM_CRATES_IO_TOKEN }} + + - name: Publish brillig_vm + run: | + cargo publish --package brillig_vm + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.ACVM_CRATES_IO_TOKEN }} + + - name: Publish acvm + run: | + cargo publish --package acvm + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.ACVM_CRATES_IO_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish-nargo.yml similarity index 100% rename from .github/workflows/publish.yml rename to .github/workflows/publish-nargo.yml diff --git a/.github/workflows/noir-js.yml b/.github/workflows/publish-noir-js.yml similarity index 84% rename from .github/workflows/noir-js.yml rename to .github/workflows/publish-noir-js.yml index e8b210feb06..4614390e053 100644 --- a/.github/workflows/noir-js.yml +++ b/.github/workflows/publish-noir-js.yml @@ -23,11 +23,6 @@ jobs: source $HOME/.cargo/env cargo install -f wasm-bindgen-cli --version 0.2.86 - - name: Install toml2json - run: | - source $HOME/.cargo/env - cargo install toml2json - - name: Install wasm-opt run: | npm i wasm-opt -g @@ -47,6 +42,6 @@ jobs: - name: Publish to NPM working-directory: ./tooling/noir_js - run: npm publish --access public + run: yarn npm publish --access public env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release-noir-wasm.yml b/.github/workflows/publish-noir-wasm.yml similarity index 100% rename from .github/workflows/release-noir-wasm.yml rename to .github/workflows/publish-noir-wasm.yml diff --git a/.github/workflows/release-source-resolver.yml b/.github/workflows/publish-source-resolver.yml similarity index 80% rename from .github/workflows/release-source-resolver.yml rename to .github/workflows/publish-source-resolver.yml index aad9ec9618c..f1f7574cc2b 100644 --- a/.github/workflows/release-source-resolver.yml +++ b/.github/workflows/publish-source-resolver.yml @@ -1,11 +1,11 @@ -name: Release and Publish Source Resolver +name: Publish Source Resolver on: workflow_dispatch: jobs: release-source-resolver: - name: Release and Publish Source Resolver + name: Publish Source Resolver runs-on: ubuntu-latest steps: - name: Checkout @@ -19,6 +19,6 @@ jobs: - name: Publish to NPM working-directory: ./compiler/source-resolver - run: npm publish + run: yarn npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request-title.yml similarity index 100% rename from .github/workflows/pull-request.yml rename to .github/workflows/pull-request-title.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5842e041285..5a1aba712f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,7 +59,7 @@ jobs: - name: Dispatch to publish workflow uses: benc-uk/workflow-dispatch@v1 with: - workflow: publish.yml + workflow: publish-nargo.yml repo: noir-lang/noir ref: master token: ${{ secrets.GITHUB_TOKEN }} @@ -74,7 +74,7 @@ jobs: - name: Dispatch to noir_wasm uses: benc-uk/workflow-dispatch@v1 with: - workflow: release-noir-wasm.yml + workflow: publish-noir-wasm.yml ref: master token: ${{ secrets.NOIR_REPO_TOKEN }} @@ -87,7 +87,7 @@ jobs: - name: Dispatch to noir_wasm uses: benc-uk/workflow-dispatch@v1 with: - workflow: noir-js.yml + workflow: publish-noir-js.yml ref: master token: ${{ secrets.NOIR_REPO_TOKEN }} @@ -100,11 +100,11 @@ jobs: - name: Dispatch to source resolver uses: benc-uk/workflow-dispatch@v1 with: - workflow: release-source-resolver.yml + workflow: publish-source-resolver.yml ref: master token: ${{ secrets.NOIR_REPO_TOKEN }} - dispatch-publish-abi-wasm: + publish-abi-wasm: name: Dispatch to publish-abi_wasm workflow needs: [release-please] if: ${{ needs.release-please.outputs.tag-name }} @@ -117,3 +117,29 @@ jobs: repo: ${{ github.repository }} token: ${{ secrets.GITHUB_TOKEN }} inputs: '{ "noir-ref": "${{ needs.release-please.outputs.tag-name }}" }' + + publish-acvm: + name: Publish crates + needs: [release-please] + if: ${{ needs.release-please.outputs.tag-name }} + runs-on: ubuntu-latest + steps: + - name: Dispatch to publish workflow + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: publish-acvm.yml + ref: master + inputs: '{ "acvm-ref": "${{ needs.release-please.outputs.tag-name }}" }' + + publish-acvm-js: + name: Dispatch to publish-acvm-js workflow + needs: [release-please] + if: ${{ needs.release-please.outputs.tag-name }} + runs-on: ubuntu-latest + steps: + - name: Trigger publish-acvm-js.yml workflow + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: publish-acvm-js.yml + ref: master + inputs: '{ "acvm-ref": "${{ needs.release-please.outputs.tag-name }}" }' diff --git a/.github/workflows/abi_wasm.yml b/.github/workflows/test-abi_wasm.yml similarity index 56% rename from .github/workflows/abi_wasm.yml rename to .github/workflows/test-abi_wasm.yml index fbbfeba2331..7fecb66fd7f 100644 --- a/.github/workflows/abi_wasm.yml +++ b/.github/workflows/test-abi_wasm.yml @@ -22,27 +22,16 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - - uses: cachix/install-nix-action@v20 + - name: Setup Nix + uses: cachix/install-nix-action@v22 with: nix_path: nixpkgs=channel:nixos-23.05 github_access_token: ${{ secrets.GITHUB_TOKEN }} - - name: Restore nix store cache - uses: actions/cache/restore@v3 - id: cache + - uses: cachix/cachix-action@v12 with: - path: ${{ env.CACHED_PATH }} - key: ${{ runner.os }}-flake-abi-wasm-${{ hashFiles('*.lock') }} - - # Based on https://github.com/marigold-dev/deku/blob/b5016f0cf4bf6ac48db9111b70dd7fb49b969dfd/.github/workflows/build.yml#L26 - - name: Copy cache into nix store - if: steps.cache.outputs.cache-hit == 'true' - # We don't check the signature because we're the one that created the cache - run: | - for narinfo in ${{ env.CACHED_PATH }}/*.narinfo; do - path=$(head -n 1 "$narinfo" | awk '{print $2}') - nix copy --no-check-sigs --from "file://${{ env.CACHED_PATH }}" "$path" - done + name: barretenberg + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Build noirc_abi_wasm run: | @@ -50,18 +39,6 @@ jobs: cp -r ./result/noirc_abi_wasm/nodejs ./tooling/noirc_abi_wasm cp -r ./result/noirc_abi_wasm/web ./tooling/noirc_abi_wasm - - name: Export cache from nix store - if: ${{ steps.cache.outputs.cache-hit != 'true' && github.event_name != 'merge_group' }} - run: | - nix copy --to "file://${{ env.CACHED_PATH }}?compression=zstd¶llel-compression=true" .#noirc-abi-wasm-cargo-artifacts - - - uses: actions/cache/save@v3 - # Don't create cache entries for the merge queue. - if: ${{ steps.cache.outputs.cache-hit != 'true' && github.event_name != 'merge_group' }} - with: - path: ${{ env.CACHED_PATH }} - key: ${{ steps.cache.outputs.cache-primary-key }} - - name: Dereference symlink run: echo "UPLOAD_PATH=$(readlink -f ./result/noirc_abi_wasm)" >> $GITHUB_ENV diff --git a/.github/workflows/test-acvm-js.yml b/.github/workflows/test-acvm-js.yml new file mode 100644 index 00000000000..8a469f6ff1e --- /dev/null +++ b/.github/workflows/test-acvm-js.yml @@ -0,0 +1,97 @@ +name: Test acvm_js + +on: [push, pull_request] + +# This will cancel previous runs when a branch or PR is updated +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref || github.run_id }} + cancel-in-progress: true + +jobs: + build-acvm-js-package: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - uses: cachix/install-nix-action@v20 + with: + nix_path: nixpkgs=channel:nixos-22.11 + github_access_token: ${{ secrets.GITHUB_TOKEN }} + + - uses: cachix/cachix-action@v12 + with: + name: barretenberg + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + + - name: Build acvm-js + run: | + nix build -L .#acvm_js + + - name: Dereference symlink + run: echo "UPLOAD_PATH=$(readlink -f result)" >> $GITHUB_ENV + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: acvm-js + path: ${{ env.UPLOAD_PATH }} + retention-days: 3 + + test-acvm_js-node: + needs: [build-acvm-js-package] + name: Node.js Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: acvm-js + path: ./result + + - name: Move build artifacts + run: | + mv ./result/acvm_js/nodejs ./acvm-repo/acvm_js/nodejs + mv ./result/acvm_js/web ./acvm-repo/acvm_js/web + + - name: Set up test environment + uses: ./.github/actions/setup + + - name: Run node tests + run: yarn workspace @noir-lang/acvm_js test + + test-acvm_js-browser: + needs: [build-acvm-js-package] + name: Browser Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: acvm-js + path: ./result + + - name: Move build artifacts + run: | + mv ./result/acvm_js/nodejs ./acvm-repo/acvm_js/nodejs + mv ./result/acvm_js/web ./acvm-repo/acvm_js/web + + - name: Set up test environment + uses: ./.github/actions/setup + + - name: Install playwright deps + run: | + npx playwright install + npx playwright install-deps + + - name: Run browser tests + working-directory: ./acvm-repo/acvm_js + run: yarn workspace @noir-lang/acvm_js test:browser diff --git a/.github/workflows/test-integration.yml b/.github/workflows/test-integration.yml index 3680a1661d8..4de55ad3f79 100644 --- a/.github/workflows/test-integration.yml +++ b/.github/workflows/test-integration.yml @@ -8,21 +8,99 @@ on: schedule: - cron: "0 2 * * *" # Run nightly at 2 AM UTC +env: + CI: true + jobs: - wasm-packages-build-test: + build-nargo: + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64-unknown-linux-gnu] + + steps: + - name: Checkout Noir repo + uses: actions/checkout@v4 + + - name: Setup toolchain + uses: dtolnay/rust-toolchain@1.66.0 + + - uses: Swatinem/rust-cache@v2 + with: + key: ${{ matrix.target }} + cache-on-failure: true + save-if: ${{ github.event_name != 'merge_group' }} + + - name: Build Nargo + run: cargo build --package nargo_cli --release + + - name: Package artifacts + run: | + mkdir dist + cp ./target/release/nargo ./dist/nargo + 7z a -ttar -so -an ./dist/* | 7z a -si ./nargo-x86_64-unknown-linux-gnu.tar.gz + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: nargo + path: ./dist/* + retention-days: 3 + + build-wasm: runs-on: ubuntu-latest env: CACHED_PATH: /tmp/nix-cache steps: - - name: Checkout noir sources + - name: Checkout sources uses: actions/checkout@v4 - - name: Checkout acvm sources - uses: actions/checkout@v3 # v3 is needed here otherwise this fails in local execution + - name: Setup Nix + uses: cachix/install-nix-action@v22 + with: + nix_path: nixpkgs=channel:nixos-23.05 + github_access_token: ${{ secrets.GITHUB_TOKEN }} + + - uses: cachix/cachix-action@v12 + with: + name: barretenberg + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + + - name: Build wasm package + run: | + nix build -L .#noir_wasm + + - name: Export cache from nix store + if: ${{ steps.cache.outputs.cache-hit != 'true' && github.event_name != 'merge_group' }} + run: | + nix copy --to "file://${{ env.CACHED_PATH }}?compression=zstd¶llel-compression=true" .#noir-wasm-cargo-artifacts + + - uses: actions/cache/save@v3 + # Don't create cache entries for the merge queue. + if: ${{ steps.cache.outputs.cache-hit != 'true' && github.event_name != 'merge_group' }} + with: + path: ${{ env.CACHED_PATH }} + key: ${{ steps.cache.outputs.cache-primary-key }} + + - name: Dereference symlink + run: echo "UPLOAD_PATH=$(readlink -f ./result/noir_wasm)" >> $GITHUB_ENV + + - name: Upload artifact + uses: actions/upload-artifact@v3 with: - repository: noir-lang/acvm - path: acvm + name: noir_wasm + path: ${{ env.UPLOAD_PATH }} + retention-days: 3 + + build-noirc: + runs-on: ubuntu-latest + env: + CACHED_PATH: /tmp/nix-cache + + steps: + - name: Checkout sources + uses: actions/checkout@v3 - name: Setup Nix uses: cachix/install-nix-action@v22 @@ -40,7 +118,7 @@ jobs: id: cache with: path: ${{ env.CACHED_PATH }} - key: ${{ runner.os }}-flake-wasm-${{ hashFiles('*.lock') }} + key: ${{ runner.os }}-flake-abi-wasm-${{ hashFiles('*.lock') }} # Based on https://github.com/marigold-dev/deku/blob/b5016f0cf4bf6ac48db9111b70dd7fb49b969dfd/.github/workflows/build.yml#L26 - name: Copy cache into nix store @@ -52,40 +130,98 @@ jobs: nix copy --no-check-sigs --from "file://${{ env.CACHED_PATH }}" "$path" done - - name: Build noir_wasm package - run: | - nix build -L .#noir_wasm - echo "UPLOAD_PATH=$(readlink -f ./result/noir_wasm)" >> $GITHUB_ENV - cp -r ./result/noir_wasm/nodejs ./compiler/wasm - cp -r ./result/noir_wasm/web ./compiler/wasm - - - name: Upload `noir_wasm` artifact - uses: actions/upload-artifact@v3 - with: - name: noir_wasm - path: ${{ env.UPLOAD_PATH }} - retention-days: 3 - - - name: Build noirc_abi_wasm package + - name: Build noirc_abi_wasm run: | nix build -L .#noirc_abi_wasm - echo "UPLOAD_PATH=$(readlink -f ./result/noirc_abi_wasm)" >> $GITHUB_ENV cp -r ./result/noirc_abi_wasm/nodejs ./tooling/noirc_abi_wasm cp -r ./result/noirc_abi_wasm/web ./tooling/noirc_abi_wasm - - name: Upload `noirc_abi_wasm` artifact + - name: Export cache from nix store + if: ${{ steps.cache.outputs.cache-hit != 'true' && github.event_name != 'merge_group' }} + run: | + nix copy --to "file://${{ env.CACHED_PATH }}?compression=zstd¶llel-compression=true" .#noirc-abi-wasm-cargo-artifacts + + - uses: actions/cache/save@v3 + # Don't create cache entries for the merge queue. + if: ${{ steps.cache.outputs.cache-hit != 'true' && github.event_name != 'merge_group' }} + with: + path: ${{ env.CACHED_PATH }} + key: ${{ steps.cache.outputs.cache-primary-key }} + + - name: Dereference symlink + run: echo "UPLOAD_PATH=$(readlink -f ./result/noirc_abi_wasm)" >> $GITHUB_ENV + + - name: Upload artifact uses: actions/upload-artifact@v3 with: name: noirc_abi_wasm path: ${{ env.UPLOAD_PATH }} - retention-days: 3 + retention-days: 10 + + test-solidity-verifier: + runs-on: ubuntu-latest + needs: [build-wasm, build-nargo, build-noirc] + env: + CACHED_PATH: /tmp/nix-cache + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download nargo binary + uses: actions/download-artifact@v3 + with: + name: nargo + path: ./nargo + + - name: Download wasm package artifact + uses: actions/download-artifact@v3 + with: + name: noir_wasm + path: ./compiler/wasm + + - name: Download noirc package artifact + uses: actions/download-artifact@v3 + with: + name: noirc_abi_wasm + path: ./tooling/noirc_abi_wasm + + - name: Set nargo on PATH + run: | + nargo_binary="${{ github.workspace }}/nargo/nargo" + chmod +x $nargo_binary + echo "$(dirname $nargo_binary)" >> $GITHUB_PATH + export PATH="$PATH:$(dirname $nargo_binary)" + nargo -V - name: Install Yarn dependencies uses: ./.github/actions/setup + - name: Install jq + run: sudo apt-get install jq + + - name: Install wasm-bindgen-cli + uses: taiki-e/install-action@v2 + with: + tool: wasm-bindgen-cli@0.2.86 + + - name: Install wasm-opt + run: | + npm i wasm-opt -g + + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + with: + version: nightly + + - name: Deploy verifier contracts to Anvil + working-directory: compiler/integration-tests + run: bash ./scripts/setup-project.sh + - name: Setup `integration-tests` run: | yarn workspace @noir-lang/source-resolver build + yarn workspace @noir-lang/acvm_js build yarn workspace @noir-lang/noir_js build - name: Run `integration-tests` diff --git a/.github/workflows/test-noir-js.yml b/.github/workflows/test-noir-js.yml new file mode 100644 index 00000000000..1dac0200027 --- /dev/null +++ b/.github/workflows/test-noir-js.yml @@ -0,0 +1,54 @@ +name: Noir JS + +on: + pull_request: + merge_group: + push: + branches: + - master + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Yarn dependencies + uses: ./.github/actions/setup + + - name: Setup toolchain + uses: dtolnay/rust-toolchain@1.66.0 + with: + targets: wasm32-unknown-unknown + + - uses: Swatinem/rust-cache@v2 + with: + key: wasm32-unknown-unknown-noir-js + cache-on-failure: true + save-if: ${{ github.event_name != 'merge_group' }} + + - name: Install jq + run: sudo apt-get install jq + + - name: Install wasm-bindgen-cli + uses: taiki-e/install-action@v2 + with: + tool: wasm-bindgen-cli@0.2.86 + + - name: Install wasm-opt + run: | + npm i wasm-opt -g + + - name: Build acvm_js + run: yarn workspace @noir-lang/acvm_js build + + - name: Build noirc_abi + run: yarn workspace @noir-lang/noirc_abi build + + - name: Run noir_js tests + run: | + yarn workspace @noir-lang/noir_js build + yarn workspace @noir-lang/noir_js test diff --git a/.github/workflows/wasm.yml b/.github/workflows/test-noir_wasm.yml similarity index 69% rename from .github/workflows/wasm.yml rename to .github/workflows/test-noir_wasm.yml index 6263fb6e915..720e76c2abe 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/test-noir_wasm.yml @@ -59,42 +59,18 @@ jobs: - name: Setup Nix uses: cachix/install-nix-action@v22 with: - nix_path: nixpkgs=channel:nixos-22.11 + nix_path: nixpkgs=channel:nixos-23.05 github_access_token: ${{ secrets.GITHUB_TOKEN }} - - name: Restore nix store cache - uses: actions/cache/restore@v3 - id: cache + - uses: cachix/cachix-action@v12 with: - path: ${{ env.CACHED_PATH }} - key: ${{ runner.os }}-flake-wasm-${{ hashFiles('*.lock') }} - - # Based on https://github.com/marigold-dev/deku/blob/b5016f0cf4bf6ac48db9111b70dd7fb49b969dfd/.github/workflows/build.yml#L26 - - name: Copy cache into nix store - if: steps.cache.outputs.cache-hit == 'true' - # We don't check the signature because we're the one that created the cache - run: | - for narinfo in ${{ env.CACHED_PATH }}/*.narinfo; do - path=$(head -n 1 "$narinfo" | awk '{print $2}') - nix copy --no-check-sigs --from "file://${{ env.CACHED_PATH }}" "$path" - done + name: barretenberg + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Build wasm package run: | nix build -L .#noir_wasm - - name: Export cache from nix store - if: ${{ steps.cache.outputs.cache-hit != 'true' && github.event_name != 'merge_group' }} - run: | - nix copy --to "file://${{ env.CACHED_PATH }}?compression=zstd¶llel-compression=true" .#noir-wasm-cargo-artifacts - - - uses: actions/cache/save@v3 - # Don't create cache entries for the merge queue. - if: ${{ steps.cache.outputs.cache-hit != 'true' && github.event_name != 'merge_group' }} - with: - path: ${{ env.CACHED_PATH }} - key: ${{ steps.cache.outputs.cache-primary-key }} - - name: Dereference symlink run: echo "UPLOAD_PATH=$(readlink -f ./result/noir_wasm)" >> $GITHUB_ENV diff --git a/.github/workflows/test.yml b/.github/workflows/test-rust-workspace.yml similarity index 100% rename from .github/workflows/test.yml rename to .github/workflows/test-rust-workspace.yml diff --git a/.github/workflows/track-acvm.yml b/.github/workflows/track-acvm.yml deleted file mode 100644 index 6d02fdc2f1a..00000000000 --- a/.github/workflows/track-acvm.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Track ACVM for Integration Tests - -on: - pull_request: - paths: - - "Cargo.lock" - - "yarn.lock" - -jobs: - check_matching_version: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Check acvm version change - run: | - # Extract acvm version from the current Cargo.lock - CURRENT_VERSION=$(awk '/name = "acvm"/ {getline; print $3}' Cargo.lock | tr -d '"') - INTEGRATION_TEST_VERSION=$(yarn workspace @noir-lang/noir_js info @noir-lang/acvm_js --json | jq .children.Version | tr -d '"') - - echo "Current ACVM Version: $CURRENT_VERSION" - echo "Integration Test ACVM Version (Noir JS): $INTEGRATION_TEST_VERSION" - - if [ "$CURRENT_VERSION" != "$INTEGRATION_TEST_VERSION" ]; then - exit 1 - else - echo "ACVM version is a match." - fi diff --git a/.gitignore b/.gitignore index e36a7458e7b..94e8f1a8db0 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ compiler/wasm/nodejs compiler/wasm/web tooling/noirc_abi_wasm/nodejs tooling/noirc_abi_wasm/web +tooling/noir_js/lib diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 8032c17e87a..77620acf99b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,4 @@ { - ".": "0.12.0" -} + ".": "0.15.0", + "acvm-repo": "0.27.4" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 749c7cfd0a9..dea0d8c70b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,104 @@ # Changelog +## [0.15.0](https://github.com/noir-lang/noir/compare/v0.14.1...v0.15.0) (2023-09-28) + + +### ⚠ BREAKING CHANGES + +* update to `bb` version 0.7.3 ([#2729](https://github.com/noir-lang/noir/issues/2729)) + +### Features + +* Contract events in artifacts ([#2873](https://github.com/noir-lang/noir/issues/2873)) ([4765c82](https://github.com/noir-lang/noir/commit/4765c8288c583a61a81ff97eea1ef49df13eeca0)) + + +### Bug Fixes + +* Finer bit size in bound constrain ([#2869](https://github.com/noir-lang/noir/issues/2869)) ([68385e2](https://github.com/noir-lang/noir/commit/68385e294a1501b19b28f3f5510e973283ed0821)) + + +### Miscellaneous Chores + +* Update to `bb` version 0.7.3 ([#2729](https://github.com/noir-lang/noir/issues/2729)) ([fce68d1](https://github.com/noir-lang/noir/commit/fce68d1404ae66bd7a71417d791dd70545bf24f2)) + +## [0.14.1](https://github.com/noir-lang/noir/compare/v0.14.0...v0.14.1) (2023-09-27) + + +### Bug Fixes + +* Remove cast for field comparisons in brillig ([#2874](https://github.com/noir-lang/noir/issues/2874)) ([1fc1fdb](https://github.com/noir-lang/noir/commit/1fc1fdb4e15d2ce625ea79d458c5346fab418e49)) +* Remove duplication of code to load stdlib files ([#2868](https://github.com/noir-lang/noir/issues/2868)) ([b694aab](https://github.com/noir-lang/noir/commit/b694aab87c4665a3a89715c9d4096eeb3efb9944)) + +## [0.14.0](https://github.com/noir-lang/noir/compare/v0.13.0...v0.14.0) (2023-09-26) + + +### ⚠ BREAKING CHANGES + +* **noir_js:** Rename inner and outer proof methods ([#2845](https://github.com/noir-lang/noir/issues/2845)) +* `generateWitness` now returns a serialized witness file ([#2842](https://github.com/noir-lang/noir/issues/2842)) +* Issue an error when a module is declared twice & fix module search path ([#2801](https://github.com/noir-lang/noir/issues/2801)) +* Default integers to u64 ([#2764](https://github.com/noir-lang/noir/issues/2764)) + +### Features + +* **abi:** Throw errors rather than returning string from `noirc_abi_wasm` ([#2817](https://github.com/noir-lang/noir/issues/2817)) ([df7b42c](https://github.com/noir-lang/noir/commit/df7b42cd253d1b908a42c367b116813f9999d93b)) +* **acir:** Enable dynamic indices on non-homogenous arrays ([#2703](https://github.com/noir-lang/noir/issues/2703)) ([622d2e4](https://github.com/noir-lang/noir/commit/622d2e436992c23e6d0885b591bd1072ca57b307)) +* Default integers to u64 ([#2764](https://github.com/noir-lang/noir/issues/2764)) ([01cb041](https://github.com/noir-lang/noir/commit/01cb041a92ef6043dd5a160e0a56a63400801980)) +* Implement string escape sequences ([#2803](https://github.com/noir-lang/noir/issues/2803)) ([f7529b8](https://github.com/noir-lang/noir/commit/f7529b80f0958fd47a525f25a123f16438bbb892)) +* Remove redundant predicate from brillig quotients ([#2784](https://github.com/noir-lang/noir/issues/2784)) ([a8f18c5](https://github.com/noir-lang/noir/commit/a8f18c55b35f47c6fa3ebfebcd827aeb55e5c850)) +* **traits:** Implement trait bounds typechecker + monomorphizer passes ([#2717](https://github.com/noir-lang/noir/issues/2717)) ([5ca99b1](https://github.com/noir-lang/noir/commit/5ca99b128e9991b5272c00292208d85415e70edf)) + + +### Bug Fixes + +* **acvm:** Return false rather than panicking on invalid ECDSA signatures ([#2783](https://github.com/noir-lang/noir/issues/2783)) ([155abc0](https://github.com/noir-lang/noir/commit/155abc0d99fff41c79163c16bf297d41e5dff0fa)) +* Conditionally run the "Create or Update PR" step in acir artifacts rebuild workflow ([#2849](https://github.com/noir-lang/noir/issues/2849)) ([63da875](https://github.com/noir-lang/noir/commit/63da875a85a2ad4ad3038443ba52eb28ea44ad10)) +* Error message for assigning the wrong type is backwards [#2804](https://github.com/noir-lang/noir/issues/2804) ([#2805](https://github.com/noir-lang/noir/issues/2805)) ([b2d62bf](https://github.com/noir-lang/noir/commit/b2d62bff3b7958b3ed62c285a7ebd45045ac2e05)) +* Fix panic in some cases when calling a private function ([#2799](https://github.com/noir-lang/noir/issues/2799)) ([078d5df](https://github.com/noir-lang/noir/commit/078d5df691d4ea48e83c9530cd40b64917eba0a7)) +* Fix subtract with underflow in flattening pass ([#2796](https://github.com/noir-lang/noir/issues/2796)) ([f2ed505](https://github.com/noir-lang/noir/commit/f2ed5054b0b0335dd3ecb17369b0d2e6eafb1171)) +* **frontend:** Error on unsupported integer annotation ([#2778](https://github.com/noir-lang/noir/issues/2778)) ([90c3d8b](https://github.com/noir-lang/noir/commit/90c3d8baa3b7ae10bc99f6a767121f556ff75967)) +* Issue an error when a module is declared twice & fix module search path ([#2801](https://github.com/noir-lang/noir/issues/2801)) ([7f76910](https://github.com/noir-lang/noir/commit/7f76910ebbd20e3d7a1db7541f2b7f43cd9b546d)) +* Lack of cjs package version ([#2848](https://github.com/noir-lang/noir/issues/2848)) ([adc2d59](https://github.com/noir-lang/noir/commit/adc2d597536b52c690dceb14ea5f8e30a493452c)) +* Silence unused variable warnings in stdlib ([#2795](https://github.com/noir-lang/noir/issues/2795)) ([5747bfe](https://github.com/noir-lang/noir/commit/5747bfed256f9179321ec0bd1e02f5f82723a4c7)) +* Split conditional_regression tests ([#2774](https://github.com/noir-lang/noir/issues/2774)) ([8ed8832](https://github.com/noir-lang/noir/commit/8ed8832c7b475cd28ae697a09f1ad07c539736db)) +* **ssa:** Do not replace previously constrained values ([#2647](https://github.com/noir-lang/noir/issues/2647)) ([d528844](https://github.com/noir-lang/noir/commit/d5288449a10d162a0340818a6beab54dd985a11a)) + + +### Miscellaneous Chores + +* `generateWitness` now returns a serialized witness file ([#2842](https://github.com/noir-lang/noir/issues/2842)) ([57d3f37](https://github.com/noir-lang/noir/commit/57d3f376d9ceadb75caf37a2bfc0e9394f76bfe6)) +* **noir_js:** Rename inner and outer proof methods ([#2845](https://github.com/noir-lang/noir/issues/2845)) ([71dbbb8](https://github.com/noir-lang/noir/commit/71dbbb863a6f262da4804c17965ace627bf3a278)) + +## [0.13.0](https://github.com/noir-lang/noir/compare/v0.12.0...v0.13.0) (2023-09-21) + + +### ⚠ BREAKING CHANGES + +* constrain is now a hard error ([#2758](https://github.com/noir-lang/noir/issues/2758)) + +### Features + +* Add `pub` modifier ([#2754](https://github.com/noir-lang/noir/issues/2754)) ([dda964e](https://github.com/noir-lang/noir/commit/dda964e82e170a59c328908117677c16f691be7b)) +* Add support for attributes on structs ([#2733](https://github.com/noir-lang/noir/issues/2733)) ([7b3df8e](https://github.com/noir-lang/noir/commit/7b3df8e8be11fe4288ed865951ef88566160f4af)) +* Add wrapping functions in stdlib and use them in relevant test cases ([#2725](https://github.com/noir-lang/noir/issues/2725)) ([49ab121](https://github.com/noir-lang/noir/commit/49ab121ef21819e028d407999a689b92c67d8df7)) +* **aztec-noir:** Abstract storage ([#2750](https://github.com/noir-lang/noir/issues/2750)) ([5481344](https://github.com/noir-lang/noir/commit/5481344feaa0403e1f6a499ff1e8e4dbbd0297aa)) +* Constrain is now a hard error ([#2758](https://github.com/noir-lang/noir/issues/2758)) ([388a2b1](https://github.com/noir-lang/noir/commit/388a2b1659b2a07bde1bc376fc4669f855780858)) +* Refine Noir.js API ([#2732](https://github.com/noir-lang/noir/issues/2732)) ([e79f1ed](https://github.com/noir-lang/noir/commit/e79f1ed357bf7002f14001689fb4b33e0346e679)) +* Short-circuit compilation and read build artifacts from file if program is unchanged ([#2743](https://github.com/noir-lang/noir/issues/2743)) ([87fea4b](https://github.com/noir-lang/noir/commit/87fea4b447596bdd11ab461f847e03d4f1cc45f2)) +* Signed arithmetic ([#2748](https://github.com/noir-lang/noir/issues/2748)) ([a84216d](https://github.com/noir-lang/noir/commit/a84216dd23513b008739ae0a749e48d0dd262a28)) +* **traits:** Implement trait bounds def collector + resolver passes ([#2716](https://github.com/noir-lang/noir/issues/2716)) ([e3d18bb](https://github.com/noir-lang/noir/commit/e3d18bb9889d84fa78eecf3783bac446eac5adef)) +* **traits:** Type checking for Trait impl method signatures ([#2652](https://github.com/noir-lang/noir/issues/2652)) ([8617008](https://github.com/noir-lang/noir/commit/8617008d572c22fd9c830c233bfc0088fe0bafe4)) +* Variable liveness analysis for brillig ([#2715](https://github.com/noir-lang/noir/issues/2715)) ([ddb05ab](https://github.com/noir-lang/noir/commit/ddb05ab8d30ea2b60c06f3cd7d36d5bf1b21b3ef)) + + +### Bug Fixes + +* Add error message for a contract package with no contracts ([#2762](https://github.com/noir-lang/noir/issues/2762)) ([9701a0c](https://github.com/noir-lang/noir/commit/9701a0cc2cde3b3e8fa55c3f8d09343f8861f2f8)) +* Check for literal overflows in expressions ([#2742](https://github.com/noir-lang/noir/issues/2742)) ([4009f30](https://github.com/noir-lang/noir/commit/4009f30e18b17b5e7ef5af324bb9eaea5ed3780a)) +* Keep the correct type for bitshift ([#2739](https://github.com/noir-lang/noir/issues/2739)) ([04fc2ea](https://github.com/noir-lang/noir/commit/04fc2ea5bc2490cdd2cb4ec90e34986fa91f43d4)) +* Make `Vec::get` accept immutable `Vec`s ([#2776](https://github.com/noir-lang/noir/issues/2776)) ([f168a54](https://github.com/noir-lang/noir/commit/f168a5407b303d2e13d5975e9dc18ec13ff68c5f)) +* Nightly js test ([#2740](https://github.com/noir-lang/noir/issues/2740)) ([36dcd48](https://github.com/noir-lang/noir/commit/36dcd4883313faabefe201be3645dcad79dc7970)) + ## [0.12.0](https://github.com/noir-lang/noir/compare/v0.11.1...v0.12.0) (2023-09-15) diff --git a/Cargo.lock b/Cargo.lock index 0242c8ef7ba..baf896afd3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,24 +4,24 @@ version = 3 [[package]] name = "acir" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8abf742e547b620f91166bd85f4925516f6841b73c7307045abdf7dd7b0c7843" +version = "0.27.4" dependencies = [ "acir_field", "bincode", "brillig", "flate2", "serde", + "serde_json", + "strum", + "strum_macros", "thiserror", ] [[package]] name = "acir_field" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6d939739bb4876374058e0705e93c64c83ece679c5c29a8d3a514061cea728" +version = "0.27.4" dependencies = [ + "ark-bls12-381", "ark-bn254", "ark-ff", "cfg-if", @@ -32,9 +32,7 @@ dependencies = [ [[package]] name = "acvm" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6782bb77fa0b3d38dd3aaa29c64c3a013a2634ddbd095668da642e747619a2e" +version = "0.27.4" dependencies = [ "acir", "acvm_blackbox_solver", @@ -43,14 +41,15 @@ dependencies = [ "indexmap 1.9.3", "num-bigint", "num-traits", + "paste", + "proptest", + "rand", "thiserror", ] [[package]] name = "acvm_blackbox_solver" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f11d9b69669e0be6f0bfc33a0e604dd9c852b128d3d52fa815259470debc75" +version = "0.27.4" dependencies = [ "acir", "blake2", @@ -61,11 +60,30 @@ dependencies = [ "thiserror", ] +[[package]] +name = "acvm_js" +version = "0.27.4" +dependencies = [ + "acvm", + "barretenberg_blackbox_solver", + "build-data", + "cfg-if", + "console_error_panic_hook", + "const-str", + "gloo-utils", + "js-sys", + "log", + "pkg-config", + "serde", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "wasm-logger", +] + [[package]] name = "acvm_stdlib" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d6fffcc2d449d50604843fecc909fd2183fb04642d943ddcd3cb08e13b3450" +version = "0.27.4" dependencies = [ "acir", ] @@ -189,11 +207,23 @@ dependencies = [ [[package]] name = "arena" -version = "0.12.0" +version = "0.15.0" dependencies = [ "generational-arena", ] +[[package]] +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", +] + [[package]] name = "ark-bn254" version = "0.4.0" @@ -409,9 +439,7 @@ dependencies = [ [[package]] name = "barretenberg_blackbox_solver" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04b1d6e25d3081809c6092f4ca14fc0c4eab716384c8e474fc4e9326d7a9a9" +version = "0.27.4" dependencies = [ "acir", "acvm_blackbox_solver", @@ -465,6 +493,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -518,9 +561,7 @@ dependencies = [ [[package]] name = "brillig" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e297d6951ff2e18f927cbb85b655efd7320ae60b641db194ede29ac503693c7f" +version = "0.27.4" dependencies = [ "acir_field", "serde", @@ -528,9 +569,7 @@ dependencies = [ [[package]] name = "brillig_vm" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72120fc18f1749dac7033cd350ca9cb970f12a00d3cd561dea3fd654cf94cc9" +version = "0.27.4" dependencies = [ "acir", "acvm_blackbox_solver", @@ -834,6 +873,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +[[package]] +name = "const-str" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aca749d3d3f5b87a0d6100509879f9cf486ab510803a4a4e1001da1ff61c2bd6" + [[package]] name = "const_format" version = "0.2.31" @@ -1457,7 +1502,7 @@ dependencies = [ [[package]] name = "fm" -version = "0.12.0" +version = "0.15.0" dependencies = [ "cfg-if", "codespan-reporting", @@ -2002,7 +2047,7 @@ dependencies = [ [[package]] name = "iter-extended" -version = "0.12.0" +version = "0.15.0" [[package]] name = "itertools" @@ -2067,6 +2112,12 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -2197,7 +2248,7 @@ checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" [[package]] name = "nargo" -version = "0.12.0" +version = "0.15.0" dependencies = [ "acvm", "base64", @@ -2216,7 +2267,7 @@ dependencies = [ [[package]] name = "nargo_cli" -version = "0.12.0" +version = "0.15.0" dependencies = [ "acvm", "assert_cmd", @@ -2262,7 +2313,7 @@ dependencies = [ [[package]] name = "nargo_toml" -version = "0.12.0" +version = "0.15.0" dependencies = [ "dirs", "fm", @@ -2288,7 +2339,7 @@ dependencies = [ [[package]] name = "noir_lsp" -version = "0.12.0" +version = "0.15.0" dependencies = [ "acvm", "async-lsp", @@ -2311,7 +2362,7 @@ dependencies = [ [[package]] name = "noir_lsp_wasm" -version = "0.12.0" +version = "0.15.0" dependencies = [ "acvm", "async-lsp", @@ -2323,7 +2374,7 @@ dependencies = [ [[package]] name = "noir_wasm" -version = "0.12.0" +version = "0.15.0" dependencies = [ "acvm", "build-data", @@ -2342,7 +2393,7 @@ dependencies = [ [[package]] name = "noirc_abi" -version = "0.12.0" +version = "0.15.0" dependencies = [ "acvm", "iter-extended", @@ -2359,7 +2410,7 @@ dependencies = [ [[package]] name = "noirc_abi_wasm" -version = "0.12.0" +version = "0.15.0" dependencies = [ "acvm", "build-data", @@ -2376,7 +2427,7 @@ dependencies = [ [[package]] name = "noirc_driver" -version = "0.12.0" +version = "0.15.0" dependencies = [ "acvm", "base64", @@ -2392,7 +2443,7 @@ dependencies = [ [[package]] name = "noirc_errors" -version = "0.12.0" +version = "0.15.0" dependencies = [ "acvm", "chumsky", @@ -2405,7 +2456,7 @@ dependencies = [ [[package]] name = "noirc_evaluator" -version = "0.12.0" +version = "0.15.0" dependencies = [ "acvm", "fxhash", @@ -2420,7 +2471,7 @@ dependencies = [ [[package]] name = "noirc_frontend" -version = "0.12.0" +version = "0.15.0" dependencies = [ "acvm", "arena", @@ -2441,7 +2492,7 @@ dependencies = [ [[package]] name = "noirc_printable_type" -version = "0.12.0" +version = "0.15.0" dependencies = [ "acvm", "iter-extended", @@ -2495,6 +2546,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2753,6 +2805,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +dependencies = [ + "bit-set", + "bitflags 1.3.2", + "byteorder", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.6.29", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "ptr_meta" version = "0.1.4" @@ -2773,6 +2845,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quick-xml" version = "0.26.0" @@ -2803,6 +2881,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", "rand_chacha", "rand_core", ] @@ -2826,6 +2905,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + [[package]] name = "rand_xoshiro" version = "0.6.0" @@ -2907,7 +2995,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax", + "regex-syntax 0.7.4", ] [[package]] @@ -2918,9 +3006,15 @@ checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.4", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.7.4" @@ -3172,6 +3266,18 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.15" @@ -3955,6 +4061,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-bidi" version = "0.3.13" diff --git a/Cargo.toml b/Cargo.toml index aacd79eb903..6f3b59f56e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,22 +21,31 @@ members = [ "tooling/nargo_toml", "tooling/noirc_abi", "tooling/noirc_abi_wasm", + # ACVM + "acvm-repo/acir_field", + "acvm-repo/acir", + "acvm-repo/acvm", + "acvm-repo/acvm_js", + "acvm-repo/stdlib", + "acvm-repo/brillig", + "acvm-repo/brillig_vm", + "acvm-repo/blackbox_solver", + "acvm-repo/barretenberg_blackbox_solver", ] default-members = ["tooling/nargo_cli"] resolver = "2" [workspace.package] # x-release-please-start-version -version = "0.12.0" +version = "0.15.0" # x-release-please-end authors = ["The Noir Team "] edition = "2021" rust-version = "1.67" license = "MIT OR Apache-2.0" +repository = "https://github.com/noir-lang/noir/" [workspace.dependencies] -acvm = "0.27.0" -barretenberg_blackbox_solver = "0.27.0" arena = { path = "compiler/utils/arena" } fm = { path = "compiler/fm" } iter-extended = { path = "compiler/utils/iter-extended" } @@ -77,3 +86,22 @@ base64 = "0.21.2" fxhash = "0.2.1" futures = { version = "0.3.28", default-features = false, features = ["async-await", "std", "executor"] } rustix = { version = "0.38", default-features = false, features = [ "std", "fs" ] } +acir = { path = "acvm-repo/acir", default-features = false } +acvm = { path = "acvm-repo/acvm" } +acir_field = { path = "acvm-repo/acir_field", default-features = false } +stdlib = { package = "acvm_stdlib", path = "acvm-repo/stdlib", default-features = false } +brillig = { path = "acvm-repo/brillig", default-features = false } +brillig_vm = { path = "acvm-repo/brillig_vm", default-features = false } +acvm_blackbox_solver = { path = "acvm-repo/blackbox_solver", default-features = false } +barretenberg_blackbox_solver = { path = "acvm-repo/barretenberg_blackbox_solver", default-features = false } + +bincode = "1.3.3" + +hex = "0.4.2" +num-bigint = "0.4" +num-traits = "0.2" + +[profile.dev] +# This is required to be able to run `cargo test` in acvm_js due to the `locals exceeds maximum` error. +# See https://ritik-mishra.medium.com/resolving-the-wasm-pack-error-locals-exceed-maximum-ec3a9d96685b +opt-level = 1 diff --git a/SUPPORT.md b/SUPPORT.md index 82d585937e4..fcf17b9891f 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -4,7 +4,7 @@ Thank you for your interest in Noir! There are many ways to participate in the c ## Asking Questions -The best place to ask general questions about Noir is in the #noir channel on the [Aztec discord](https://discord.gg/aztec). +The best place to ask general questions about Noir is in the #noir channel on the [Noir discord](https://discord.gg/YpCUTkzTC7). It might also be helpful to review existing and previous Github issues to see if your question has been answered already. @@ -21,7 +21,7 @@ When submitting an issue, please include as much detail as possible about the er ## Contributing to the Standard Library -Noir is still very new and there are many cryptographic primitives that we have yet to build that will be useful for the community. If you have other ideas, please reach out on the [Aztec Discord](https://discord.gg/aztec) to discuss. You can find the current list of requested primitives in the [issues section](https://github.com/noir-lang/noir/labels/noir-stdlib) marked with the label `noir-stdlib`. +Noir is still very new and there are many cryptographic primitives that we have yet to build that will be useful for the community. If you have other ideas, please reach out on the [Noir Discord](https://discord.gg/YpCUTkzTC7) to discuss. You can find the current list of requested primitives in the [issues section](https://github.com/noir-lang/noir/labels/noir-stdlib) marked with the label `noir-stdlib`. ## Funding Opportunities diff --git a/acvm-repo/CHANGELOG.md b/acvm-repo/CHANGELOG.md new file mode 100644 index 00000000000..70e998bf036 --- /dev/null +++ b/acvm-repo/CHANGELOG.md @@ -0,0 +1,764 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.27.4](https://github.com/noir-lang/noir/compare/v0.27.3...v0.27.4) (2023-09-28) + + +### Bug Fixes + +* **acvm:** Return false rather than panicking on invalid ECDSA signatures ([#2783](https://github.com/noir-lang/noir/issues/2783)) ([155abc0](https://github.com/noir-lang/noir/commit/155abc0d99fff41c79163c16bf297d41e5dff0fa)) + +## [0.27.3](https://github.com/noir-lang/noir/compare/v0.27.2...v0.27.3) (2023-09-27) + + +### Bug Fixes + +* **acvm:** Return false rather than panicking on invalid ECDSA signatures ([#2783](https://github.com/noir-lang/noir/issues/2783)) ([155abc0](https://github.com/noir-lang/noir/commit/155abc0d99fff41c79163c16bf297d41e5dff0fa)) + +## [0.27.2](https://github.com/noir-lang/noir/compare/v0.27.1...v0.27.2) (2023-09-27) + + +### Bug Fixes + +* **acvm:** Return false rather than panicking on invalid ECDSA signatures ([#2783](https://github.com/noir-lang/noir/issues/2783)) ([155abc0](https://github.com/noir-lang/noir/commit/155abc0d99fff41c79163c16bf297d41e5dff0fa)) + +## [0.27.1](https://github.com/noir-lang/noir/compare/v0.27.0...v0.27.1) (2023-09-26) + + +### Bug Fixes + +* **acvm:** Return false rather than panicking on invalid ECDSA signatures ([#2783](https://github.com/noir-lang/noir/issues/2783)) ([155abc0](https://github.com/noir-lang/noir/commit/155abc0d99fff41c79163c16bf297d41e5dff0fa)) + +## [0.27.0](https://github.com/noir-lang/acvm/compare/root-v0.26.1...root-v0.27.0) (2023-09-19) + + +### ⚠ BREAKING CHANGES + +* Separate barretenberg solver from generic blackbox solver code ([#554](https://github.com/noir-lang/acvm/issues/554)) + +### Features + +* **acir:** add method on `Circuit` to return assert message ([#551](https://github.com/noir-lang/acvm/issues/551)) ([ee18cde](https://github.com/noir-lang/acvm/commit/ee18cde3537b2be6714061af0bc9ef3793929f7f)) + + +### Bug Fixes + +* bump brillig_vm version in release please ([#556](https://github.com/noir-lang/acvm/issues/556)) ([f6c7823](https://github.com/noir-lang/acvm/commit/f6c7823b3be2b85a6ce8dc4af7a3b57ee7a577db)) +* use the exact version for the hex crate ([#546](https://github.com/noir-lang/acvm/issues/546)) ([2a546e5](https://github.com/noir-lang/acvm/commit/2a546e5b5cc9f39737ad81f8e96d58313a74eced)) + + +### Miscellaneous Chores + +* Separate barretenberg solver from generic blackbox solver code ([#554](https://github.com/noir-lang/acvm/issues/554)) ([a4b9772](https://github.com/noir-lang/acvm/commit/a4b97722a0892fe379ff075e6080675adafdce0e)) + +## [0.26.1](https://github.com/noir-lang/acvm/compare/root-v0.26.0...root-v0.26.1) (2023-09-12) + + +### Bug Fixes + +* Implements handling of the high limb during fixed base scalar multiplication ([#535](https://github.com/noir-lang/acvm/issues/535)) ([551504a](https://github.com/noir-lang/acvm/commit/551504aa572d3f9d56b5576d25ce1211296ee488)) + +## [0.26.0](https://github.com/noir-lang/acvm/compare/root-v0.25.0...root-v0.26.0) (2023-09-07) + + +### ⚠ BREAKING CHANGES + +* Add a low and high limb to scalar mul opcode ([#532](https://github.com/noir-lang/acvm/issues/532)) + +### Miscellaneous Chores + +* Add a low and high limb to scalar mul opcode ([#532](https://github.com/noir-lang/acvm/issues/532)) ([b054f66](https://github.com/noir-lang/acvm/commit/b054f66be9c73d4e02dbecdab80874a907f19242)) + +## [0.25.0](https://github.com/noir-lang/acvm/compare/root-v0.24.1...root-v0.25.0) (2023-09-04) + + +### ⚠ BREAKING CHANGES + +* Provide runtime callstacks for brillig failures and return errors in acvm_js ([#523](https://github.com/noir-lang/acvm/issues/523)) + +### Features + +* Provide runtime callstacks for brillig failures and return errors in acvm_js ([#523](https://github.com/noir-lang/acvm/issues/523)) ([7ab7cff](https://github.com/noir-lang/acvm/commit/7ab7cff48a9aba61a97fad2a759fc8e55740b098)) + + +### Bug Fixes + +* initialize recursive proof output to zero ([#524](https://github.com/noir-lang/acvm/issues/524)) ([5453074](https://github.com/noir-lang/acvm/commit/545307457dd7634b20ea3977e2d2cc751eba06d2)) + +## [0.24.1](https://github.com/noir-lang/acvm/compare/root-v0.24.0...root-v0.24.1) (2023-09-03) + + +### Bug Fixes + +* Add WASI 20 `_initialize` call to `acvm_backend.wasm` binary ([#518](https://github.com/noir-lang/acvm/issues/518)) ([ec6ab0c](https://github.com/noir-lang/acvm/commit/ec6ab0c6fb2753209abe1e03a449873e255ffd76)) + +## [0.24.0](https://github.com/noir-lang/acvm/compare/root-v0.23.0...root-v0.24.0) (2023-08-31) + + +### ⚠ BREAKING CHANGES + +* **acvm:** Remove the `Backend` trait ([#514](https://github.com/noir-lang/acvm/issues/514)) +* **acir:** Remove unused `Directive` opcodes ([#510](https://github.com/noir-lang/acvm/issues/510)) +* **acir:** Add predicate to MemoryOp ([#503](https://github.com/noir-lang/acvm/issues/503)) +* **acvm:** Remove unused arguments from `Backend` trait ([#511](https://github.com/noir-lang/acvm/issues/511)) +* Assertion messages embedded in the circuit ([#484](https://github.com/noir-lang/acvm/issues/484)) + +### Features + +* **acir:** Add predicate to MemoryOp ([#503](https://github.com/noir-lang/acvm/issues/503)) ([ca9eebe](https://github.com/noir-lang/acvm/commit/ca9eebe34e61adabf97318c8ccaf60c8a424aafd)) +* Assertion messages embedded in the circuit ([#484](https://github.com/noir-lang/acvm/issues/484)) ([06b97c5](https://github.com/noir-lang/acvm/commit/06b97c51041e16651cf8b2be8bc18214e276c6c9)) + + +### Miscellaneous Chores + +* **acir:** Remove unused `Directive` opcodes ([#510](https://github.com/noir-lang/acvm/issues/510)) ([cfd8cbf](https://github.com/noir-lang/acvm/commit/cfd8cbf58307511ac0cc9106c299695c2ca779de)) +* **acvm:** Remove the `Backend` trait ([#514](https://github.com/noir-lang/acvm/issues/514)) ([681535d](https://github.com/noir-lang/acvm/commit/681535da52815a4a164ee4f48f7b48329664af98)) +* **acvm:** Remove unused arguments from `Backend` trait ([#511](https://github.com/noir-lang/acvm/issues/511)) ([ae65355](https://github.com/noir-lang/acvm/commit/ae65355afb7df98c71f81d5a54e89f39f9333920)) + +## [0.23.0](https://github.com/noir-lang/acvm/compare/root-v0.22.0...root-v0.23.0) (2023-08-30) + + +### ⚠ BREAKING CHANGES + +* Return an iterator from `new_locations()` instead of collecting ([#507](https://github.com/noir-lang/acvm/issues/507)) +* **acvm:** remove `CommonReferenceString` trait and preprocess method ([#508](https://github.com/noir-lang/acvm/issues/508)) +* **acvm:** Remove `BlackBoxFunctionSolver` from `Backend` trait ([#494](https://github.com/noir-lang/acvm/issues/494)) +* **acvm:** Pass `BlackBoxFunctionSolver` to `ACVM` by reference + +### Features + +* **acvm_js:** Add `execute_circuit_with_black_box_solver` to prevent reinitialization of `BlackBoxFunctionSolver` ([3877e0e](https://github.com/noir-lang/acvm/commit/3877e0e438a8d0e5545a4da7210767dec05c342f)) +* Expose a `BlackBoxFunctionSolver` containing a barretenberg wasm from `blackbox_solver` ([#494](https://github.com/noir-lang/acvm/issues/494)) ([a1d4b71](https://github.com/noir-lang/acvm/commit/a1d4b71256dfbf1e883e770dd9c45479235aa860)) + + +### Miscellaneous Chores + +* **acvm:** Pass `BlackBoxFunctionSolver` to `ACVM` by reference ([3877e0e](https://github.com/noir-lang/acvm/commit/3877e0e438a8d0e5545a4da7210767dec05c342f)) +* **acvm:** Remove `BlackBoxFunctionSolver` from `Backend` trait ([#494](https://github.com/noir-lang/acvm/issues/494)) ([a1d4b71](https://github.com/noir-lang/acvm/commit/a1d4b71256dfbf1e883e770dd9c45479235aa860)) +* **acvm:** remove `CommonReferenceString` trait and preprocess method ([#508](https://github.com/noir-lang/acvm/issues/508)) ([3827dd3](https://github.com/noir-lang/acvm/commit/3827dd3ce487650843ba4df8337b423e39f97edf)) +* Return an iterator from `new_locations()` instead of collecting ([#507](https://github.com/noir-lang/acvm/issues/507)) ([8d49a5c](https://github.com/noir-lang/acvm/commit/8d49a5c15b1e962cd59252467a20a922edadc2f2)) + +## [0.22.0](https://github.com/noir-lang/acvm/compare/root-v0.21.0...root-v0.22.0) (2023-08-18) + + +### ⚠ BREAKING CHANGES + +* Switched from OpcodeLabel to OpcodeLocation and ErrorLocation ([#493](https://github.com/noir-lang/acvm/issues/493)) +* **acvm:** check for index out-of-bounds on memory operations ([#468](https://github.com/noir-lang/acvm/issues/468)) + +### Features + +* **acvm:** check for index out-of-bounds on memory operations ([#468](https://github.com/noir-lang/acvm/issues/468)) ([740468c](https://github.com/noir-lang/acvm/commit/740468c0a144f7179c38f615cfda31b2fcc77359)) +* print error location with fmt ([#497](https://github.com/noir-lang/acvm/issues/497)) ([575a9e5](https://github.com/noir-lang/acvm/commit/575a9e50e97afb04a7b91799e06752cec3093f0b)) +* Switched from OpcodeLabel to OpcodeLocation and ErrorLocation ([#493](https://github.com/noir-lang/acvm/issues/493)) ([27a5a93](https://github.com/noir-lang/acvm/commit/27a5a935849f8904e10056b08089f532a06962b8)) + + +### Bug Fixes + +* add opcode label to unsatisfied constrain string ([#482](https://github.com/noir-lang/acvm/issues/482)) ([cbbbe67](https://github.com/noir-lang/acvm/commit/cbbbe67b9a19a4a560b2dfa8f27ea1c6ebd61f28)) + +## [0.21.0](https://github.com/noir-lang/acvm/compare/root-v0.20.1...root-v0.21.0) (2023-07-26) + + +### ⚠ BREAKING CHANGES + +* **acir:** Remove `Block`, `RAM` and `ROM` opcodes ([#457](https://github.com/noir-lang/acvm/issues/457)) +* **acvm:** Remove `OpcodeResolution` enum ([#400](https://github.com/noir-lang/acvm/issues/400)) +* **acvm:** Support stepwise execution of ACIR ([#399](https://github.com/noir-lang/acvm/issues/399)) + +### Features + +* **acvm:** Remove `OpcodeResolution` enum ([#400](https://github.com/noir-lang/acvm/issues/400)) ([d0ce48c](https://github.com/noir-lang/acvm/commit/d0ce48c506619a5560412ef6693bfa11036b501e)) +* **acvm:** Support stepwise execution of ACIR ([#399](https://github.com/noir-lang/acvm/issues/399)) ([6a03950](https://github.com/noir-lang/acvm/commit/6a0395021779a2711353c2fe2948e09b5b538fc0)) + + +### Miscellaneous Chores + +* **acir:** Remove `Block`, `RAM` and `ROM` opcodes ([#457](https://github.com/noir-lang/acvm/issues/457)) ([8dd220a](https://github.com/noir-lang/acvm/commit/8dd220ae127baf6cc5a31d8ab7ffdeeb161f6109)) + +## [0.20.1](https://github.com/noir-lang/acvm/compare/root-v0.20.0...root-v0.20.1) (2023-07-26) + + +### Features + +* add optimisations to fallback black box functions on booleans ([#446](https://github.com/noir-lang/acvm/issues/446)) ([2cfb2a8](https://github.com/noir-lang/acvm/commit/2cfb2a8cf911a81eedbd9da13ab2c616abd67f83)) +* **stdlib:** Add fallback implementation of `Keccak256` black box function ([#445](https://github.com/noir-lang/acvm/issues/445)) ([f7ebb03](https://github.com/noir-lang/acvm/commit/f7ebb03653c971f119700ff8126d9eb5ff01be0f)) + +## [0.20.0](https://github.com/noir-lang/acvm/compare/root-v0.19.1...root-v0.20.0) (2023-07-20) + + +### ⚠ BREAKING CHANGES + +* atomic memory opcodes ([#447](https://github.com/noir-lang/acvm/issues/447)) + +### Features + +* atomic memory opcodes ([#447](https://github.com/noir-lang/acvm/issues/447)) ([3261c7a](https://github.com/noir-lang/acvm/commit/3261c7a2fd4f3a300bc5f39ef4febccd8a853560)) +* **brillig:** Support integers which fit inside a `FieldElement` ([#403](https://github.com/noir-lang/acvm/issues/403)) ([f992412](https://github.com/noir-lang/acvm/commit/f992412617ade875fa26fe3a2cc3c06dbcad503b)) +* **stdlib:** Add fallback implementation of `HashToField128Security` black box function ([#435](https://github.com/noir-lang/acvm/issues/435)) ([ed40f22](https://github.com/noir-lang/acvm/commit/ed40f228529e888d1960bfa70cb92b277e24b37f)) + +## [0.19.1](https://github.com/noir-lang/acvm/compare/root-v0.19.0...root-v0.19.1) (2023-07-17) + + +### Bug Fixes + +* Remove panic when we divide 0/0 in quotient directive ([#437](https://github.com/noir-lang/acvm/issues/437)) ([9c8ff64](https://github.com/noir-lang/acvm/commit/9c8ff64ebf27a86787ae184e10ed9581041ec0ff)) + +## [0.19.0](https://github.com/noir-lang/acvm/compare/root-v0.18.2...root-v0.19.0) (2023-07-15) + + +### ⚠ BREAKING CHANGES + +* move to bincode and GzEncoding for artifacts ([#436](https://github.com/noir-lang/acvm/issues/436)) + +### Features + +* move to bincode and GzEncoding for artifacts ([#436](https://github.com/noir-lang/acvm/issues/436)) ([4683240](https://github.com/noir-lang/acvm/commit/46832400a8bc20135a8a895ab9477b14449734d9)) + +## [0.18.2](https://github.com/noir-lang/acvm/compare/root-v0.18.1...root-v0.18.2) (2023-07-12) + + +### Features + +* **acvm:** reexport `blackbox_solver` crate from `acvm` ([#431](https://github.com/noir-lang/acvm/issues/431)) ([517e942](https://github.com/noir-lang/acvm/commit/517e942b732d7107f6e064c6791917d1508229b3)) +* **stdlib:** Add fallback implementation of `Blake2s` black box function ([#424](https://github.com/noir-lang/acvm/issues/424)) ([982d940](https://github.com/noir-lang/acvm/commit/982d94087d46092ce7a5e94dbd7e732195f58e42)) + +## [0.18.1](https://github.com/noir-lang/acvm/compare/root-v0.18.0...root-v0.18.1) (2023-07-12) + + +### Bug Fixes + +* Crate publishing order ([#428](https://github.com/noir-lang/acvm/issues/428)) ([4f69cb5](https://github.com/noir-lang/acvm/commit/4f69cb5782435a2fcf45bb0985e1bb0eb944b194)) + +## [0.18.0](https://github.com/noir-lang/acvm/compare/root-v0.17.0...root-v0.18.0) (2023-07-12) + + +### ⚠ BREAKING CHANGES + +* add backend-solvable blackboxes to brillig & unify implementations ([#422](https://github.com/noir-lang/acvm/issues/422)) +* **acvm:** Remove `CircuitSimplifer` ([#421](https://github.com/noir-lang/acvm/issues/421)) +* **acvm:** Add `circuit: &Circuit` to `eth_contract_from_vk` function signature ([#420](https://github.com/noir-lang/acvm/issues/420)) +* Returns index of failing opcode and transformation mapping ([#412](https://github.com/noir-lang/acvm/issues/412)) + +### Features + +* **acvm:** Add `circuit: &Circuit` to `eth_contract_from_vk` function signature ([#420](https://github.com/noir-lang/acvm/issues/420)) ([744e9da](https://github.com/noir-lang/acvm/commit/744e9da71f7ca477a5390a63f47211dd4dffb8b3)) +* add backend-solvable blackboxes to brillig & unify implementations ([#422](https://github.com/noir-lang/acvm/issues/422)) ([093342e](https://github.com/noir-lang/acvm/commit/093342ea9481a311fa71343b8b7a22774788838a)) +* derive PartialOrd, Ord, and Hash on RegisterIndex ([#425](https://github.com/noir-lang/acvm/issues/425)) ([7f6b0dc](https://github.com/noir-lang/acvm/commit/7f6b0dc138c4e11d2b5847f0c9603979cc43493a)) +* Returns index of failing opcode and transformation mapping ([#412](https://github.com/noir-lang/acvm/issues/412)) ([79950e9](https://github.com/noir-lang/acvm/commit/79950e943f60e4082e1cf5ec4442aa67ea91aade)) +* **stdlib:** Add fallback implementation of `SHA256` black box function ([#407](https://github.com/noir-lang/acvm/issues/407)) ([040369a](https://github.com/noir-lang/acvm/commit/040369adc8749fa5ec2edd255ff54c105c3140f5)) + + +### Miscellaneous Chores + +* **acvm:** Remove `CircuitSimplifer` ([#421](https://github.com/noir-lang/acvm/issues/421)) ([e07a56d](https://github.com/noir-lang/acvm/commit/e07a56d9c542a7f03ce156761054cd403de0bd23)) + +## [0.17.0](https://github.com/noir-lang/acvm/compare/root-v0.16.0...root-v0.17.0) (2023-07-07) + + +### ⚠ BREAKING CHANGES + +* **acir:** add `EcdsaSecp256r1` blackbox function ([#408](https://github.com/noir-lang/acvm/issues/408)) + +### Features + +* **acir:** add `EcdsaSecp256r1` blackbox function ([#408](https://github.com/noir-lang/acvm/issues/408)) ([9895817](https://github.com/noir-lang/acvm/commit/98958170c9fa9b4731e33b31cb494a72bb90549e)) + +## [0.16.0](https://github.com/noir-lang/acvm/compare/root-v0.15.1...root-v0.16.0) (2023-07-06) + + +### ⚠ BREAKING CHANGES + +* **acvm:** replace `PartialWitnessGeneratorStatus` with `ACVMStatus` ([#410](https://github.com/noir-lang/acvm/issues/410)) +* **acir:** revert changes to `SchnorrVerify` opcode ([#409](https://github.com/noir-lang/acvm/issues/409)) +* **acvm:** Replace `PartialWitnessGenerator` trait with `BlackBoxFunctionSolver` ([#378](https://github.com/noir-lang/acvm/issues/378)) +* **acvm:** Encapsulate internal state of ACVM within a struct ([#384](https://github.com/noir-lang/acvm/issues/384)) +* remove unused `OpcodeResolutionError::IncorrectNumFunctionArguments` variant ([#397](https://github.com/noir-lang/acvm/issues/397)) +* **acir:** Remove `Oracle` opcode ([#368](https://github.com/noir-lang/acvm/issues/368)) +* **acir:** Use fixed length data structures in black box function inputs/outputs where possible. ([#386](https://github.com/noir-lang/acvm/issues/386)) +* **acir:** Implement `Add` trait for `Witness` & make output of `Mul` on `Expression` optional ([#393](https://github.com/noir-lang/acvm/issues/393)) + +### Features + +* **acir:** Implement `Add` trait for `Witness` & make output of `Mul` on `Expression` optional ([#393](https://github.com/noir-lang/acvm/issues/393)) ([5bcdfc6](https://github.com/noir-lang/acvm/commit/5bcdfc62e4936922135add171d60a948922581ff)) +* **acir:** Remove `Oracle` opcode ([#368](https://github.com/noir-lang/acvm/issues/368)) ([63354df](https://github.com/noir-lang/acvm/commit/63354df1fe47a4f1128b91641d1b66dfc1281794)) +* **acir:** Use fixed length data structures in black box function inputs/outputs where possible. ([#386](https://github.com/noir-lang/acvm/issues/386)) ([b139d4d](https://github.com/noir-lang/acvm/commit/b139d4d566c715009465a430aab0fb819aacab4f)) +* **acvm:** Derive `Copy` for `Language` ([#406](https://github.com/noir-lang/acvm/issues/406)) ([69a6c22](https://github.com/noir-lang/acvm/commit/69a6c224d80be556ac5388ffeb7a02424df22031)) +* **acvm:** Encapsulate internal state of ACVM within a struct ([#384](https://github.com/noir-lang/acvm/issues/384)) ([84d4867](https://github.com/noir-lang/acvm/commit/84d4867b2d97097d451d59174781555dafd2591f)) +* **acvm:** Replace `PartialWitnessGenerator` trait with `BlackBoxFunctionSolver` ([#378](https://github.com/noir-lang/acvm/issues/378)) ([73fbc95](https://github.com/noir-lang/acvm/commit/73fbc95942b0039565c93719809975f66dc9ec53)) +* **acvm:** replace `PartialWitnessGeneratorStatus` with `ACVMStatus` ([#410](https://github.com/noir-lang/acvm/issues/410)) ([fc3240d](https://github.com/noir-lang/acvm/commit/fc3240d456d0128f6eb42096beb8b7a586ea48da)) +* **brillig:** implemented first blackbox functions ([#401](https://github.com/noir-lang/acvm/issues/401)) ([62d40f7](https://github.com/noir-lang/acvm/commit/62d40f7c03cd1102f615b8d565f82496962db637)) + + +### Bug Fixes + +* **acir:** revert changes to `SchnorrVerify` opcode ([#409](https://github.com/noir-lang/acvm/issues/409)) ([f1c7940](https://github.com/noir-lang/acvm/commit/f1c7940f4ac618c7b440b6ed30199f85cbe72cca)) + + +### Miscellaneous Chores + +* remove unused `OpcodeResolutionError::IncorrectNumFunctionArguments` variant ([#397](https://github.com/noir-lang/acvm/issues/397)) ([d1368d0](https://github.com/noir-lang/acvm/commit/d1368d041eb42d265a4ef385e066b82bc36d0743)) + +## [0.15.1](https://github.com/noir-lang/acvm/compare/root-v0.15.0...root-v0.15.1) (2023-06-20) + + +### Features + +* **brillig:** Allow dynamic-size foreign calls ([#370](https://github.com/noir-lang/acvm/issues/370)) ([5ba0349](https://github.com/noir-lang/acvm/commit/5ba0349420cc1b20113cb5e96490a0808a769757)) + + +### Bug Fixes + +* **brillig:** remove register initialization check ([#392](https://github.com/noir-lang/acvm/issues/392)) ([1a53143](https://github.com/noir-lang/acvm/commit/1a531438b5c1ab7ce8c4bd599dda3515bdd5cfcd)) + +## [0.15.0](https://github.com/noir-lang/acvm/compare/root-v0.14.2...root-v0.15.0) (2023-06-15) + + +### ⚠ BREAKING CHANGES + +* **brillig:** Accept multiple inputs/outputs for foreign calls ([#367](https://github.com/noir-lang/acvm/issues/367)) +* **acvm:** Make internals of ACVM private ([#353](https://github.com/noir-lang/acvm/issues/353)) + +### Features + +* Add method to generate updated `Brillig` opcode from `UnresolvedBrilligCall` ([#363](https://github.com/noir-lang/acvm/issues/363)) ([fda5dbe](https://github.com/noir-lang/acvm/commit/fda5dbe57c28dc4bc28dfd8fe0a4a8ba29635393)) +* **brillig:** Accept multiple inputs/outputs for foreign calls ([#367](https://github.com/noir-lang/acvm/issues/367)) ([78d62b2](https://github.com/noir-lang/acvm/commit/78d62b2d7c1c8b884e1f3fe7983e6e5029700e70)) +* **brillig:** Set `VMStatus` to `Failure` rather than panicking on invalid foreign call response ([#375](https://github.com/noir-lang/acvm/issues/375)) ([c49d82c](https://github.com/noir-lang/acvm/commit/c49d82c99c73c60e264585ed201af2b6a2b7ee0f)) + + +### Bug Fixes + +* **brillig:** Correct signed division implementation ([#356](https://github.com/noir-lang/acvm/issues/356)) ([4eefda0](https://github.com/noir-lang/acvm/commit/4eefda01e7b371035314f77631df4687608b4782)) +* **brillig:** Explicitly wrap on arithmetic operations ([#365](https://github.com/noir-lang/acvm/issues/365)) ([c0544a9](https://github.com/noir-lang/acvm/commit/c0544a99930d3c8d534376c8f8a91645a39aecf8)) + + +### Miscellaneous Chores + +* **acvm:** Make internals of ACVM private ([#353](https://github.com/noir-lang/acvm/issues/353)) ([c902a01](https://github.com/noir-lang/acvm/commit/c902a01639033665d106e2d9f4e5c7070af8c0bb)) + +## [0.14.2](https://github.com/noir-lang/acvm/compare/root-v0.14.1...root-v0.14.2) (2023-06-08) + + +### Bug Fixes + +* **brillig:** expand memory with zeroes on store ([#350](https://github.com/noir-lang/acvm/issues/350)) ([4d2dadd](https://github.com/noir-lang/acvm/commit/4d2dadd3acd9dc25f0feae865b74cbaea7250f3d)) + +## [0.14.1](https://github.com/noir-lang/acvm/compare/root-v0.14.0...root-v0.14.1) (2023-06-07) + + +### Features + +* Re-use intermediate variables created during width reduction, with proper scale. ([#343](https://github.com/noir-lang/acvm/issues/343)) ([6bd0baa](https://github.com/noir-lang/acvm/commit/6bd0baa4bc9ac204e7710ec6d17d1752d2e924c0)) + +## [0.14.0](https://github.com/noir-lang/acvm/compare/root-v0.13.3...root-v0.14.0) (2023-06-06) + + +### ⚠ BREAKING CHANGES + +* **acir:** Verify Proof ([#291](https://github.com/noir-lang/acvm/issues/291)) + +### Features + +* **acir:** Verify Proof ([#291](https://github.com/noir-lang/acvm/issues/291)) ([9f34428](https://github.com/noir-lang/acvm/commit/9f34428b7084c7c38de401a16ca76e748d8b1d77)) + +## [0.13.3](https://github.com/noir-lang/acvm/compare/root-v0.13.2...root-v0.13.3) (2023-06-05) + + +### Bug Fixes + +* Empty commit to trigger release-please ([e8f0748](https://github.com/noir-lang/acvm/commit/e8f0748042ef505d59ab63266d3c36c5358ee30d)) + +## [0.13.2](https://github.com/noir-lang/acvm/compare/root-v0.13.1...root-v0.13.2) (2023-06-02) + + +### Bug Fixes + +* re-use intermediate vars during width reduction ([#278](https://github.com/noir-lang/acvm/issues/278)) ([5b32920](https://github.com/noir-lang/acvm/commit/5b32920263c4481c60faf0b84f0031aa8149b6b2)) + +## [0.13.1](https://github.com/noir-lang/acvm/compare/root-v0.13.0...root-v0.13.1) (2023-06-01) + + +### Bug Fixes + +* **brillig:** Proper error handling for Brillig failures ([#329](https://github.com/noir-lang/acvm/issues/329)) ([cffa110](https://github.com/noir-lang/acvm/commit/cffa110c8df30ee3dd8b635d38b17b1fcd54b03e)) +* **ci:** Add brillig_vm to release-please & link versions ([#332](https://github.com/noir-lang/acvm/issues/332)) ([84bd22e](https://github.com/noir-lang/acvm/commit/84bd22eea46cdfef3a5dbf534b878e819d44f755)) +* **ci:** Correct typo to avoid `undefined` in changelogs ([#333](https://github.com/noir-lang/acvm/issues/333)) ([d3424c0](https://github.com/noir-lang/acvm/commit/d3424c04fd303c9cbe25d03118d8b358cbb84b83)) + +## [0.13.0](https://github.com/noir-lang/acvm/compare/root-v0.12.0...root-v0.13.0) (2023-06-01) + + +### ⚠ BREAKING CHANGES + +* added hash index to pedersen ([#281](https://github.com/noir-lang/acvm/issues/281)) +* Add variable length keccak opcode ([#314](https://github.com/noir-lang/acvm/issues/314)) +* Remove AES opcode ([#302](https://github.com/noir-lang/acvm/issues/302)) +* **acir, acvm:** Remove ComputeMerkleRoot opcode #296 +* Remove manual serialization of `Opcode`s in favour of `serde` ([#286](https://github.com/noir-lang/acvm/issues/286)) +* Remove backend solvable methods from the interface and solve them in ACVM ([#264](https://github.com/noir-lang/acvm/issues/264)) +* Reorganize code related to `PartialWitnessGenerator` ([#287](https://github.com/noir-lang/acvm/issues/287)) + +### Features + +* **acir, acvm:** Remove ComputeMerkleRoot opcode [#296](https://github.com/noir-lang/acvm/issues/296) ([8b3923e](https://github.com/noir-lang/acvm/commit/8b3923e191e4ac399400025496e8bb4453734040)) +* Add `Brillig` opcode to introduce custom non-determinism to ACVM ([#152](https://github.com/noir-lang/acvm/issues/152)) ([3c6740a](https://github.com/noir-lang/acvm/commit/3c6740af75125afc8ebb4379f781f8274015e2e2)) +* Add variable length keccak opcode ([#314](https://github.com/noir-lang/acvm/issues/314)) ([7bfd169](https://github.com/noir-lang/acvm/commit/7bfd1695b6f119cd70fce4866314c9bb4991eaab)) +* added hash index to pedersen ([#281](https://github.com/noir-lang/acvm/issues/281)) ([61820b6](https://github.com/noir-lang/acvm/commit/61820b651900aac8d9557b4b9477ed0e1763c124)) +* Remove backend solvable methods from the interface and solve them in ACVM ([#264](https://github.com/noir-lang/acvm/issues/264)) ([69916cb](https://github.com/noir-lang/acvm/commit/69916cbdd928875b2e8fe4775f2251f71c3f3c92)) + + +### Bug Fixes + +* Allow async functions without send on async trait ([#292](https://github.com/noir-lang/acvm/issues/292)) ([9f9fc21](https://github.com/noir-lang/acvm/commit/9f9fc216a6d09ca97352ffd365bfd347e94ad8eb)) + + +### Miscellaneous Chores + +* Remove AES opcode ([#302](https://github.com/noir-lang/acvm/issues/302)) ([a429a54](https://github.com/noir-lang/acvm/commit/a429a5422d6f001b6db0d0a0f30c79ec0f96de89)) +* Remove manual serialization of `Opcode`s in favour of `serde` ([#286](https://github.com/noir-lang/acvm/issues/286)) ([8a3812f](https://github.com/noir-lang/acvm/commit/8a3812fe6ed3b267692284bdcd909d9dd32b9747)) +* Reorganize code related to `PartialWitnessGenerator` ([#287](https://github.com/noir-lang/acvm/issues/287)) ([b9d61a1](https://github.com/noir-lang/acvm/commit/b9d61a16210d70e350a7e953951362c94f497f89)) + +## [0.12.0](https://github.com/noir-lang/acvm/compare/root-v0.11.0...root-v0.12.0) (2023-05-17) + + +### ⚠ BREAKING CHANGES + +* remove deprecated circuit hash functions ([#288](https://github.com/noir-lang/acvm/issues/288)) +* allow backends to specify support for all opcode variants ([#273](https://github.com/noir-lang/acvm/issues/273)) +* **acvm:** Add CommonReferenceString backend trait ([#231](https://github.com/noir-lang/acvm/issues/231)) +* Introduce WitnessMap data structure to avoid leaking internal structure ([#252](https://github.com/noir-lang/acvm/issues/252)) +* use struct variants for blackbox function calls ([#269](https://github.com/noir-lang/acvm/issues/269)) +* **acvm:** Backend trait must implement Debug ([#275](https://github.com/noir-lang/acvm/issues/275)) +* remove `OpcodeResolutionError::UnexpectedOpcode` ([#274](https://github.com/noir-lang/acvm/issues/274)) +* **acvm:** rename `hash_to_field128_security` to `hash_to_field_128_security` ([#271](https://github.com/noir-lang/acvm/issues/271)) +* **acvm:** update black box solver interfaces to match `pwg:black_box::solve` ([#268](https://github.com/noir-lang/acvm/issues/268)) +* **acvm:** expose separate solvers for AND and XOR opcodes ([#266](https://github.com/noir-lang/acvm/issues/266)) +* **acvm:** Simplification pass for ACIR ([#151](https://github.com/noir-lang/acvm/issues/151)) +* Remove `solve` from PWG trait & introduce separate solvers for each blackbox ([#257](https://github.com/noir-lang/acvm/issues/257)) + +### Features + +* **acvm:** Add CommonReferenceString backend trait ([#231](https://github.com/noir-lang/acvm/issues/231)) ([eeddcf1](https://github.com/noir-lang/acvm/commit/eeddcf179880f246383f7f67a11e589269c4e3ff)) +* **acvm:** Simplification pass for ACIR ([#151](https://github.com/noir-lang/acvm/issues/151)) ([7bc42c6](https://github.com/noir-lang/acvm/commit/7bc42c62b6e095f838b781c87cbb1ecd2af5f179)) +* **acvm:** update black box solver interfaces to match `pwg:black_box::solve` ([#268](https://github.com/noir-lang/acvm/issues/268)) ([0098b7d](https://github.com/noir-lang/acvm/commit/0098b7d9640076d970e6c15d5fd6f368eb1513ff)) +* Introduce WitnessMap data structure to avoid leaking internal structure ([#252](https://github.com/noir-lang/acvm/issues/252)) ([b248e60](https://github.com/noir-lang/acvm/commit/b248e606dd69c25d33ae77c5c5c0541adbf80cd6)) +* Remove `solve` from PWG trait & introduce separate solvers for each blackbox ([#257](https://github.com/noir-lang/acvm/issues/257)) ([3f3dd74](https://github.com/noir-lang/acvm/commit/3f3dd7460b27ab06b55dfc3fe5dd733f08e30a9f)) +* use struct variants for blackbox function calls ([#269](https://github.com/noir-lang/acvm/issues/269)) ([a83333b](https://github.com/noir-lang/acvm/commit/a83333b9e270dfcfd40a36271896840ec0201bc4)) + + +### Bug Fixes + +* **acir:** Hide variants of WitnessMapError and export it from package ([#283](https://github.com/noir-lang/acvm/issues/283)) ([bbd9ab7](https://github.com/noir-lang/acvm/commit/bbd9ab7ca5be3fb31f3e141fee2522704852f5de)) + + +### Miscellaneous Chores + +* **acvm:** Backend trait must implement Debug ([#275](https://github.com/noir-lang/acvm/issues/275)) ([3288b4c](https://github.com/noir-lang/acvm/commit/3288b4c7eb01f5621e577d5ff9e7c92c7757e021)) +* **acvm:** expose separate solvers for AND and XOR opcodes ([#266](https://github.com/noir-lang/acvm/issues/266)) ([84b5d18](https://github.com/noir-lang/acvm/commit/84b5d18d29a111a42bfc1c3d122129c8f062c3db)) +* **acvm:** rename `hash_to_field128_security` to `hash_to_field_128_security` ([#271](https://github.com/noir-lang/acvm/issues/271)) ([fad9af2](https://github.com/noir-lang/acvm/commit/fad9af27fb102fa34bf7511f8ed7b16b3ec2d115)) +* allow backends to specify support for all opcode variants ([#273](https://github.com/noir-lang/acvm/issues/273)) ([efd37fe](https://github.com/noir-lang/acvm/commit/efd37fedcbbabb3fac810e662731439e07fef49a)) +* remove `OpcodeResolutionError::UnexpectedOpcode` ([#274](https://github.com/noir-lang/acvm/issues/274)) ([0e71aac](https://github.com/noir-lang/acvm/commit/0e71aac7aa85b3e9142972a26ba122c2c7c51d9b)) +* remove deprecated circuit hash functions ([#288](https://github.com/noir-lang/acvm/issues/288)) ([1a22c75](https://github.com/noir-lang/acvm/commit/1a22c752de3354a2a6d34892331ab6623b24c0b0)) + +## [0.11.0](https://github.com/noir-lang/acvm/compare/root-v0.10.3...root-v0.11.0) (2023-05-04) + + +### ⚠ BREAKING CHANGES + +* **acvm:** Introduce Error type for fallible Backend traits ([#248](https://github.com/noir-lang/acvm/issues/248)) + +### Features + +* **acvm:** Add generic error for failing to solve an opcode ([#251](https://github.com/noir-lang/acvm/issues/251)) ([bc89528](https://github.com/noir-lang/acvm/commit/bc8952820de610e585d505decfac6e590bbb1a35)) +* **acvm:** Introduce Error type for fallible Backend traits ([#248](https://github.com/noir-lang/acvm/issues/248)) ([45c45f7](https://github.com/noir-lang/acvm/commit/45c45f7cdb79c3ccb0373ca0e698b282d4dabc39)) +* Add Keccak Hash function ([#259](https://github.com/noir-lang/acvm/issues/259)) ([443c734](https://github.com/noir-lang/acvm/commit/443c73482eeef6cc42a1a254bf0d7706698ee353)) + + +### Bug Fixes + +* **acir:** Fix `Expression` multiplication to correctly handle degree 1 terms ([#255](https://github.com/noir-lang/acvm/issues/255)) ([e399396](https://github.com/noir-lang/acvm/commit/e399396f7e06deb6b831517af17018607df3f252)) + +## [0.10.3](https://github.com/noir-lang/acvm/compare/root-v0.10.2...root-v0.10.3) (2023-04-28) + + +### Bug Fixes + +* add default feature flag to ACVM crate ([#245](https://github.com/noir-lang/acvm/issues/245)) ([455fddb](https://github.com/noir-lang/acvm/commit/455fddbc19af81cb01d54e29cad199691e1a1d98)) + +## [0.10.2](https://github.com/noir-lang/acvm/compare/root-v0.10.1...root-v0.10.2) (2023-04-28) + + +### Bug Fixes + +* add default flag to `acvm_stdlib` ([#242](https://github.com/noir-lang/acvm/issues/242)) ([83b6fa8](https://github.com/noir-lang/acvm/commit/83b6fa8302569add7e3ac8481b2fd2a6a1ff3576)) + +## [0.10.1](https://github.com/noir-lang/acvm/compare/root-v0.10.0...root-v0.10.1) (2023-04-28) + + +### Bug Fixes + +* **acir:** add `bn254` as default feature flag ([#240](https://github.com/noir-lang/acvm/issues/240)) ([e56973d](https://github.com/noir-lang/acvm/commit/e56973d8dc1745fe9bb844ec8347acd4d836d42f)) + +## [0.10.0](https://github.com/noir-lang/acvm/compare/root-v0.9.0...root-v0.10.0) (2023-04-26) + + +### ⚠ BREAKING CHANGES + +* return `Result` from `solve_range_opcode` ([#238](https://github.com/noir-lang/acvm/issues/238)) +* **acvm:** have all black box functions return `Result` ([#237](https://github.com/noir-lang/acvm/issues/237)) +* **acvm:** implement `hash_to_field_128_security` ([#230](https://github.com/noir-lang/acvm/issues/230)) +* replace `MerkleMembership` opcode with `ComputeMerkleRoot` ([#233](https://github.com/noir-lang/acvm/issues/233)) +* require `Backend` to implement `Default` trait ([#223](https://github.com/noir-lang/acvm/issues/223)) +* Make GeneralOptimizer crate visible ([#220](https://github.com/noir-lang/acvm/issues/220)) +* return `PartialWitnessGeneratorStatus` from `PartialWitnessGenerator.solve` ([#213](https://github.com/noir-lang/acvm/issues/213)) +* organise operator implementations for Expression ([#190](https://github.com/noir-lang/acvm/issues/190)) + +### Features + +* **acvm:** have all black box functions return `Result<OpcodeResolution, OpcodeResolutionError>` ([#237](https://github.com/noir-lang/acvm/issues/237)) ([e8e93fd](https://github.com/noir-lang/acvm/commit/e8e93fda0db18f0d266dd1aacbb53ec787992dc9)) +* **acvm:** implement `hash_to_field_128_security` ([#230](https://github.com/noir-lang/acvm/issues/230)) ([198fb69](https://github.com/noir-lang/acvm/commit/198fb69e90a5ed3c0a8716d888b4dc6c2f9b18aa)) +* Add range opcode optimization ([#219](https://github.com/noir-lang/acvm/issues/219)) ([7abe6e5](https://github.com/noir-lang/acvm/commit/7abe6e5f6d6fea379c3748a910afd00db066eb45)) +* implement `add_mul` on `Expression` ([#207](https://github.com/noir-lang/acvm/issues/207)) ([f156e18](https://github.com/noir-lang/acvm/commit/f156e18cf7a0f1a99bbe1683b8e75fec8325e6dd)) +* implement `FieldElement::from<bool>()` ([#203](https://github.com/noir-lang/acvm/issues/203)) ([476cfa2](https://github.com/noir-lang/acvm/commit/476cfa247fddb515c64c2801c6868357c9375294)) +* replace `MerkleMembership` opcode with `ComputeMerkleRoot` ([#233](https://github.com/noir-lang/acvm/issues/233)) ([74bfee8](https://github.com/noir-lang/acvm/commit/74bfee80e0ff0d205aee1eea548c97ade8bd0e41)) +* require `Backend` to implement `Default` trait ([#223](https://github.com/noir-lang/acvm/issues/223)) ([00282dc](https://github.com/noir-lang/acvm/commit/00282dc5e2b03947bf709a088d829f3e0ba80eed)) +* return `PartialWitnessGeneratorStatus` from `PartialWitnessGenerator.solve` ([#213](https://github.com/noir-lang/acvm/issues/213)) ([e877bed](https://github.com/noir-lang/acvm/commit/e877bed2cca76bd486e9bed66b4230e65a01f0a2)) +* return `Result<OpcodeResolution, OpcodeResolutionError>` from `solve_range_opcode` ([#238](https://github.com/noir-lang/acvm/issues/238)) ([15d3c5a](https://github.com/noir-lang/acvm/commit/15d3c5a9be2dd92f266fcb7e672da17cada9fec5)) + + +### Bug Fixes + +* prevent `bn254` feature flag always being enabled ([#225](https://github.com/noir-lang/acvm/issues/225)) ([82eee6a](https://github.com/noir-lang/acvm/commit/82eee6ab08ae480f04904ca8571fd88f4466c000)) + + +### Miscellaneous Chores + +* Make GeneralOptimizer crate visible ([#220](https://github.com/noir-lang/acvm/issues/220)) ([64bb346](https://github.com/noir-lang/acvm/commit/64bb346524428a0ce196826ea1e5ccde08ad6201)) +* organise operator implementations for Expression ([#190](https://github.com/noir-lang/acvm/issues/190)) ([a619df6](https://github.com/noir-lang/acvm/commit/a619df614bbb9b2518b788b42a7553b069823a0f)) + +## [0.9.0](https://github.com/noir-lang/acvm/compare/root-v0.8.1...root-v0.9.0) (2023-04-07) + + +### ⚠ BREAKING CHANGES + +* **acvm:** Remove deprecated eth_contract_from_cs from SmartContract trait ([#185](https://github.com/noir-lang/acvm/issues/185)) +* **acvm:** make `Backend` trait object safe ([#180](https://github.com/noir-lang/acvm/issues/180)) + +### Features + +* **acvm:** make `Backend` trait object safe ([#180](https://github.com/noir-lang/acvm/issues/180)) ([fd28657](https://github.com/noir-lang/acvm/commit/fd28657426260ce3c53517b75a27eb5c4a74e234)) + + +### Bug Fixes + +* Add test for Out of Memory ([#188](https://github.com/noir-lang/acvm/issues/188)) ([c3db985](https://github.com/noir-lang/acvm/commit/c3db985893e7e59ea04005bb3a57eda5c6ce28c7)) + + +### Miscellaneous Chores + +* **acvm:** Remove deprecated eth_contract_from_cs from SmartContract trait ([#185](https://github.com/noir-lang/acvm/issues/185)) ([ee59c9e](https://github.com/noir-lang/acvm/commit/ee59c9efe9a54ff6b97e4daaebf64f3e327e97d9)) + +## [0.8.1](https://github.com/noir-lang/acvm/compare/root-v0.8.0...root-v0.8.1) (2023-03-30) + + +### Bug Fixes + +* unwraps if inputs is zero ([#171](https://github.com/noir-lang/acvm/issues/171)) ([10a3bb2](https://github.com/noir-lang/acvm/commit/10a3bb2a9930ccf422b3f08227aae07775686860)) + +## [0.8.0](https://github.com/noir-lang/acvm/compare/root-v0.7.1...root-v0.8.0) (2023-03-28) + + +### ⚠ BREAKING CHANGES + +* **acir:** Read Log Directive ([#156](https://github.com/noir-lang/acvm/issues/156)) + +### Bug Fixes + +* **acir:** Read Log Directive ([#156](https://github.com/noir-lang/acvm/issues/156)) ([1cc2b7f](https://github.com/noir-lang/acvm/commit/1cc2b7f2179cecc338fe0def72bb2dd17eaed0cd)) + +## [0.7.1](https://github.com/noir-lang/acvm/compare/root-v0.7.0...root-v0.7.1) (2023-03-27) + + +### Bug Fixes + +* **pwg:** stall instead of fail for unassigned black box ([#154](https://github.com/noir-lang/acvm/issues/154)) ([412a1a6](https://github.com/noir-lang/acvm/commit/412a1a60b434bef53e12d37c3b2bb3d51a317994)) + +## [0.7.0](https://github.com/noir-lang/acvm/compare/root-v0.6.0...root-v0.7.0) (2023-03-23) + + +### ⚠ BREAKING CHANGES + +* Add initial oracle opcode ([#149](https://github.com/noir-lang/acvm/issues/149)) +* **acir:** Add RAM and ROM opcodes +* **acir:** Add a public outputs field ([#56](https://github.com/noir-lang/acvm/issues/56)) +* **acir:** remove `Linear` struct ([#145](https://github.com/noir-lang/acvm/issues/145)) +* **acvm:** remove `prove_with_meta` and `verify_from_cs` from `ProofSystemCompiler` ([#140](https://github.com/noir-lang/acvm/issues/140)) +* **acvm:** Remove truncate and oddrange directives ([#142](https://github.com/noir-lang/acvm/issues/142)) + +### Features + +* **acir:** Add a public outputs field ([#56](https://github.com/noir-lang/acvm/issues/56)) ([5f358a9](https://github.com/noir-lang/acvm/commit/5f358a97aaa81d87956e182cd8a6d60de75f9752)) +* **acir:** Add RAM and ROM opcodes ([73e9f25](https://github.com/noir-lang/acvm/commit/73e9f25dd87b2ca91245e93d2445eadc0f522fac)) +* Add initial oracle opcode ([#149](https://github.com/noir-lang/acvm/issues/149)) ([88ee2f8](https://github.com/noir-lang/acvm/commit/88ee2f89f37abf5dd1d9f91b4d2eed44dc651348)) + + +### Miscellaneous Chores + +* **acir:** remove `Linear` struct ([#145](https://github.com/noir-lang/acvm/issues/145)) ([bbb6d92](https://github.com/noir-lang/acvm/commit/bbb6d92e25c43dd33b12f5fcd639fc9ad2a9c9d8)) +* **acvm:** remove `prove_with_meta` and `verify_from_cs` from `ProofSystemCompiler` ([#140](https://github.com/noir-lang/acvm/issues/140)) ([35dd181](https://github.com/noir-lang/acvm/commit/35dd181102203df17eef510666b327ef41f4b036)) +* **acvm:** Remove truncate and oddrange directives ([#142](https://github.com/noir-lang/acvm/issues/142)) ([85dd6e8](https://github.com/noir-lang/acvm/commit/85dd6e85bfba85bfb97651f7e30e1f75deb986d5)) + +## [0.6.0](https://github.com/noir-lang/acvm/compare/root-v0.5.0...root-v0.6.0) (2023-03-03) + + +### ⚠ BREAKING CHANGES + +* **acir:** rename `term_addition` to `push_addition_term` +* **acir:** rename `term_multiplication` to `push_multiplication_term` ([#122](https://github.com/noir-lang/acvm/issues/122)) +* **acir:** remove `UnknownWitness` ([#123](https://github.com/noir-lang/acvm/issues/123)) +* add block opcode ([#114](https://github.com/noir-lang/acvm/issues/114)) + +### Features + +* **acir:** add useful methods from `noirc_evaluator` onto `Expression` ([#125](https://github.com/noir-lang/acvm/issues/125)) ([d3d5f89](https://github.com/noir-lang/acvm/commit/d3d5f8917482ce5649602695829862a5df4ea712)) +* add block opcode ([#114](https://github.com/noir-lang/acvm/issues/114)) ([097cfb0](https://github.com/noir-lang/acvm/commit/097cfb069291705ddb4bf1fca77ddcef21dbbd08)) + + +### Bug Fixes + +* **acir:** correctly display expressions with non-unit coefficients ([d3d5f89](https://github.com/noir-lang/acvm/commit/d3d5f8917482ce5649602695829862a5df4ea712)) +* **ci:** publish acvm_stdlib before acvm ([#117](https://github.com/noir-lang/acvm/issues/117)) ([ca6defc](https://github.com/noir-lang/acvm/commit/ca6defc9bb5f51241b2fc4d9cd732f9678b4688f)) + + +### Miscellaneous Chores + +* **acir:** remove `UnknownWitness` ([#123](https://github.com/noir-lang/acvm/issues/123)) ([9f002c7](https://github.com/noir-lang/acvm/commit/9f002c7b49a5cf222d4a01732cc4917a47690863)) +* **acir:** rename `term_addition` to `push_addition_term` ([d389385](https://github.com/noir-lang/acvm/commit/d38938542851a97dc01727438391e6a65e44c689)) +* **acir:** rename `term_multiplication` to `push_multiplication_term` ([#122](https://github.com/noir-lang/acvm/issues/122)) ([d389385](https://github.com/noir-lang/acvm/commit/d38938542851a97dc01727438391e6a65e44c689)) + +## [0.5.0](https://github.com/noir-lang/acvm/compare/root-v0.4.1...root-v0.5.0) (2023-02-22) + + +### ⚠ BREAKING CHANGES + +* **acvm:** switch to accepting public inputs as a map ([#96](https://github.com/noir-lang/acvm/issues/96)) +* **acvm:** add `eth_contract_from_vk` to `SmartContract +* update `ProofSystemCompiler` to not take ownership of keys ([#111](https://github.com/noir-lang/acvm/issues/111)) +* update `ProofSystemCompiler` methods to take `&Circuit` ([#108](https://github.com/noir-lang/acvm/issues/108)) +* **acir:** make PublicInputs use a BTreeSet rather than Vec ([#99](https://github.com/noir-lang/acvm/issues/99)) +* refactor ToRadix to ToRadixLe and ToRadixBe ([#58](https://github.com/noir-lang/acvm/issues/58)) +* **acir:** Add keccak256 Opcode ([#91](https://github.com/noir-lang/acvm/issues/91)) +* reorganise compiler in terms of optimisers and transformers ([#88](https://github.com/noir-lang/acvm/issues/88)) + +### Features + +* **acir:** Add keccak256 Opcode ([#91](https://github.com/noir-lang/acvm/issues/91)) ([b909146](https://github.com/noir-lang/acvm/commit/b9091461e199bacdd073cc9b31f03dade0b4fb2d)) +* **acir:** make PublicInputs use a BTreeSet rather than Vec ([#99](https://github.com/noir-lang/acvm/issues/99)) ([53666b7](https://github.com/noir-lang/acvm/commit/53666b782d89c65cd755f9e4ded2c9cf5a141e46)) +* **acvm:** add `eth_contract_from_vk` to `SmartContract ([#113](https://github.com/noir-lang/acvm/issues/113)) ([373c18f](https://github.com/noir-lang/acvm/commit/373c18fc05edf673cfec9e8bbb78bd7d7514999e)) +* **acvm:** switch to accepting public inputs as a map ([#96](https://github.com/noir-lang/acvm/issues/96)) ([f57ba57](https://github.com/noir-lang/acvm/commit/f57ba57c2bb2597edf2b02fb1321c69cf11993ee)) +* **ci:** Add release workflow ([#89](https://github.com/noir-lang/acvm/issues/89)) ([db8e828](https://github.com/noir-lang/acvm/commit/db8e828341f59241ef7f437c908277fb8fbca9e3)) +* **ci:** Publish crates upon release ([#104](https://github.com/noir-lang/acvm/issues/104)) ([b265920](https://github.com/noir-lang/acvm/commit/b265920bc1b0c776d20326a0b74fc635c22af4b9)) +* update `ProofSystemCompiler` methods to take `&Circuit` ([#108](https://github.com/noir-lang/acvm/issues/108)) ([af56ca9](https://github.com/noir-lang/acvm/commit/af56ca9da06068c650c66e76bfd09e65eb0ec213)) +* update `ProofSystemCompiler` to not take ownership of keys ([#111](https://github.com/noir-lang/acvm/issues/111)) ([39b8a41](https://github.com/noir-lang/acvm/commit/39b8a41293e567971f700f61103852cb987a8d16)) +* Update Arkworks' dependencies on `acir_field` ([#69](https://github.com/noir-lang/acvm/issues/69)) ([65d6130](https://github.com/noir-lang/acvm/commit/65d61307a12f25e04afad2d50e4c4db5ce97dd8c)) + + +### Bug Fixes + +* **ci:** Update dependency versions in the workspace file ([#103](https://github.com/noir-lang/acvm/issues/103)) ([9acc266](https://github.com/noir-lang/acvm/commit/9acc266c7dc5a6ad2fa9c466cc82cb81d984b7ed)) +* Clean up Log Directive hex output ([#97](https://github.com/noir-lang/acvm/issues/97)) ([d23c735](https://github.com/noir-lang/acvm/commit/d23c7352523ffb42f3e8f4229b61f9803ab78a7e)) + + +### Miscellaneous Chores + +* refactor ToRadix to ToRadixLe and ToRadixBe ([#58](https://github.com/noir-lang/acvm/issues/58)) ([2427a27](https://github.com/noir-lang/acvm/commit/2427a275048e598c6d651cce8348a4c55148f235)) +* reorganise compiler in terms of optimisers and transformers ([#88](https://github.com/noir-lang/acvm/issues/88)) ([9329307](https://github.com/noir-lang/acvm/commit/9329307e054de202cfc55207162ad952b70d515e)) + +## [0.4.1] - 2023-02-08 + +### Added + +### Fixed + +- Removed duplicated logic in match branch + +### Changed + +### Removed + +## [0.4.0] - 2023-02-08 + +### Added + +- Add log directive +- Expose `acir_field` through `acir` crate +- Add permutation directive +- Add preprocess methods to ACVM interface + +### Fixed + +### Changed + +- Changed spellings of many functions to be correct using spellchecker + +### Removed + +## [0.3.1] - 2023-01-18 + +### Added + +### Fixed + +### Changed + +- ACVM compile method now returns an Error for when a function cannot be reduced to arithmetic gates + +- Backtrack changes from noir-lang/noir/587 + +### Removed + +## [0.3.0] - 2022-12-31 + +### Added + +- Added stdlib module to hold all of the standard opcodes +- added `read` , `write` methods for circuit + +### Fixed + +### Changed + +- XOR, Range and AND gates are no longer special case. They are now another opcode in the GadgetCall +- Move fallback module to `stdlib` +- Optimizer code and any other passes will live in acvm. acir is solely for defining the IR now. +- ACIR passes now live under the compiler parent module +- Moved opcode module in acir crate to circuit/opcode +- Rename GadgetCall to BlackBoxFuncCall +- Rename opcode file to blackbox_functions . Similarly OPCODE is now BlackBoxFunc +- Renamed GateResolution::UnsupportedOpcode to GateResolution::UnsupportedBlackBoxFunc +- Renamed GadgetDefinition to FuncDefinition +- Rename GadgetInput to FunctionInput +- Rename Gate -> Opcode . Similarly gate.rs is now opcodes.rs +- Rename CustomGate::supports_gate -> CustomGate::supports_opcode +- Rename GateResolution to OpcodeResolution +- Rename Split directive to ToBits +- Field element printing function was modified to uses ascii superscript numbers and ascii multiplication +- Refactor the way we print ACIR (This is a first draft and will change with more feedback) +- Rename `solve_gadget_call` trait method on ProofSystemCompile to `solve_blackbox_function_call` +- API for `compile` now requires a function pointer which tells us whether a blackbox function is supported +- Renamed Directive::Oddrange to Directive::OddRange +- Renamed FieldElement::to_bytes to FieldElement::to_be_bytes + +### Removed + +- Selector struct has been removed as it is no longer being used. It is also not being used by Noir. +- CustomGate trait -- There is a method in the ProofSystemCompiler Trait that backends can use to indicate whether +they support a particular black box function +- Remove OpcodeResolution enum from pwg. The happy case is strictly when the witness has been solved + +## [0.2.1] - 2022-12-23 + +- Removed ToBits and ToBytes opcode diff --git a/acvm-repo/README.md b/acvm-repo/README.md new file mode 100644 index 00000000000..96353252f27 --- /dev/null +++ b/acvm-repo/README.md @@ -0,0 +1,20 @@ +# ACIR - Abstract Circuit Intermediate Representation + +ACIR is an NP complete language that generalizes R1CS and arithmetic circuits while not losing proving system specific optimizations through the use of black box functions. + +# ACVM - Abstract Circuit Virtual Machine + +This can be seen as the ACIR compiler. It will take an ACIR instance and convert it to the format required +by a particular proving system to create a proof. + +# How to add a new crate to the workspace + +- Create the new crate with the current version of the other crates. +- In root `Cargo.toml`, add the new crate to the workspace members list. +- If you want to import it from multiple packages, you can add it as a dependency in the root `Cargo.toml`. +- In `release-please-config.json`: + - Add a package entry + - Add the crate name to the `linked-versions` plugin list + - If you added the new crate as a dependency in the root `Cargo.toml`, add it to the extra-files of the root package. +- In `.release-please-manifest.json`, add the new crate with the same version of the others. +- In [publish.yml](.github/workflows/publish.yml), add the new crate to the `publish` job after its dependencies. diff --git a/acvm-repo/acir/CHANGELOG.md b/acvm-repo/acir/CHANGELOG.md new file mode 100644 index 00000000000..e31ee66379a --- /dev/null +++ b/acvm-repo/acir/CHANGELOG.md @@ -0,0 +1,521 @@ +# Changelog + +## [0.27.0](https://github.com/noir-lang/acvm/compare/acir-v0.26.1...acir-v0.27.0) (2023-09-19) + + +### Features + +* **acir:** add method on `Circuit` to return assert message ([#551](https://github.com/noir-lang/acvm/issues/551)) ([ee18cde](https://github.com/noir-lang/acvm/commit/ee18cde3537b2be6714061af0bc9ef3793929f7f)) + +## [0.26.1](https://github.com/noir-lang/acvm/compare/acir-v0.26.0...acir-v0.26.1) (2023-09-12) + + +### Bug Fixes + +* Implements handling of the high limb during fixed base scalar multiplication ([#535](https://github.com/noir-lang/acvm/issues/535)) ([551504a](https://github.com/noir-lang/acvm/commit/551504aa572d3f9d56b5576d25ce1211296ee488)) + +## [0.26.0](https://github.com/noir-lang/acvm/compare/acir-v0.25.0...acir-v0.26.0) (2023-09-07) + + +### ⚠ BREAKING CHANGES + +* Add a low and high limb to scalar mul opcode ([#532](https://github.com/noir-lang/acvm/issues/532)) + +### Miscellaneous Chores + +* Add a low and high limb to scalar mul opcode ([#532](https://github.com/noir-lang/acvm/issues/532)) ([b054f66](https://github.com/noir-lang/acvm/commit/b054f66be9c73d4e02dbecdab80874a907f19242)) + +## [0.25.0](https://github.com/noir-lang/acvm/compare/acir-v0.24.1...acir-v0.25.0) (2023-09-04) + + +### Miscellaneous Chores + +* **acir:** Synchronize acvm versions + +## [0.24.1](https://github.com/noir-lang/acvm/compare/acir-v0.24.0...acir-v0.24.1) (2023-09-03) + + +### Miscellaneous Chores + +* **acir:** Synchronize acvm versions + +## [0.24.0](https://github.com/noir-lang/acvm/compare/acir-v0.23.0...acir-v0.24.0) (2023-08-31) + + +### ⚠ BREAKING CHANGES + +* **acir:** Remove unused `Directive` opcodes ([#510](https://github.com/noir-lang/acvm/issues/510)) +* **acir:** Add predicate to MemoryOp ([#503](https://github.com/noir-lang/acvm/issues/503)) +* Assertion messages embedded in the circuit ([#484](https://github.com/noir-lang/acvm/issues/484)) + +### Features + +* **acir:** Add predicate to MemoryOp ([#503](https://github.com/noir-lang/acvm/issues/503)) ([ca9eebe](https://github.com/noir-lang/acvm/commit/ca9eebe34e61adabf97318c8ccaf60c8a424aafd)) +* Assertion messages embedded in the circuit ([#484](https://github.com/noir-lang/acvm/issues/484)) ([06b97c5](https://github.com/noir-lang/acvm/commit/06b97c51041e16651cf8b2be8bc18214e276c6c9)) + + +### Miscellaneous Chores + +* **acir:** Remove unused `Directive` opcodes ([#510](https://github.com/noir-lang/acvm/issues/510)) ([cfd8cbf](https://github.com/noir-lang/acvm/commit/cfd8cbf58307511ac0cc9106c299695c2ca779de)) + +## [0.23.0](https://github.com/noir-lang/acvm/compare/acir-v0.22.0...acir-v0.23.0) (2023-08-30) + + +### Miscellaneous Chores + +* **acir:** Synchronize acvm versions + +## [0.22.0](https://github.com/noir-lang/acvm/compare/acir-v0.21.0...acir-v0.22.0) (2023-08-18) + + +### ⚠ BREAKING CHANGES + +* Switched from OpcodeLabel to OpcodeLocation and ErrorLocation ([#493](https://github.com/noir-lang/acvm/issues/493)) + +### Features + +* Switched from OpcodeLabel to OpcodeLocation and ErrorLocation ([#493](https://github.com/noir-lang/acvm/issues/493)) ([27a5a93](https://github.com/noir-lang/acvm/commit/27a5a935849f8904e10056b08089f532a06962b8)) + +## [0.21.0](https://github.com/noir-lang/acvm/compare/acir-v0.20.1...acir-v0.21.0) (2023-07-26) + + +### ⚠ BREAKING CHANGES + +* **acir:** Remove `Block`, `RAM` and `ROM` opcodes ([#457](https://github.com/noir-lang/acvm/issues/457)) +* **acvm:** Support stepwise execution of ACIR ([#399](https://github.com/noir-lang/acvm/issues/399)) + +### Features + +* **acvm:** Support stepwise execution of ACIR ([#399](https://github.com/noir-lang/acvm/issues/399)) ([6a03950](https://github.com/noir-lang/acvm/commit/6a0395021779a2711353c2fe2948e09b5b538fc0)) + + +### Miscellaneous Chores + +* **acir:** Remove `Block`, `RAM` and `ROM` opcodes ([#457](https://github.com/noir-lang/acvm/issues/457)) ([8dd220a](https://github.com/noir-lang/acvm/commit/8dd220ae127baf6cc5a31d8ab7ffdeeb161f6109)) + +## [0.20.1](https://github.com/noir-lang/acvm/compare/acir-v0.20.0...acir-v0.20.1) (2023-07-26) + + +### Miscellaneous Chores + +* **acir:** Synchronize acvm versions + +## [0.20.0](https://github.com/noir-lang/acvm/compare/acir-v0.19.1...acir-v0.20.0) (2023-07-20) + + +### ⚠ BREAKING CHANGES + +* atomic memory opcodes ([#447](https://github.com/noir-lang/acvm/issues/447)) + +### Features + +* atomic memory opcodes ([#447](https://github.com/noir-lang/acvm/issues/447)) ([3261c7a](https://github.com/noir-lang/acvm/commit/3261c7a2fd4f3a300bc5f39ef4febccd8a853560)) + +## [0.19.1](https://github.com/noir-lang/acvm/compare/acir-v0.19.0...acir-v0.19.1) (2023-07-17) + + +### Miscellaneous Chores + +* **acir:** Synchronize acvm versions + +## [0.19.0](https://github.com/noir-lang/acvm/compare/acir-v0.18.2...acir-v0.19.0) (2023-07-15) + + +### ⚠ BREAKING CHANGES + +* move to bincode and GzEncoding for artifacts ([#436](https://github.com/noir-lang/acvm/issues/436)) + +### Features + +* move to bincode and GzEncoding for artifacts ([#436](https://github.com/noir-lang/acvm/issues/436)) ([4683240](https://github.com/noir-lang/acvm/commit/46832400a8bc20135a8a895ab9477b14449734d9)) + +## [0.18.2](https://github.com/noir-lang/acvm/compare/acir-v0.18.1...acir-v0.18.2) (2023-07-12) + + +### Miscellaneous Chores + +* **acir:** Synchronize acvm versions + +## [0.18.1](https://github.com/noir-lang/acvm/compare/acir-v0.18.0...acir-v0.18.1) (2023-07-12) + + +### Miscellaneous Chores + +* **acir:** Synchronize acvm versions + +## [0.18.0](https://github.com/noir-lang/acvm/compare/acir-v0.17.0...acir-v0.18.0) (2023-07-12) + + +### ⚠ BREAKING CHANGES + +* add backend-solvable blackboxes to brillig & unify implementations ([#422](https://github.com/noir-lang/acvm/issues/422)) +* Returns index of failing opcode and transformation mapping ([#412](https://github.com/noir-lang/acvm/issues/412)) + +### Features + +* add backend-solvable blackboxes to brillig & unify implementations ([#422](https://github.com/noir-lang/acvm/issues/422)) ([093342e](https://github.com/noir-lang/acvm/commit/093342ea9481a311fa71343b8b7a22774788838a)) +* Returns index of failing opcode and transformation mapping ([#412](https://github.com/noir-lang/acvm/issues/412)) ([79950e9](https://github.com/noir-lang/acvm/commit/79950e943f60e4082e1cf5ec4442aa67ea91aade)) + +## [0.17.0](https://github.com/noir-lang/acvm/compare/acir-v0.16.0...acir-v0.17.0) (2023-07-07) + + +### ⚠ BREAKING CHANGES + +* **acir:** add `EcdsaSecp256r1` blackbox function ([#408](https://github.com/noir-lang/acvm/issues/408)) + +### Features + +* **acir:** add `EcdsaSecp256r1` blackbox function ([#408](https://github.com/noir-lang/acvm/issues/408)) ([9895817](https://github.com/noir-lang/acvm/commit/98958170c9fa9b4731e33b31cb494a72bb90549e)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.16.0 to 0.17.0 + +## [0.16.0](https://github.com/noir-lang/acvm/compare/acir-v0.15.1...acir-v0.16.0) (2023-07-06) + + +### ⚠ BREAKING CHANGES + +* **acir:** revert changes to `SchnorrVerify` opcode ([#409](https://github.com/noir-lang/acvm/issues/409)) +* **acir:** Remove `Oracle` opcode ([#368](https://github.com/noir-lang/acvm/issues/368)) +* **acir:** Use fixed length data structures in black box function inputs/outputs where possible. ([#386](https://github.com/noir-lang/acvm/issues/386)) +* **acir:** Implement `Add` trait for `Witness` & make output of `Mul` on `Expression` optional ([#393](https://github.com/noir-lang/acvm/issues/393)) + +### Features + +* **acir:** Implement `Add` trait for `Witness` & make output of `Mul` on `Expression` optional ([#393](https://github.com/noir-lang/acvm/issues/393)) ([5bcdfc6](https://github.com/noir-lang/acvm/commit/5bcdfc62e4936922135add171d60a948922581ff)) +* **acir:** Remove `Oracle` opcode ([#368](https://github.com/noir-lang/acvm/issues/368)) ([63354df](https://github.com/noir-lang/acvm/commit/63354df1fe47a4f1128b91641d1b66dfc1281794)) +* **acir:** Use fixed length data structures in black box function inputs/outputs where possible. ([#386](https://github.com/noir-lang/acvm/issues/386)) ([b139d4d](https://github.com/noir-lang/acvm/commit/b139d4d566c715009465a430aab0fb819aacab4f)) + + +### Bug Fixes + +* **acir:** revert changes to `SchnorrVerify` opcode ([#409](https://github.com/noir-lang/acvm/issues/409)) ([f1c7940](https://github.com/noir-lang/acvm/commit/f1c7940f4ac618c7b440b6ed30199f85cbe72cca)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.15.1 to 0.16.0 + +## [0.15.1](https://github.com/noir-lang/acvm/compare/acir-v0.15.0...acir-v0.15.1) (2023-06-20) + + +### Miscellaneous Chores + +* **acir:** Synchronize acvm versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.15.0 to 0.15.1 + +## [0.15.0](https://github.com/noir-lang/acvm/compare/acir-v0.14.2...acir-v0.15.0) (2023-06-15) + + +### Features + +* Add method to generate updated `Brillig` opcode from `UnresolvedBrilligCall` ([#363](https://github.com/noir-lang/acvm/issues/363)) ([fda5dbe](https://github.com/noir-lang/acvm/commit/fda5dbe57c28dc4bc28dfd8fe0a4a8ba29635393)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.14.2 to 0.15.0 + +## [0.14.2](https://github.com/noir-lang/acvm/compare/acir-v0.14.1...acir-v0.14.2) (2023-06-08) + + +### Miscellaneous Chores + +* **acir:** Synchronize acvm versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.14.1 to 0.14.2 + +## [0.14.1](https://github.com/noir-lang/acvm/compare/acir-v0.14.0...acir-v0.14.1) (2023-06-07) + + +### Features + +* Re-use intermediate variables created during width reduction, with proper scale. ([#343](https://github.com/noir-lang/acvm/issues/343)) ([6bd0baa](https://github.com/noir-lang/acvm/commit/6bd0baa4bc9ac204e7710ec6d17d1752d2e924c0)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.14.0 to 0.14.1 + +## [0.14.0](https://github.com/noir-lang/acvm/compare/acir-v0.13.3...acir-v0.14.0) (2023-06-06) + + +### ⚠ BREAKING CHANGES + +* **acir:** Verify Proof ([#291](https://github.com/noir-lang/acvm/issues/291)) + +### Features + +* **acir:** Verify Proof ([#291](https://github.com/noir-lang/acvm/issues/291)) ([9f34428](https://github.com/noir-lang/acvm/commit/9f34428b7084c7c38de401a16ca76e748d8b1d77)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.13.3 to 0.14.0 + +## [0.13.3](https://github.com/noir-lang/acvm/compare/acir-v0.13.2...acir-v0.13.3) (2023-06-05) + + +### Bug Fixes + +* Empty commit to trigger release-please ([e8f0748](https://github.com/noir-lang/acvm/commit/e8f0748042ef505d59ab63266d3c36c5358ee30d)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.13.2 to 0.13.3 + +## [0.13.2](https://github.com/noir-lang/acvm/compare/acir-v0.13.1...acir-v0.13.2) (2023-06-02) + + +### Bug Fixes + +* re-use intermediate vars during width reduction ([#278](https://github.com/noir-lang/acvm/issues/278)) ([5b32920](https://github.com/noir-lang/acvm/commit/5b32920263c4481c60faf0b84f0031aa8149b6b2)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.13.1 to 0.13.2 + +## [0.13.1](https://github.com/noir-lang/acvm/compare/acir-v0.13.0...acir-v0.13.1) (2023-06-01) + + +### Bug Fixes + +* **ci:** Correct typo to avoid `undefined` in changelogs ([#333](https://github.com/noir-lang/acvm/issues/333)) ([d3424c0](https://github.com/noir-lang/acvm/commit/d3424c04fd303c9cbe25d03118d8b358cbb84b83)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.1.1 to 0.13.1 + +## [0.13.0](https://github.com/noir-lang/acvm/compare/acir-v0.12.0...acir-v0.13.0) (2023-06-01) + + +### ⚠ BREAKING CHANGES + +* added hash index to pedersen ([#281](https://github.com/noir-lang/acvm/issues/281)) +* Add variable length keccak opcode ([#314](https://github.com/noir-lang/acvm/issues/314)) +* Remove AES opcode ([#302](https://github.com/noir-lang/acvm/issues/302)) +* **acir, acvm:** Remove ComputeMerkleRoot opcode #296 +* Remove manual serialization of `Opcode`s in favour of `serde` ([#286](https://github.com/noir-lang/acvm/issues/286)) + +### Features + +* **acir, acvm:** Remove ComputeMerkleRoot opcode [#296](https://github.com/noir-lang/acvm/issues/296) ([8b3923e](https://github.com/noir-lang/acvm/commit/8b3923e191e4ac399400025496e8bb4453734040)) +* Add `Brillig` opcode to introduce custom non-determinism to ACVM ([#152](https://github.com/noir-lang/acvm/issues/152)) ([3c6740a](https://github.com/noir-lang/acvm/commit/3c6740af75125afc8ebb4379f781f8274015e2e2)) +* Add variable length keccak opcode ([#314](https://github.com/noir-lang/acvm/issues/314)) ([7bfd169](https://github.com/noir-lang/acvm/commit/7bfd1695b6f119cd70fce4866314c9bb4991eaab)) +* added hash index to pedersen ([#281](https://github.com/noir-lang/acvm/issues/281)) ([61820b6](https://github.com/noir-lang/acvm/commit/61820b651900aac8d9557b4b9477ed0e1763c124)) + + +### Miscellaneous Chores + +* Remove AES opcode ([#302](https://github.com/noir-lang/acvm/issues/302)) ([a429a54](https://github.com/noir-lang/acvm/commit/a429a5422d6f001b6db0d0a0f30c79ec0f96de89)) +* Remove manual serialization of `Opcode`s in favour of `serde` ([#286](https://github.com/noir-lang/acvm/issues/286)) ([8a3812f](https://github.com/noir-lang/acvm/commit/8a3812fe6ed3b267692284bdcd909d9dd32b9747)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.1.0 to 0.1.1 + +## [0.12.0](https://github.com/noir-lang/acvm/compare/acir-v0.11.0...acir-v0.12.0) (2023-05-17) + + +### ⚠ BREAKING CHANGES + +* Introduce WitnessMap data structure to avoid leaking internal structure ([#252](https://github.com/noir-lang/acvm/issues/252)) +* use struct variants for blackbox function calls ([#269](https://github.com/noir-lang/acvm/issues/269)) +* **acvm:** Simplification pass for ACIR ([#151](https://github.com/noir-lang/acvm/issues/151)) + +### Features + +* **acvm:** Simplification pass for ACIR ([#151](https://github.com/noir-lang/acvm/issues/151)) ([7bc42c6](https://github.com/noir-lang/acvm/commit/7bc42c62b6e095f838b781c87cbb1ecd2af5f179)) +* Introduce WitnessMap data structure to avoid leaking internal structure ([#252](https://github.com/noir-lang/acvm/issues/252)) ([b248e60](https://github.com/noir-lang/acvm/commit/b248e606dd69c25d33ae77c5c5c0541adbf80cd6)) +* use struct variants for blackbox function calls ([#269](https://github.com/noir-lang/acvm/issues/269)) ([a83333b](https://github.com/noir-lang/acvm/commit/a83333b9e270dfcfd40a36271896840ec0201bc4)) + + +### Bug Fixes + +* **acir:** Hide variants of WitnessMapError and export it from package ([#283](https://github.com/noir-lang/acvm/issues/283)) ([bbd9ab7](https://github.com/noir-lang/acvm/commit/bbd9ab7ca5be3fb31f3e141fee2522704852f5de)) + +## [0.11.0](https://github.com/noir-lang/acvm/compare/acir-v0.10.3...acir-v0.11.0) (2023-05-04) + + +### Bug Fixes + +* **acir:** Fix `Expression` multiplication to correctly handle degree 1 terms ([#255](https://github.com/noir-lang/acvm/issues/255)) ([e399396](https://github.com/noir-lang/acvm/commit/e399396f7e06deb6b831517af17018607df3f252)) + +## [0.10.3](https://github.com/noir-lang/acvm/compare/acir-v0.10.2...acir-v0.10.3) (2023-04-28) + + +### Miscellaneous Chores + +* **acir:** Synchronize acvm versions + +## [0.10.2](https://github.com/noir-lang/acvm/compare/acir-v0.10.1...acir-v0.10.2) (2023-04-28) + + +### Miscellaneous Chores + +* **acir:** Synchronize acvm versions + +## [0.10.1](https://github.com/noir-lang/acvm/compare/acir-v0.10.0...acir-v0.10.1) (2023-04-28) + + +### Bug Fixes + +* **acir:** add `bn254` as default feature flag ([#240](https://github.com/noir-lang/acvm/issues/240)) ([e56973d](https://github.com/noir-lang/acvm/commit/e56973d8dc1745fe9bb844ec8347acd4d836d42f)) + +## [0.10.0](https://github.com/noir-lang/acvm/compare/acir-v0.9.0...acir-v0.10.0) (2023-04-26) + + +### ⚠ BREAKING CHANGES + +* replace `MerkleMembership` opcode with `ComputeMerkleRoot` ([#233](https://github.com/noir-lang/acvm/issues/233)) +* return `PartialWitnessGeneratorStatus` from `PartialWitnessGenerator.solve` ([#213](https://github.com/noir-lang/acvm/issues/213)) +* organise operator implementations for Expression ([#190](https://github.com/noir-lang/acvm/issues/190)) + +### Features + +* implement `add_mul` on `Expression` ([#207](https://github.com/noir-lang/acvm/issues/207)) ([f156e18](https://github.com/noir-lang/acvm/commit/f156e18cf7a0f1a99bbe1683b8e75fec8325e6dd)) +* replace `MerkleMembership` opcode with `ComputeMerkleRoot` ([#233](https://github.com/noir-lang/acvm/issues/233)) ([74bfee8](https://github.com/noir-lang/acvm/commit/74bfee80e0ff0d205aee1eea548c97ade8bd0e41)) +* return `PartialWitnessGeneratorStatus` from `PartialWitnessGenerator.solve` ([#213](https://github.com/noir-lang/acvm/issues/213)) ([e877bed](https://github.com/noir-lang/acvm/commit/e877bed2cca76bd486e9bed66b4230e65a01f0a2)) + + +### Miscellaneous Chores + +* organise operator implementations for Expression ([#190](https://github.com/noir-lang/acvm/issues/190)) ([a619df6](https://github.com/noir-lang/acvm/commit/a619df614bbb9b2518b788b42a7553b069823a0f)) + +## [0.9.0](https://github.com/noir-lang/acvm/compare/acir-v0.8.1...acir-v0.9.0) (2023-04-07) + + +### Bug Fixes + +* Add test for Out of Memory ([#188](https://github.com/noir-lang/acvm/issues/188)) ([c3db985](https://github.com/noir-lang/acvm/commit/c3db985893e7e59ea04005bb3a57eda5c6ce28c7)) + +## [0.8.1](https://github.com/noir-lang/acvm/compare/acir-v0.8.0...acir-v0.8.1) (2023-03-30) + + +### Bug Fixes + +* unwraps if inputs is zero ([#171](https://github.com/noir-lang/acvm/issues/171)) ([10a3bb2](https://github.com/noir-lang/acvm/commit/10a3bb2a9930ccf422b3f08227aae07775686860)) + +## [0.8.0](https://github.com/noir-lang/acvm/compare/acir-v0.7.1...acir-v0.8.0) (2023-03-28) + + +### ⚠ BREAKING CHANGES + +* **acir:** Read Log Directive ([#156](https://github.com/noir-lang/acvm/issues/156)) + +### Bug Fixes + +* **acir:** Read Log Directive ([#156](https://github.com/noir-lang/acvm/issues/156)) ([1cc2b7f](https://github.com/noir-lang/acvm/commit/1cc2b7f2179cecc338fe0def72bb2dd17eaed0cd)) + +## [0.7.1](https://github.com/noir-lang/acvm/compare/acir-v0.7.0...acir-v0.7.1) (2023-03-27) + + +### Miscellaneous Chores + +* **acir:** Synchronize acvm versions + +## [0.7.0](https://github.com/noir-lang/acvm/compare/acir-v0.6.0...acir-v0.7.0) (2023-03-23) + + +### ⚠ BREAKING CHANGES + +* Add initial oracle opcode ([#149](https://github.com/noir-lang/acvm/issues/149)) +* **acir:** Add RAM and ROM opcodes +* **acir:** Add a public outputs field ([#56](https://github.com/noir-lang/acvm/issues/56)) +* **acir:** remove `Linear` struct ([#145](https://github.com/noir-lang/acvm/issues/145)) +* **acvm:** Remove truncate and oddrange directives ([#142](https://github.com/noir-lang/acvm/issues/142)) + +### Features + +* **acir:** Add a public outputs field ([#56](https://github.com/noir-lang/acvm/issues/56)) ([5f358a9](https://github.com/noir-lang/acvm/commit/5f358a97aaa81d87956e182cd8a6d60de75f9752)) +* **acir:** Add RAM and ROM opcodes ([73e9f25](https://github.com/noir-lang/acvm/commit/73e9f25dd87b2ca91245e93d2445eadc0f522fac)) +* Add initial oracle opcode ([#149](https://github.com/noir-lang/acvm/issues/149)) ([88ee2f8](https://github.com/noir-lang/acvm/commit/88ee2f89f37abf5dd1d9f91b4d2eed44dc651348)) + + +### Miscellaneous Chores + +* **acir:** remove `Linear` struct ([#145](https://github.com/noir-lang/acvm/issues/145)) ([bbb6d92](https://github.com/noir-lang/acvm/commit/bbb6d92e25c43dd33b12f5fcd639fc9ad2a9c9d8)) +* **acvm:** Remove truncate and oddrange directives ([#142](https://github.com/noir-lang/acvm/issues/142)) ([85dd6e8](https://github.com/noir-lang/acvm/commit/85dd6e85bfba85bfb97651f7e30e1f75deb986d5)) + +## [0.6.0](https://github.com/noir-lang/acvm/compare/acir-v0.5.0...acir-v0.6.0) (2023-03-03) + + +### ⚠ BREAKING CHANGES + +* **acir:** rename `term_addition` to `push_addition_term` +* **acir:** rename `term_multiplication` to `push_multiplication_term` ([#122](https://github.com/noir-lang/acvm/issues/122)) +* **acir:** remove `UnknownWitness` ([#123](https://github.com/noir-lang/acvm/issues/123)) +* add block opcode ([#114](https://github.com/noir-lang/acvm/issues/114)) + +### Features + +* **acir:** add useful methods from `noirc_evaluator` onto `Expression` ([#125](https://github.com/noir-lang/acvm/issues/125)) ([d3d5f89](https://github.com/noir-lang/acvm/commit/d3d5f8917482ce5649602695829862a5df4ea712)) +* add block opcode ([#114](https://github.com/noir-lang/acvm/issues/114)) ([097cfb0](https://github.com/noir-lang/acvm/commit/097cfb069291705ddb4bf1fca77ddcef21dbbd08)) + + +### Bug Fixes + +* **acir:** correctly display expressions with non-unit coefficients ([d3d5f89](https://github.com/noir-lang/acvm/commit/d3d5f8917482ce5649602695829862a5df4ea712)) + + +### Miscellaneous Chores + +* **acir:** remove `UnknownWitness` ([#123](https://github.com/noir-lang/acvm/issues/123)) ([9f002c7](https://github.com/noir-lang/acvm/commit/9f002c7b49a5cf222d4a01732cc4917a47690863)) +* **acir:** rename `term_addition` to `push_addition_term` ([d389385](https://github.com/noir-lang/acvm/commit/d38938542851a97dc01727438391e6a65e44c689)) +* **acir:** rename `term_multiplication` to `push_multiplication_term` ([#122](https://github.com/noir-lang/acvm/issues/122)) ([d389385](https://github.com/noir-lang/acvm/commit/d38938542851a97dc01727438391e6a65e44c689)) + +## [0.5.0](https://github.com/noir-lang/acvm/compare/acir-v0.4.1...acir-v0.5.0) (2023-02-22) + + +### ⚠ BREAKING CHANGES + +* **acir:** make PublicInputs use a BTreeSet rather than Vec ([#99](https://github.com/noir-lang/acvm/issues/99)) +* refactor ToRadix to ToRadixLe and ToRadixBe ([#58](https://github.com/noir-lang/acvm/issues/58)) +* **acir:** Add keccak256 Opcode ([#91](https://github.com/noir-lang/acvm/issues/91)) +* reorganise compiler in terms of optimisers and transformers ([#88](https://github.com/noir-lang/acvm/issues/88)) + +### Features + +* **acir:** Add keccak256 Opcode ([#91](https://github.com/noir-lang/acvm/issues/91)) ([b909146](https://github.com/noir-lang/acvm/commit/b9091461e199bacdd073cc9b31f03dade0b4fb2d)) +* **acir:** make PublicInputs use a BTreeSet rather than Vec ([#99](https://github.com/noir-lang/acvm/issues/99)) ([53666b7](https://github.com/noir-lang/acvm/commit/53666b782d89c65cd755f9e4ded2c9cf5a141e46)) + + +### Miscellaneous Chores + +* refactor ToRadix to ToRadixLe and ToRadixBe ([#58](https://github.com/noir-lang/acvm/issues/58)) ([2427a27](https://github.com/noir-lang/acvm/commit/2427a275048e598c6d651cce8348a4c55148f235)) +* reorganise compiler in terms of optimisers and transformers ([#88](https://github.com/noir-lang/acvm/issues/88)) ([9329307](https://github.com/noir-lang/acvm/commit/9329307e054de202cfc55207162ad952b70d515e)) diff --git a/acvm-repo/acir/Cargo.toml b/acvm-repo/acir/Cargo.toml new file mode 100644 index 00000000000..b4cd24701db --- /dev/null +++ b/acvm-repo/acir/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "acir" +description = "ACIR is the IR that the VM processes, it is analogous to LLVM IR" +# x-release-please-start-version +version = "0.27.4" +# x-release-please-end +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +acir_field.workspace = true +brillig.workspace = true +serde.workspace = true +thiserror.workspace = true +flate2 = "1.0.24" +bincode.workspace = true + +[dev-dependencies] +serde_json = "1.0" +strum = "0.24" +strum_macros = "0.24" + +[features] +default = ["bn254"] +bn254 = ["acir_field/bn254", "brillig/bn254"] +bls12_381 = ["acir_field/bls12_381", "brillig/bls12_381"] diff --git a/acvm-repo/acir/src/circuit/black_box_functions.rs b/acvm-repo/acir/src/circuit/black_box_functions.rs new file mode 100644 index 00000000000..17c990c7670 --- /dev/null +++ b/acvm-repo/acir/src/circuit/black_box_functions.rs @@ -0,0 +1,118 @@ +//! Black box functions are ACIR opcodes which rely on backends implementing support for specialized constraints. +//! This makes certain zk-snark unfriendly computations cheaper than if they were implemented in more basic constraints. +//! +//! It is possible to fallback to less efficient implementations written in ACIR in some cases. +//! These are implemented inside the ACVM stdlib. + +use serde::{Deserialize, Serialize}; +#[cfg(test)] +use strum_macros::EnumIter; + +#[allow(clippy::upper_case_acronyms)] +#[derive(Clone, Debug, Hash, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(test, derive(EnumIter))] +pub enum BlackBoxFunc { + /// Bitwise AND. + AND, + /// Bitwise XOR. + XOR, + /// Range constraint to ensure that a [`FieldElement`][acir_field::FieldElement] can be represented in a specified number of bits. + RANGE, + /// Calculates the SHA256 hash of the inputs. + SHA256, + /// Calculates the Blake2s hash of the inputs. + Blake2s, + /// Verifies a Schnorr signature over a curve which is "pairing friendly" with the curve on which the ACIR circuit is defined. + /// + /// The exact curve which this signature uses will vary based on the curve being used by ACIR. + /// For example, the BN254 curve supports Schnorr signatures over the [Grumpkin][grumpkin] curve. + /// + /// [grumpkin]: https://hackmd.io/@aztec-network/ByzgNxBfd#2-Grumpkin---A-curve-on-top-of-BN-254-for-SNARK-efficient-group-operations + SchnorrVerify, + /// Calculates a Pedersen commitment to the inputs. + Pedersen, + /// Hashes a set of inputs and applies the field modulus to the result + /// to return a value which can be represented as a [`FieldElement`][acir_field::FieldElement] + /// + /// This is implemented using the `Blake2s` hash function. + /// The "128" in the name specifies that this function should have 128 bits of security. + HashToField128Security, + /// Verifies a ECDSA signature over the secp256k1 curve. + EcdsaSecp256k1, + /// Verifies a ECDSA signature over the secp256r1 curve. + EcdsaSecp256r1, + /// Performs scalar multiplication over the embedded curve on which [`FieldElement`][acir_field::FieldElement] is defined. + FixedBaseScalarMul, + /// Calculates the Keccak256 hash of the inputs. + Keccak256, + /// Compute a recursive aggregation object when verifying a proof inside another circuit. + /// This outputted aggregation object will then be either checked in a top-level verifier or aggregated upon again. + RecursiveAggregation, +} + +impl std::fmt::Display for BlackBoxFunc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name()) + } +} + +impl BlackBoxFunc { + pub fn name(&self) -> &'static str { + match self { + BlackBoxFunc::SHA256 => "sha256", + BlackBoxFunc::SchnorrVerify => "schnorr_verify", + BlackBoxFunc::Blake2s => "blake2s", + BlackBoxFunc::Pedersen => "pedersen", + BlackBoxFunc::HashToField128Security => "hash_to_field_128_security", + BlackBoxFunc::EcdsaSecp256k1 => "ecdsa_secp256k1", + BlackBoxFunc::FixedBaseScalarMul => "fixed_base_scalar_mul", + BlackBoxFunc::AND => "and", + BlackBoxFunc::XOR => "xor", + BlackBoxFunc::RANGE => "range", + BlackBoxFunc::Keccak256 => "keccak256", + BlackBoxFunc::RecursiveAggregation => "recursive_aggregation", + BlackBoxFunc::EcdsaSecp256r1 => "ecdsa_secp256r1", + } + } + pub fn lookup(op_name: &str) -> Option { + match op_name { + "sha256" => Some(BlackBoxFunc::SHA256), + "schnorr_verify" => Some(BlackBoxFunc::SchnorrVerify), + "blake2s" => Some(BlackBoxFunc::Blake2s), + "pedersen" => Some(BlackBoxFunc::Pedersen), + "hash_to_field_128_security" => Some(BlackBoxFunc::HashToField128Security), + "ecdsa_secp256k1" => Some(BlackBoxFunc::EcdsaSecp256k1), + "ecdsa_secp256r1" => Some(BlackBoxFunc::EcdsaSecp256r1), + "fixed_base_scalar_mul" => Some(BlackBoxFunc::FixedBaseScalarMul), + "and" => Some(BlackBoxFunc::AND), + "xor" => Some(BlackBoxFunc::XOR), + "range" => Some(BlackBoxFunc::RANGE), + "keccak256" => Some(BlackBoxFunc::Keccak256), + "recursive_aggregation" => Some(BlackBoxFunc::RecursiveAggregation), + _ => None, + } + } + pub fn is_valid_black_box_func_name(op_name: &str) -> bool { + BlackBoxFunc::lookup(op_name).is_some() + } +} + +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::BlackBoxFunc; + + #[test] + fn consistent_function_names() { + for bb_func in BlackBoxFunc::iter() { + let resolved_func = BlackBoxFunc::lookup(bb_func.name()).unwrap_or_else(|| { + panic!("BlackBoxFunc::lookup couldn't find black box function {}", bb_func) + }); + assert_eq!( + resolved_func, bb_func, + "BlackBoxFunc::lookup returns unexpected BlackBoxFunc" + ) + } + } +} diff --git a/acvm-repo/acir/src/circuit/brillig.rs b/acvm-repo/acir/src/circuit/brillig.rs new file mode 100644 index 00000000000..5b1ec9d032a --- /dev/null +++ b/acvm-repo/acir/src/circuit/brillig.rs @@ -0,0 +1,33 @@ +use crate::native_types::{Expression, Witness}; +use brillig::ForeignCallResult; +use brillig::Opcode as BrilligOpcode; +use serde::{Deserialize, Serialize}; + +/// Inputs for the Brillig VM. These are the initial inputs +/// that the Brillig VM will use to start. +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +pub enum BrilligInputs { + Single(Expression), + Array(Vec), +} + +/// Outputs for the Brillig VM. Once the VM has completed +/// execution, this will be the object that is returned. +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +pub enum BrilligOutputs { + Simple(Witness), + Array(Vec), +} + +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +pub struct Brillig { + pub inputs: Vec, + pub outputs: Vec, + /// Results of oracles/functions external to brillig like a database read. + // Each element of this vector corresponds to a single foreign call but may contain several values. + pub foreign_call_results: Vec, + /// The Brillig VM bytecode to be executed by this ACIR opcode. + pub bytecode: Vec, + /// Predicate of the Brillig execution - indicates if it should be skipped + pub predicate: Option, +} diff --git a/acvm-repo/acir/src/circuit/directives.rs b/acvm-repo/acir/src/circuit/directives.rs new file mode 100644 index 00000000000..32c0bd6337a --- /dev/null +++ b/acvm-repo/acir/src/circuit/directives.rs @@ -0,0 +1,46 @@ +use crate::native_types::{Expression, Witness}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct QuotientDirective { + pub a: Expression, + pub b: Expression, + pub q: Witness, + pub r: Witness, + pub predicate: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +/// Directives do not apply any constraints. +/// You can think of them as opcodes that allow one to use non-determinism +/// In the future, this can be replaced with asm non-determinism blocks +pub enum Directive { + //Performs euclidian division of a / b (as integers) and stores the quotient in q and the rest in r + Quotient(QuotientDirective), + + //decomposition of a: a=\sum b[i]*radix^i where b is an array of witnesses < radix in little endian form + ToLeRadix { + a: Expression, + b: Vec, + radix: u32, + }, + + // Sort directive, using a sorting network + // This directive is used to generate the values of the control bits for the sorting network such that its outputs are properly sorted according to sort_by + PermutationSort { + inputs: Vec>, // Array of tuples to sort + tuple: u32, // tuple size; if 1 then inputs is a single array [a0,a1,..], if 2 then inputs=[(a0,b0),..] is [a0,b0,a1,b1,..], etc.. + bits: Vec, // control bits of the network which permutes the inputs into its sorted version + sort_by: Vec, // specify primary index to sort by, then the secondary,... For instance, if tuple is 2 and sort_by is [1,0], then a=[(a0,b0),..] is sorted by bi and then ai. + }, +} + +impl Directive { + pub fn name(&self) -> &str { + match self { + Directive::Quotient(_) => "quotient", + Directive::ToLeRadix { .. } => "to_le_radix", + Directive::PermutationSort { .. } => "permutation_sort", + } + } +} diff --git a/acvm-repo/acir/src/circuit/mod.rs b/acvm-repo/acir/src/circuit/mod.rs new file mode 100644 index 00000000000..eef5a5fb6bc --- /dev/null +++ b/acvm-repo/acir/src/circuit/mod.rs @@ -0,0 +1,266 @@ +pub mod black_box_functions; +pub mod brillig; +pub mod directives; +pub mod opcodes; + +use crate::native_types::Witness; +pub use opcodes::Opcode; +use thiserror::Error; + +use std::{io::prelude::*, num::ParseIntError, str::FromStr}; + +use flate2::Compression; + +use serde::{Deserialize, Serialize}; +use std::collections::BTreeSet; + +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct Circuit { + // current_witness_index is the highest witness index in the circuit. The next witness to be added to this circuit + // will take on this value. (The value is cached here as an optimization.) + pub current_witness_index: u32, + pub opcodes: Vec, + + /// The set of private inputs to the circuit. + pub private_parameters: BTreeSet, + // ACIR distinguishes between the public inputs which are provided externally or calculated within the circuit and returned. + // The elements of these sets may not be mutually exclusive, i.e. a parameter may be returned from the circuit. + // All public inputs (parameters and return values) must be provided to the verifier at verification time. + /// The set of public inputs provided by the prover. + pub public_parameters: PublicInputs, + /// The set of public inputs calculated within the circuit. + pub return_values: PublicInputs, + /// Maps opcode locations to failed assertion messages. + /// These messages are embedded in the circuit to provide useful feedback to users + /// when a constraint in the circuit is not satisfied. + /// + // Note: This should be a BTreeMap, but serde-reflect is creating invalid + // c++ code at the moment when it is, due to OpcodeLocation needing a comparison + // implementation which is never generated. + pub assert_messages: Vec<(OpcodeLocation, String)>, +} + +impl Circuit { + /// Returns the assert message associated with the provided [`OpcodeLocation`]. + /// Returns `None` if no such assert message exists. + pub fn get_assert_message(&self, opcode_location: OpcodeLocation) -> Option<&str> { + self.assert_messages + .iter() + .find(|(loc, _)| *loc == opcode_location) + .map(|(_, message)| message.as_str()) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +/// Opcodes are locatable so that callers can +/// map opcodes to debug information related to their context. +pub enum OpcodeLocation { + Acir(usize), + Brillig { acir_index: usize, brillig_index: usize }, +} + +impl std::fmt::Display for OpcodeLocation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + OpcodeLocation::Acir(index) => write!(f, "{index}"), + OpcodeLocation::Brillig { acir_index, brillig_index } => { + write!(f, "{acir_index}.{brillig_index}") + } + } + } +} + +#[derive(Error, Debug)] +pub enum OpcodeLocationFromStrError { + #[error("Invalid opcode location string: {0}")] + InvalidOpcodeLocationString(String), +} + +/// The implementation of display and FromStr allows serializing and deserializing a OpcodeLocation to a string. +/// This is useful when used as key in a map that has to be serialized to JSON/TOML, for example when mapping an opcode to its metadata. +impl FromStr for OpcodeLocation { + type Err = OpcodeLocationFromStrError; + fn from_str(s: &str) -> Result { + let parts: Vec<_> = s.split('.').collect(); + + if parts.is_empty() || parts.len() > 2 { + return Err(OpcodeLocationFromStrError::InvalidOpcodeLocationString(s.to_string())); + } + + fn parse_components(parts: Vec<&str>) -> Result { + match parts.len() { + 1 => { + let index = parts[0].parse()?; + Ok(OpcodeLocation::Acir(index)) + } + 2 => { + let acir_index = parts[0].parse()?; + let brillig_index = parts[1].parse()?; + Ok(OpcodeLocation::Brillig { acir_index, brillig_index }) + } + _ => unreachable!(), + } + } + + parse_components(parts) + .map_err(|_| OpcodeLocationFromStrError::InvalidOpcodeLocationString(s.to_string())) + } +} + +impl Circuit { + pub fn num_vars(&self) -> u32 { + self.current_witness_index + 1 + } + + /// Returns all witnesses which are required to execute the circuit successfully. + pub fn circuit_arguments(&self) -> BTreeSet { + self.private_parameters.union(&self.public_parameters.0).cloned().collect() + } + + /// Returns all public inputs. This includes those provided as parameters to the circuit and those + /// computed as return values. + pub fn public_inputs(&self) -> PublicInputs { + let public_inputs = + self.public_parameters.0.union(&self.return_values.0).cloned().collect(); + PublicInputs(public_inputs) + } + + pub fn write(&self, writer: W) -> std::io::Result<()> { + let buf = bincode::serialize(&self).unwrap(); + let mut encoder = flate2::write::GzEncoder::new(writer, Compression::default()); + encoder.write_all(&buf).unwrap(); + encoder.finish().unwrap(); + Ok(()) + } + + pub fn read(reader: R) -> std::io::Result { + let mut gz_decoder = flate2::read::GzDecoder::new(reader); + let mut buf_d = Vec::new(); + gz_decoder.read_to_end(&mut buf_d).unwrap(); + let circuit = bincode::deserialize(&buf_d).unwrap(); + Ok(circuit) + } +} + +impl std::fmt::Display for Circuit { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "current witness index : {}", self.current_witness_index)?; + + let write_public_inputs = |f: &mut std::fmt::Formatter<'_>, + public_inputs: &PublicInputs| + -> Result<(), std::fmt::Error> { + write!(f, "[")?; + let public_input_indices = public_inputs.indices(); + for (index, public_input) in public_input_indices.iter().enumerate() { + write!(f, "{public_input}")?; + if index != public_input_indices.len() - 1 { + write!(f, ", ")?; + } + } + writeln!(f, "]") + }; + + write!(f, "public parameters indices : ")?; + write_public_inputs(f, &self.public_parameters)?; + + write!(f, "return value indices : ")?; + write_public_inputs(f, &self.return_values)?; + + for opcode in &self.opcodes { + writeln!(f, "{opcode}")? + } + Ok(()) + } +} + +impl std::fmt::Debug for Circuit { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self, f) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct PublicInputs(pub BTreeSet); + +impl PublicInputs { + /// Returns the witness index of each public input + pub fn indices(&self) -> Vec { + self.0.iter().map(|witness| witness.witness_index()).collect() + } + + pub fn contains(&self, index: usize) -> bool { + self.0.contains(&Witness(index as u32)) + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeSet; + + use super::{ + opcodes::{BlackBoxFuncCall, FunctionInput}, + Circuit, Opcode, PublicInputs, + }; + use crate::native_types::Witness; + use acir_field::FieldElement; + + fn and_opcode() -> Opcode { + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::AND { + lhs: FunctionInput { witness: Witness(1), num_bits: 4 }, + rhs: FunctionInput { witness: Witness(2), num_bits: 4 }, + output: Witness(3), + }) + } + fn range_opcode() -> Opcode { + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness: Witness(1), num_bits: 8 }, + }) + } + + #[test] + fn serialization_roundtrip() { + let circuit = Circuit { + current_witness_index: 5, + opcodes: vec![and_opcode(), range_opcode()], + private_parameters: BTreeSet::new(), + public_parameters: PublicInputs(BTreeSet::from_iter(vec![Witness(2), Witness(12)])), + return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(4), Witness(12)])), + assert_messages: Default::default(), + }; + + fn read_write(circuit: Circuit) -> (Circuit, Circuit) { + let mut bytes = Vec::new(); + circuit.write(&mut bytes).unwrap(); + let got_circuit = Circuit::read(&*bytes).unwrap(); + (circuit, got_circuit) + } + + let (circ, got_circ) = read_write(circuit); + assert_eq!(circ, got_circ) + } + + #[test] + fn test_serialize() { + let circuit = Circuit { + current_witness_index: 0, + opcodes: vec![ + Opcode::Arithmetic(crate::native_types::Expression { + mul_terms: vec![], + linear_combinations: vec![], + q_c: FieldElement::from(8u128), + }), + range_opcode(), + and_opcode(), + ], + private_parameters: BTreeSet::new(), + public_parameters: PublicInputs(BTreeSet::from_iter(vec![Witness(2)])), + return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(2)])), + assert_messages: Default::default(), + }; + + let json = serde_json::to_string_pretty(&circuit).unwrap(); + + let deserialized = serde_json::from_str(&json).unwrap(); + assert_eq!(circuit, deserialized); + } +} diff --git a/acvm-repo/acir/src/circuit/opcodes.rs b/acvm-repo/acir/src/circuit/opcodes.rs new file mode 100644 index 00000000000..dc7f73b47e5 --- /dev/null +++ b/acvm-repo/acir/src/circuit/opcodes.rs @@ -0,0 +1,179 @@ +use super::{ + brillig::Brillig, + directives::{Directive, QuotientDirective}, +}; +use crate::native_types::{Expression, Witness}; +use serde::{Deserialize, Serialize}; + +mod black_box_function_call; +mod memory_operation; + +pub use black_box_function_call::{BlackBoxFuncCall, FunctionInput}; +pub use memory_operation::{BlockId, MemOp}; + +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Opcode { + Arithmetic(Expression), + /// Calls to "gadgets" which rely on backends implementing support for specialized constraints. + /// + /// Often used for exposing more efficient implementations of SNARK-unfriendly computations. + BlackBoxFuncCall(BlackBoxFuncCall), + Directive(Directive), + Brillig(Brillig), + /// Atomic operation on a block of memory + MemoryOp { + block_id: BlockId, + op: MemOp, + /// Predicate of the memory operation - indicates if it should be skipped + predicate: Option, + }, + MemoryInit { + block_id: BlockId, + init: Vec, + }, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum UnsupportedMemoryOpcode { + MemoryOp, + MemoryInit, +} + +impl std::fmt::Display for UnsupportedMemoryOpcode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UnsupportedMemoryOpcode::MemoryOp => write!(f, "MemoryOp"), + UnsupportedMemoryOpcode::MemoryInit => write!(f, "MemoryInit"), + } + } +} + +impl Opcode { + // TODO We can add a domain separator by doing something like: + // TODO concat!("directive:", directive.name) + pub fn name(&self) -> &str { + match self { + Opcode::Arithmetic(_) => "arithmetic", + Opcode::Directive(directive) => directive.name(), + Opcode::BlackBoxFuncCall(g) => g.name(), + Opcode::Brillig(_) => "brillig", + Opcode::MemoryOp { .. } => "mem", + Opcode::MemoryInit { .. } => "init memory block", + } + } + + pub fn unsupported_opcode(&self) -> UnsupportedMemoryOpcode { + match self { + Opcode::MemoryOp { .. } => UnsupportedMemoryOpcode::MemoryOp, + Opcode::MemoryInit { .. } => UnsupportedMemoryOpcode::MemoryInit, + Opcode::BlackBoxFuncCall(_) => { + unreachable!("Unsupported Blackbox function should not be reported here") + } + _ => unreachable!("Opcode is supported"), + } + } + + pub fn is_arithmetic(&self) -> bool { + matches!(self, Opcode::Arithmetic(_)) + } + + pub fn arithmetic(self) -> Option { + match self { + Opcode::Arithmetic(expr) => Some(expr), + _ => None, + } + } +} + +impl std::fmt::Display for Opcode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Opcode::Arithmetic(expr) => { + write!(f, "EXPR [ ")?; + for i in &expr.mul_terms { + write!(f, "({}, _{}, _{}) ", i.0, i.1.witness_index(), i.2.witness_index())?; + } + for i in &expr.linear_combinations { + write!(f, "({}, _{}) ", i.0, i.1.witness_index())?; + } + write!(f, "{}", expr.q_c)?; + + write!(f, " ]") + } + Opcode::Directive(Directive::Quotient(QuotientDirective { a, b, q, r, predicate })) => { + write!(f, "DIR::QUOTIENT ")?; + if let Some(pred) = predicate { + writeln!(f, "PREDICATE = {pred}")?; + } + + write!( + f, + "(out : _{}, (_{}, {}), _{})", + a, + q.witness_index(), + b, + r.witness_index() + ) + } + Opcode::BlackBoxFuncCall(g) => write!(f, "{g}"), + Opcode::Directive(Directive::ToLeRadix { a, b, radix: _ }) => { + write!(f, "DIR::TORADIX ")?; + write!( + f, + // TODO (Note): this assumes that the decomposed bits have contiguous witness indices + // This should be the case, however, we can also have a function which checks this + "(_{}, [_{}..._{}] )", + a, + b.first().unwrap().witness_index(), + b.last().unwrap().witness_index(), + ) + } + Opcode::Directive(Directive::PermutationSort { inputs: a, tuple, bits, sort_by }) => { + write!(f, "DIR::PERMUTATIONSORT ")?; + write!( + f, + "(permutation size: {} {}-tuples, sort_by: {:#?}, bits: [_{}..._{}]))", + a.len(), + tuple, + sort_by, + // (Note): the bits do not have contiguous index but there are too many for display + bits.first().unwrap().witness_index(), + bits.last().unwrap().witness_index(), + ) + } + + Opcode::Brillig(brillig) => { + write!(f, "BRILLIG: ")?; + writeln!(f, "inputs: {:?}", brillig.inputs)?; + writeln!(f, "outputs: {:?}", brillig.outputs)?; + writeln!(f, "{:?}", brillig.bytecode) + } + Opcode::MemoryOp { block_id, op, predicate } => { + write!(f, "MEM ")?; + if let Some(pred) = predicate { + writeln!(f, "PREDICATE = {pred}")?; + } + + let is_read = op.operation.is_zero(); + let is_write = op.operation == Expression::one(); + if is_read { + write!(f, "(id: {}, read at: {}, value: {}) ", block_id.0, op.index, op.value) + } else if is_write { + write!(f, "(id: {}, write {} at: {}) ", block_id.0, op.value, op.index) + } else { + write!(f, "(id: {}, op {} at: {}) ", block_id.0, op.operation, op.index) + } + } + Opcode::MemoryInit { block_id, init } => { + write!(f, "INIT ")?; + write!(f, "(id: {}, len: {}) ", block_id.0, init.len()) + } + } + } +} + +impl std::fmt::Debug for Opcode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self, f) + } +} diff --git a/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs b/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs new file mode 100644 index 00000000000..b2ca0440b59 --- /dev/null +++ b/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs @@ -0,0 +1,410 @@ +use crate::native_types::Witness; +use crate::BlackBoxFunc; +use serde::{Deserialize, Serialize}; + +// Note: Some functions will not use all of the witness +// So we need to supply how many bits of the witness is needed +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct FunctionInput { + pub witness: Witness, + pub num_bits: u32, +} + +impl FunctionInput { + pub fn dummy() -> Self { + Self { witness: Witness(0), num_bits: 0 } + } +} + +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum BlackBoxFuncCall { + AND { + lhs: FunctionInput, + rhs: FunctionInput, + output: Witness, + }, + XOR { + lhs: FunctionInput, + rhs: FunctionInput, + output: Witness, + }, + RANGE { + input: FunctionInput, + }, + SHA256 { + inputs: Vec, + outputs: Vec, + }, + Blake2s { + inputs: Vec, + outputs: Vec, + }, + SchnorrVerify { + public_key_x: FunctionInput, + public_key_y: FunctionInput, + signature: Vec, + message: Vec, + output: Witness, + }, + Pedersen { + inputs: Vec, + domain_separator: u32, + outputs: (Witness, Witness), + }, + // 128 here specifies that this function + // should have 128 bits of security + HashToField128Security { + inputs: Vec, + output: Witness, + }, + EcdsaSecp256k1 { + public_key_x: Vec, + public_key_y: Vec, + signature: Vec, + hashed_message: Vec, + output: Witness, + }, + EcdsaSecp256r1 { + public_key_x: Vec, + public_key_y: Vec, + signature: Vec, + hashed_message: Vec, + output: Witness, + }, + FixedBaseScalarMul { + low: FunctionInput, + high: FunctionInput, + outputs: (Witness, Witness), + }, + Keccak256 { + inputs: Vec, + outputs: Vec, + }, + Keccak256VariableLength { + inputs: Vec, + /// This is the number of bytes to take + /// from the input. Note: if `var_message_size` + /// is more than the number of bytes in the input, + /// then an error is returned. + var_message_size: FunctionInput, + outputs: Vec, + }, + RecursiveAggregation { + verification_key: Vec, + proof: Vec, + /// These represent the public inputs of the proof we are verifying + /// They should be checked against in the circuit after construction + /// of a new aggregation state + public_inputs: Vec, + /// A key hash is used to check the validity of the verification key. + /// The circuit implementing this opcode can use this hash to ensure that the + /// key provided to the circuit matches the key produced by the circuit creator + key_hash: FunctionInput, + /// An aggregation object is blob of data that the top-level verifier must run some proof system specific + /// algorithm on to complete verification. The size is proof system specific and will be set by the backend integrating this opcode. + /// The input aggregation object is only not `None` when we are verifying a previous recursive aggregation in + /// the current circuit. If this is the first recursive aggregation there is no input aggregation object. + /// It is left to the backend to determine how to handle when there is no input aggregation object. + input_aggregation_object: Option>, + /// This is the result of a recursive aggregation and is what will be fed into the next verifier. + /// The next verifier can either perform a final verification (returning true or false) + /// or perform another recursive aggregation where this output aggregation object + /// will be the input aggregation object of the next recursive aggregation. + output_aggregation_object: Vec, + }, +} + +impl BlackBoxFuncCall { + #[deprecated = "BlackBoxFuncCall::dummy() is unnecessary and will be removed in ACVM 0.24.0"] + pub fn dummy(bb_func: BlackBoxFunc) -> Self { + match bb_func { + BlackBoxFunc::AND => BlackBoxFuncCall::AND { + lhs: FunctionInput::dummy(), + rhs: FunctionInput::dummy(), + output: Witness(0), + }, + BlackBoxFunc::XOR => BlackBoxFuncCall::XOR { + lhs: FunctionInput::dummy(), + rhs: FunctionInput::dummy(), + output: Witness(0), + }, + BlackBoxFunc::RANGE => BlackBoxFuncCall::RANGE { input: FunctionInput::dummy() }, + BlackBoxFunc::SHA256 => BlackBoxFuncCall::SHA256 { inputs: vec![], outputs: vec![] }, + BlackBoxFunc::Blake2s => BlackBoxFuncCall::Blake2s { inputs: vec![], outputs: vec![] }, + BlackBoxFunc::SchnorrVerify => BlackBoxFuncCall::SchnorrVerify { + public_key_x: FunctionInput::dummy(), + public_key_y: FunctionInput::dummy(), + signature: vec![], + message: vec![], + output: Witness(0), + }, + BlackBoxFunc::Pedersen => BlackBoxFuncCall::Pedersen { + inputs: vec![], + domain_separator: 0, + outputs: (Witness(0), Witness(0)), + }, + BlackBoxFunc::HashToField128Security => { + BlackBoxFuncCall::HashToField128Security { inputs: vec![], output: Witness(0) } + } + BlackBoxFunc::EcdsaSecp256k1 => BlackBoxFuncCall::EcdsaSecp256k1 { + public_key_x: vec![], + public_key_y: vec![], + signature: vec![], + hashed_message: vec![], + output: Witness(0), + }, + BlackBoxFunc::EcdsaSecp256r1 => BlackBoxFuncCall::EcdsaSecp256r1 { + public_key_x: vec![], + public_key_y: vec![], + signature: vec![], + hashed_message: vec![], + output: Witness(0), + }, + BlackBoxFunc::FixedBaseScalarMul => BlackBoxFuncCall::FixedBaseScalarMul { + low: FunctionInput::dummy(), + high: FunctionInput::dummy(), + outputs: (Witness(0), Witness(0)), + }, + BlackBoxFunc::Keccak256 => { + BlackBoxFuncCall::Keccak256 { inputs: vec![], outputs: vec![] } + } + BlackBoxFunc::RecursiveAggregation => BlackBoxFuncCall::RecursiveAggregation { + verification_key: vec![], + proof: vec![], + public_inputs: vec![], + key_hash: FunctionInput::dummy(), + input_aggregation_object: None, + output_aggregation_object: vec![], + }, + } + } + + pub fn get_black_box_func(&self) -> BlackBoxFunc { + match self { + BlackBoxFuncCall::AND { .. } => BlackBoxFunc::AND, + BlackBoxFuncCall::XOR { .. } => BlackBoxFunc::XOR, + BlackBoxFuncCall::RANGE { .. } => BlackBoxFunc::RANGE, + BlackBoxFuncCall::SHA256 { .. } => BlackBoxFunc::SHA256, + BlackBoxFuncCall::Blake2s { .. } => BlackBoxFunc::Blake2s, + BlackBoxFuncCall::SchnorrVerify { .. } => BlackBoxFunc::SchnorrVerify, + BlackBoxFuncCall::Pedersen { .. } => BlackBoxFunc::Pedersen, + BlackBoxFuncCall::HashToField128Security { .. } => BlackBoxFunc::HashToField128Security, + BlackBoxFuncCall::EcdsaSecp256k1 { .. } => BlackBoxFunc::EcdsaSecp256k1, + BlackBoxFuncCall::EcdsaSecp256r1 { .. } => BlackBoxFunc::EcdsaSecp256r1, + BlackBoxFuncCall::FixedBaseScalarMul { .. } => BlackBoxFunc::FixedBaseScalarMul, + BlackBoxFuncCall::Keccak256 { .. } => BlackBoxFunc::Keccak256, + BlackBoxFuncCall::Keccak256VariableLength { .. } => BlackBoxFunc::Keccak256, + BlackBoxFuncCall::RecursiveAggregation { .. } => BlackBoxFunc::RecursiveAggregation, + } + } + + pub fn name(&self) -> &str { + self.get_black_box_func().name() + } + + pub fn get_inputs_vec(&self) -> Vec { + match self { + BlackBoxFuncCall::SHA256 { inputs, .. } + | BlackBoxFuncCall::Blake2s { inputs, .. } + | BlackBoxFuncCall::Keccak256 { inputs, .. } + | BlackBoxFuncCall::Pedersen { inputs, .. } + | BlackBoxFuncCall::HashToField128Security { inputs, .. } => inputs.to_vec(), + BlackBoxFuncCall::AND { lhs, rhs, .. } | BlackBoxFuncCall::XOR { lhs, rhs, .. } => { + vec![*lhs, *rhs] + } + BlackBoxFuncCall::FixedBaseScalarMul { low, high, .. } => vec![*low, *high], + BlackBoxFuncCall::RANGE { input } => vec![*input], + BlackBoxFuncCall::SchnorrVerify { + public_key_x, + public_key_y, + signature, + message, + .. + } => { + let mut inputs = Vec::with_capacity(2 + signature.len() + message.len()); + inputs.push(*public_key_x); + inputs.push(*public_key_y); + inputs.extend(signature.iter().copied()); + inputs.extend(message.iter().copied()); + inputs + } + BlackBoxFuncCall::EcdsaSecp256k1 { + public_key_x, + public_key_y, + signature, + hashed_message, + .. + } => { + let mut inputs = Vec::with_capacity( + public_key_x.len() + + public_key_y.len() + + signature.len() + + hashed_message.len(), + ); + inputs.extend(public_key_x.iter().copied()); + inputs.extend(public_key_y.iter().copied()); + inputs.extend(signature.iter().copied()); + inputs.extend(hashed_message.iter().copied()); + inputs + } + BlackBoxFuncCall::EcdsaSecp256r1 { + public_key_x, + public_key_y, + signature, + hashed_message, + .. + } => { + let mut inputs = Vec::with_capacity( + public_key_x.len() + + public_key_y.len() + + signature.len() + + hashed_message.len(), + ); + inputs.extend(public_key_x.iter().copied()); + inputs.extend(public_key_y.iter().copied()); + inputs.extend(signature.iter().copied()); + inputs.extend(hashed_message.iter().copied()); + inputs + } + BlackBoxFuncCall::Keccak256VariableLength { inputs, var_message_size, .. } => { + let mut inputs = inputs.clone(); + inputs.push(*var_message_size); + inputs + } + BlackBoxFuncCall::RecursiveAggregation { + verification_key: key, + proof, + public_inputs, + key_hash, + .. + } => { + let mut inputs = Vec::new(); + inputs.extend(key.iter().copied()); + inputs.extend(proof.iter().copied()); + inputs.extend(public_inputs.iter().copied()); + inputs.push(*key_hash); + // NOTE: we do not return an input aggregation object as it will either be non-existent for the first recursive aggregation + // or the output aggregation object of a previous recursive aggregation. We do not simulate recursive aggregation + // thus the input aggregation object will always be unassigned until proving + inputs + } + } + } + + pub fn get_outputs_vec(&self) -> Vec { + match self { + BlackBoxFuncCall::SHA256 { outputs, .. } + | BlackBoxFuncCall::Blake2s { outputs, .. } + | BlackBoxFuncCall::Keccak256 { outputs, .. } + | BlackBoxFuncCall::RecursiveAggregation { + output_aggregation_object: outputs, .. + } => outputs.to_vec(), + BlackBoxFuncCall::AND { output, .. } + | BlackBoxFuncCall::XOR { output, .. } + | BlackBoxFuncCall::HashToField128Security { output, .. } + | BlackBoxFuncCall::SchnorrVerify { output, .. } + | BlackBoxFuncCall::EcdsaSecp256k1 { output, .. } + | BlackBoxFuncCall::EcdsaSecp256r1 { output, .. } => vec![*output], + BlackBoxFuncCall::FixedBaseScalarMul { outputs, .. } + | BlackBoxFuncCall::Pedersen { outputs, .. } => vec![outputs.0, outputs.1], + BlackBoxFuncCall::RANGE { .. } => vec![], + BlackBoxFuncCall::Keccak256VariableLength { outputs, .. } => outputs.to_vec(), + } + } +} + +const ABBREVIATION_LIMIT: usize = 5; + +fn get_inputs_string(inputs: &[FunctionInput]) -> String { + // Once a vectors length gets above this limit, + // instead of listing all of their elements, we use ellipses + // to abbreviate them + let should_abbreviate_inputs = inputs.len() <= ABBREVIATION_LIMIT; + + if should_abbreviate_inputs { + let mut result = String::new(); + for (index, inp) in inputs.iter().enumerate() { + result += &format!("(_{}, num_bits: {})", inp.witness.witness_index(), inp.num_bits); + // Add a comma, unless it is the last entry + if index != inputs.len() - 1 { + result += ", " + } + } + result + } else { + let first = inputs.first().unwrap(); + let last = inputs.last().unwrap(); + + let mut result = String::new(); + + result += &format!( + "(_{}, num_bits: {})...(_{}, num_bits: {})", + first.witness.witness_index(), + first.num_bits, + last.witness.witness_index(), + last.num_bits, + ); + + result + } +} + +fn get_outputs_string(outputs: &[Witness]) -> String { + let should_abbreviate_outputs = outputs.len() <= ABBREVIATION_LIMIT; + + if should_abbreviate_outputs { + let mut result = String::new(); + for (index, output) in outputs.iter().enumerate() { + result += &format!("_{}", output.witness_index()); + // Add a comma, unless it is the last entry + if index != outputs.len() - 1 { + result += ", " + } + } + result + } else { + let first = outputs.first().unwrap(); + let last = outputs.last().unwrap(); + + let mut result = String::new(); + result += &format!("(_{},...,_{})", first.witness_index(), last.witness_index()); + result + } +} + +impl std::fmt::Display for BlackBoxFuncCall { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let uppercase_name = self.name().to_uppercase(); + write!(f, "BLACKBOX::{uppercase_name} ")?; + // INPUTS + write!(f, "[")?; + + let inputs_str = get_inputs_string(&self.get_inputs_vec()); + + write!(f, "{inputs_str}")?; + write!(f, "] ")?; + + // OUTPUTS + write!(f, "[ ")?; + + let outputs_str = get_outputs_string(&self.get_outputs_vec()); + + write!(f, "{outputs_str}")?; + + write!(f, "]")?; + + // SPECIFIC PARAMETERS + match self { + BlackBoxFuncCall::Pedersen { domain_separator, .. } => { + write!(f, " domain_separator: {domain_separator}") + } + _ => write!(f, ""), + } + } +} + +impl std::fmt::Debug for BlackBoxFuncCall { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self, f) + } +} diff --git a/acvm-repo/acir/src/circuit/opcodes/memory_operation.rs b/acvm-repo/acir/src/circuit/opcodes/memory_operation.rs new file mode 100644 index 00000000000..9e45dc4ee8c --- /dev/null +++ b/acvm-repo/acir/src/circuit/opcodes/memory_operation.rs @@ -0,0 +1,28 @@ +use crate::native_types::{Expression, Witness}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash, Copy, Default)] +pub struct BlockId(pub u32); + +/// Operation on a block of memory +/// We can either write or read at an index in memory +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] +pub struct MemOp { + /// Can be 0 (read) or 1 (write) + pub operation: Expression, + pub index: Expression, + pub value: Expression, +} + +impl MemOp { + /// Creates a `MemOp` which reads from memory at `index` and inserts the read value + /// into the [`WitnessMap`][crate::native_types::WitnessMap] at `witness` + pub fn read_at_mem_index(index: Expression, witness: Witness) -> Self { + MemOp { operation: Expression::zero(), index, value: witness.into() } + } + + /// Creates a `MemOp` which writes the [`Expression`] `value` into memory at `index`. + pub fn write_to_mem_index(index: Expression, value: Expression) -> Self { + MemOp { operation: Expression::one(), index, value } + } +} diff --git a/acvm-repo/acir/src/lib.rs b/acvm-repo/acir/src/lib.rs new file mode 100644 index 00000000000..96ac444b6d9 --- /dev/null +++ b/acvm-repo/acir/src/lib.rs @@ -0,0 +1,12 @@ +#![warn(unused_crate_dependencies)] +#![warn(unreachable_pub)] + +// Arbitrary Circuit Intermediate Representation + +pub mod circuit; +pub mod native_types; + +pub use acir_field; +pub use acir_field::FieldElement; +pub use brillig; +pub use circuit::black_box_functions::BlackBoxFunc; diff --git a/acvm-repo/acir/src/native_types/expression/mod.rs b/acvm-repo/acir/src/native_types/expression/mod.rs new file mode 100644 index 00000000000..368630c2e06 --- /dev/null +++ b/acvm-repo/acir/src/native_types/expression/mod.rs @@ -0,0 +1,398 @@ +use crate::native_types::Witness; +use acir_field::FieldElement; +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; + +mod operators; +mod ordering; + +// In the addition polynomial +// We can have arbitrary fan-in/out, so we need more than wL,wR and wO +// When looking at the arithmetic opcode for the quotient polynomial in standard plonk +// You can think of it as fan-in 2 and fan out-1 , or you can think of it as fan-in 1 and fan-out 2 +// +// In the multiplication polynomial +// XXX: If we allow the degree of the quotient polynomial to be arbitrary, then we will need a vector of wire values +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] +pub struct Expression { + // To avoid having to create intermediate variables pre-optimization + // We collect all of the multiplication terms in the arithmetic opcode + // A multiplication term if of the form q_M * wL * wR + // Hence this vector represents the following sum: q_M1 * wL1 * wR1 + q_M2 * wL2 * wR2 + .. + + pub mul_terms: Vec<(FieldElement, Witness, Witness)>, + + pub linear_combinations: Vec<(FieldElement, Witness)>, + // TODO: rename q_c to `constant` moreover q_X is not clear to those who + // TODO are not familiar with PLONK + pub q_c: FieldElement, +} + +impl Default for Expression { + fn default() -> Expression { + Expression { + mul_terms: Vec::new(), + linear_combinations: Vec::new(), + q_c: FieldElement::zero(), + } + } +} + +impl std::fmt::Display for Expression { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Some(witness) = self.to_witness() { + write!(f, "x{}", witness.witness_index()) + } else { + write!(f, "%{:?}%", crate::circuit::opcodes::Opcode::Arithmetic(self.clone())) + } + } +} + +impl Expression { + // TODO: possibly remove, and move to noir repo. + pub const fn can_defer_constraint(&self) -> bool { + false + } + + /// Returns the number of multiplication terms + pub fn num_mul_terms(&self) -> usize { + self.mul_terms.len() + } + + pub fn from_field(q_c: FieldElement) -> Expression { + Self { q_c, ..Default::default() } + } + + pub fn one() -> Expression { + Self::from_field(FieldElement::one()) + } + + pub fn zero() -> Expression { + Self::default() + } + + /// Adds a new linear term to the `Expression`. + pub fn push_addition_term(&mut self, coefficient: FieldElement, variable: Witness) { + self.linear_combinations.push((coefficient, variable)) + } + + /// Adds a new quadratic term to the `Expression`. + pub fn push_multiplication_term( + &mut self, + coefficient: FieldElement, + lhs: Witness, + rhs: Witness, + ) { + self.mul_terms.push((coefficient, lhs, rhs)) + } + + /// Returns `true` if the expression represents a constant polynomial. + /// + /// Examples: + /// - f(x,y) = x + y would return false + /// - f(x,y) = xy would return false, the degree here is 2 + /// - f(x,y) = 5 would return true, the degree is 0 + pub fn is_const(&self) -> bool { + self.mul_terms.is_empty() && self.linear_combinations.is_empty() + } + + /// Returns `true` if highest degree term in the expression is one or less. + /// + /// - `mul_term` in an expression contains degree-2 terms + /// - `linear_combinations` contains degree-1 terms + /// Hence, it is sufficient to check that there are no `mul_terms` + /// + /// Examples: + /// - f(x,y) = x + y would return true + /// - f(x,y) = xy would return false, the degree here is 2 + /// - f(x,y) = 0 would return true, the degree is 0 + pub fn is_linear(&self) -> bool { + self.mul_terms.is_empty() + } + + /// Returns `true` if the expression can be seen as a degree-1 univariate polynomial + /// + /// - `mul_terms` in an expression can be univariate, however unless the coefficient + /// is zero, it is always degree-2. + /// - `linear_combinations` contains the sum of degree-1 terms, these terms do not + /// need to contain the same variable and so it can be multivariate. However, we + /// have thus far only checked if `linear_combinations` contains one term, so this + /// method will return false, if the `Expression` has not been simplified. + /// + /// Hence, we check in the simplest case if an expression is a degree-1 univariate, + /// by checking if it contains no `mul_terms` and it contains one `linear_combination` term. + /// + /// Examples: + /// - f(x,y) = x would return true + /// - f(x,y) = x + 6 would return true + /// - f(x,y) = 2*y + 6 would return true + /// - f(x,y) = x + y would return false + /// - f(x,y) = x + x should return true, but we return false *** (we do not simplify) + /// - f(x,y) = 5 would return false + pub fn is_degree_one_univariate(&self) -> bool { + self.is_linear() && self.linear_combinations.len() == 1 + } + + pub fn is_zero(&self) -> bool { + *self == Self::zero() + } + + /// Returns a `FieldElement` if the expression represents a constant polynomial. + /// Otherwise returns `None`. + /// + /// Examples: + /// - f(x,y) = x would return `None` + /// - f(x,y) = x + 6 would return `None` + /// - f(x,y) = 2*y + 6 would return `None` + /// - f(x,y) = x + y would return `None` + /// - f(x,y) = 5 would return `FieldElement(5)` + pub fn to_const(&self) -> Option { + self.is_const().then_some(self.q_c) + } + + /// Returns a `Witness` if the `Expression` can be represented as a degree-1 + /// univariate polynomial. Otherwise returns `None`. + /// + /// Note that `Witness` is only capable of expressing polynomials of the form + /// f(x) = x and not polynomials of the form f(x) = mx+c , so this method has + /// extra checks to ensure that m=1 and c=0 + pub fn to_witness(&self) -> Option { + if self.is_degree_one_univariate() { + // If we get here, we know that our expression is of the form `f(x) = mx+c` + // We want to now restrict ourselves to expressions of the form f(x) = x + // ie where the constant term is 0 and the coefficient in front of the variable is + // one. + let (coefficient, variable) = self.linear_combinations[0]; + let constant = self.q_c; + + if coefficient.is_one() && constant.is_zero() { + return Some(variable); + } + } + None + } + + /// Sorts opcode in a deterministic order + /// XXX: We can probably make this more efficient by sorting on each phase. We only care if it is deterministic + pub fn sort(&mut self) { + self.mul_terms.sort_by(|a, b| a.1.cmp(&b.1).then(a.2.cmp(&b.2))); + self.linear_combinations.sort_by(|a, b| a.1.cmp(&b.1)); + } + + /// Checks if this polynomial can fit into one arithmetic identity + pub fn fits_in_one_identity(&self, width: usize) -> bool { + // A Polynomial with more than one mul term cannot fit into one opcode + if self.mul_terms.len() > 1 { + return false; + }; + // A Polynomial with more terms than fan-in cannot fit within a single opcode + if self.linear_combinations.len() > width { + return false; + } + + // A polynomial with no mul term and a fan-in that fits inside of the width can fit into a single opcode + if self.mul_terms.is_empty() { + return true; + } + + // A polynomial with width-2 fan-in terms and a single non-zero mul term can fit into one opcode + // Example: Axy + Dz . Notice, that the mul term places a constraint on the first two terms, but not the last term + // XXX: This would change if our arithmetic polynomial equation was changed to Axyz for example, but for now it is not. + if self.linear_combinations.len() <= (width - 2) { + return true; + } + + // We now know that we have a single mul term. We also know that the mul term must match up with two other terms + // A polynomial whose mul terms are non zero which do not match up with two terms in the fan-in cannot fit into one opcode + // An example of this is: Axy + Bx + Cy + ... + // Notice how the bivariate monomial xy has two univariate monomials with their respective coefficients + // XXX: note that if x or y is zero, then we could apply a further optimization, but this would be done in another algorithm. + // It would be the same as when we have zero coefficients - Can only work if wire is constrained to be zero publicly + let mul_term = &self.mul_terms[0]; + + // The coefficient should be non-zero, as this method is ran after the compiler removes all zero coefficient terms + assert_ne!(mul_term.0, FieldElement::zero()); + + let mut found_x = false; + let mut found_y = false; + + for term in self.linear_combinations.iter() { + let witness = &term.1; + let x = &mul_term.1; + let y = &mul_term.2; + if witness == x { + found_x = true; + }; + if witness == y { + found_y = true; + }; + if found_x & found_y { + break; + } + } + + found_x & found_y + } + + /// Returns `self + k*b` + pub fn add_mul(&self, k: FieldElement, b: &Expression) -> Expression { + if k.is_zero() { + return self.clone(); + } else if self.is_const() { + return self.q_c + (k * b); + } else if b.is_const() { + return self.clone() + (k * b.q_c); + } + + let mut mul_terms: Vec<(FieldElement, Witness, Witness)> = + Vec::with_capacity(self.mul_terms.len() + b.mul_terms.len()); + let mut linear_combinations: Vec<(FieldElement, Witness)> = + Vec::with_capacity(self.linear_combinations.len() + b.linear_combinations.len()); + let q_c = self.q_c + k * b.q_c; + + //linear combinations + let mut i1 = 0; //a + let mut i2 = 0; //b + while i1 < self.linear_combinations.len() && i2 < b.linear_combinations.len() { + let (a_c, a_w) = self.linear_combinations[i1]; + let (b_c, b_w) = b.linear_combinations[i2]; + + let (coeff, witness) = match a_w.cmp(&b_w) { + Ordering::Greater => { + i2 += 1; + (k * b_c, b_w) + } + Ordering::Less => { + i1 += 1; + (a_c, a_w) + } + Ordering::Equal => { + // Here we're taking both witnesses as the witness indices are equal. + // We then advance both `i1` and `i2`. + i1 += 1; + i2 += 1; + (a_c + k * b_c, a_w) + } + }; + + if !coeff.is_zero() { + linear_combinations.push((coeff, witness)); + } + } + + // Finally process all the remaining terms which we didn't handle in the above loop. + while i1 < self.linear_combinations.len() { + linear_combinations.push(self.linear_combinations[i1]); + i1 += 1; + } + while i2 < b.linear_combinations.len() { + let (b_c, b_w) = b.linear_combinations[i2]; + let coeff = b_c * k; + if !coeff.is_zero() { + linear_combinations.push((coeff, b_w)); + } + i2 += 1; + } + + //mul terms + + i1 = 0; //a + i2 = 0; //b + while i1 < self.mul_terms.len() && i2 < b.mul_terms.len() { + let (a_c, a_wl, a_wr) = self.mul_terms[i1]; + let (b_c, b_wl, b_wr) = b.mul_terms[i2]; + + let (coeff, wl, wr) = match (a_wl, a_wr).cmp(&(b_wl, b_wr)) { + Ordering::Greater => { + i2 += 1; + (k * b_c, b_wl, b_wr) + } + Ordering::Less => { + i1 += 1; + (a_c, a_wl, a_wr) + } + Ordering::Equal => { + // Here we're taking both terms as the witness indices are equal. + // We then advance both `i1` and `i2`. + i2 += 1; + i1 += 1; + (a_c + k * b_c, a_wl, a_wr) + } + }; + + if !coeff.is_zero() { + mul_terms.push((coeff, wl, wr)); + } + } + + // Finally process all the remaining terms which we didn't handle in the above loop. + while i1 < self.mul_terms.len() { + mul_terms.push(self.mul_terms[i1]); + i1 += 1; + } + while i2 < b.mul_terms.len() { + let (b_c, b_wl, b_wr) = b.mul_terms[i2]; + let coeff = b_c * k; + if coeff != FieldElement::zero() { + mul_terms.push((coeff, b_wl, b_wr)); + } + i2 += 1; + } + + Expression { mul_terms, linear_combinations, q_c } + } +} + +impl From for Expression { + fn from(constant: FieldElement) -> Expression { + Expression { q_c: constant, linear_combinations: Vec::new(), mul_terms: Vec::new() } + } +} + +impl From for Expression { + /// Creates an Expression from a Witness. + /// + /// This is infallible since an `Expression` is + /// a multi-variate polynomial and a `Witness` + /// can be seen as a univariate polynomial + fn from(wit: Witness) -> Expression { + Expression { + q_c: FieldElement::zero(), + linear_combinations: vec![(FieldElement::one(), wit)], + mul_terms: Vec::new(), + } + } +} + +#[test] +fn add_mul_smoketest() { + let a = Expression { + mul_terms: vec![(FieldElement::from(2u128), Witness(1), Witness(2))], + ..Default::default() + }; + + let k = FieldElement::from(10u128); + + let b = Expression { + mul_terms: vec![ + (FieldElement::from(3u128), Witness(0), Witness(2)), + (FieldElement::from(3u128), Witness(1), Witness(2)), + (FieldElement::from(4u128), Witness(4), Witness(5)), + ], + linear_combinations: vec![(FieldElement::from(4u128), Witness(4))], + q_c: FieldElement::one(), + }; + + let result = a.add_mul(k, &b); + assert_eq!( + result, + Expression { + mul_terms: vec![ + (FieldElement::from(30u128), Witness(0), Witness(2)), + (FieldElement::from(32u128), Witness(1), Witness(2)), + (FieldElement::from(40u128), Witness(4), Witness(5)), + ], + linear_combinations: vec![(FieldElement::from(40u128), Witness(4))], + q_c: FieldElement::from(10u128) + } + ) +} diff --git a/acvm-repo/acir/src/native_types/expression/operators.rs b/acvm-repo/acir/src/native_types/expression/operators.rs new file mode 100644 index 00000000000..35a548a2e3f --- /dev/null +++ b/acvm-repo/acir/src/native_types/expression/operators.rs @@ -0,0 +1,290 @@ +use crate::native_types::Witness; +use acir_field::FieldElement; +use std::{ + cmp::Ordering, + ops::{Add, Mul, Neg, Sub}, +}; + +use super::Expression; + +// Negation + +impl Neg for &Expression { + type Output = Expression; + fn neg(self) -> Self::Output { + // XXX(med) : Implement an efficient way to do this + + let mul_terms: Vec<_> = + self.mul_terms.iter().map(|(q_m, w_l, w_r)| (-*q_m, *w_l, *w_r)).collect(); + + let linear_combinations: Vec<_> = + self.linear_combinations.iter().map(|(q_k, w_k)| (-*q_k, *w_k)).collect(); + let q_c = -self.q_c; + + Expression { mul_terms, linear_combinations, q_c } + } +} + +// FieldElement + +impl Add for Expression { + type Output = Expression; + fn add(self, rhs: FieldElement) -> Self::Output { + // Increase the constant + let q_c = self.q_c + rhs; + + Expression { mul_terms: self.mul_terms, q_c, linear_combinations: self.linear_combinations } + } +} + +impl Add for FieldElement { + type Output = Expression; + #[inline] + fn add(self, rhs: Expression) -> Self::Output { + rhs + self + } +} + +impl Sub for Expression { + type Output = Expression; + fn sub(self, rhs: FieldElement) -> Self::Output { + // Increase the constant + let q_c = self.q_c - rhs; + + Expression { mul_terms: self.mul_terms, q_c, linear_combinations: self.linear_combinations } + } +} + +impl Sub for FieldElement { + type Output = Expression; + #[inline] + fn sub(self, rhs: Expression) -> Self::Output { + rhs - self + } +} + +impl Mul for &Expression { + type Output = Expression; + fn mul(self, rhs: FieldElement) -> Self::Output { + // Scale the mul terms + let mul_terms: Vec<_> = + self.mul_terms.iter().map(|(q_m, w_l, w_r)| (*q_m * rhs, *w_l, *w_r)).collect(); + + // Scale the linear combinations terms + let lin_combinations: Vec<_> = + self.linear_combinations.iter().map(|(q_l, w_l)| (*q_l * rhs, *w_l)).collect(); + + // Scale the constant + let q_c = self.q_c * rhs; + + Expression { mul_terms, q_c, linear_combinations: lin_combinations } + } +} + +impl Mul<&Expression> for FieldElement { + type Output = Expression; + #[inline] + fn mul(self, rhs: &Expression) -> Self::Output { + rhs * self + } +} + +// Witness + +impl Add for &Expression { + type Output = Expression; + fn add(self, rhs: Witness) -> Expression { + self + &Expression::from(rhs) + } +} + +impl Add<&Expression> for Witness { + type Output = Expression; + #[inline] + fn add(self, rhs: &Expression) -> Expression { + rhs + self + } +} + +impl Sub for &Expression { + type Output = Expression; + fn sub(self, rhs: Witness) -> Expression { + self - &Expression::from(rhs) + } +} + +impl Sub<&Expression> for Witness { + type Output = Expression; + #[inline] + fn sub(self, rhs: &Expression) -> Expression { + rhs - self + } +} + +// Mul is not implemented as this could result in degree 3 terms. + +// Expression + +impl Add<&Expression> for &Expression { + type Output = Expression; + fn add(self, rhs: &Expression) -> Expression { + self.add_mul(FieldElement::one(), rhs) + } +} + +impl Sub<&Expression> for &Expression { + type Output = Expression; + fn sub(self, rhs: &Expression) -> Expression { + self.add_mul(-FieldElement::one(), rhs) + } +} + +impl Mul<&Expression> for &Expression { + type Output = Option; + fn mul(self, rhs: &Expression) -> Option { + if self.is_const() { + return Some(self.q_c * rhs); + } else if rhs.is_const() { + return Some(self * rhs.q_c); + } else if !(self.is_linear() && rhs.is_linear()) { + // `Expression`s can only represent terms which are up to degree 2. + // We then disallow multiplication of `Expression`s which have degree 2 terms. + return None; + } + + let mut output = Expression::from_field(self.q_c * rhs.q_c); + + //TODO to optimize... + for lc in &self.linear_combinations { + let single = single_mul(lc.1, rhs); + output = output.add_mul(lc.0, &single); + } + + //linear terms + let mut i1 = 0; //a + let mut i2 = 0; //b + while i1 < self.linear_combinations.len() && i2 < rhs.linear_combinations.len() { + let (a_c, a_w) = self.linear_combinations[i1]; + let (b_c, b_w) = rhs.linear_combinations[i2]; + + // Apply scaling from multiplication + let a_c = rhs.q_c * a_c; + let b_c = self.q_c * b_c; + + let (coeff, witness) = match a_w.cmp(&b_w) { + Ordering::Greater => { + i2 += 1; + (b_c, b_w) + } + Ordering::Less => { + i1 += 1; + (a_c, a_w) + } + Ordering::Equal => { + // Here we're taking both terms as the witness indices are equal. + // We then advance both `i1` and `i2`. + i1 += 1; + i2 += 1; + (a_c + b_c, a_w) + } + }; + + if !coeff.is_zero() { + output.linear_combinations.push((coeff, witness)); + } + } + while i1 < self.linear_combinations.len() { + let (a_c, a_w) = self.linear_combinations[i1]; + let coeff = rhs.q_c * a_c; + if !coeff.is_zero() { + output.linear_combinations.push((coeff, a_w)); + } + i1 += 1; + } + while i2 < rhs.linear_combinations.len() { + let (b_c, b_w) = rhs.linear_combinations[i2]; + let coeff = self.q_c * b_c; + if !coeff.is_zero() { + output.linear_combinations.push((coeff, b_w)); + } + i2 += 1; + } + + Some(output) + } +} + +/// Returns `w*b.linear_combinations` +fn single_mul(w: Witness, b: &Expression) -> Expression { + Expression { + mul_terms: b + .linear_combinations + .iter() + .map(|(a, wit)| { + let (wl, wr) = if w < *wit { (w, *wit) } else { (*wit, w) }; + (*a, wl, wr) + }) + .collect(), + ..Default::default() + } +} + +#[test] +fn add_smoketest() { + let a = Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::from(2u128), Witness(2))], + q_c: FieldElement::from(2u128), + }; + + let b = Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::from(4u128), Witness(4))], + q_c: FieldElement::one(), + }; + + assert_eq!( + &a + &b, + Expression { + mul_terms: vec![], + linear_combinations: vec![ + (FieldElement::from(2u128), Witness(2)), + (FieldElement::from(4u128), Witness(4)) + ], + q_c: FieldElement::from(3u128) + } + ); + + // Enforce commutativity + assert_eq!(&a + &b, &b + &a); +} + +#[test] +fn mul_smoketest() { + let a = Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::from(2u128), Witness(2))], + q_c: FieldElement::from(2u128), + }; + + let b = Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::from(4u128), Witness(4))], + q_c: FieldElement::one(), + }; + + assert_eq!( + (&a * &b).unwrap(), + Expression { + mul_terms: vec![(FieldElement::from(8u128), Witness(2), Witness(4)),], + linear_combinations: vec![ + (FieldElement::from(2u128), Witness(2)), + (FieldElement::from(8u128), Witness(4)) + ], + q_c: FieldElement::from(2u128) + } + ); + + // Enforce commutativity + assert_eq!(&a * &b, &b * &a); +} diff --git a/acvm-repo/acir/src/native_types/expression/ordering.rs b/acvm-repo/acir/src/native_types/expression/ordering.rs new file mode 100644 index 00000000000..e24a25ec3af --- /dev/null +++ b/acvm-repo/acir/src/native_types/expression/ordering.rs @@ -0,0 +1,99 @@ +use crate::native_types::Witness; +use std::cmp::Ordering; + +use super::Expression; + +// TODO: It's undecided whether `Expression` should implement `Ord/PartialOrd`. +// This is currently used in ACVM in the compiler. + +impl Ord for Expression { + fn cmp(&self, other: &Self) -> Ordering { + let mut i1 = self.get_max_idx(); + let mut i2 = other.get_max_idx(); + let mut result = Ordering::Equal; + while result == Ordering::Equal { + let m1 = self.get_max_term(&mut i1); + let m2 = other.get_max_term(&mut i2); + if m1.is_none() && m2.is_none() { + return Ordering::Equal; + } + result = Expression::cmp_max(m1, m2); + } + result + } +} + +impl PartialOrd for Expression { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +struct WitnessIdx { + linear: usize, + mul: usize, + second_term: bool, +} + +impl Expression { + fn get_max_idx(&self) -> WitnessIdx { + WitnessIdx { + linear: self.linear_combinations.len(), + mul: self.mul_terms.len(), + second_term: true, + } + } + + /// Returns the maximum witness at the provided position, and decrement the position. + /// + /// This function assumes the gate is sorted + fn get_max_term(&self, idx: &mut WitnessIdx) -> Option { + if idx.linear > 0 { + if idx.mul > 0 { + let mul_term = if idx.second_term { + self.mul_terms[idx.mul - 1].2 + } else { + self.mul_terms[idx.mul - 1].1 + }; + if self.linear_combinations[idx.linear - 1].1 > mul_term { + idx.linear -= 1; + Some(self.linear_combinations[idx.linear].1) + } else { + if idx.second_term { + idx.second_term = false; + } else { + idx.mul -= 1; + } + Some(mul_term) + } + } else { + idx.linear -= 1; + Some(self.linear_combinations[idx.linear].1) + } + } else if idx.mul > 0 { + if idx.second_term { + idx.second_term = false; + Some(self.mul_terms[idx.mul - 1].2) + } else { + idx.mul -= 1; + Some(self.mul_terms[idx.mul].1) + } + } else { + None + } + } + + fn cmp_max(m1: Option, m2: Option) -> Ordering { + if let Some(m1) = m1 { + if let Some(m2) = m2 { + m1.cmp(&m2) + } else { + Ordering::Greater + } + } else if m2.is_some() { + Ordering::Less + } else { + Ordering::Equal + } + } +} diff --git a/acvm-repo/acir/src/native_types/mod.rs b/acvm-repo/acir/src/native_types/mod.rs new file mode 100644 index 00000000000..66c822bfff8 --- /dev/null +++ b/acvm-repo/acir/src/native_types/mod.rs @@ -0,0 +1,8 @@ +mod expression; +mod witness; +mod witness_map; + +pub use expression::Expression; +pub use witness::Witness; +pub use witness_map::WitnessMap; +pub use witness_map::WitnessMapError; diff --git a/acvm-repo/acir/src/native_types/witness.rs b/acvm-repo/acir/src/native_types/witness.rs new file mode 100644 index 00000000000..740d10d2951 --- /dev/null +++ b/acvm-repo/acir/src/native_types/witness.rs @@ -0,0 +1,43 @@ +use std::ops::Add; + +use acir_field::FieldElement; +use serde::{Deserialize, Serialize}; + +use super::Expression; + +// Witness might be a misnomer. This is an index that represents the position a witness will take +#[derive( + Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize, +)] +pub struct Witness(pub u32); + +impl Witness { + pub fn new(witness_index: u32) -> Witness { + Witness(witness_index) + } + pub fn witness_index(&self) -> u32 { + self.0 + } + pub fn as_usize(&self) -> usize { + // This is safe as long as the architecture is 32bits minimum + self.0 as usize + } + + pub const fn can_defer_constraint(&self) -> bool { + true + } +} + +impl From for Witness { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl Add for Witness { + type Output = Expression; + + fn add(self, rhs: Witness) -> Self::Output { + Expression::from(self).add_mul(FieldElement::one(), &Expression::from(rhs)) + } +} diff --git a/acvm-repo/acir/src/native_types/witness_map.rs b/acvm-repo/acir/src/native_types/witness_map.rs new file mode 100644 index 00000000000..1734b0b907f --- /dev/null +++ b/acvm-repo/acir/src/native_types/witness_map.rs @@ -0,0 +1,103 @@ +use std::{ + collections::{btree_map, BTreeMap}, + io::Read, + ops::Index, +}; + +use acir_field::FieldElement; +use flate2::bufread::GzDecoder; +use flate2::bufread::GzEncoder; +use flate2::Compression; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::native_types::Witness; + +#[derive(Debug, Error)] +enum SerializationError { + #[error(transparent)] + Deflate(#[from] std::io::Error), +} + +#[derive(Debug, Error)] +#[error(transparent)] +pub struct WitnessMapError(#[from] SerializationError); + +/// A map from the witnesses in a constraint system to the field element values +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)] +pub struct WitnessMap(BTreeMap); + +impl WitnessMap { + pub fn new() -> Self { + Self(BTreeMap::new()) + } + pub fn get(&self, witness: &Witness) -> Option<&FieldElement> { + self.0.get(witness) + } + pub fn get_index(&self, index: u32) -> Option<&FieldElement> { + self.0.get(&index.into()) + } + pub fn contains_key(&self, key: &Witness) -> bool { + self.0.contains_key(key) + } + pub fn insert(&mut self, key: Witness, value: FieldElement) -> Option { + self.0.insert(key, value) + } +} + +impl Index<&Witness> for WitnessMap { + type Output = FieldElement; + + fn index(&self, index: &Witness) -> &Self::Output { + &self.0[index] + } +} + +pub struct IntoIter(btree_map::IntoIter); + +impl Iterator for IntoIter { + type Item = (Witness, FieldElement); + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl IntoIterator for WitnessMap { + type Item = (Witness, FieldElement); + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter(self.0.into_iter()) + } +} + +impl From> for WitnessMap { + fn from(value: BTreeMap) -> Self { + Self(value) + } +} + +impl TryFrom for Vec { + type Error = WitnessMapError; + + fn try_from(val: WitnessMap) -> Result { + let buf = bincode::serialize(&val).unwrap(); + 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| WitnessMapError(err.into()))?; + Ok(buf_c) + } +} + +impl TryFrom<&[u8]> for WitnessMap { + type Error = WitnessMapError; + + fn try_from(bytes: &[u8]) -> Result { + let mut deflater = GzDecoder::new(bytes); + let mut buf_d = Vec::new(); + deflater.read_to_end(&mut buf_d).map_err(|err| WitnessMapError(err.into()))?; + let witness_map = bincode::deserialize(buf_d.as_slice()).unwrap(); + Ok(Self(witness_map)) + } +} diff --git a/acvm-repo/acir/tests/test_program_serialization.rs b/acvm-repo/acir/tests/test_program_serialization.rs new file mode 100644 index 00000000000..5aa51237dd9 --- /dev/null +++ b/acvm-repo/acir/tests/test_program_serialization.rs @@ -0,0 +1,330 @@ +//! This integration test defines a set of circuits which are used in order to test the acvm_js package. +//! +//! The acvm_js test suite contains serialized [circuits][`Circuit`] which must be kept in sync with the format +//! outputted from the [ACIR crate][acir]. +//! Breaking changes to the serialization format then require refreshing acvm_js's test suite. +//! This file contains Rust definitions of these circuits and outputs the updated serialized format. +//! +//! These tests also check this circuit serialization against an expected value, erroring if the serialization changes. +//! Generally in this situation we just need to refresh the `expected_serialization` variables to match the +//! actual output, **HOWEVER** note that this results in a breaking change to the ACIR format. + +use std::collections::BTreeSet; + +use acir::{ + circuit::{ + brillig::{Brillig, BrilligInputs, BrilligOutputs}, + opcodes::{BlackBoxFuncCall, BlockId, FunctionInput, MemOp}, + Circuit, Opcode, PublicInputs, + }, + native_types::{Expression, Witness}, +}; +use acir_field::FieldElement; +use brillig::{HeapArray, RegisterIndex, RegisterOrMemory}; + +#[test] +fn addition_circuit() { + let addition = Opcode::Arithmetic(Expression { + mul_terms: Vec::new(), + linear_combinations: vec![ + (FieldElement::one(), Witness(1)), + (FieldElement::one(), Witness(2)), + (-FieldElement::one(), Witness(3)), + ], + q_c: FieldElement::zero(), + }); + + let circuit = Circuit { + current_witness_index: 4, + opcodes: vec![addition], + private_parameters: BTreeSet::from([Witness(1), Witness(2)]), + return_values: PublicInputs([Witness(3)].into()), + ..Circuit::default() + }; + + let mut bytes = Vec::new(); + circuit.write(&mut bytes).unwrap(); + + let expected_serialization: Vec = vec![ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 144, 187, 13, 192, 32, 12, 68, 249, 100, 32, 27, + 219, 96, 119, 89, 37, 40, 176, 255, 8, 17, 18, 5, 74, 202, 240, 154, 235, 158, 238, 238, + 112, 206, 121, 247, 37, 206, 60, 103, 194, 63, 208, 111, 116, 133, 197, 69, 144, 153, 91, + 73, 13, 9, 47, 72, 86, 85, 128, 165, 102, 69, 69, 81, 185, 147, 18, 53, 101, 45, 86, 173, + 128, 33, 83, 195, 46, 70, 125, 202, 226, 190, 94, 16, 166, 103, 108, 13, 203, 151, 254, + 245, 233, 224, 1, 1, 52, 166, 127, 120, 1, 0, 0, + ]; + + assert_eq!(bytes, expected_serialization) +} + +#[test] +fn fixed_base_scalar_mul_circuit() { + let fixed_base_scalar_mul = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::FixedBaseScalarMul { + low: FunctionInput { witness: Witness(1), num_bits: 128 }, + high: FunctionInput { witness: Witness(2), num_bits: 128 }, + outputs: (Witness(3), Witness(4)), + }); + + let circuit = Circuit { + current_witness_index: 5, + opcodes: vec![fixed_base_scalar_mul], + private_parameters: BTreeSet::from([Witness(1), Witness(2)]), + return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(3), Witness(4)])), + ..Circuit::default() + }; + + let mut bytes = Vec::new(); + circuit.write(&mut bytes).unwrap(); + + let expected_serialization: Vec = vec![ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 77, 138, 91, 10, 0, 48, 12, 194, 178, 215, 207, 78, 189, + 163, 175, 165, 10, 21, 36, 10, 57, 192, 160, 146, 188, 226, 139, 78, 113, 69, 183, 190, 61, + 111, 218, 182, 231, 124, 68, 185, 243, 207, 92, 0, 0, 0, + ]; + + assert_eq!(bytes, expected_serialization) +} + +#[test] +fn pedersen_circuit() { + let pedersen = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::Pedersen { + inputs: vec![FunctionInput { witness: Witness(1), num_bits: FieldElement::max_num_bits() }], + outputs: (Witness(2), Witness(3)), + domain_separator: 0, + }); + + let circuit = Circuit { + current_witness_index: 4, + opcodes: vec![pedersen], + private_parameters: BTreeSet::from([Witness(1)]), + return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(2), Witness(3)])), + ..Circuit::default() + }; + + let mut bytes = Vec::new(); + circuit.write(&mut bytes).unwrap(); + + let expected_serialization: Vec = vec![ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 93, 138, 9, 10, 0, 64, 8, 2, 103, 15, 250, 255, 139, + 163, 162, 130, 72, 16, 149, 241, 3, 135, 84, 164, 172, 173, 213, 175, 251, 45, 198, 96, + 243, 211, 50, 152, 67, 220, 211, 92, 0, 0, 0, + ]; + + assert_eq!(bytes, expected_serialization) +} + +#[test] +fn schnorr_verify_circuit() { + let public_key_x = + FunctionInput { witness: Witness(1), num_bits: FieldElement::max_num_bits() }; + let public_key_y = + FunctionInput { witness: Witness(2), num_bits: FieldElement::max_num_bits() }; + let signature = + (3..(3 + 64)).map(|i| FunctionInput { witness: Witness(i), num_bits: 8 }).collect(); + let message = ((3 + 64)..(3 + 64 + 10)) + .map(|i| FunctionInput { witness: Witness(i), num_bits: 8 }) + .collect(); + let output = Witness(3 + 64 + 10); + let last_input = output.witness_index() - 1; + + let schnorr = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::SchnorrVerify { + public_key_x, + public_key_y, + signature, + message, + output, + }); + + let circuit = Circuit { + current_witness_index: 100, + opcodes: vec![schnorr], + private_parameters: BTreeSet::from_iter((1..=last_input).map(Witness)), + return_values: PublicInputs(BTreeSet::from([output])), + ..Circuit::default() + }; + + let mut bytes = Vec::new(); + circuit.write(&mut bytes).unwrap(); + + let expected_serialization: Vec = vec![ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 77, 210, 87, 78, 2, 1, 20, 134, 209, 177, 247, 222, 123, + 71, 68, 68, 68, 68, 68, 68, 68, 68, 68, 221, 133, 251, 95, 130, 145, 27, 206, 36, 78, 50, + 57, 16, 94, 200, 253, 191, 159, 36, 73, 134, 146, 193, 19, 142, 241, 183, 255, 14, 179, + 233, 247, 145, 254, 59, 217, 127, 71, 57, 198, 113, 78, 48, 125, 167, 56, 205, 25, 206, + 114, 142, 243, 92, 224, 34, 151, 184, 204, 21, 174, 114, 141, 235, 220, 224, 38, 183, 184, + 205, 29, 238, 114, 143, 251, 60, 224, 33, 143, 120, 204, 19, 158, 242, 140, 25, 158, 51, + 203, 11, 230, 120, 201, 60, 175, 88, 224, 53, 139, 188, 97, 137, 183, 44, 243, 142, 21, + 222, 179, 202, 7, 214, 248, 200, 58, 159, 216, 224, 51, 155, 124, 97, 235, 223, 142, 241, + 188, 250, 222, 230, 27, 59, 124, 103, 151, 31, 236, 241, 147, 95, 252, 246, 57, 158, 104, + 47, 186, 139, 214, 162, 179, 104, 44, 250, 74, 219, 154, 242, 63, 162, 165, 232, 40, 26, + 138, 126, 162, 157, 232, 38, 154, 137, 94, 162, 149, 232, 36, 26, 137, 62, 162, 141, 232, + 34, 154, 136, 30, 162, 133, 232, 32, 26, 136, 253, 99, 251, 195, 100, 176, 121, 236, 29, + 91, 159, 218, 56, 99, 219, 172, 77, 115, 182, 204, 219, 176, 96, 187, 162, 205, 74, 182, + 42, 219, 168, 98, 155, 170, 77, 106, 182, 168, 219, 160, 225, 246, 77, 55, 111, 185, 113, + 219, 109, 59, 110, 218, 117, 203, 158, 27, 166, 55, 75, 239, 150, 184, 101, 250, 252, 1, + 19, 89, 159, 101, 220, 3, 0, 0, + ]; + + assert_eq!(bytes, expected_serialization) +} + +#[test] +fn simple_brillig_foreign_call() { + let w_input = Witness(1); + let w_inverted = Witness(2); + + let brillig_data = Brillig { + inputs: vec![ + BrilligInputs::Single(w_input.into()), // Input Register 0, + ], + // This tells the BrilligSolver which witnesses its output registers correspond to + outputs: vec![ + BrilligOutputs::Simple(w_inverted), // Output Register 1 + ], + // stack of foreign call/oracle resolutions, starts empty + foreign_call_results: vec![], + bytecode: vec![brillig::Opcode::ForeignCall { + function: "invert".into(), + destinations: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(0))], + inputs: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(0))], + }], + predicate: None, + }; + + let opcodes = vec![Opcode::Brillig(brillig_data)]; + let circuit = Circuit { + current_witness_index: 8, + opcodes, + private_parameters: BTreeSet::from([Witness(1), Witness(2)]), + ..Circuit::default() + }; + + let mut bytes = Vec::new(); + circuit.write(&mut bytes).unwrap(); + + let expected_serialization: Vec = vec![ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 143, 81, 10, 0, 16, 16, 68, 199, 42, 57, 14, 55, + 112, 25, 31, 126, 124, 72, 206, 79, 161, 86, 225, 135, 87, 219, 78, 187, 53, 205, 104, 0, + 2, 29, 201, 52, 103, 222, 220, 216, 230, 13, 43, 254, 121, 25, 158, 151, 54, 153, 117, 27, + 53, 116, 136, 197, 167, 124, 107, 184, 64, 236, 73, 56, 83, 1, 18, 139, 122, 157, 67, 1, 0, + 0, + ]; + + assert_eq!(bytes, expected_serialization) +} + +#[test] +fn complex_brillig_foreign_call() { + let fe_0 = FieldElement::zero(); + let fe_1 = FieldElement::one(); + let a = Witness(1); + let b = Witness(2); + let c = Witness(3); + + let a_times_2 = Witness(4); + let b_times_3 = Witness(5); + let c_times_4 = Witness(6); + let a_plus_b_plus_c = Witness(7); + let a_plus_b_plus_c_times_2 = Witness(8); + + let brillig_data = Brillig { + inputs: vec![ + // Input Register 0 + BrilligInputs::Array(vec![ + Expression::from(a), + Expression::from(b), + Expression::from(c), + ]), + // Input Register 1 + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, a), (fe_1, b), (fe_1, c)], + q_c: fe_0, + }), + ], + // This tells the BrilligSolver which witnesses its output registers correspond to + outputs: vec![ + BrilligOutputs::Array(vec![a_times_2, b_times_3, c_times_4]), // Output Register 0 + BrilligOutputs::Simple(a_plus_b_plus_c), // Output Register 1 + BrilligOutputs::Simple(a_plus_b_plus_c_times_2), // Output Register 2 + ], + // stack of foreign call/oracle resolutions, starts empty + foreign_call_results: vec![], + bytecode: vec![ + // Oracles are named 'foreign calls' in brillig + brillig::Opcode::ForeignCall { + function: "complex".into(), + inputs: vec![ + RegisterOrMemory::HeapArray(HeapArray { pointer: 0.into(), size: 3 }), + RegisterOrMemory::RegisterIndex(RegisterIndex::from(1)), + ], + destinations: vec![ + RegisterOrMemory::HeapArray(HeapArray { pointer: 0.into(), size: 3 }), + RegisterOrMemory::RegisterIndex(RegisterIndex::from(1)), + RegisterOrMemory::RegisterIndex(RegisterIndex::from(2)), + ], + }, + ], + predicate: None, + }; + + let opcodes = vec![Opcode::Brillig(brillig_data)]; + let circuit = Circuit { + current_witness_index: 8, + opcodes, + private_parameters: BTreeSet::from([Witness(1), Witness(2), Witness(3)]), + ..Circuit::default() + }; + + let mut bytes = Vec::new(); + circuit.write(&mut bytes).unwrap(); + + let expected_serialization: Vec = vec![ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 83, 219, 10, 128, 48, 8, 245, 210, 101, 159, 179, + 254, 160, 127, 137, 222, 138, 122, 236, 243, 27, 228, 64, 44, 232, 33, 7, 237, 128, 56, + 157, 147, 131, 103, 6, 0, 64, 184, 192, 201, 72, 206, 40, 177, 70, 174, 27, 197, 199, 111, + 24, 208, 175, 87, 44, 197, 145, 42, 224, 200, 5, 56, 230, 255, 240, 83, 189, 61, 117, 113, + 157, 31, 63, 236, 79, 147, 172, 77, 214, 73, 220, 139, 15, 106, 214, 168, 114, 249, 126, + 218, 214, 125, 153, 15, 54, 37, 90, 26, 155, 39, 227, 95, 223, 232, 230, 4, 247, 157, 215, + 56, 1, 153, 86, 63, 138, 44, 4, 0, 0, + ]; + + assert_eq!(bytes, expected_serialization) +} + +#[test] +fn memory_op_circuit() { + let init = vec![Witness(1), Witness(2)]; + + let memory_init = Opcode::MemoryInit { block_id: BlockId(0), init }; + let write = Opcode::MemoryOp { + block_id: BlockId(0), + op: MemOp::write_to_mem_index(FieldElement::from(1u128).into(), Witness(3).into()), + predicate: None, + }; + let read = Opcode::MemoryOp { + block_id: BlockId(0), + op: MemOp::read_at_mem_index(FieldElement::one().into(), Witness(4)), + predicate: None, + }; + + let circuit = Circuit { + current_witness_index: 5, + opcodes: vec![memory_init, write, read], + private_parameters: BTreeSet::from([Witness(1), Witness(2), Witness(3)]), + return_values: PublicInputs([Witness(4)].into()), + ..Circuit::default() + }; + let mut bytes = Vec::new(); + circuit.write(&mut bytes).unwrap(); + + let expected_serialization: Vec = vec![ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 146, 49, 14, 0, 32, 8, 3, 139, 192, 127, 240, 7, + 254, 255, 85, 198, 136, 9, 131, 155, 48, 216, 165, 76, 77, 57, 80, 0, 140, 45, 117, 111, + 238, 228, 179, 224, 174, 225, 110, 111, 234, 213, 185, 148, 156, 203, 121, 89, 86, 13, 215, + 126, 131, 43, 153, 187, 115, 40, 185, 62, 153, 3, 136, 83, 60, 30, 96, 2, 12, 235, 225, + 124, 14, 3, 0, 0, + ]; + + assert_eq!(bytes, expected_serialization) +} diff --git a/acvm-repo/acir_field/.gitignore b/acvm-repo/acir_field/.gitignore new file mode 100644 index 00000000000..c41cc9e35e3 --- /dev/null +++ b/acvm-repo/acir_field/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/acvm-repo/acir_field/CHANGELOG.md b/acvm-repo/acir_field/CHANGELOG.md new file mode 100644 index 00000000000..1d80bfd6a20 --- /dev/null +++ b/acvm-repo/acir_field/CHANGELOG.md @@ -0,0 +1,296 @@ +# Changelog + +## [0.27.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.26.1...acir_field-v0.27.0) (2023-09-19) + + +### ⚠ BREAKING CHANGES + +* Separate barretenberg solver from generic blackbox solver code ([#554](https://github.com/noir-lang/acvm/issues/554)) + +### Miscellaneous Chores + +* Separate barretenberg solver from generic blackbox solver code ([#554](https://github.com/noir-lang/acvm/issues/554)) ([a4b9772](https://github.com/noir-lang/acvm/commit/a4b97722a0892fe379ff075e6080675adafdce0e)) + +## [0.26.1](https://github.com/noir-lang/acvm/compare/acir_field-v0.26.0...acir_field-v0.26.1) (2023-09-12) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.26.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.25.0...acir_field-v0.26.0) (2023-09-07) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.25.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.24.1...acir_field-v0.25.0) (2023-09-04) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.24.1](https://github.com/noir-lang/acvm/compare/acir_field-v0.24.0...acir_field-v0.24.1) (2023-09-03) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.24.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.23.0...acir_field-v0.24.0) (2023-08-31) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.23.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.22.0...acir_field-v0.23.0) (2023-08-30) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.22.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.21.0...acir_field-v0.22.0) (2023-08-18) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.21.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.20.1...acir_field-v0.21.0) (2023-07-26) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.20.1](https://github.com/noir-lang/acvm/compare/acir_field-v0.20.0...acir_field-v0.20.1) (2023-07-26) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.20.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.19.1...acir_field-v0.20.0) (2023-07-20) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.19.1](https://github.com/noir-lang/acvm/compare/acir_field-v0.19.0...acir_field-v0.19.1) (2023-07-17) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.19.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.18.2...acir_field-v0.19.0) (2023-07-15) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.18.2](https://github.com/noir-lang/acvm/compare/acir_field-v0.18.1...acir_field-v0.18.2) (2023-07-12) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.18.1](https://github.com/noir-lang/acvm/compare/acir_field-v0.18.0...acir_field-v0.18.1) (2023-07-12) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.18.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.17.0...acir_field-v0.18.0) (2023-07-12) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.17.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.16.0...acir_field-v0.17.0) (2023-07-07) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.16.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.15.1...acir_field-v0.16.0) (2023-07-06) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.15.1](https://github.com/noir-lang/acvm/compare/acir_field-v0.15.0...acir_field-v0.15.1) (2023-06-20) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.15.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.14.2...acir_field-v0.15.0) (2023-06-15) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.14.2](https://github.com/noir-lang/acvm/compare/acir_field-v0.14.1...acir_field-v0.14.2) (2023-06-08) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.14.1](https://github.com/noir-lang/acvm/compare/acir_field-v0.14.0...acir_field-v0.14.1) (2023-06-07) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.14.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.13.3...acir_field-v0.14.0) (2023-06-06) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.13.3](https://github.com/noir-lang/acvm/compare/acir_field-v0.13.2...acir_field-v0.13.3) (2023-06-05) + + +### Bug Fixes + +* Empty commit to trigger release-please ([e8f0748](https://github.com/noir-lang/acvm/commit/e8f0748042ef505d59ab63266d3c36c5358ee30d)) + +## [0.13.2](https://github.com/noir-lang/acvm/compare/acir_field-v0.13.1...acir_field-v0.13.2) (2023-06-02) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.13.1](https://github.com/noir-lang/acvm/compare/acir_field-v0.13.0...acir_field-v0.13.1) (2023-06-01) + + +### Bug Fixes + +* **ci:** Correct typo to avoid `undefined` in changelogs ([#333](https://github.com/noir-lang/acvm/issues/333)) ([d3424c0](https://github.com/noir-lang/acvm/commit/d3424c04fd303c9cbe25d03118d8b358cbb84b83)) + +## [0.13.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.12.0...acir_field-v0.13.0) (2023-06-01) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.12.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.11.0...acir_field-v0.12.0) (2023-05-17) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.11.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.10.3...acir_field-v0.11.0) (2023-05-04) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.10.3](https://github.com/noir-lang/acvm/compare/acir_field-v0.10.2...acir_field-v0.10.3) (2023-04-28) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.10.2](https://github.com/noir-lang/acvm/compare/acir_field-v0.10.1...acir_field-v0.10.2) (2023-04-28) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.10.1](https://github.com/noir-lang/acvm/compare/acir_field-v0.10.0...acir_field-v0.10.1) (2023-04-28) + + +### Features + +* implement `FieldElement::from<bool>()` ([#203](https://github.com/noir-lang/acvm/issues/203)) ([476cfa2](https://github.com/noir-lang/acvm/commit/476cfa247fddb515c64c2801c6868357c9375294)) +* Update Arkworks' dependencies on `acir_field` ([#69](https://github.com/noir-lang/acvm/issues/69)) ([65d6130](https://github.com/noir-lang/acvm/commit/65d61307a12f25e04afad2d50e4c4db5ce97dd8c)) + + +### Bug Fixes + +* prevent `bn254` feature flag always being enabled ([#225](https://github.com/noir-lang/acvm/issues/225)) ([82eee6a](https://github.com/noir-lang/acvm/commit/82eee6ab08ae480f04904ca8571fd88f4466c000)) + +## [0.10.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.9.0...acir_field-v0.10.0) (2023-04-26) + + +### Features + +* implement `FieldElement::from<bool>()` ([#203](https://github.com/noir-lang/acvm/issues/203)) ([476cfa2](https://github.com/noir-lang/acvm/commit/476cfa247fddb515c64c2801c6868357c9375294)) + + +### Bug Fixes + +* prevent `bn254` feature flag always being enabled ([#225](https://github.com/noir-lang/acvm/issues/225)) ([82eee6a](https://github.com/noir-lang/acvm/commit/82eee6ab08ae480f04904ca8571fd88f4466c000)) + +## [0.9.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.8.1...acir_field-v0.9.0) (2023-04-07) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.8.1](https://github.com/noir-lang/acvm/compare/acir_field-v0.8.0...acir_field-v0.8.1) (2023-03-30) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.8.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.7.1...acir_field-v0.8.0) (2023-03-28) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.7.1](https://github.com/noir-lang/acvm/compare/acir_field-v0.7.0...acir_field-v0.7.1) (2023-03-27) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.7.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.6.0...acir_field-v0.7.0) (2023-03-23) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.6.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.5.0...acir_field-v0.6.0) (2023-03-03) + + +### Miscellaneous Chores + +* **acir_field:** Synchronize acvm versions + +## [0.5.0](https://github.com/noir-lang/acvm/compare/acir_field-v0.4.1...acir_field-v0.5.0) (2023-02-22) + + +### Features + +* Update Arkworks' dependencies on `acir_field` ([#69](https://github.com/noir-lang/acvm/issues/69)) ([65d6130](https://github.com/noir-lang/acvm/commit/65d61307a12f25e04afad2d50e4c4db5ce97dd8c)) diff --git a/acvm-repo/acir_field/Cargo.toml b/acvm-repo/acir_field/Cargo.toml new file mode 100644 index 00000000000..dded63cdd72 --- /dev/null +++ b/acvm-repo/acir_field/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "acir_field" +description = "The field implementation being used by ACIR." +# x-release-please-start-version +version = "0.27.4" +# x-release-please-end +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hex.workspace = true +num-bigint.workspace = true +serde.workspace = true + +ark-bn254 = { version = "^0.4.0", optional = true, default-features = false, features = [ + "curve", +] } +ark-bls12-381 = { version = "^0.4.0", optional = true, default-features = false, features = [ + "curve", +] } +ark-ff = { version = "^0.4.0", optional = true, default-features = false } + +cfg-if = "1.0.0" + +[features] +default = ["bn254"] +bn254 = ["dep:ark-bn254", "dep:ark-ff"] +bls12_381 = ["dep:ark-bls12-381", "dep:ark-ff"] diff --git a/acvm-repo/acir_field/src/generic_ark.rs b/acvm-repo/acir_field/src/generic_ark.rs new file mode 100644 index 00000000000..59600549f32 --- /dev/null +++ b/acvm-repo/acir_field/src/generic_ark.rs @@ -0,0 +1,502 @@ +use ark_ff::PrimeField; +use ark_ff::Zero; +use num_bigint::BigUint; +use serde::{Deserialize, Serialize}; + +// XXX: Switch out for a trait and proper implementations +// This implementation is in-efficient, can definitely remove hex usage and Iterator instances for trivial functionality +#[derive(Clone, Copy, Eq, PartialOrd, Ord)] +pub struct FieldElement(F); + +impl std::fmt::Display for FieldElement { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + // First check if the number is zero + // + let number = BigUint::from_bytes_be(&self.to_be_bytes()); + if number == BigUint::zero() { + return write!(f, "0"); + } + // Check if the negative version is smaller to represent + // + let minus_number = BigUint::from_bytes_be(&(self.neg()).to_be_bytes()); + let (smaller_repr, is_negative) = + if minus_number.to_string().len() < number.to_string().len() { + (minus_number, true) + } else { + (number, false) + }; + if is_negative { + write!(f, "-")?; + } + + // Number of bits needed to represent the smaller representation + let num_bits = smaller_repr.bits(); + + // Check if the number represents a power of 2 + if smaller_repr.count_ones() == 1 { + let mut bit_index = 0; + for i in 0..num_bits { + if smaller_repr.bit(i) { + bit_index = i; + break; + } + } + return match bit_index { + 0 => write!(f, "1"), + 1 => write!(f, "2"), + 2 => write!(f, "4"), + 3 => write!(f, "8"), + _ => write!(f, "2{}", superscript(bit_index)), + }; + } + + // Check if number is a multiple of a power of 2. + // This is used because when computing the quotient + // we usually have numbers in the form 2^t * q + r + // We focus on 2^64, 2^32, 2^16, 2^8, 2^4 because + // they are common. We could extend this to a more + // general factorization strategy, but we pay in terms of CPU time + let mul_sign = "×"; + for power in [64, 32, 16, 8, 4] { + let power_of_two = BigUint::from(2_u128).pow(power); + if &smaller_repr % &power_of_two == BigUint::zero() { + return write!( + f, + "2{}{}{}", + superscript(power as u64), + mul_sign, + smaller_repr / &power_of_two, + ); + } + } + write!(f, "{smaller_repr}") + } +} + +impl std::fmt::Debug for FieldElement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self, f) + } +} + +impl std::hash::Hash for FieldElement { + fn hash(&self, state: &mut H) { + state.write(&self.to_be_bytes()) + } +} + +impl PartialEq for FieldElement { + fn eq(&self, other: &Self) -> bool { + self.to_be_bytes() == other.to_be_bytes() + } +} + +impl From for FieldElement { + fn from(mut a: i128) -> FieldElement { + let mut negative = false; + if a < 0 { + a = -a; + negative = true; + } + + let mut result = match F::from_str(&a.to_string()) { + Ok(result) => result, + Err(_) => panic!("Cannot convert i128 as a string to a field element"), + }; + + if negative { + result = -result; + } + FieldElement(result) + } +} + +impl Serialize for FieldElement { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_hex().serialize(serializer) + } +} + +impl<'de, T: ark_ff::PrimeField> Deserialize<'de> for FieldElement { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = <&str>::deserialize(deserializer)?; + match Self::from_hex(s) { + Some(value) => Ok(value), + None => Err(serde::de::Error::custom(format!("Invalid hex for FieldElement: {s}",))), + } + } +} + +impl From for FieldElement { + fn from(a: u128) -> FieldElement { + let result = match F::from_str(&a.to_string()) { + Ok(result) => result, + Err(_) => panic!("Cannot convert u128 as a string to a field element"), + }; + FieldElement(result) + } +} + +impl From for FieldElement { + fn from(boolean: bool) -> FieldElement { + if boolean { + FieldElement::one() + } else { + FieldElement::zero() + } + } +} + +impl FieldElement { + pub fn one() -> FieldElement { + FieldElement(F::one()) + } + pub fn zero() -> FieldElement { + FieldElement(F::zero()) + } + + pub fn is_zero(&self) -> bool { + self == &Self::zero() + } + pub fn is_one(&self) -> bool { + self == &Self::one() + } + + pub fn pow(&self, exponent: &Self) -> Self { + FieldElement(self.0.pow(exponent.0.into_bigint())) + } + + /// Maximum number of bits needed to represent a field element + /// This is not the amount of bits being used to represent a field element + /// Example, you only need 254 bits to represent a field element in BN256 + /// But the representation uses 256 bits, so the top two bits are always zero + /// This method would return 254 + pub const fn max_num_bits() -> u32 { + F::MODULUS_BIT_SIZE + } + + /// Maximum numbers of bytes needed to represent a field element + /// We are not guaranteed that the number of bits being used to represent a field element + /// will always be divisible by 8. If the case that it is not, we add one to the max number of bytes + /// For example, a max bit size of 254 would give a max byte size of 32. + pub const fn max_num_bytes() -> u32 { + let num_bytes = Self::max_num_bits() / 8; + if Self::max_num_bits() % 8 == 0 { + num_bytes + } else { + num_bytes + 1 + } + } + + pub fn modulus() -> BigUint { + F::MODULUS.into() + } + /// Returns None, if the string is not a canonical + /// representation of a field element; less than the order + /// or if the hex string is invalid. + /// This method can be used for both hex and decimal representations. + pub fn try_from_str(input: &str) -> Option> { + if input.contains('x') { + return FieldElement::from_hex(input); + } + + let fr = F::from_str(input).ok()?; + Some(FieldElement(fr)) + } + + /// This is the number of bits required to represent this specific field element + pub fn num_bits(&self) -> u32 { + let bits = self.bits(); + // Iterate the number of bits and pop off all leading zeroes + let iter = bits.iter().skip_while(|x| !(**x)); + // Note: count will panic if it goes over usize::MAX. + // This may not be suitable for devices whose usize < u16 + iter.count() as u32 + } + + pub fn fits_in_u128(&self) -> bool { + self.num_bits() <= 128 + } + + pub fn to_u128(self) -> u128 { + let bytes = self.to_be_bytes(); + u128::from_be_bytes(bytes[16..32].try_into().unwrap()) + } + + pub fn try_into_u128(self) -> Option { + self.fits_in_u128().then(|| self.to_u128()) + } + + pub fn try_to_u64(&self) -> Option { + (self.num_bits() <= 64).then(|| self.to_u128() as u64) + } + + /// Computes the inverse or returns zero if the inverse does not exist + /// Before using this FieldElement, please ensure that this behavior is necessary + pub fn inverse(&self) -> FieldElement { + let inv = self.0.inverse().unwrap_or_else(F::zero); + FieldElement(inv) + } + + pub fn try_inverse(mut self) -> Option { + self.0.inverse_in_place().map(|f| FieldElement(*f)) + } + + // XXX: This method is used while this field element + // implementation is not generic. + pub fn into_repr(self) -> F { + self.0 + } + + pub fn to_hex(self) -> String { + let mut bytes = Vec::new(); + self.0.serialize_uncompressed(&mut bytes).unwrap(); + bytes.reverse(); + hex::encode(bytes) + } + pub fn from_hex(hex_str: &str) -> Option> { + let value = hex_str.strip_prefix("0x").unwrap_or(hex_str); + let hex_as_bytes = hex::decode(value).ok()?; + Some(FieldElement::from_be_bytes_reduce(&hex_as_bytes)) + } + + pub fn to_be_bytes(self) -> Vec { + // to_be_bytes! uses little endian which is why we reverse the output + // TODO: Add a little endian equivalent, so the caller can use whichever one + // TODO they desire + let mut bytes = Vec::new(); + self.0.serialize_uncompressed(&mut bytes).unwrap(); + bytes.reverse(); + bytes + } + + /// Converts bytes into a FieldElement and applies a + /// reduction if needed. + pub fn from_be_bytes_reduce(bytes: &[u8]) -> FieldElement { + FieldElement(F::from_be_bytes_mod_order(bytes)) + } + + pub fn bits(&self) -> Vec { + let bytes = self.to_be_bytes(); + let mut bits = Vec::with_capacity(bytes.len() * 8); + for byte in bytes { + let _bits = FieldElement::::byte_to_bit(byte); + bits.extend(_bits); + } + bits + } + + fn byte_to_bit(byte: u8) -> Vec { + let mut bits = Vec::with_capacity(8); + for index in (0..=7).rev() { + bits.push((byte & (1 << index)) >> index == 1) + } + bits + } + + /// Returns the closest number of bytes to the bits specified + /// This method truncates + pub fn fetch_nearest_bytes(&self, num_bits: usize) -> Vec { + fn nearest_bytes(num_bits: usize) -> usize { + ((num_bits + 7) / 8) * 8 + } + + let num_bytes = nearest_bytes(num_bits); + let num_elements = num_bytes / 8; + + let mut bytes = self.to_be_bytes(); + bytes.reverse(); // put it in big endian format. XXX(next refactor): we should be explicit about endianness. + + bytes[0..num_elements].to_vec() + } + + // mask_to methods will not remove any bytes from the field + // they are simply zeroed out + // Whereas truncate_to will remove those bits and make the byte array smaller + fn mask_to_be_bytes(&self, num_bits: u32) -> Vec { + let mut bytes = self.to_be_bytes(); + mask_vector_le(&mut bytes, num_bits as usize); + bytes + } + + fn and_xor(&self, rhs: &FieldElement, num_bits: u32, is_xor: bool) -> FieldElement { + // XXX: Gadgets like SHA256 need to have their input be a multiple of 8 + // This is not a restriction caused by SHA256, as it works on bits + // but most backends assume bytes. + // We could implicitly pad, however this may not be intuitive for users. + // assert!( + // num_bits % 8 == 0, + // "num_bits is not a multiple of 8, it is {}", + // num_bits + // ); + + let lhs_bytes = self.mask_to_be_bytes(num_bits); + let rhs_bytes = rhs.mask_to_be_bytes(num_bits); + + let and_byte_arr: Vec<_> = lhs_bytes + .into_iter() + .zip(rhs_bytes.into_iter()) + .map(|(lhs, rhs)| if is_xor { lhs ^ rhs } else { lhs & rhs }) + .collect(); + + FieldElement::from_be_bytes_reduce(&and_byte_arr) + } + pub fn and(&self, rhs: &FieldElement, num_bits: u32) -> FieldElement { + self.and_xor(rhs, num_bits, false) + } + pub fn xor(&self, rhs: &FieldElement, num_bits: u32) -> FieldElement { + self.and_xor(rhs, num_bits, true) + } +} + +use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}; + +impl Neg for FieldElement { + type Output = FieldElement; + + fn neg(self) -> Self::Output { + FieldElement(-self.0) + } +} + +impl Mul for FieldElement { + type Output = FieldElement; + fn mul(mut self, rhs: FieldElement) -> Self::Output { + self.0.mul_assign(&rhs.0); + FieldElement(self.0) + } +} +impl Div for FieldElement { + type Output = FieldElement; + #[allow(clippy::suspicious_arithmetic_impl)] + fn div(self, rhs: FieldElement) -> Self::Output { + self * rhs.inverse() + } +} +impl Add for FieldElement { + type Output = FieldElement; + fn add(mut self, rhs: FieldElement) -> Self::Output { + self.0.add_assign(&rhs.0); + FieldElement(self.0) + } +} +impl AddAssign for FieldElement { + fn add_assign(&mut self, rhs: FieldElement) { + self.0.add_assign(&rhs.0); + } +} + +impl Sub for FieldElement { + type Output = FieldElement; + fn sub(mut self, rhs: FieldElement) -> Self::Output { + self.0.sub_assign(&rhs.0); + FieldElement(self.0) + } +} +impl SubAssign for FieldElement { + fn sub_assign(&mut self, rhs: FieldElement) { + self.0.sub_assign(&rhs.0); + } +} + +#[cfg(test)] +mod tests { + #[test] + fn and() { + let max = 10_000u32; + + let num_bits = (std::mem::size_of::() * 8) as u32 - max.leading_zeros(); + + for x in 0..max { + let x = crate::generic_ark::FieldElement::::from(x as i128); + let res = x.and(&x, num_bits); + assert_eq!(res.to_be_bytes(), x.to_be_bytes()); + } + } + + #[test] + fn serialize_fixed_test_vectors() { + // Serialized field elements from of 0, -1, -2, -3 + let hex_strings = vec![ + "0000000000000000000000000000000000000000000000000000000000000000", + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000", + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff", + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffffe", + ]; + + for (i, string) in hex_strings.into_iter().enumerate() { + let minus_i_field_element = + -crate::generic_ark::FieldElement::::from(i as i128); + assert_eq!(minus_i_field_element.to_hex(), string) + } + } + #[test] + fn max_num_bits_smoke() { + let max_num_bits_bn254 = crate::generic_ark::FieldElement::::max_num_bits(); + assert_eq!(max_num_bits_bn254, 254) + } +} + +fn mask_vector_le(bytes: &mut [u8], num_bits: usize) { + // reverse to big endian format + bytes.reverse(); + + let mask_power = num_bits % 8; + let array_mask_index = num_bits / 8; + + for (index, byte) in bytes.iter_mut().enumerate() { + match index.cmp(&array_mask_index) { + std::cmp::Ordering::Less => { + // do nothing if the current index is less than + // the array index. + } + std::cmp::Ordering::Equal => { + let mask = 2u8.pow(mask_power as u32) - 1; + // mask the byte + *byte &= mask; + } + std::cmp::Ordering::Greater => { + // Anything greater than the array index + // will be set to zero + *byte = 0; + } + } + } + // reverse back to little endian + bytes.reverse(); +} + +// For pretty printing powers +fn superscript(n: u64) -> String { + if n == 0 { + "⁰".to_owned() + } else if n == 1 { + "¹".to_owned() + } else if n == 2 { + "²".to_owned() + } else if n == 3 { + "³".to_owned() + } else if n == 4 { + "⁴".to_owned() + } else if n == 5 { + "⁵".to_owned() + } else if n == 6 { + "⁶".to_owned() + } else if n == 7 { + "⁷".to_owned() + } else if n == 8 { + "⁸".to_owned() + } else if n == 9 { + "⁹".to_owned() + } else if n >= 10 { + superscript(n / 10) + &superscript(n % 10) + } else { + panic!("{}", n.to_string() + " can't be converted to superscript."); + } +} diff --git a/acvm-repo/acir_field/src/lib.rs b/acvm-repo/acir_field/src/lib.rs new file mode 100644 index 00000000000..97cd8c66c71 --- /dev/null +++ b/acvm-repo/acir_field/src/lib.rs @@ -0,0 +1,40 @@ +#![warn(unused_crate_dependencies)] +#![warn(unreachable_pub)] + +cfg_if::cfg_if! { + if #[cfg(feature = "bn254")] { + mod generic_ark; + pub type FieldElement = generic_ark::FieldElement; + pub const CHOSEN_FIELD : FieldOptions = FieldOptions::BN254; + + } else if #[cfg(feature = "bls12_381")] { + mod generic_ark; + pub type FieldElement = generic_ark::FieldElement; + pub const CHOSEN_FIELD : FieldOptions = FieldOptions::BLS12_381; + } else { + compile_error!("please specify a field to compile with"); + } +} + +#[derive(Debug)] +pub enum FieldOptions { + BN254, + BLS12_381, +} + +// This is needed because features are additive through the dependency graph; if a dependency turns on the bn254, then it +// will be turned on in all crates that depend on it +#[macro_export] +macro_rules! assert_unique_feature { + () => {}; + ($first:tt $(,$rest:tt)*) => { + $( + #[cfg(all(feature = $first, feature = $rest))] + compile_error!(concat!("features \"", $first, "\" and \"", $rest, "\" cannot be used together")); + )* + assert_unique_feature!($($rest),*); + } +} +// https://internals.rust-lang.org/t/mutually-exclusive-feature-flags/8601/7 +// If another field/feature is added, we add it here too +assert_unique_feature!("bn254", "bls12_381"); diff --git a/acvm-repo/acvm/CHANGELOG.md b/acvm-repo/acvm/CHANGELOG.md new file mode 100644 index 00000000000..29a4aa93adc --- /dev/null +++ b/acvm-repo/acvm/CHANGELOG.md @@ -0,0 +1,662 @@ +# Changelog + +## [0.27.0](https://github.com/noir-lang/acvm/compare/acvm-v0.26.1...acvm-v0.27.0) (2023-09-19) + + +### ⚠ BREAKING CHANGES + +* Separate barretenberg solver from generic blackbox solver code ([#554](https://github.com/noir-lang/acvm/issues/554)) + +### Miscellaneous Chores + +* Separate barretenberg solver from generic blackbox solver code ([#554](https://github.com/noir-lang/acvm/issues/554)) ([a4b9772](https://github.com/noir-lang/acvm/commit/a4b97722a0892fe379ff075e6080675adafdce0e)) + +## [0.26.1](https://github.com/noir-lang/acvm/compare/acvm-v0.26.0...acvm-v0.26.1) (2023-09-12) + + +### Miscellaneous Chores + +* **acvm:** Synchronize acvm versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.26.0 to 0.26.1 + +## [0.26.0](https://github.com/noir-lang/acvm/compare/acvm-v0.25.0...acvm-v0.26.0) (2023-09-07) + + +### ⚠ BREAKING CHANGES + +* Add a low and high limb to scalar mul opcode ([#532](https://github.com/noir-lang/acvm/issues/532)) + +### Miscellaneous Chores + +* Add a low and high limb to scalar mul opcode ([#532](https://github.com/noir-lang/acvm/issues/532)) ([b054f66](https://github.com/noir-lang/acvm/commit/b054f66be9c73d4e02dbecdab80874a907f19242)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.25.0 to 0.26.0 + +## [0.25.0](https://github.com/noir-lang/acvm/compare/acvm-v0.24.1...acvm-v0.25.0) (2023-09-04) + + +### ⚠ BREAKING CHANGES + +* Provide runtime callstacks for brillig failures and return errors in acvm_js ([#523](https://github.com/noir-lang/acvm/issues/523)) + +### Features + +* Provide runtime callstacks for brillig failures and return errors in acvm_js ([#523](https://github.com/noir-lang/acvm/issues/523)) ([7ab7cff](https://github.com/noir-lang/acvm/commit/7ab7cff48a9aba61a97fad2a759fc8e55740b098)) + + +### Bug Fixes + +* initialize recursive proof output to zero ([#524](https://github.com/noir-lang/acvm/issues/524)) ([5453074](https://github.com/noir-lang/acvm/commit/545307457dd7634b20ea3977e2d2cc751eba06d2)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.24.1 to 0.25.0 + +## [0.24.1](https://github.com/noir-lang/acvm/compare/acvm-v0.24.0...acvm-v0.24.1) (2023-09-03) + + +### Miscellaneous Chores + +* **acvm:** Synchronize acvm versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.24.0 to 0.24.1 + +## [0.24.0](https://github.com/noir-lang/acvm/compare/acvm-v0.23.0...acvm-v0.24.0) (2023-08-31) + + +### ⚠ BREAKING CHANGES + +* **acvm:** Remove the `Backend` trait ([#514](https://github.com/noir-lang/acvm/issues/514)) +* **acir:** Remove unused `Directive` opcodes ([#510](https://github.com/noir-lang/acvm/issues/510)) +* **acir:** Add predicate to MemoryOp ([#503](https://github.com/noir-lang/acvm/issues/503)) +* **acvm:** Remove unused arguments from `Backend` trait ([#511](https://github.com/noir-lang/acvm/issues/511)) +* Assertion messages embedded in the circuit ([#484](https://github.com/noir-lang/acvm/issues/484)) + +### Features + +* **acir:** Add predicate to MemoryOp ([#503](https://github.com/noir-lang/acvm/issues/503)) ([ca9eebe](https://github.com/noir-lang/acvm/commit/ca9eebe34e61adabf97318c8ccaf60c8a424aafd)) +* Assertion messages embedded in the circuit ([#484](https://github.com/noir-lang/acvm/issues/484)) ([06b97c5](https://github.com/noir-lang/acvm/commit/06b97c51041e16651cf8b2be8bc18214e276c6c9)) + + +### Miscellaneous Chores + +* **acir:** Remove unused `Directive` opcodes ([#510](https://github.com/noir-lang/acvm/issues/510)) ([cfd8cbf](https://github.com/noir-lang/acvm/commit/cfd8cbf58307511ac0cc9106c299695c2ca779de)) +* **acvm:** Remove the `Backend` trait ([#514](https://github.com/noir-lang/acvm/issues/514)) ([681535d](https://github.com/noir-lang/acvm/commit/681535da52815a4a164ee4f48f7b48329664af98)) +* **acvm:** Remove unused arguments from `Backend` trait ([#511](https://github.com/noir-lang/acvm/issues/511)) ([ae65355](https://github.com/noir-lang/acvm/commit/ae65355afb7df98c71f81d5a54e89f39f9333920)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.23.0 to 0.24.0 + +## [0.23.0](https://github.com/noir-lang/acvm/compare/acvm-v0.22.0...acvm-v0.23.0) (2023-08-30) + + +### ⚠ BREAKING CHANGES + +* Return an iterator from `new_locations()` instead of collecting ([#507](https://github.com/noir-lang/acvm/issues/507)) +* **acvm:** remove `CommonReferenceString` trait and preprocess method ([#508](https://github.com/noir-lang/acvm/issues/508)) +* **acvm:** Pass `BlackBoxFunctionSolver` to `ACVM` by reference + +### Features + +* **acvm_js:** Add `execute_circuit_with_black_box_solver` to prevent reinitialization of `BlackBoxFunctionSolver` ([3877e0e](https://github.com/noir-lang/acvm/commit/3877e0e438a8d0e5545a4da7210767dec05c342f)) + + +### Miscellaneous Chores + +* **acvm:** Pass `BlackBoxFunctionSolver` to `ACVM` by reference ([3877e0e](https://github.com/noir-lang/acvm/commit/3877e0e438a8d0e5545a4da7210767dec05c342f)) +* **acvm:** remove `CommonReferenceString` trait and preprocess method ([#508](https://github.com/noir-lang/acvm/issues/508)) ([3827dd3](https://github.com/noir-lang/acvm/commit/3827dd3ce487650843ba4df8337b423e39f97edf)) +* Return an iterator from `new_locations()` instead of collecting ([#507](https://github.com/noir-lang/acvm/issues/507)) ([8d49a5c](https://github.com/noir-lang/acvm/commit/8d49a5c15b1e962cd59252467a20a922edadc2f2)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.22.0 to 0.23.0 + +## [0.22.0](https://github.com/noir-lang/acvm/compare/acvm-v0.21.0...acvm-v0.22.0) (2023-08-18) + + +### ⚠ BREAKING CHANGES + +* Switched from OpcodeLabel to OpcodeLocation and ErrorLocation ([#493](https://github.com/noir-lang/acvm/issues/493)) +* **acvm:** check for index out-of-bounds on memory operations ([#468](https://github.com/noir-lang/acvm/issues/468)) + +### Features + +* **acvm:** check for index out-of-bounds on memory operations ([#468](https://github.com/noir-lang/acvm/issues/468)) ([740468c](https://github.com/noir-lang/acvm/commit/740468c0a144f7179c38f615cfda31b2fcc77359)) +* print error location with fmt ([#497](https://github.com/noir-lang/acvm/issues/497)) ([575a9e5](https://github.com/noir-lang/acvm/commit/575a9e50e97afb04a7b91799e06752cec3093f0b)) +* Switched from OpcodeLabel to OpcodeLocation and ErrorLocation ([#493](https://github.com/noir-lang/acvm/issues/493)) ([27a5a93](https://github.com/noir-lang/acvm/commit/27a5a935849f8904e10056b08089f532a06962b8)) + + +### Bug Fixes + +* add opcode label to unsatisfied constrain string ([#482](https://github.com/noir-lang/acvm/issues/482)) ([cbbbe67](https://github.com/noir-lang/acvm/commit/cbbbe67b9a19a4a560b2dfa8f27ea1c6ebd61f28)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.21.0 to 0.22.0 + +## [0.21.0](https://github.com/noir-lang/acvm/compare/acvm-v0.20.1...acvm-v0.21.0) (2023-07-26) + + +### ⚠ BREAKING CHANGES + +* **acir:** Remove `Block`, `RAM` and `ROM` opcodes ([#457](https://github.com/noir-lang/acvm/issues/457)) +* **acvm:** Remove `OpcodeResolution` enum ([#400](https://github.com/noir-lang/acvm/issues/400)) +* **acvm:** Support stepwise execution of ACIR ([#399](https://github.com/noir-lang/acvm/issues/399)) + +### Features + +* **acvm:** Remove `OpcodeResolution` enum ([#400](https://github.com/noir-lang/acvm/issues/400)) ([d0ce48c](https://github.com/noir-lang/acvm/commit/d0ce48c506619a5560412ef6693bfa11036b501e)) +* **acvm:** Support stepwise execution of ACIR ([#399](https://github.com/noir-lang/acvm/issues/399)) ([6a03950](https://github.com/noir-lang/acvm/commit/6a0395021779a2711353c2fe2948e09b5b538fc0)) + + +### Miscellaneous Chores + +* **acir:** Remove `Block`, `RAM` and `ROM` opcodes ([#457](https://github.com/noir-lang/acvm/issues/457)) ([8dd220a](https://github.com/noir-lang/acvm/commit/8dd220ae127baf6cc5a31d8ab7ffdeeb161f6109)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.20.1 to 0.21.0 + +## [0.20.1](https://github.com/noir-lang/acvm/compare/acvm-v0.20.0...acvm-v0.20.1) (2023-07-26) + + +### Features + +* **stdlib:** Add fallback implementation of `Keccak256` black box function ([#445](https://github.com/noir-lang/acvm/issues/445)) ([f7ebb03](https://github.com/noir-lang/acvm/commit/f7ebb03653c971f119700ff8126d9eb5ff01be0f)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.20.0 to 0.20.1 + +## [0.20.0](https://github.com/noir-lang/acvm/compare/acvm-v0.19.1...acvm-v0.20.0) (2023-07-20) + + +### ⚠ BREAKING CHANGES + +* atomic memory opcodes ([#447](https://github.com/noir-lang/acvm/issues/447)) + +### Features + +* atomic memory opcodes ([#447](https://github.com/noir-lang/acvm/issues/447)) ([3261c7a](https://github.com/noir-lang/acvm/commit/3261c7a2fd4f3a300bc5f39ef4febccd8a853560)) +* **stdlib:** Add fallback implementation of `HashToField128Security` black box function ([#435](https://github.com/noir-lang/acvm/issues/435)) ([ed40f22](https://github.com/noir-lang/acvm/commit/ed40f228529e888d1960bfa70cb92b277e24b37f)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.19.1 to 0.20.0 + +## [0.19.1](https://github.com/noir-lang/acvm/compare/acvm-v0.19.0...acvm-v0.19.1) (2023-07-17) + + +### Bug Fixes + +* Remove panic when we divide 0/0 in quotient directive ([#437](https://github.com/noir-lang/acvm/issues/437)) ([9c8ff64](https://github.com/noir-lang/acvm/commit/9c8ff64ebf27a86787ae184e10ed9581041ec0ff)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.19.0 to 0.19.1 + +## [0.19.0](https://github.com/noir-lang/acvm/compare/acvm-v0.18.2...acvm-v0.19.0) (2023-07-15) + + +### Miscellaneous Chores + +* **acvm:** Synchronize acvm versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.18.2 to 0.19.0 + +## [0.18.2](https://github.com/noir-lang/acvm/compare/acvm-v0.18.1...acvm-v0.18.2) (2023-07-12) + + +### Features + +* **acvm:** reexport `blackbox_solver` crate from `acvm` ([#431](https://github.com/noir-lang/acvm/issues/431)) ([517e942](https://github.com/noir-lang/acvm/commit/517e942b732d7107f6e064c6791917d1508229b3)) +* **stdlib:** Add fallback implementation of `Blake2s` black box function ([#424](https://github.com/noir-lang/acvm/issues/424)) ([982d940](https://github.com/noir-lang/acvm/commit/982d94087d46092ce7a5e94dbd7e732195f58e42)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.18.1 to 0.18.2 + +## [0.18.1](https://github.com/noir-lang/acvm/compare/acvm-v0.18.0...acvm-v0.18.1) (2023-07-12) + + +### Miscellaneous Chores + +* **acvm:** Synchronize acvm versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.18.0 to 0.18.1 + +## [0.18.0](https://github.com/noir-lang/acvm/compare/acvm-v0.17.0...acvm-v0.18.0) (2023-07-12) + + +### ⚠ BREAKING CHANGES + +* add backend-solvable blackboxes to brillig & unify implementations ([#422](https://github.com/noir-lang/acvm/issues/422)) +* **acvm:** Remove `CircuitSimplifer` ([#421](https://github.com/noir-lang/acvm/issues/421)) +* **acvm:** Add `circuit: &Circuit` to `eth_contract_from_vk` function signature ([#420](https://github.com/noir-lang/acvm/issues/420)) +* Returns index of failing opcode and transformation mapping ([#412](https://github.com/noir-lang/acvm/issues/412)) + +### Features + +* **acvm:** Add `circuit: &Circuit` to `eth_contract_from_vk` function signature ([#420](https://github.com/noir-lang/acvm/issues/420)) ([744e9da](https://github.com/noir-lang/acvm/commit/744e9da71f7ca477a5390a63f47211dd4dffb8b3)) +* add backend-solvable blackboxes to brillig & unify implementations ([#422](https://github.com/noir-lang/acvm/issues/422)) ([093342e](https://github.com/noir-lang/acvm/commit/093342ea9481a311fa71343b8b7a22774788838a)) +* Returns index of failing opcode and transformation mapping ([#412](https://github.com/noir-lang/acvm/issues/412)) ([79950e9](https://github.com/noir-lang/acvm/commit/79950e943f60e4082e1cf5ec4442aa67ea91aade)) +* **stdlib:** Add fallback implementation of `SHA256` black box function ([#407](https://github.com/noir-lang/acvm/issues/407)) ([040369a](https://github.com/noir-lang/acvm/commit/040369adc8749fa5ec2edd255ff54c105c3140f5)) + + +### Miscellaneous Chores + +* **acvm:** Remove `CircuitSimplifer` ([#421](https://github.com/noir-lang/acvm/issues/421)) ([e07a56d](https://github.com/noir-lang/acvm/commit/e07a56d9c542a7f03ce156761054cd403de0bd23)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * brillig_vm bumped from 0.17.0 to 0.18.0 + +## [0.17.0](https://github.com/noir-lang/acvm/compare/acvm-v0.16.0...acvm-v0.17.0) (2023-07-07) + + +### ⚠ BREAKING CHANGES + +* **acir:** add `EcdsaSecp256r1` blackbox function ([#408](https://github.com/noir-lang/acvm/issues/408)) + +### Features + +* **acir:** add `EcdsaSecp256r1` blackbox function ([#408](https://github.com/noir-lang/acvm/issues/408)) ([9895817](https://github.com/noir-lang/acvm/commit/98958170c9fa9b4731e33b31cb494a72bb90549e)) + +## [0.16.0](https://github.com/noir-lang/acvm/compare/acvm-v0.15.1...acvm-v0.16.0) (2023-07-06) + + +### ⚠ BREAKING CHANGES + +* **acvm:** replace `PartialWitnessGeneratorStatus` with `ACVMStatus` ([#410](https://github.com/noir-lang/acvm/issues/410)) +* **acir:** revert changes to `SchnorrVerify` opcode ([#409](https://github.com/noir-lang/acvm/issues/409)) +* **acvm:** Replace `PartialWitnessGenerator` trait with `BlackBoxFunctionSolver` ([#378](https://github.com/noir-lang/acvm/issues/378)) +* **acvm:** Encapsulate internal state of ACVM within a struct ([#384](https://github.com/noir-lang/acvm/issues/384)) +* remove unused `OpcodeResolutionError::IncorrectNumFunctionArguments` variant ([#397](https://github.com/noir-lang/acvm/issues/397)) +* **acir:** Remove `Oracle` opcode ([#368](https://github.com/noir-lang/acvm/issues/368)) +* **acir:** Use fixed length data structures in black box function inputs/outputs where possible. ([#386](https://github.com/noir-lang/acvm/issues/386)) + +### Features + +* **acir:** Remove `Oracle` opcode ([#368](https://github.com/noir-lang/acvm/issues/368)) ([63354df](https://github.com/noir-lang/acvm/commit/63354df1fe47a4f1128b91641d1b66dfc1281794)) +* **acir:** Use fixed length data structures in black box function inputs/outputs where possible. ([#386](https://github.com/noir-lang/acvm/issues/386)) ([b139d4d](https://github.com/noir-lang/acvm/commit/b139d4d566c715009465a430aab0fb819aacab4f)) +* **acvm:** Derive `Copy` for `Language` ([#406](https://github.com/noir-lang/acvm/issues/406)) ([69a6c22](https://github.com/noir-lang/acvm/commit/69a6c224d80be556ac5388ffeb7a02424df22031)) +* **acvm:** Encapsulate internal state of ACVM within a struct ([#384](https://github.com/noir-lang/acvm/issues/384)) ([84d4867](https://github.com/noir-lang/acvm/commit/84d4867b2d97097d451d59174781555dafd2591f)) +* **acvm:** Replace `PartialWitnessGenerator` trait with `BlackBoxFunctionSolver` ([#378](https://github.com/noir-lang/acvm/issues/378)) ([73fbc95](https://github.com/noir-lang/acvm/commit/73fbc95942b0039565c93719809975f66dc9ec53)) +* **acvm:** replace `PartialWitnessGeneratorStatus` with `ACVMStatus` ([#410](https://github.com/noir-lang/acvm/issues/410)) ([fc3240d](https://github.com/noir-lang/acvm/commit/fc3240d456d0128f6eb42096beb8b7a586ea48da)) +* **brillig:** implemented first blackbox functions ([#401](https://github.com/noir-lang/acvm/issues/401)) ([62d40f7](https://github.com/noir-lang/acvm/commit/62d40f7c03cd1102f615b8d565f82496962db637)) + + +### Bug Fixes + +* **acir:** revert changes to `SchnorrVerify` opcode ([#409](https://github.com/noir-lang/acvm/issues/409)) ([f1c7940](https://github.com/noir-lang/acvm/commit/f1c7940f4ac618c7b440b6ed30199f85cbe72cca)) + + +### Miscellaneous Chores + +* remove unused `OpcodeResolutionError::IncorrectNumFunctionArguments` variant ([#397](https://github.com/noir-lang/acvm/issues/397)) ([d1368d0](https://github.com/noir-lang/acvm/commit/d1368d041eb42d265a4ef385e066b82bc36d0743)) + +## [0.15.1](https://github.com/noir-lang/acvm/compare/acvm-v0.15.0...acvm-v0.15.1) (2023-06-20) + + +### Features + +* **brillig:** Allow dynamic-size foreign calls ([#370](https://github.com/noir-lang/acvm/issues/370)) ([5ba0349](https://github.com/noir-lang/acvm/commit/5ba0349420cc1b20113cb5e96490a0808a769757)) + +## [0.15.0](https://github.com/noir-lang/acvm/compare/acvm-v0.14.2...acvm-v0.15.0) (2023-06-15) + + +### ⚠ BREAKING CHANGES + +* **brillig:** Accept multiple inputs/outputs for foreign calls ([#367](https://github.com/noir-lang/acvm/issues/367)) +* **acvm:** Make internals of ACVM private ([#353](https://github.com/noir-lang/acvm/issues/353)) + +### Features + +* Add method to generate updated `Brillig` opcode from `UnresolvedBrilligCall` ([#363](https://github.com/noir-lang/acvm/issues/363)) ([fda5dbe](https://github.com/noir-lang/acvm/commit/fda5dbe57c28dc4bc28dfd8fe0a4a8ba29635393)) +* **brillig:** Accept multiple inputs/outputs for foreign calls ([#367](https://github.com/noir-lang/acvm/issues/367)) ([78d62b2](https://github.com/noir-lang/acvm/commit/78d62b2d7c1c8b884e1f3fe7983e6e5029700e70)) + + +### Miscellaneous Chores + +* **acvm:** Make internals of ACVM private ([#353](https://github.com/noir-lang/acvm/issues/353)) ([c902a01](https://github.com/noir-lang/acvm/commit/c902a01639033665d106e2d9f4e5c7070af8c0bb)) + +## [0.14.2](https://github.com/noir-lang/acvm/compare/acvm-v0.14.1...acvm-v0.14.2) (2023-06-08) + + +### Miscellaneous Chores + +* **acvm:** Synchronize acvm versions + +## [0.14.1](https://github.com/noir-lang/acvm/compare/acvm-v0.14.0...acvm-v0.14.1) (2023-06-07) + + +### Features + +* Re-use intermediate variables created during width reduction, with proper scale. ([#343](https://github.com/noir-lang/acvm/issues/343)) ([6bd0baa](https://github.com/noir-lang/acvm/commit/6bd0baa4bc9ac204e7710ec6d17d1752d2e924c0)) + +## [0.14.0](https://github.com/noir-lang/acvm/compare/acvm-v0.13.3...acvm-v0.14.0) (2023-06-06) + + +### ⚠ BREAKING CHANGES + +* **acir:** Verify Proof ([#291](https://github.com/noir-lang/acvm/issues/291)) + +### Features + +* **acir:** Verify Proof ([#291](https://github.com/noir-lang/acvm/issues/291)) ([9f34428](https://github.com/noir-lang/acvm/commit/9f34428b7084c7c38de401a16ca76e748d8b1d77)) + +## [0.13.3](https://github.com/noir-lang/acvm/compare/acvm-v0.13.2...acvm-v0.13.3) (2023-06-05) + + +### Bug Fixes + +* Empty commit to trigger release-please ([e8f0748](https://github.com/noir-lang/acvm/commit/e8f0748042ef505d59ab63266d3c36c5358ee30d)) + +## [0.13.2](https://github.com/noir-lang/acvm/compare/acvm-v0.13.1...acvm-v0.13.2) (2023-06-02) + + +### Bug Fixes + +* re-use intermediate vars during width reduction ([#278](https://github.com/noir-lang/acvm/issues/278)) ([5b32920](https://github.com/noir-lang/acvm/commit/5b32920263c4481c60faf0b84f0031aa8149b6b2)) + +## [0.13.1](https://github.com/noir-lang/acvm/compare/acvm-v0.13.0...acvm-v0.13.1) (2023-06-01) + + +### Bug Fixes + +* **brillig:** Proper error handling for Brillig failures ([#329](https://github.com/noir-lang/acvm/issues/329)) ([cffa110](https://github.com/noir-lang/acvm/commit/cffa110c8df30ee3dd8b635d38b17b1fcd54b03e)) +* **ci:** Correct typo to avoid `undefined` in changelogs ([#333](https://github.com/noir-lang/acvm/issues/333)) ([d3424c0](https://github.com/noir-lang/acvm/commit/d3424c04fd303c9cbe25d03118d8b358cbb84b83)) + +## [0.13.0](https://github.com/noir-lang/acvm/compare/acvm-v0.12.0...acvm-v0.13.0) (2023-06-01) + + +### ⚠ BREAKING CHANGES + +* added hash index to pedersen ([#281](https://github.com/noir-lang/acvm/issues/281)) +* Add variable length keccak opcode ([#314](https://github.com/noir-lang/acvm/issues/314)) +* Remove AES opcode ([#302](https://github.com/noir-lang/acvm/issues/302)) +* **acir, acvm:** Remove ComputeMerkleRoot opcode #296 +* Remove backend solvable methods from the interface and solve them in ACVM ([#264](https://github.com/noir-lang/acvm/issues/264)) +* Reorganize code related to `PartialWitnessGenerator` ([#287](https://github.com/noir-lang/acvm/issues/287)) + +### Features + +* **acir, acvm:** Remove ComputeMerkleRoot opcode [#296](https://github.com/noir-lang/acvm/issues/296) ([8b3923e](https://github.com/noir-lang/acvm/commit/8b3923e191e4ac399400025496e8bb4453734040)) +* Add `Brillig` opcode to introduce custom non-determinism to ACVM ([#152](https://github.com/noir-lang/acvm/issues/152)) ([3c6740a](https://github.com/noir-lang/acvm/commit/3c6740af75125afc8ebb4379f781f8274015e2e2)) +* Add variable length keccak opcode ([#314](https://github.com/noir-lang/acvm/issues/314)) ([7bfd169](https://github.com/noir-lang/acvm/commit/7bfd1695b6f119cd70fce4866314c9bb4991eaab)) +* added hash index to pedersen ([#281](https://github.com/noir-lang/acvm/issues/281)) ([61820b6](https://github.com/noir-lang/acvm/commit/61820b651900aac8d9557b4b9477ed0e1763c124)) +* Remove backend solvable methods from the interface and solve them in ACVM ([#264](https://github.com/noir-lang/acvm/issues/264)) ([69916cb](https://github.com/noir-lang/acvm/commit/69916cbdd928875b2e8fe4775f2251f71c3f3c92)) + + +### Bug Fixes + +* Allow async functions without send on async trait ([#292](https://github.com/noir-lang/acvm/issues/292)) ([9f9fc21](https://github.com/noir-lang/acvm/commit/9f9fc216a6d09ca97352ffd365bfd347e94ad8eb)) + + +### Miscellaneous Chores + +* Remove AES opcode ([#302](https://github.com/noir-lang/acvm/issues/302)) ([a429a54](https://github.com/noir-lang/acvm/commit/a429a5422d6f001b6db0d0a0f30c79ec0f96de89)) +* Reorganize code related to `PartialWitnessGenerator` ([#287](https://github.com/noir-lang/acvm/issues/287)) ([b9d61a1](https://github.com/noir-lang/acvm/commit/b9d61a16210d70e350a7e953951362c94f497f89)) + +## [0.12.0](https://github.com/noir-lang/acvm/compare/acvm-v0.11.0...acvm-v0.12.0) (2023-05-17) + + +### ⚠ BREAKING CHANGES + +* remove deprecated circuit hash functions ([#288](https://github.com/noir-lang/acvm/issues/288)) +* allow backends to specify support for all opcode variants ([#273](https://github.com/noir-lang/acvm/issues/273)) +* **acvm:** Add CommonReferenceString backend trait ([#231](https://github.com/noir-lang/acvm/issues/231)) +* Introduce WitnessMap data structure to avoid leaking internal structure ([#252](https://github.com/noir-lang/acvm/issues/252)) +* use struct variants for blackbox function calls ([#269](https://github.com/noir-lang/acvm/issues/269)) +* **acvm:** Backend trait must implement Debug ([#275](https://github.com/noir-lang/acvm/issues/275)) +* remove `OpcodeResolutionError::UnexpectedOpcode` ([#274](https://github.com/noir-lang/acvm/issues/274)) +* **acvm:** rename `hash_to_field128_security` to `hash_to_field_128_security` ([#271](https://github.com/noir-lang/acvm/issues/271)) +* **acvm:** update black box solver interfaces to match `pwg:black_box::solve` ([#268](https://github.com/noir-lang/acvm/issues/268)) +* **acvm:** expose separate solvers for AND and XOR opcodes ([#266](https://github.com/noir-lang/acvm/issues/266)) +* **acvm:** Simplification pass for ACIR ([#151](https://github.com/noir-lang/acvm/issues/151)) +* Remove `solve` from PWG trait & introduce separate solvers for each blackbox ([#257](https://github.com/noir-lang/acvm/issues/257)) + +### Features + +* **acvm:** Add CommonReferenceString backend trait ([#231](https://github.com/noir-lang/acvm/issues/231)) ([eeddcf1](https://github.com/noir-lang/acvm/commit/eeddcf179880f246383f7f67a11e589269c4e3ff)) +* **acvm:** Simplification pass for ACIR ([#151](https://github.com/noir-lang/acvm/issues/151)) ([7bc42c6](https://github.com/noir-lang/acvm/commit/7bc42c62b6e095f838b781c87cbb1ecd2af5f179)) +* **acvm:** update black box solver interfaces to match `pwg:black_box::solve` ([#268](https://github.com/noir-lang/acvm/issues/268)) ([0098b7d](https://github.com/noir-lang/acvm/commit/0098b7d9640076d970e6c15d5fd6f368eb1513ff)) +* Introduce WitnessMap data structure to avoid leaking internal structure ([#252](https://github.com/noir-lang/acvm/issues/252)) ([b248e60](https://github.com/noir-lang/acvm/commit/b248e606dd69c25d33ae77c5c5c0541adbf80cd6)) +* Remove `solve` from PWG trait & introduce separate solvers for each blackbox ([#257](https://github.com/noir-lang/acvm/issues/257)) ([3f3dd74](https://github.com/noir-lang/acvm/commit/3f3dd7460b27ab06b55dfc3fe5dd733f08e30a9f)) +* use struct variants for blackbox function calls ([#269](https://github.com/noir-lang/acvm/issues/269)) ([a83333b](https://github.com/noir-lang/acvm/commit/a83333b9e270dfcfd40a36271896840ec0201bc4)) + + +### Miscellaneous Chores + +* **acvm:** Backend trait must implement Debug ([#275](https://github.com/noir-lang/acvm/issues/275)) ([3288b4c](https://github.com/noir-lang/acvm/commit/3288b4c7eb01f5621e577d5ff9e7c92c7757e021)) +* **acvm:** expose separate solvers for AND and XOR opcodes ([#266](https://github.com/noir-lang/acvm/issues/266)) ([84b5d18](https://github.com/noir-lang/acvm/commit/84b5d18d29a111a42bfc1c3d122129c8f062c3db)) +* **acvm:** rename `hash_to_field128_security` to `hash_to_field_128_security` ([#271](https://github.com/noir-lang/acvm/issues/271)) ([fad9af2](https://github.com/noir-lang/acvm/commit/fad9af27fb102fa34bf7511f8ed7b16b3ec2d115)) +* allow backends to specify support for all opcode variants ([#273](https://github.com/noir-lang/acvm/issues/273)) ([efd37fe](https://github.com/noir-lang/acvm/commit/efd37fedcbbabb3fac810e662731439e07fef49a)) +* remove `OpcodeResolutionError::UnexpectedOpcode` ([#274](https://github.com/noir-lang/acvm/issues/274)) ([0e71aac](https://github.com/noir-lang/acvm/commit/0e71aac7aa85b3e9142972a26ba122c2c7c51d9b)) +* remove deprecated circuit hash functions ([#288](https://github.com/noir-lang/acvm/issues/288)) ([1a22c75](https://github.com/noir-lang/acvm/commit/1a22c752de3354a2a6d34892331ab6623b24c0b0)) + +## [0.11.0](https://github.com/noir-lang/acvm/compare/acvm-v0.10.3...acvm-v0.11.0) (2023-05-04) + + +### ⚠ BREAKING CHANGES + +* **acvm:** Introduce Error type for fallible Backend traits ([#248](https://github.com/noir-lang/acvm/issues/248)) + +### Features + +* **acvm:** Add generic error for failing to solve an opcode ([#251](https://github.com/noir-lang/acvm/issues/251)) ([bc89528](https://github.com/noir-lang/acvm/commit/bc8952820de610e585d505decfac6e590bbb1a35)) +* **acvm:** Introduce Error type for fallible Backend traits ([#248](https://github.com/noir-lang/acvm/issues/248)) ([45c45f7](https://github.com/noir-lang/acvm/commit/45c45f7cdb79c3ccb0373ca0e698b282d4dabc39)) +* Add Keccak Hash function ([#259](https://github.com/noir-lang/acvm/issues/259)) ([443c734](https://github.com/noir-lang/acvm/commit/443c73482eeef6cc42a1a254bf0d7706698ee353)) + +## [0.10.3](https://github.com/noir-lang/acvm/compare/acvm-v0.10.2...acvm-v0.10.3) (2023-04-28) + + +### Bug Fixes + +* add default feature flag to ACVM crate ([#245](https://github.com/noir-lang/acvm/issues/245)) ([455fddb](https://github.com/noir-lang/acvm/commit/455fddbc19af81cb01d54e29cad199691e1a1d98)) + +## [0.10.2](https://github.com/noir-lang/acvm/compare/acvm-v0.10.1...acvm-v0.10.2) (2023-04-28) + + +### Miscellaneous Chores + +* **acvm:** Synchronize acvm versions + +## [0.10.1](https://github.com/noir-lang/acvm/compare/acvm-v0.10.0...acvm-v0.10.1) (2023-04-28) + + +### Miscellaneous Chores + +* **acvm:** Synchronize acvm versions + +## [0.10.0](https://github.com/noir-lang/acvm/compare/acvm-v0.9.0...acvm-v0.10.0) (2023-04-26) + + +### ⚠ BREAKING CHANGES + +* return `Result` from `solve_range_opcode` ([#238](https://github.com/noir-lang/acvm/issues/238)) +* **acvm:** have all black box functions return `Result` ([#237](https://github.com/noir-lang/acvm/issues/237)) +* **acvm:** implement `hash_to_field_128_security` ([#230](https://github.com/noir-lang/acvm/issues/230)) +* require `Backend` to implement `Default` trait ([#223](https://github.com/noir-lang/acvm/issues/223)) +* Make GeneralOptimizer crate visible ([#220](https://github.com/noir-lang/acvm/issues/220)) +* return `PartialWitnessGeneratorStatus` from `PartialWitnessGenerator.solve` ([#213](https://github.com/noir-lang/acvm/issues/213)) +* organise operator implementations for Expression ([#190](https://github.com/noir-lang/acvm/issues/190)) + +### Features + +* **acvm:** have all black box functions return `Result<OpcodeResolution, OpcodeResolutionError>` ([#237](https://github.com/noir-lang/acvm/issues/237)) ([e8e93fd](https://github.com/noir-lang/acvm/commit/e8e93fda0db18f0d266dd1aacbb53ec787992dc9)) +* **acvm:** implement `hash_to_field_128_security` ([#230](https://github.com/noir-lang/acvm/issues/230)) ([198fb69](https://github.com/noir-lang/acvm/commit/198fb69e90a5ed3c0a8716d888b4dc6c2f9b18aa)) +* Add range opcode optimization ([#219](https://github.com/noir-lang/acvm/issues/219)) ([7abe6e5](https://github.com/noir-lang/acvm/commit/7abe6e5f6d6fea379c3748a910afd00db066eb45)) +* require `Backend` to implement `Default` trait ([#223](https://github.com/noir-lang/acvm/issues/223)) ([00282dc](https://github.com/noir-lang/acvm/commit/00282dc5e2b03947bf709a088d829f3e0ba80eed)) +* return `PartialWitnessGeneratorStatus` from `PartialWitnessGenerator.solve` ([#213](https://github.com/noir-lang/acvm/issues/213)) ([e877bed](https://github.com/noir-lang/acvm/commit/e877bed2cca76bd486e9bed66b4230e65a01f0a2)) +* return `Result<OpcodeResolution, OpcodeResolutionError>` from `solve_range_opcode` ([#238](https://github.com/noir-lang/acvm/issues/238)) ([15d3c5a](https://github.com/noir-lang/acvm/commit/15d3c5a9be2dd92f266fcb7e672da17cada9fec5)) + + +### Bug Fixes + +* prevent `bn254` feature flag always being enabled ([#225](https://github.com/noir-lang/acvm/issues/225)) ([82eee6a](https://github.com/noir-lang/acvm/commit/82eee6ab08ae480f04904ca8571fd88f4466c000)) + + +### Miscellaneous Chores + +* Make GeneralOptimizer crate visible ([#220](https://github.com/noir-lang/acvm/issues/220)) ([64bb346](https://github.com/noir-lang/acvm/commit/64bb346524428a0ce196826ea1e5ccde08ad6201)) +* organise operator implementations for Expression ([#190](https://github.com/noir-lang/acvm/issues/190)) ([a619df6](https://github.com/noir-lang/acvm/commit/a619df614bbb9b2518b788b42a7553b069823a0f)) + +## [0.9.0](https://github.com/noir-lang/acvm/compare/acvm-v0.8.1...acvm-v0.9.0) (2023-04-07) + + +### ⚠ BREAKING CHANGES + +* **acvm:** Remove deprecated eth_contract_from_cs from SmartContract trait ([#185](https://github.com/noir-lang/acvm/issues/185)) +* **acvm:** make `Backend` trait object safe ([#180](https://github.com/noir-lang/acvm/issues/180)) + +### Features + +* **acvm:** make `Backend` trait object safe ([#180](https://github.com/noir-lang/acvm/issues/180)) ([fd28657](https://github.com/noir-lang/acvm/commit/fd28657426260ce3c53517b75a27eb5c4a74e234)) + + +### Miscellaneous Chores + +* **acvm:** Remove deprecated eth_contract_from_cs from SmartContract trait ([#185](https://github.com/noir-lang/acvm/issues/185)) ([ee59c9e](https://github.com/noir-lang/acvm/commit/ee59c9efe9a54ff6b97e4daaebf64f3e327e97d9)) + +## [0.8.1](https://github.com/noir-lang/acvm/compare/acvm-v0.8.0...acvm-v0.8.1) (2023-03-30) + + +### Miscellaneous Chores + +* **acvm:** Synchronize acvm versions + +## [0.8.0](https://github.com/noir-lang/acvm/compare/acvm-v0.7.1...acvm-v0.8.0) (2023-03-28) + + +### Miscellaneous Chores + +* **acvm:** Synchronize acvm versions + +## [0.7.1](https://github.com/noir-lang/acvm/compare/acvm-v0.7.0...acvm-v0.7.1) (2023-03-27) + + +### Bug Fixes + +* **pwg:** stall instead of fail for unassigned black box ([#154](https://github.com/noir-lang/acvm/issues/154)) ([412a1a6](https://github.com/noir-lang/acvm/commit/412a1a60b434bef53e12d37c3b2bb3d51a317994)) + +## [0.7.0](https://github.com/noir-lang/acvm/compare/acvm-v0.6.0...acvm-v0.7.0) (2023-03-23) + + +### ⚠ BREAKING CHANGES + +* Add initial oracle opcode ([#149](https://github.com/noir-lang/acvm/issues/149)) +* **acir:** Add RAM and ROM opcodes +* **acir:** Add a public outputs field ([#56](https://github.com/noir-lang/acvm/issues/56)) +* **acvm:** remove `prove_with_meta` and `verify_from_cs` from `ProofSystemCompiler` ([#140](https://github.com/noir-lang/acvm/issues/140)) +* **acvm:** Remove truncate and oddrange directives ([#142](https://github.com/noir-lang/acvm/issues/142)) + +### Features + +* **acir:** Add a public outputs field ([#56](https://github.com/noir-lang/acvm/issues/56)) ([5f358a9](https://github.com/noir-lang/acvm/commit/5f358a97aaa81d87956e182cd8a6d60de75f9752)) +* **acir:** Add RAM and ROM opcodes ([73e9f25](https://github.com/noir-lang/acvm/commit/73e9f25dd87b2ca91245e93d2445eadc0f522fac)) +* Add initial oracle opcode ([#149](https://github.com/noir-lang/acvm/issues/149)) ([88ee2f8](https://github.com/noir-lang/acvm/commit/88ee2f89f37abf5dd1d9f91b4d2eed44dc651348)) + + +### Miscellaneous Chores + +* **acvm:** remove `prove_with_meta` and `verify_from_cs` from `ProofSystemCompiler` ([#140](https://github.com/noir-lang/acvm/issues/140)) ([35dd181](https://github.com/noir-lang/acvm/commit/35dd181102203df17eef510666b327ef41f4b036)) +* **acvm:** Remove truncate and oddrange directives ([#142](https://github.com/noir-lang/acvm/issues/142)) ([85dd6e8](https://github.com/noir-lang/acvm/commit/85dd6e85bfba85bfb97651f7e30e1f75deb986d5)) + +## [0.6.0](https://github.com/noir-lang/acvm/compare/acvm-v0.5.0...acvm-v0.6.0) (2023-03-03) + + +### ⚠ BREAKING CHANGES + +* add block opcode ([#114](https://github.com/noir-lang/acvm/issues/114)) + +### Features + +* add block opcode ([#114](https://github.com/noir-lang/acvm/issues/114)) ([097cfb0](https://github.com/noir-lang/acvm/commit/097cfb069291705ddb4bf1fca77ddcef21dbbd08)) + +## [0.5.0](https://github.com/noir-lang/acvm/compare/acvm-v0.4.1...acvm-v0.5.0) (2023-02-22) + + +### ⚠ BREAKING CHANGES + +* **acvm:** switch to accepting public inputs as a map ([#96](https://github.com/noir-lang/acvm/issues/96)) +* **acvm:** add `eth_contract_from_vk` to `SmartContract +* update `ProofSystemCompiler` to not take ownership of keys ([#111](https://github.com/noir-lang/acvm/issues/111)) +* update `ProofSystemCompiler` methods to take `&Circuit` ([#108](https://github.com/noir-lang/acvm/issues/108)) +* refactor ToRadix to ToRadixLe and ToRadixBe ([#58](https://github.com/noir-lang/acvm/issues/58)) +* reorganise compiler in terms of optimisers and transformers ([#88](https://github.com/noir-lang/acvm/issues/88)) + +### Features + +* **acvm:** add `eth_contract_from_vk` to `SmartContract ([#113](https://github.com/noir-lang/acvm/issues/113)) ([373c18f](https://github.com/noir-lang/acvm/commit/373c18fc05edf673cfec9e8bbb78bd7d7514999e)) +* **acvm:** switch to accepting public inputs as a map ([#96](https://github.com/noir-lang/acvm/issues/96)) ([f57ba57](https://github.com/noir-lang/acvm/commit/f57ba57c2bb2597edf2b02fb1321c69cf11993ee)) +* update `ProofSystemCompiler` methods to take `&Circuit` ([#108](https://github.com/noir-lang/acvm/issues/108)) ([af56ca9](https://github.com/noir-lang/acvm/commit/af56ca9da06068c650c66e76bfd09e65eb0ec213)) +* update `ProofSystemCompiler` to not take ownership of keys ([#111](https://github.com/noir-lang/acvm/issues/111)) ([39b8a41](https://github.com/noir-lang/acvm/commit/39b8a41293e567971f700f61103852cb987a8d16)) + + +### Bug Fixes + +* Clean up Log Directive hex output ([#97](https://github.com/noir-lang/acvm/issues/97)) ([d23c735](https://github.com/noir-lang/acvm/commit/d23c7352523ffb42f3e8f4229b61f9803ab78a7e)) + + +### Miscellaneous Chores + +* refactor ToRadix to ToRadixLe and ToRadixBe ([#58](https://github.com/noir-lang/acvm/issues/58)) ([2427a27](https://github.com/noir-lang/acvm/commit/2427a275048e598c6d651cce8348a4c55148f235)) +* reorganise compiler in terms of optimisers and transformers ([#88](https://github.com/noir-lang/acvm/issues/88)) ([9329307](https://github.com/noir-lang/acvm/commit/9329307e054de202cfc55207162ad952b70d515e)) diff --git a/acvm-repo/acvm/Cargo.toml b/acvm-repo/acvm/Cargo.toml new file mode 100644 index 00000000000..a4fdc3e2b2b --- /dev/null +++ b/acvm-repo/acvm/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "acvm" +description = "The virtual machine that processes ACIR given a backend/proof system." +# x-release-please-start-version +version = "0.27.4" +# x-release-please-end +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +num-bigint.workspace = true +num-traits.workspace = true +thiserror.workspace = true + +acir.workspace = true +stdlib.workspace = true +brillig_vm.workspace = true +acvm_blackbox_solver.workspace = true + +indexmap = "1.7.0" + +[features] +default = ["bn254", "testing"] +bn254 = [ + "acir/bn254", + "stdlib/bn254", + "brillig_vm/bn254", + "acvm_blackbox_solver/bn254", +] +bls12_381 = [ + "acir/bls12_381", + "stdlib/bls12_381", + "brillig_vm/bls12_381", + "acvm_blackbox_solver/bls12_381", +] +testing = ["stdlib/testing", "unstable-fallbacks"] +unstable-fallbacks = [] + +[dev-dependencies] +rand = "0.8.5" +proptest = "1.2.0" +paste = "1.0.14" diff --git a/acvm-repo/acvm/src/compiler/mod.rs b/acvm-repo/acvm/src/compiler/mod.rs new file mode 100644 index 00000000000..e824117180f --- /dev/null +++ b/acvm-repo/acvm/src/compiler/mod.rs @@ -0,0 +1,280 @@ +use acir::{ + circuit::{ + brillig::BrilligOutputs, directives::Directive, opcodes::UnsupportedMemoryOpcode, Circuit, + Opcode, OpcodeLocation, + }, + native_types::{Expression, Witness}, + BlackBoxFunc, FieldElement, +}; +use indexmap::IndexMap; +use thiserror::Error; + +use crate::Language; + +// The various passes that we can use over ACIR +mod optimizers; +mod transformers; + +use optimizers::{GeneralOptimizer, RangeOptimizer}; +use transformers::{CSatTransformer, FallbackTransformer, R1CSTransformer}; + +#[derive(PartialEq, Eq, Debug, Error)] +pub enum CompileError { + #[error("The blackbox function {0} is not supported by the backend and acvm does not have a fallback implementation")] + UnsupportedBlackBox(BlackBoxFunc), + #[error("The opcode {0} is not supported by the backend and acvm does not have a fallback implementation")] + UnsupportedMemoryOpcode(UnsupportedMemoryOpcode), +} + +/// This module moves and decomposes acir opcodes. The transformation map allows consumers of this module to map +/// metadata they had about the opcodes to the new opcode structure generated after the transformation. +#[derive(Debug)] +pub struct AcirTransformationMap { + /// This is a vector of pointers to the old acir opcodes. The index of the vector is the new opcode index. + /// The value of the vector is the old opcode index pointed. + acir_opcode_positions: Vec, +} + +impl AcirTransformationMap { + pub fn new_locations( + &self, + old_location: OpcodeLocation, + ) -> impl Iterator + '_ { + let old_acir_index = match old_location { + OpcodeLocation::Acir(index) => index, + OpcodeLocation::Brillig { acir_index, .. } => acir_index, + }; + + self.acir_opcode_positions + .iter() + .enumerate() + .filter(move |(_, &old_index)| old_index == old_acir_index) + .map(move |(new_index, _)| match old_location { + OpcodeLocation::Acir(_) => OpcodeLocation::Acir(new_index), + OpcodeLocation::Brillig { brillig_index, .. } => { + OpcodeLocation::Brillig { acir_index: new_index, brillig_index } + } + }) + } +} + +fn transform_assert_messages( + assert_messages: Vec<(OpcodeLocation, String)>, + map: &AcirTransformationMap, +) -> Vec<(OpcodeLocation, String)> { + assert_messages + .into_iter() + .flat_map(|(location, message)| { + let new_locations = map.new_locations(location); + new_locations.into_iter().map(move |new_location| (new_location, message.clone())) + }) + .collect() +} + +/// Applies [`ProofSystemCompiler`][crate::ProofSystemCompiler] specific optimizations to a [`Circuit`]. +pub fn compile( + acir: Circuit, + np_language: Language, + is_opcode_supported: impl Fn(&Opcode) -> bool, +) -> Result<(Circuit, AcirTransformationMap), CompileError> { + // Instantiate the optimizer. + // Currently the optimizer and reducer are one in the same + // for CSAT + + // Track original acir opcode positions throughout the transformation passes of the compilation + // by applying the modifications done to the circuit opcodes and also to the opcode_positions (delete and insert) + let acir_opcode_positions = acir.opcodes.iter().enumerate().map(|(i, _)| i).collect(); + + // Fallback transformer pass + let (acir, acir_opcode_positions) = + FallbackTransformer::transform(acir, is_opcode_supported, acir_opcode_positions)?; + + // General optimizer pass + let mut opcodes: Vec = Vec::new(); + for opcode in acir.opcodes { + match opcode { + Opcode::Arithmetic(arith_expr) => { + opcodes.push(Opcode::Arithmetic(GeneralOptimizer::optimize(arith_expr))) + } + other_opcode => opcodes.push(other_opcode), + }; + } + let acir = Circuit { opcodes, ..acir }; + + // Range optimization pass + let range_optimizer = RangeOptimizer::new(acir); + let (mut acir, acir_opcode_positions) = + range_optimizer.replace_redundant_ranges(acir_opcode_positions); + + let mut transformer = match &np_language { + crate::Language::R1CS => { + let transformation_map = AcirTransformationMap { acir_opcode_positions }; + acir.assert_messages = + transform_assert_messages(acir.assert_messages, &transformation_map); + let transformer = R1CSTransformer::new(acir); + return Ok((transformer.transform(), transformation_map)); + } + crate::Language::PLONKCSat { width } => { + let mut csat = CSatTransformer::new(*width); + for value in acir.circuit_arguments() { + csat.mark_solvable(value); + } + csat + } + }; + + // TODO: the code below is only for CSAT transformer + // TODO it may be possible to refactor it in a way that we do not need to return early from the r1cs + // TODO or at the very least, we could put all of it inside of CSatOptimizer pass + + let mut new_acir_opcode_positions: Vec = Vec::with_capacity(acir_opcode_positions.len()); + // Optimize the arithmetic gates by reducing them into the correct width and + // creating intermediate variables when necessary + let mut transformed_opcodes = Vec::new(); + + let mut next_witness_index = acir.current_witness_index + 1; + // maps a normalized expression to the intermediate variable which represents the expression, along with its 'norm' + // the 'norm' is simply the value of the first non zero coefficient in the expression, taken from the linear terms, or quadratic terms if there is none. + let mut intermediate_variables: IndexMap = IndexMap::new(); + for (index, opcode) in acir.opcodes.iter().enumerate() { + match opcode { + Opcode::Arithmetic(arith_expr) => { + let len = intermediate_variables.len(); + + let arith_expr = transformer.transform( + arith_expr.clone(), + &mut intermediate_variables, + &mut next_witness_index, + ); + + // Update next_witness counter + next_witness_index += (intermediate_variables.len() - len) as u32; + let mut new_opcodes = Vec::new(); + for (g, (norm, w)) in intermediate_variables.iter().skip(len) { + // de-normalize + let mut intermediate_opcode = g * *norm; + // constrain the intermediate opcode to the intermediate variable + intermediate_opcode.linear_combinations.push((-FieldElement::one(), *w)); + intermediate_opcode.sort(); + new_opcodes.push(intermediate_opcode); + } + new_opcodes.push(arith_expr); + for opcode in new_opcodes { + new_acir_opcode_positions.push(acir_opcode_positions[index]); + transformed_opcodes.push(Opcode::Arithmetic(opcode)); + } + } + Opcode::BlackBoxFuncCall(func) => { + match func { + acir::circuit::opcodes::BlackBoxFuncCall::AND { output, .. } + | acir::circuit::opcodes::BlackBoxFuncCall::XOR { output, .. } => { + transformer.mark_solvable(*output) + } + acir::circuit::opcodes::BlackBoxFuncCall::RANGE { .. } => (), + acir::circuit::opcodes::BlackBoxFuncCall::SHA256 { outputs, .. } + | acir::circuit::opcodes::BlackBoxFuncCall::Keccak256 { outputs, .. } + | acir::circuit::opcodes::BlackBoxFuncCall::Keccak256VariableLength { + outputs, + .. + } + | acir::circuit::opcodes::BlackBoxFuncCall::RecursiveAggregation { + output_aggregation_object: outputs, + .. + } + | acir::circuit::opcodes::BlackBoxFuncCall::Blake2s { outputs, .. } => { + for witness in outputs { + transformer.mark_solvable(*witness); + } + } + acir::circuit::opcodes::BlackBoxFuncCall::FixedBaseScalarMul { + outputs, + .. + } + | acir::circuit::opcodes::BlackBoxFuncCall::Pedersen { outputs, .. } => { + transformer.mark_solvable(outputs.0); + transformer.mark_solvable(outputs.1) + } + acir::circuit::opcodes::BlackBoxFuncCall::HashToField128Security { + output, + .. + } + | acir::circuit::opcodes::BlackBoxFuncCall::EcdsaSecp256k1 { output, .. } + | acir::circuit::opcodes::BlackBoxFuncCall::EcdsaSecp256r1 { output, .. } + | acir::circuit::opcodes::BlackBoxFuncCall::SchnorrVerify { output, .. } => { + transformer.mark_solvable(*output) + } + } + + new_acir_opcode_positions.push(acir_opcode_positions[index]); + transformed_opcodes.push(opcode.clone()); + } + Opcode::Directive(directive) => { + match directive { + Directive::Quotient(quotient_directive) => { + transformer.mark_solvable(quotient_directive.q); + transformer.mark_solvable(quotient_directive.r); + } + Directive::ToLeRadix { b, .. } => { + for witness in b { + transformer.mark_solvable(*witness); + } + } + Directive::PermutationSort { bits, .. } => { + for witness in bits { + transformer.mark_solvable(*witness); + } + } + } + new_acir_opcode_positions.push(acir_opcode_positions[index]); + transformed_opcodes.push(opcode.clone()); + } + Opcode::MemoryInit { .. } => { + // `MemoryInit` does not write values to the `WitnessMap` + new_acir_opcode_positions.push(acir_opcode_positions[index]); + transformed_opcodes.push(opcode.clone()); + } + Opcode::MemoryOp { op, .. } => { + for (_, witness1, witness2) in &op.value.mul_terms { + transformer.mark_solvable(*witness1); + transformer.mark_solvable(*witness2); + } + for (_, witness) in &op.value.linear_combinations { + transformer.mark_solvable(*witness); + } + new_acir_opcode_positions.push(acir_opcode_positions[index]); + transformed_opcodes.push(opcode.clone()); + } + Opcode::Brillig(brillig) => { + for output in &brillig.outputs { + match output { + BrilligOutputs::Simple(w) => transformer.mark_solvable(*w), + BrilligOutputs::Array(v) => { + for witness in v { + transformer.mark_solvable(*witness); + } + } + } + } + new_acir_opcode_positions.push(acir_opcode_positions[index]); + transformed_opcodes.push(opcode.clone()); + } + } + } + + let current_witness_index = next_witness_index - 1; + + let transformation_map = + AcirTransformationMap { acir_opcode_positions: new_acir_opcode_positions }; + + let acir = Circuit { + current_witness_index, + opcodes: transformed_opcodes, + // The optimizer does not add new public inputs + private_parameters: acir.private_parameters, + public_parameters: acir.public_parameters, + return_values: acir.return_values, + assert_messages: transform_assert_messages(acir.assert_messages, &transformation_map), + }; + + Ok((acir, transformation_map)) +} diff --git a/acvm-repo/acvm/src/compiler/optimizers/general.rs b/acvm-repo/acvm/src/compiler/optimizers/general.rs new file mode 100644 index 00000000000..be5359a4114 --- /dev/null +++ b/acvm-repo/acvm/src/compiler/optimizers/general.rs @@ -0,0 +1,44 @@ +use acir::{ + native_types::{Expression, Witness}, + FieldElement, +}; +use indexmap::IndexMap; + +/// The `GeneralOptimizer` processes all [`Expression`]s to: +/// - remove any zero-coefficient terms. +/// - merge any quadratic terms containing the same two witnesses. +pub(crate) struct GeneralOptimizer; + +impl GeneralOptimizer { + pub(crate) fn optimize(opcode: Expression) -> Expression { + // XXX: Perhaps this optimization can be done on the fly + let opcode = remove_zero_coefficients(opcode); + simplify_mul_terms(opcode) + } +} + +// Remove all terms with zero as a coefficient +fn remove_zero_coefficients(mut opcode: Expression) -> Expression { + // Check the mul terms + opcode.mul_terms.retain(|(scale, _, _)| !scale.is_zero()); + // Check the linear combination terms + opcode.linear_combinations.retain(|(scale, _)| !scale.is_zero()); + opcode +} + +// Simplifies all mul terms with the same bi-variate variables +fn simplify_mul_terms(mut gate: Expression) -> Expression { + let mut hash_map: IndexMap<(Witness, Witness), FieldElement> = IndexMap::new(); + + // Canonicalize the ordering of the multiplication, lets just order by variable name + for (scale, w_l, w_r) in gate.mul_terms.clone().into_iter() { + let mut pair = vec![w_l, w_r]; + // Sort using rust sort algorithm + pair.sort(); + + *hash_map.entry((pair[0], pair[1])).or_insert_with(FieldElement::zero) += scale; + } + + gate.mul_terms = hash_map.into_iter().map(|((w_l, w_r), scale)| (scale, w_l, w_r)).collect(); + gate +} diff --git a/acvm-repo/acvm/src/compiler/optimizers/mod.rs b/acvm-repo/acvm/src/compiler/optimizers/mod.rs new file mode 100644 index 00000000000..cde7bdd2064 --- /dev/null +++ b/acvm-repo/acvm/src/compiler/optimizers/mod.rs @@ -0,0 +1,5 @@ +mod general; +mod redundant_range; + +pub(crate) use general::GeneralOptimizer; +pub(crate) use redundant_range::RangeOptimizer; diff --git a/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs b/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs new file mode 100644 index 00000000000..ac6ffef305d --- /dev/null +++ b/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs @@ -0,0 +1,238 @@ +use acir::{ + circuit::{opcodes::BlackBoxFuncCall, Circuit, Opcode}, + native_types::Witness, +}; +use std::collections::{BTreeMap, HashSet}; + +/// `RangeOptimizer` will remove redundant range constraints. +/// +/// # Example +/// +/// Suppose we had the following pseudo-code: +/// +/// ```noir +/// let z1 = x as u16; +/// let z2 = x as u32; +/// ``` +/// It is clear that if `x` fits inside of a 16-bit integer, +/// it must also fit inside of a 32-bit integer. +/// +/// The generated ACIR may produce two range opcodes however; +/// - One for the 16 bit range constraint of `x` +/// - One for the 32-bit range constraint of `x` +/// +/// This optimization pass will keep the 16-bit range constraint +/// and remove the 32-bit range constraint opcode. +pub(crate) struct RangeOptimizer { + /// Maps witnesses to their lowest known bit sizes. + lists: BTreeMap, + circuit: Circuit, +} + +impl RangeOptimizer { + /// Creates a new `RangeOptimizer` by collecting all known range + /// constraints from `Circuit`. + pub(crate) fn new(circuit: Circuit) -> Self { + let range_list = Self::collect_ranges(&circuit); + Self { circuit, lists: range_list } + } + + /// Stores the lowest bit range, that a witness + /// has been constrained to be. + /// For example, if we constrain a witness `x` to be + /// both 32 bits and 16 bits. This function will + /// only store the fact that we have constrained it to + /// be 16 bits. + fn collect_ranges(circuit: &Circuit) -> BTreeMap { + let mut witness_to_bit_sizes = BTreeMap::new(); + + for opcode in &circuit.opcodes { + // Extract the witness index and number of bits, + // if it is a range constraint + let (witness, num_bits) = match extract_range_opcode(opcode) { + Some(func_inputs) => func_inputs, + None => continue, + }; + + // Check if the witness has already been recorded and if the witness + // size is more than the current one, we replace it + let should_replace = match witness_to_bit_sizes.get(&witness).copied() { + Some(old_range_bits) => old_range_bits > num_bits, + None => true, + }; + if should_replace { + witness_to_bit_sizes.insert(witness, num_bits); + } + } + witness_to_bit_sizes + } + + /// Returns a `Circuit` where each Witness is only range constrained + /// once to the lowest number `bit size` possible. + pub(crate) fn replace_redundant_ranges(self, order_list: Vec) -> (Circuit, Vec) { + let mut already_seen_witness = HashSet::new(); + + let mut new_order_list = Vec::with_capacity(order_list.len()); + let mut optimized_opcodes = Vec::with_capacity(self.circuit.opcodes.len()); + for (idx, opcode) in self.circuit.opcodes.iter().enumerate() { + let (witness, num_bits) = match extract_range_opcode(opcode) { + Some(range_opcode) => range_opcode, + None => { + // If its not the range opcode, add it to the opcode + // list and continue; + optimized_opcodes.push(opcode.clone()); + new_order_list.push(order_list[idx]); + continue; + } + }; + // If we've already applied the range constraint for this witness then skip this opcode. + let already_added = already_seen_witness.contains(&witness); + if already_added { + continue; + } + + // Check if this is the lowest number of bits in the circuit + let stored_num_bits = self.lists.get(&witness).expect("Could not find witness. This should never be the case if `collect_ranges` is called"); + let is_lowest_bit_size = num_bits <= *stored_num_bits; + + // If the opcode is associated with the lowest bit size + // and we have not added a duplicate of this opcode yet, + // then we should add retain this range opcode. + if is_lowest_bit_size { + already_seen_witness.insert(witness); + new_order_list.push(order_list[idx]); + optimized_opcodes.push(opcode.clone()); + } + } + + ( + Circuit { + current_witness_index: self.circuit.current_witness_index, + opcodes: optimized_opcodes, + ..self.circuit + }, + new_order_list, + ) + } +} + +/// Extract the range opcode from the `Opcode` enum +/// Returns None, if `Opcode` is not the range opcode. +fn extract_range_opcode(opcode: &Opcode) -> Option<(Witness, u32)> { + // Range constraints are blackbox function calls + // so we first extract the function call + let func_call = match opcode { + acir::circuit::Opcode::BlackBoxFuncCall(func_call) => func_call, + _ => return None, + }; + + // Skip if it is not a range constraint + match func_call { + BlackBoxFuncCall::RANGE { input } => Some((input.witness, input.num_bits)), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeSet; + + use crate::compiler::optimizers::redundant_range::{extract_range_opcode, RangeOptimizer}; + use acir::{ + circuit::{ + opcodes::{BlackBoxFuncCall, FunctionInput}, + Circuit, Opcode, PublicInputs, + }, + native_types::{Expression, Witness}, + }; + + fn test_circuit(ranges: Vec<(Witness, u32)>) -> Circuit { + fn test_range_constraint(witness: Witness, num_bits: u32) -> Opcode { + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness, num_bits }, + }) + } + + let opcodes: Vec<_> = ranges + .into_iter() + .map(|(witness, num_bits)| test_range_constraint(witness, num_bits)) + .collect(); + + Circuit { + current_witness_index: 1, + opcodes, + private_parameters: BTreeSet::new(), + public_parameters: PublicInputs::default(), + return_values: PublicInputs::default(), + assert_messages: Default::default(), + } + } + + #[test] + fn retain_lowest_range_size() { + // The optimizer should keep the lowest bit size range constraint + let circuit = test_circuit(vec![(Witness(1), 32), (Witness(1), 16)]); + let acir_opcode_positions = circuit.opcodes.iter().enumerate().map(|(i, _)| i).collect(); + let optimizer = RangeOptimizer::new(circuit); + + let range_size = *optimizer + .lists + .get(&Witness(1)) + .expect("Witness(1) was inserted, but it is missing from the map"); + assert_eq!( + range_size, 16, + "expected a range size of 16 since that was the lowest bit size provided" + ); + + let (optimized_circuit, _) = optimizer.replace_redundant_ranges(acir_opcode_positions); + assert_eq!(optimized_circuit.opcodes.len(), 1); + + let (witness, num_bits) = + extract_range_opcode(&optimized_circuit.opcodes[0]).expect("expected one range opcode"); + + assert_eq!(witness, Witness(1)); + assert_eq!(num_bits, 16); + } + + #[test] + fn remove_duplicates() { + // The optimizer should remove all duplicate range opcodes. + + let circuit = test_circuit(vec![ + (Witness(1), 16), + (Witness(1), 16), + (Witness(2), 23), + (Witness(2), 23), + ]); + let acir_opcode_positions = circuit.opcodes.iter().enumerate().map(|(i, _)| i).collect(); + let optimizer = RangeOptimizer::new(circuit); + let (optimized_circuit, _) = optimizer.replace_redundant_ranges(acir_opcode_positions); + assert_eq!(optimized_circuit.opcodes.len(), 2); + + let (witness_a, num_bits_a) = + extract_range_opcode(&optimized_circuit.opcodes[0]).expect("expected two range opcode"); + let (witness_b, num_bits_b) = + extract_range_opcode(&optimized_circuit.opcodes[1]).expect("expected two range opcode"); + + assert_eq!(witness_a, Witness(1)); + assert_eq!(witness_b, Witness(2)); + assert_eq!(num_bits_a, 16); + assert_eq!(num_bits_b, 23); + } + + #[test] + fn non_range_opcodes() { + // The optimizer should not remove or change non-range opcodes + // The four Arithmetic opcodes should remain unchanged. + let mut circuit = test_circuit(vec![(Witness(1), 16), (Witness(1), 16)]); + + circuit.opcodes.push(Opcode::Arithmetic(Expression::default())); + circuit.opcodes.push(Opcode::Arithmetic(Expression::default())); + circuit.opcodes.push(Opcode::Arithmetic(Expression::default())); + circuit.opcodes.push(Opcode::Arithmetic(Expression::default())); + let acir_opcode_positions = circuit.opcodes.iter().enumerate().map(|(i, _)| i).collect(); + let optimizer = RangeOptimizer::new(circuit); + let (optimized_circuit, _) = optimizer.replace_redundant_ranges(acir_opcode_positions); + assert_eq!(optimized_circuit.opcodes.len(), 5) + } +} diff --git a/acvm-repo/acvm/src/compiler/transformers/csat.rs b/acvm-repo/acvm/src/compiler/transformers/csat.rs new file mode 100644 index 00000000000..9f89ac4671a --- /dev/null +++ b/acvm-repo/acvm/src/compiler/transformers/csat.rs @@ -0,0 +1,509 @@ +use std::{cmp::Ordering, collections::HashSet}; + +use acir::{ + native_types::{Expression, Witness}, + FieldElement, +}; +use indexmap::IndexMap; + +/// A transformer which processes any [`Expression`]s to break them up such that they +/// fit within the [`ProofSystemCompiler`][crate::ProofSystemCompiler]'s width. +/// +/// This transformer is only used when targetting the [`PLONKCSat`][crate::Language::PLONKCSat] language. +/// +/// This is done by creating intermediate variables to hold partial calculations and then combining them +/// to calculate the original expression. +// Should we give it all of the opcodes? +// Have a single transformer that you instantiate with a width, then pass many opcodes through +pub(crate) struct CSatTransformer { + width: usize, + /// Track the witness that can be solved + solvable_witness: HashSet, +} + +impl CSatTransformer { + // Configure the width for the optimizer + pub(crate) fn new(width: usize) -> CSatTransformer { + assert!(width > 2); + + CSatTransformer { width, solvable_witness: HashSet::new() } + } + + /// Check if the equation 'expression=0' can be solved, and if yes, add the solved witness to set of solvable witness + fn try_solve(&mut self, opcode: &Expression) { + let mut unresolved = Vec::new(); + for (_, w1, w2) in &opcode.mul_terms { + if !self.solvable_witness.contains(w1) { + unresolved.push(w1); + if !self.solvable_witness.contains(w2) { + return; + } + } + if !self.solvable_witness.contains(w2) { + unresolved.push(w2); + if !self.solvable_witness.contains(w1) { + return; + } + } + } + for (_, w) in &opcode.linear_combinations { + if !self.solvable_witness.contains(w) { + unresolved.push(w); + } + } + if unresolved.len() == 1 { + self.mark_solvable(*unresolved[0]); + } + } + + /// Adds the witness to set of solvable witness + pub(crate) fn mark_solvable(&mut self, witness: Witness) { + self.solvable_witness.insert(witness); + } + + // Still missing dead witness optimization. + // To do this, we will need the whole set of arithmetic opcodes + // I think it can also be done before the local optimization seen here, as dead variables will come from the user + pub(crate) fn transform( + &mut self, + opcode: Expression, + intermediate_variables: &mut IndexMap, + num_witness: &mut u32, + ) -> Expression { + // Here we create intermediate variables and constrain them to be equal to any subset of the polynomial that can be represented as a full opcode + let opcode = + self.full_opcode_scan_optimization(opcode, intermediate_variables, num_witness); + // The last optimization to do is to create intermediate variables in order to flatten the fan-in and the amount of mul terms + // If a opcode has more than one mul term. We may need an intermediate variable for each one. Since not every variable will need to link to + // the mul term, we could possibly do it that way. + // We wil call this a partial opcode scan optimization which will result in the opcodes being able to fit into the correct width + let mut opcode = + self.partial_opcode_scan_optimization(opcode, intermediate_variables, num_witness); + opcode.sort(); + self.try_solve(&opcode); + opcode + } + + // This optimization will search for combinations of terms which can be represented in a single arithmetic opcode + // Case 1 : qM * wL * wR + qL * wL + qR * wR + qO * wO + qC + // This polynomial does not require any further optimizations, it can be safely represented in one opcode + // ie a polynomial with 1 mul(bi-variate) term and 3 (univariate) terms where 2 of those terms match the bivariate term + // wL and wR, we can represent it in one opcode + // GENERALIZED for WIDTH: instead of the number 3, we use `WIDTH` + // + // + // Case 2: qM * wL * wR + qL * wL + qR * wR + qO * wO + qC + qM2 * wL2 * wR2 + qL * wL2 + qR * wR2 + qO * wO2 + qC2 + // This polynomial cannot be represented using one arithmetic opcode. + // + // This algorithm will first extract the first full opcode(if possible): + // t = qM * wL * wR + qL * wL + qR * wR + qO * wO + qC + // + // The polynomial now looks like so t + qM2 * wL2 * wR2 + qL * wL2 + qR * wR2 + qO * wO2 + qC2 + // This polynomial cannot be represented using one arithmetic opcode. + // + // This algorithm will then extract the second full opcode(if possible): + // t2 = qM2 * wL2 * wR2 + qL * wL2 + qR * wR2 + qO * wO2 + qC2 + // + // The polynomial now looks like so t + t2 + // We can no longer extract another full opcode, hence the algorithm terminates. Creating two intermediate variables t and t2. + // This stage of preprocessing does not guarantee that all polynomials can fit into a opcode. It only guarantees that all full opcodes have been extracted from each polynomial + fn full_opcode_scan_optimization( + &mut self, + mut opcode: Expression, + intermediate_variables: &mut IndexMap, + num_witness: &mut u32, + ) -> Expression { + // We pass around this intermediate variable IndexMap, so that we do not create intermediate variables that we have created before + // One instance where this might happen is t1 = wL * wR and t2 = wR * wL + + // First check that this is not a simple opcode which does not need optimization + // + // If the opcode only has one mul term, then this algorithm cannot optimize it any further + // Either it can be represented in a single arithmetic equation or it's fan-in is too large and we need intermediate variables for those + // large-fan-in optimization is not this algorithms purpose. + // If the opcode has 0 mul terms, then it is an add opcode and similarly it can either fit into a single arithmetic opcode or it has a large fan-in + if opcode.mul_terms.len() <= 1 { + return opcode; + } + + // We now know that this opcode has multiple mul terms and can possibly be simplified into multiple full opcodes + // We need to create a (wl, wr) IndexMap and then check the simplified fan-in to verify if we have terms both with wl and wr + // In general, we can then push more terms into the opcode until we are at width-1 then the last variable will be the intermediate variable + // + + // This will be our new opcode which will be equal to `self` except we will have intermediate variables that will be constrained to any + // subset of the terms that can be represented as full opcodes + let mut new_opcode = Expression::default(); + let mut remaining_mul_terms = Vec::with_capacity(opcode.mul_terms.len()); + for pair in opcode.mul_terms { + // We want to layout solvable intermediate variable, if we cannot solve one of the witness + // that means the intermediate opcode will not be immediately solvable + if !self.solvable_witness.contains(&pair.1) || !self.solvable_witness.contains(&pair.2) + { + remaining_mul_terms.push(pair); + continue; + } + + // Check if this pair is present in the simplified fan-in + // We are assuming that the fan-in/fan-out has been simplified. + // Note this function is not public, and can only be called within the optimize method, so this guarantee will always hold + let index_wl = + opcode.linear_combinations.iter().position(|(_scale, witness)| *witness == pair.1); + let index_wr = + opcode.linear_combinations.iter().position(|(_scale, witness)| *witness == pair.2); + + match (index_wl, index_wr) { + (None, _) => { + // This means that the polynomial does not contain both terms + // Just push the Qm term as it cannot form a full opcode + new_opcode.mul_terms.push(pair); + } + (_, None) => { + // This means that the polynomial does not contain both terms + // Just push the Qm term as it cannot form a full opcode + new_opcode.mul_terms.push(pair); + } + (Some(x), Some(y)) => { + // This means that we can form a full opcode with this Qm term + + // First fetch the left and right wires which match the mul term + let left_wire_term = opcode.linear_combinations[x]; + let right_wire_term = opcode.linear_combinations[y]; + + // Lets create an intermediate opcode to store this full opcode + // + let mut intermediate_opcode = Expression::default(); + intermediate_opcode.mul_terms.push(pair); + + // Add the left and right wires + intermediate_opcode.linear_combinations.push(left_wire_term); + intermediate_opcode.linear_combinations.push(right_wire_term); + // Remove the left and right wires so we do not re-add them + match x.cmp(&y) { + Ordering::Greater => { + opcode.linear_combinations.remove(x); + opcode.linear_combinations.remove(y); + } + Ordering::Less => { + opcode.linear_combinations.remove(y); + opcode.linear_combinations.remove(x); + } + Ordering::Equal => { + opcode.linear_combinations.remove(x); + intermediate_opcode.linear_combinations.pop(); + } + } + + // Now we have used up 2 spaces in our arithmetic opcode. The width now dictates, how many more we can add + let mut remaining_space = self.width - 2 - 1; // We minus 1 because we need an extra space to contain the intermediate variable + // Keep adding terms until we have no more left, or we reach the width + let mut remaining_linear_terms = + Vec::with_capacity(opcode.linear_combinations.len()); + while remaining_space > 0 { + if let Some(wire_term) = opcode.linear_combinations.pop() { + // Add this element into the new opcode + if self.solvable_witness.contains(&wire_term.1) { + intermediate_opcode.linear_combinations.push(wire_term); + remaining_space -= 1; + } else { + remaining_linear_terms.push(wire_term); + } + } else { + // No more usable elements left in the old opcode + opcode.linear_combinations = remaining_linear_terms; + break; + } + } + // Constraint this intermediate_opcode to be equal to the temp variable by adding it into the IndexMap + // We need a unique name for our intermediate variable + // XXX: Another optimization, which could be applied in another algorithm + // If two opcodes have a large fan-in/out and they share a few common terms, then we should create intermediate variables for them + // Do some sort of subset matching algorithm for this on the terms of the polynomial + + let inter_var = Self::get_or_create_intermediate_vars( + intermediate_variables, + intermediate_opcode, + num_witness, + ); + + // Add intermediate variable to the new opcode instead of the full opcode + self.mark_solvable(inter_var.1); + new_opcode.linear_combinations.push(inter_var); + } + }; + } + opcode.mul_terms = remaining_mul_terms; + + // Add the rest of the elements back into the new_opcode + new_opcode.mul_terms.extend(opcode.mul_terms); + new_opcode.linear_combinations.extend(opcode.linear_combinations); + new_opcode.q_c = opcode.q_c; + new_opcode.sort(); + new_opcode + } + + /// Normalize an expression by dividing it by its first coefficient + /// The first coefficient here means coefficient of the first linear term, or of the first quadratic term if no linear terms exist. + /// The function panic if the input expression is constant + fn normalize(mut expr: Expression) -> (FieldElement, Expression) { + expr.sort(); + let a = if !expr.linear_combinations.is_empty() { + expr.linear_combinations[0].0 + } else { + expr.mul_terms[0].0 + }; + (a, &expr * a.inverse()) + } + + /// Get or generate a scaled intermediate witness which is equal to the provided expression + /// The sets of previously generated witness and their (normalized) expression is cached in the intermediate_variables map + /// If there is no cache hit, we generate a new witness (and add the expression to the cache) + /// else, we return the cached witness along with the scaling factor so it is equal to the provided expression + fn get_or_create_intermediate_vars( + intermediate_variables: &mut IndexMap, + expr: Expression, + num_witness: &mut u32, + ) -> (FieldElement, Witness) { + let (k, normalized_expr) = Self::normalize(expr); + + if intermediate_variables.contains_key(&normalized_expr) { + let (l, iv) = intermediate_variables[&normalized_expr]; + (k / l, iv) + } else { + let inter_var = Witness(*num_witness); + *num_witness += 1; + // Add intermediate opcode and variable to map + intermediate_variables.insert(normalized_expr, (k, inter_var)); + (FieldElement::one(), inter_var) + } + } + + // A partial opcode scan optimization aim to create intermediate variables in order to compress the polynomial + // So that it fits within the given width + // Note that this opcode follows the full opcode scan optimization. + // We define the partial width as equal to the full width - 2. + // This is because two of our variables cannot be used as they are linked to the multiplication terms + // Example: qM1 * wL1 * wR2 + qL1 * wL3 + qR1 * wR4+ qR2 * wR5 + qO1 * wO5 + qC + // One thing to note is that the multiplication wires do not match any of the fan-in/out wires. This is guaranteed as we have + // just completed the full opcode optimization algorithm. + // + //Actually we can optimize in two ways here: We can create an intermediate variable which is equal to the fan-in terms + // t = qL1 * wL3 + qR1 * wR4 -> width = 3 + // This `t` value can only use width - 1 terms + // The opcode now looks like: qM1 * wL1 * wR2 + t + qR2 * wR5+ qO1 * wO5 + qC + // But this is still not acceptable since wR5 is not wR2, so we need another intermediate variable + // t2 = t + qR2 * wR5 + // + // The opcode now looks like: qM1 * wL1 * wR2 + t2 + qO1 * wO5 + qC + // This is still not good, so we do it one more time: + // t3 = t2 + qO1 * wO5 + // The opcode now looks like: qM1 * wL1 * wR2 + t3 + qC + // + // Another strategy is to create a temporary variable for the multiplier term and then we can see it as a term in the fan-in + // + // Same Example: qM1 * wL1 * wR2 + qL1 * wL3 + qR1 * wR4+ qR2 * wR5 + qO1 * wO5 + qC + // t = qM1 * wL1 * wR2 + // The opcode now looks like: t + qL1 * wL3 + qR1 * wR4+ qR2 * wR5 + qO1 * wO5 + qC + // Still assuming width3, we still need to use width-1 terms for the intermediate variables, however we can stop at an earlier stage because + // the opcode does not need the multiplier term to match with any of the fan-in terms + // t2 = t + qL1 * wL3 + // The opcode now looks like: t2 + qR1 * wR4+ qR2 * wR5 + qO1 * wO5 + qC + // t3 = t2 + qR1 * wR4 + // The opcode now looks like: t3 + qR2 * wR5 + qO1 * wO5 + qC + // This took the same amount of opcodes, but which one is better when the width increases? Compute this and maybe do both optimizations + // naming : partial_opcode_mul_first_opt and partial_opcode_fan_first_opt + // Also remember that since we did full opcode scan, there is no way we can have a non-zero mul term along with the wL and wR terms being non-zero + // + // Cases, a lot of mul terms, a lot of fan-in terms, 50/50 + fn partial_opcode_scan_optimization( + &mut self, + mut opcode: Expression, + intermediate_variables: &mut IndexMap, + num_witness: &mut u32, + ) -> Expression { + // We will go for the easiest route, which is to convert all multiplications into additions using intermediate variables + // Then use intermediate variables again to squash the fan-in, so that it can fit into the appropriate width + + // First check if this polynomial actually needs a partial opcode optimization + // There is the chance that it fits perfectly within the arithmetic opcode + if opcode.fits_in_one_identity(self.width) { + return opcode; + } + + // 2. Create Intermediate variables for the multiplication opcodes + let mut remaining_mul_terms = Vec::with_capacity(opcode.mul_terms.len()); + for mul_term in opcode.mul_terms { + if self.solvable_witness.contains(&mul_term.1) + && self.solvable_witness.contains(&mul_term.2) + { + let mut intermediate_opcode = Expression::default(); + + // Push mul term into the opcode + intermediate_opcode.mul_terms.push(mul_term); + // Get an intermediate variable which squashes the multiplication term + let inter_var = Self::get_or_create_intermediate_vars( + intermediate_variables, + intermediate_opcode, + num_witness, + ); + + // Add intermediate variable as a part of the fan-in for the original opcode + opcode.linear_combinations.push(inter_var); + self.mark_solvable(inter_var.1); + } else { + remaining_mul_terms.push(mul_term); + } + } + + // Remove all of the mul terms as we have intermediate variables to represent them now + opcode.mul_terms = remaining_mul_terms; + + // We now only have a polynomial with only fan-in/fan-out terms i.e. terms of the form Ax + By + Cd + ... + // Lets create intermediate variables if all of them cannot fit into the width + // + // If the polynomial fits perfectly within the given width, we are finished + if opcode.linear_combinations.len() <= self.width { + return opcode; + } + + // Stores the intermediate variables that are used to + // reduce the fan in. + let mut added = Vec::new(); + + while opcode.linear_combinations.len() > self.width { + // Collect as many terms up to the given width-1 and constrain them to an intermediate variable + let mut intermediate_opcode = Expression::default(); + + let mut remaining_linear_terms = Vec::with_capacity(opcode.linear_combinations.len()); + + for term in opcode.linear_combinations { + if self.solvable_witness.contains(&term.1) + && intermediate_opcode.linear_combinations.len() < self.width - 1 + { + intermediate_opcode.linear_combinations.push(term); + } else { + remaining_linear_terms.push(term); + } + } + opcode.linear_combinations = remaining_linear_terms; + let not_full = intermediate_opcode.linear_combinations.len() < self.width - 1; + if intermediate_opcode.linear_combinations.len() > 1 { + let inter_var = Self::get_or_create_intermediate_vars( + intermediate_variables, + intermediate_opcode, + num_witness, + ); + self.mark_solvable(inter_var.1); + added.push(inter_var); + } + // The intermediate opcode is not full, but the opcode still has too many terms + if not_full && opcode.linear_combinations.len() > self.width { + unreachable!("Could not reduce the expression"); + } + } + + // Add back the intermediate variables to + // keep consistency with the original equation. + opcode.linear_combinations.extend(added); + self.partial_opcode_scan_optimization(opcode, intermediate_variables, num_witness) + } +} + +#[test] +fn simple_reduction_smoke_test() { + let a = Witness(0); + let b = Witness(1); + let c = Witness(2); + let d = Witness(3); + + // a = b + c + d; + let opcode_a = Expression { + mul_terms: vec![], + linear_combinations: vec![ + (FieldElement::one(), a), + (-FieldElement::one(), b), + (-FieldElement::one(), c), + (-FieldElement::one(), d), + ], + q_c: FieldElement::zero(), + }; + + let mut intermediate_variables: IndexMap = IndexMap::new(); + + let mut num_witness = 4; + + let mut optimizer = CSatTransformer::new(3); + optimizer.mark_solvable(b); + optimizer.mark_solvable(c); + optimizer.mark_solvable(d); + let got_optimized_opcode_a = + optimizer.transform(opcode_a, &mut intermediate_variables, &mut num_witness); + + // a = b + c + d => a - b - c - d = 0 + // For width3, the result becomes: + // a - d + e = 0 + // - c - b - e = 0 + // + // a - b + e = 0 + let e = Witness(4); + let expected_optimized_opcode_a = Expression { + mul_terms: vec![], + linear_combinations: vec![ + (FieldElement::one(), a), + (-FieldElement::one(), d), + (FieldElement::one(), e), + ], + q_c: FieldElement::zero(), + }; + assert_eq!(expected_optimized_opcode_a, got_optimized_opcode_a); + + assert_eq!(intermediate_variables.len(), 1); + + // e = - c - b + let expected_intermediate_opcode = Expression { + mul_terms: vec![], + linear_combinations: vec![(-FieldElement::one(), c), (-FieldElement::one(), b)], + q_c: FieldElement::zero(), + }; + let (_, normalized_opcode) = CSatTransformer::normalize(expected_intermediate_opcode); + assert!(intermediate_variables.contains_key(&normalized_opcode)); + assert_eq!(intermediate_variables[&normalized_opcode].1, e); +} + +#[test] +fn stepwise_reduction_test() { + let a = Witness(0); + let b = Witness(1); + let c = Witness(2); + let d = Witness(3); + let e = Witness(4); + + // a = b + c + d + e; + let opcode_a = Expression { + mul_terms: vec![], + linear_combinations: vec![ + (-FieldElement::one(), a), + (FieldElement::one(), b), + (FieldElement::one(), c), + (FieldElement::one(), d), + (FieldElement::one(), e), + ], + q_c: FieldElement::zero(), + }; + + let mut intermediate_variables: IndexMap = IndexMap::new(); + + let mut num_witness = 4; + + let mut optimizer = CSatTransformer::new(3); + optimizer.mark_solvable(a); + optimizer.mark_solvable(c); + optimizer.mark_solvable(d); + optimizer.mark_solvable(e); + let got_optimized_opcode_a = + optimizer.transform(opcode_a, &mut intermediate_variables, &mut num_witness); + + // Since b is not known, it cannot be put inside intermediate opcodes, so it must belong to the transformed opcode. + let contains_b = got_optimized_opcode_a.linear_combinations.iter().any(|(_, w)| *w == b); + assert!(contains_b); +} diff --git a/acvm-repo/acvm/src/compiler/transformers/fallback.rs b/acvm-repo/acvm/src/compiler/transformers/fallback.rs new file mode 100644 index 00000000000..d3e4ea506f8 --- /dev/null +++ b/acvm-repo/acvm/src/compiler/transformers/fallback.rs @@ -0,0 +1,154 @@ +use super::super::CompileError; +use acir::{ + circuit::{opcodes::BlackBoxFuncCall, Circuit, Opcode}, + native_types::Expression, +}; + +/// The initial transformer to act on a [`Circuit`]. This replaces any unsupported opcodes with +/// fallback implementations consisting of well supported opcodes. +pub(crate) struct FallbackTransformer; + +impl FallbackTransformer { + //ACIR pass which replace unsupported opcodes using arithmetic fallback + pub(crate) fn transform( + acir: Circuit, + is_supported: impl Fn(&Opcode) -> bool, + opcode_positions: Vec, + ) -> Result<(Circuit, Vec), CompileError> { + let mut acir_supported_opcodes = Vec::with_capacity(acir.opcodes.len()); + let mut new_opcode_positions = Vec::with_capacity(opcode_positions.len()); + let mut witness_idx = acir.current_witness_index + 1; + + for (idx, opcode) in acir.opcodes.into_iter().enumerate() { + match &opcode { + Opcode::Arithmetic(_) | Opcode::Directive(_) | Opcode::Brillig(_) => { + // directive, arithmetic expression or blocks are handled by acvm + new_opcode_positions.push(opcode_positions[idx]); + acir_supported_opcodes.push(opcode); + continue; + } + Opcode::MemoryInit { .. } | Opcode::MemoryOp { .. } => { + if !is_supported(&opcode) { + return Err(CompileError::UnsupportedMemoryOpcode( + opcode.unsupported_opcode(), + )); + } + new_opcode_positions.push(opcode_positions[idx]); + acir_supported_opcodes.push(opcode); + } + Opcode::BlackBoxFuncCall(bb_func_call) => { + // We know it is an black box function. Now check if it is + // supported by the backend. If it is supported, then we can simply + // collect the opcode + if is_supported(&opcode) { + new_opcode_positions.push(opcode_positions[idx]); + acir_supported_opcodes.push(opcode); + continue; + } else { + // If we get here then we know that this black box function is not supported + // so we need to replace it with a version of the opcode which only uses arithmetic + // expressions + let (updated_witness_index, opcodes_fallback) = + Self::opcode_fallback(bb_func_call, witness_idx)?; + witness_idx = updated_witness_index; + new_opcode_positions + .extend(vec![opcode_positions[idx]; opcodes_fallback.len()]); + acir_supported_opcodes.extend(opcodes_fallback); + } + } + } + } + + Ok(( + Circuit { current_witness_index: witness_idx, opcodes: acir_supported_opcodes, ..acir }, + new_opcode_positions, + )) + } + + fn opcode_fallback( + gc: &BlackBoxFuncCall, + current_witness_idx: u32, + ) -> Result<(u32, Vec), CompileError> { + let (updated_witness_index, opcodes_fallback) = match gc { + BlackBoxFuncCall::AND { lhs, rhs, output } => { + assert_eq!( + lhs.num_bits, rhs.num_bits, + "number of bits specified for each input must be the same" + ); + stdlib::blackbox_fallbacks::and( + Expression::from(lhs.witness), + Expression::from(rhs.witness), + *output, + lhs.num_bits, + current_witness_idx, + ) + } + BlackBoxFuncCall::XOR { lhs, rhs, output } => { + assert_eq!( + lhs.num_bits, rhs.num_bits, + "number of bits specified for each input must be the same" + ); + stdlib::blackbox_fallbacks::xor( + Expression::from(lhs.witness), + Expression::from(rhs.witness), + *output, + lhs.num_bits, + current_witness_idx, + ) + } + BlackBoxFuncCall::RANGE { input } => { + // Note there are no outputs because range produces no outputs + stdlib::blackbox_fallbacks::range( + Expression::from(input.witness), + input.num_bits, + current_witness_idx, + ) + } + #[cfg(feature = "unstable-fallbacks")] + BlackBoxFuncCall::SHA256 { inputs, outputs } => { + let sha256_inputs = + inputs.iter().map(|input| (input.witness.into(), input.num_bits)).collect(); + stdlib::blackbox_fallbacks::sha256( + sha256_inputs, + outputs.to_vec(), + current_witness_idx, + ) + } + #[cfg(feature = "unstable-fallbacks")] + BlackBoxFuncCall::Blake2s { inputs, outputs } => { + let blake2s_inputs = + inputs.iter().map(|input| (input.witness.into(), input.num_bits)).collect(); + stdlib::blackbox_fallbacks::blake2s( + blake2s_inputs, + outputs.to_vec(), + current_witness_idx, + ) + } + #[cfg(feature = "unstable-fallbacks")] + BlackBoxFuncCall::HashToField128Security { inputs, output } => { + let hash_to_field_inputs = + inputs.iter().map(|input| (input.witness.into(), input.num_bits)).collect(); + stdlib::blackbox_fallbacks::hash_to_field( + hash_to_field_inputs, + *output, + current_witness_idx, + ) + } + #[cfg(feature = "unstable-fallbacks")] + BlackBoxFuncCall::Keccak256 { inputs, outputs } => { + let keccak_inputs = + inputs.iter().map(|input| (input.witness.into(), input.num_bits)).collect(); + stdlib::blackbox_fallbacks::keccak256( + keccak_inputs, + outputs.to_vec(), + current_witness_idx, + ) + } + _ => { + return Err(CompileError::UnsupportedBlackBox(gc.get_black_box_func())); + } + }; + + Ok((updated_witness_index, opcodes_fallback)) + } +} diff --git a/acvm-repo/acvm/src/compiler/transformers/mod.rs b/acvm-repo/acvm/src/compiler/transformers/mod.rs new file mode 100644 index 00000000000..89e17ca68d0 --- /dev/null +++ b/acvm-repo/acvm/src/compiler/transformers/mod.rs @@ -0,0 +1,7 @@ +mod csat; +mod fallback; +mod r1cs; + +pub(crate) use csat::CSatTransformer; +pub(crate) use fallback::FallbackTransformer; +pub(crate) use r1cs::R1CSTransformer; diff --git a/acvm-repo/acvm/src/compiler/transformers/r1cs.rs b/acvm-repo/acvm/src/compiler/transformers/r1cs.rs new file mode 100644 index 00000000000..3bdd29c9c53 --- /dev/null +++ b/acvm-repo/acvm/src/compiler/transformers/r1cs.rs @@ -0,0 +1,16 @@ +use acir::circuit::Circuit; + +/// Currently a "noop" transformer. +pub(crate) struct R1CSTransformer { + acir: Circuit, +} + +impl R1CSTransformer { + pub(crate) fn new(acir: Circuit) -> Self { + Self { acir } + } + // TODO: We could possibly make sure that all polynomials are at most degree-2 + pub(crate) fn transform(self) -> Circuit { + self.acir + } +} diff --git a/acvm-repo/acvm/src/lib.rs b/acvm-repo/acvm/src/lib.rs new file mode 100644 index 00000000000..f32d30ee9aa --- /dev/null +++ b/acvm-repo/acvm/src/lib.rs @@ -0,0 +1,25 @@ +#![warn(unused_crate_dependencies)] +#![warn(unreachable_pub)] + +pub mod compiler; +pub mod pwg; + +pub use acvm_blackbox_solver::{BlackBoxFunctionSolver, BlackBoxResolutionError}; +use core::fmt::Debug; +use pwg::OpcodeResolutionError; + +// re-export acir +pub use acir; +pub use acir::FieldElement; +// re-export brillig vm +pub use brillig_vm; +// re-export blackbox solver +pub use acvm_blackbox_solver as blackbox_solver; + +/// Supported NP complete languages +/// This might need to be in ACIR instead +#[derive(Debug, Clone, Copy)] +pub enum Language { + R1CS, + PLONKCSat { width: usize }, +} diff --git a/acvm-repo/acvm/src/pwg/arithmetic.rs b/acvm-repo/acvm/src/pwg/arithmetic.rs new file mode 100644 index 00000000000..93a39fb249c --- /dev/null +++ b/acvm-repo/acvm/src/pwg/arithmetic.rs @@ -0,0 +1,281 @@ +use acir::{ + native_types::{Expression, Witness, WitnessMap}, + FieldElement, +}; + +use super::{insert_value, ErrorLocation, OpcodeNotSolvable, OpcodeResolutionError}; + +/// An Arithmetic solver will take a Circuit's arithmetic opcodes with witness assignments +/// and create the other witness variables +pub(super) struct ArithmeticSolver; + +#[allow(clippy::enum_variant_names)] +pub(super) enum OpcodeStatus { + OpcodeSatisfied(FieldElement), + OpcodeSolvable(FieldElement, (FieldElement, Witness)), + OpcodeUnsolvable, +} + +pub(crate) enum MulTerm { + OneUnknown(FieldElement, Witness), // (qM * known_witness, unknown_witness) + TooManyUnknowns, + Solved(FieldElement), +} + +impl ArithmeticSolver { + /// Derives the rest of the witness based on the initial low level variables + pub(super) fn solve( + initial_witness: &mut WitnessMap, + opcode: &Expression, + ) -> Result<(), OpcodeResolutionError> { + let opcode = &ArithmeticSolver::evaluate(opcode, initial_witness); + // Evaluate multiplication term + let mul_result = ArithmeticSolver::solve_mul_term(opcode, initial_witness); + // Evaluate the fan-in terms + let opcode_status = ArithmeticSolver::solve_fan_in_term(opcode, initial_witness); + + match (mul_result, opcode_status) { + (MulTerm::TooManyUnknowns, _) | (_, OpcodeStatus::OpcodeUnsolvable) => { + Err(OpcodeResolutionError::OpcodeNotSolvable( + OpcodeNotSolvable::ExpressionHasTooManyUnknowns(opcode.clone()), + )) + } + (MulTerm::OneUnknown(q, w1), OpcodeStatus::OpcodeSolvable(a, (b, w2))) => { + if w1 == w2 { + // We have one unknown so we can solve the equation + let total_sum = a + opcode.q_c; + if (q + b).is_zero() { + if !total_sum.is_zero() { + Err(OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: ErrorLocation::Unresolved, + }) + } else { + Ok(()) + } + } else { + let assignment = -total_sum / (q + b); + // Add this into the witness assignments + insert_value(&w1, assignment, initial_witness)?; + Ok(()) + } + } else { + // TODO: can we be more specific with this error? + Err(OpcodeResolutionError::OpcodeNotSolvable( + OpcodeNotSolvable::ExpressionHasTooManyUnknowns(opcode.clone()), + )) + } + } + ( + MulTerm::OneUnknown(partial_prod, unknown_var), + OpcodeStatus::OpcodeSatisfied(sum), + ) => { + // We have one unknown in the mul term and the fan-in terms are solved. + // Hence the equation is solvable, since there is a single unknown + // The equation is: partial_prod * unknown_var + sum + qC = 0 + + let total_sum = sum + opcode.q_c; + if partial_prod.is_zero() { + if !total_sum.is_zero() { + Err(OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: ErrorLocation::Unresolved, + }) + } else { + Ok(()) + } + } else { + let assignment = -(total_sum / partial_prod); + // Add this into the witness assignments + insert_value(&unknown_var, assignment, initial_witness)?; + Ok(()) + } + } + (MulTerm::Solved(a), OpcodeStatus::OpcodeSatisfied(b)) => { + // All the variables in the MulTerm are solved and the Fan-in is also solved + // There is nothing to solve + if !(a + b + opcode.q_c).is_zero() { + Err(OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: ErrorLocation::Unresolved, + }) + } else { + Ok(()) + } + } + ( + MulTerm::Solved(total_prod), + OpcodeStatus::OpcodeSolvable(partial_sum, (coeff, unknown_var)), + ) => { + // The variables in the MulTerm are solved nad there is one unknown in the Fan-in + // Hence the equation is solvable, since we have one unknown + // The equation is total_prod + partial_sum + coeff * unknown_var + q_C = 0 + let total_sum = total_prod + partial_sum + opcode.q_c; + if coeff.is_zero() { + if !total_sum.is_zero() { + Err(OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: ErrorLocation::Unresolved, + }) + } else { + Ok(()) + } + } else { + let assignment = -(total_sum / coeff); + // Add this into the witness assignments + insert_value(&unknown_var, assignment, initial_witness)?; + Ok(()) + } + } + } + } + + /// Returns the evaluation of the multiplication term in the arithmetic opcode + /// If the witness values are not known, then the function returns a None + /// XXX: Do we need to account for the case where 5xy + 6x = 0 ? We do not know y, but it can be solved given x . But I believe x can be solved with another opcode + /// XXX: What about making a mul opcode = a constant 5xy + 7 = 0 ? This is the same as the above. + fn solve_mul_term(arith_opcode: &Expression, witness_assignments: &WitnessMap) -> MulTerm { + // First note that the mul term can only contain one/zero term + // We are assuming it has been optimized. + match arith_opcode.mul_terms.len() { + 0 => MulTerm::Solved(FieldElement::zero()), + 1 => ArithmeticSolver::solve_mul_term_helper( + &arith_opcode.mul_terms[0], + witness_assignments, + ), + _ => panic!("Mul term in the arithmetic opcode must contain either zero or one term"), + } + } + + fn solve_mul_term_helper( + term: &(FieldElement, Witness, Witness), + witness_assignments: &WitnessMap, + ) -> MulTerm { + let (q_m, w_l, w_r) = term; + // Check if these values are in the witness assignments + let w_l_value = witness_assignments.get(w_l); + let w_r_value = witness_assignments.get(w_r); + + match (w_l_value, w_r_value) { + (None, None) => MulTerm::TooManyUnknowns, + (Some(w_l), Some(w_r)) => MulTerm::Solved(*q_m * *w_l * *w_r), + (None, Some(w_r)) => MulTerm::OneUnknown(*q_m * *w_r, *w_l), + (Some(w_l), None) => MulTerm::OneUnknown(*q_m * *w_l, *w_r), + } + } + + fn solve_fan_in_term_helper( + term: &(FieldElement, Witness), + witness_assignments: &WitnessMap, + ) -> Option { + let (q_l, w_l) = term; + // Check if we have w_l + let w_l_value = witness_assignments.get(w_l); + w_l_value.map(|a| *q_l * *a) + } + + /// Returns the summation of all of the variables, plus the unknown variable + /// Returns None, if there is more than one unknown variable + /// We cannot assign + pub(super) fn solve_fan_in_term( + arith_opcode: &Expression, + witness_assignments: &WitnessMap, + ) -> OpcodeStatus { + // This is assuming that the fan-in is more than 0 + + // This is the variable that we want to assign the value to + let mut unknown_variable = (FieldElement::zero(), Witness::default()); + let mut num_unknowns = 0; + // This is the sum of all of the known variables + let mut result = FieldElement::zero(); + + for term in arith_opcode.linear_combinations.iter() { + let value = ArithmeticSolver::solve_fan_in_term_helper(term, witness_assignments); + match value { + Some(a) => result += a, + None => { + unknown_variable = *term; + num_unknowns += 1; + } + } + + // If we have more than 1 unknown, then we cannot solve this equation + if num_unknowns > 1 { + return OpcodeStatus::OpcodeUnsolvable; + } + } + + if num_unknowns == 0 { + return OpcodeStatus::OpcodeSatisfied(result); + } + + OpcodeStatus::OpcodeSolvable(result, unknown_variable) + } + + // Partially evaluate the opcode using the known witnesses + pub(super) fn evaluate(expr: &Expression, initial_witness: &WitnessMap) -> Expression { + let mut result = Expression::default(); + for &(c, w1, w2) in &expr.mul_terms { + let mul_result = ArithmeticSolver::solve_mul_term_helper(&(c, w1, w2), initial_witness); + match mul_result { + MulTerm::OneUnknown(v, w) => { + if !v.is_zero() { + result.linear_combinations.push((v, w)); + } + } + MulTerm::TooManyUnknowns => { + if !c.is_zero() { + result.mul_terms.push((c, w1, w2)); + } + } + MulTerm::Solved(f) => result.q_c += f, + } + } + for &(c, w) in &expr.linear_combinations { + if let Some(f) = ArithmeticSolver::solve_fan_in_term_helper(&(c, w), initial_witness) { + result.q_c += f; + } else if !c.is_zero() { + result.linear_combinations.push((c, w)); + } + } + result.q_c += expr.q_c; + result + } +} + +#[test] +fn arithmetic_smoke_test() { + let a = Witness(0); + let b = Witness(1); + let c = Witness(2); + let d = Witness(3); + + // a = b + c + d; + let opcode_a = Expression { + mul_terms: vec![], + linear_combinations: vec![ + (FieldElement::one(), a), + (-FieldElement::one(), b), + (-FieldElement::one(), c), + (-FieldElement::one(), d), + ], + q_c: FieldElement::zero(), + }; + + let e = Witness(4); + let opcode_b = Expression { + mul_terms: vec![], + linear_combinations: vec![ + (FieldElement::one(), e), + (-FieldElement::one(), a), + (-FieldElement::one(), b), + ], + 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!(ArithmeticSolver::solve(&mut values, &opcode_a), Ok(())); + assert_eq!(ArithmeticSolver::solve(&mut values, &opcode_b), Ok(())); + + assert_eq!(values.get(&a).unwrap(), &FieldElement::from(4_i128)); +} diff --git a/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs b/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs new file mode 100644 index 00000000000..975025971dc --- /dev/null +++ b/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs @@ -0,0 +1,27 @@ +use acir::{ + circuit::opcodes::FunctionInput, + native_types::{Witness, WitnessMap}, +}; + +use crate::{ + pwg::{insert_value, witness_to_value, OpcodeResolutionError}, + BlackBoxFunctionSolver, +}; + +pub(super) fn fixed_base_scalar_mul( + backend: &impl BlackBoxFunctionSolver, + initial_witness: &mut WitnessMap, + low: FunctionInput, + high: FunctionInput, + outputs: (Witness, Witness), +) -> Result<(), OpcodeResolutionError> { + let low = witness_to_value(initial_witness, low.witness)?; + let high = witness_to_value(initial_witness, high.witness)?; + + let (pub_x, pub_y) = backend.fixed_base_scalar_mul(low, high)?; + + insert_value(&outputs.0, pub_x, initial_witness)?; + insert_value(&outputs.1, pub_y, initial_witness)?; + + Ok(()) +} diff --git a/acvm-repo/acvm/src/pwg/blackbox/hash.rs b/acvm-repo/acvm/src/pwg/blackbox/hash.rs new file mode 100644 index 00000000000..80665a743c4 --- /dev/null +++ b/acvm-repo/acvm/src/pwg/blackbox/hash.rs @@ -0,0 +1,103 @@ +use acir::{ + circuit::opcodes::FunctionInput, + native_types::{Witness, WitnessMap}, + BlackBoxFunc, FieldElement, +}; +use acvm_blackbox_solver::{hash_to_field_128_security, BlackBoxResolutionError}; + +use crate::pwg::{insert_value, witness_to_value}; +use crate::OpcodeResolutionError; + +/// Attempts to solve a `HashToField128Security` opcode +/// If successful, `initial_witness` will be mutated to contain the new witness assignment. +pub(super) fn solve_hash_to_field( + initial_witness: &mut WitnessMap, + inputs: &[FunctionInput], + output: &Witness, +) -> Result<(), OpcodeResolutionError> { + let message_input = get_hash_input(initial_witness, inputs, None)?; + let field = hash_to_field_128_security(&message_input)?; + + insert_value(output, field, initial_witness)?; + + Ok(()) +} + +/// Attempts to solve a 256 bit hash function opcode. +/// If successful, `initial_witness` will be mutated to contain the new witness assignment. +pub(super) fn solve_generic_256_hash_opcode( + initial_witness: &mut WitnessMap, + inputs: &[FunctionInput], + var_message_size: Option<&FunctionInput>, + outputs: &[Witness], + hash_function: fn(data: &[u8]) -> Result<[u8; 32], BlackBoxResolutionError>, + black_box_func: BlackBoxFunc, +) -> Result<(), OpcodeResolutionError> { + let message_input = get_hash_input(initial_witness, inputs, var_message_size)?; + let digest: [u8; 32] = hash_function(&message_input)?; + + let outputs: [Witness; 32] = outputs.try_into().map_err(|_| { + OpcodeResolutionError::BlackBoxFunctionFailed( + black_box_func, + format!("Expected 32 outputs but encountered {}", outputs.len()), + ) + })?; + write_digest_to_outputs(initial_witness, outputs, digest)?; + + Ok(()) +} + +/// Reads the hash function input from a [`WitnessMap`]. +fn get_hash_input( + initial_witness: &WitnessMap, + inputs: &[FunctionInput], + message_size: Option<&FunctionInput>, +) -> Result, OpcodeResolutionError> { + // Read witness assignments. + let mut message_input = Vec::new(); + for input in inputs.iter() { + let witness = input.witness; + let num_bits = input.num_bits as usize; + + let witness_assignment = witness_to_value(initial_witness, witness)?; + let bytes = witness_assignment.fetch_nearest_bytes(num_bits); + message_input.extend(bytes); + } + + // Truncate the message if there is a `message_size` parameter given + match message_size { + Some(input) => { + let num_bytes_to_take = + witness_to_value(initial_witness, input.witness)?.to_u128() as usize; + + // If the number of bytes to take is more than the amount of bytes available + // in the message, then we error. + if num_bytes_to_take > message_input.len() { + return Err(OpcodeResolutionError::BlackBoxFunctionFailed( + acir::BlackBoxFunc::Keccak256, + format!("the number of bytes to take from the message is more than the number of bytes in the message. {} > {}", num_bytes_to_take, message_input.len()), + )); + } + let truncated_message = message_input[0..num_bytes_to_take].to_vec(); + Ok(truncated_message) + } + None => Ok(message_input), + } +} + +/// Writes a `digest` to the [`WitnessMap`] at witness indices `outputs`. +fn write_digest_to_outputs( + initial_witness: &mut WitnessMap, + outputs: [Witness; 32], + digest: [u8; 32], +) -> Result<(), OpcodeResolutionError> { + for (output_witness, value) in outputs.iter().zip(digest.into_iter()) { + insert_value( + output_witness, + FieldElement::from_be_bytes_reduce(&[value]), + initial_witness, + )?; + } + + Ok(()) +} diff --git a/acvm-repo/acvm/src/pwg/blackbox/logic.rs b/acvm-repo/acvm/src/pwg/blackbox/logic.rs new file mode 100644 index 00000000000..8e69730f71d --- /dev/null +++ b/acvm-repo/acvm/src/pwg/blackbox/logic.rs @@ -0,0 +1,56 @@ +use crate::pwg::{insert_value, witness_to_value}; +use crate::OpcodeResolutionError; +use acir::{ + circuit::opcodes::FunctionInput, + native_types::{Witness, WitnessMap}, + FieldElement, +}; + +/// Solves a [`BlackBoxFunc::And`][acir::circuit::black_box_functions::BlackBoxFunc::AND] opcode and inserts +/// the result into the supplied witness map +pub(super) fn and( + initial_witness: &mut WitnessMap, + lhs: &FunctionInput, + rhs: &FunctionInput, + output: &Witness, +) -> Result<(), OpcodeResolutionError> { + assert_eq!( + lhs.num_bits, rhs.num_bits, + "number of bits specified for each input must be the same" + ); + solve_logic_opcode(initial_witness, &lhs.witness, &rhs.witness, *output, |left, right| { + left.and(right, lhs.num_bits) + }) +} + +/// Solves a [`BlackBoxFunc::XOR`][acir::circuit::black_box_functions::BlackBoxFunc::XOR] opcode and inserts +/// the result into the supplied witness map +pub(super) fn xor( + initial_witness: &mut WitnessMap, + lhs: &FunctionInput, + rhs: &FunctionInput, + output: &Witness, +) -> Result<(), OpcodeResolutionError> { + assert_eq!( + lhs.num_bits, rhs.num_bits, + "number of bits specified for each input must be the same" + ); + solve_logic_opcode(initial_witness, &lhs.witness, &rhs.witness, *output, |left, right| { + left.xor(right, lhs.num_bits) + }) +} + +/// Derives the rest of the witness based on the initial low level variables +fn solve_logic_opcode( + initial_witness: &mut WitnessMap, + a: &Witness, + b: &Witness, + result: Witness, + logic_op: impl Fn(&FieldElement, &FieldElement) -> FieldElement, +) -> Result<(), OpcodeResolutionError> { + let w_l_value = witness_to_value(initial_witness, *a)?; + let w_r_value = witness_to_value(initial_witness, *b)?; + let assignment = logic_op(w_l_value, w_r_value); + + insert_value(&result, assignment, initial_witness) +} diff --git a/acvm-repo/acvm/src/pwg/blackbox/mod.rs b/acvm-repo/acvm/src/pwg/blackbox/mod.rs new file mode 100644 index 00000000000..c4d9d561f46 --- /dev/null +++ b/acvm-repo/acvm/src/pwg/blackbox/mod.rs @@ -0,0 +1,163 @@ +use acir::{ + circuit::opcodes::{BlackBoxFuncCall, FunctionInput}, + native_types::{Witness, WitnessMap}, + FieldElement, +}; +use acvm_blackbox_solver::{blake2s, keccak256, sha256}; + +use super::{insert_value, OpcodeNotSolvable, OpcodeResolutionError}; +use crate::BlackBoxFunctionSolver; + +mod fixed_base_scalar_mul; +mod hash; +mod logic; +mod pedersen; +mod range; +mod signature; + +use fixed_base_scalar_mul::fixed_base_scalar_mul; +// Hash functions should eventually be exposed for external consumers. +use hash::{solve_generic_256_hash_opcode, solve_hash_to_field}; +use logic::{and, xor}; +use pedersen::pedersen; +use range::solve_range_opcode; +use signature::{ + ecdsa::{secp256k1_prehashed, secp256r1_prehashed}, + schnorr::schnorr_verify, +}; + +/// Check if all of the inputs to the function have assignments +/// +/// Returns the first missing assignment if any are missing +fn first_missing_assignment( + witness_assignments: &WitnessMap, + inputs: &[FunctionInput], +) -> Option { + inputs.iter().find_map(|input| { + if witness_assignments.contains_key(&input.witness) { + None + } else { + Some(input.witness) + } + }) +} + +/// Check if all of the inputs to the function have assignments +fn contains_all_inputs(witness_assignments: &WitnessMap, inputs: &[FunctionInput]) -> bool { + inputs.iter().all(|input| witness_assignments.contains_key(&input.witness)) +} + +pub(crate) fn solve( + backend: &impl BlackBoxFunctionSolver, + initial_witness: &mut WitnessMap, + bb_func: &BlackBoxFuncCall, +) -> Result<(), OpcodeResolutionError> { + let inputs = bb_func.get_inputs_vec(); + if !contains_all_inputs(initial_witness, &inputs) { + let unassigned_witness = first_missing_assignment(initial_witness, &inputs) + .expect("Some assignments must be missing because it does not contains all inputs"); + return Err(OpcodeResolutionError::OpcodeNotSolvable( + OpcodeNotSolvable::MissingAssignment(unassigned_witness.0), + )); + } + + match bb_func { + BlackBoxFuncCall::AND { lhs, rhs, output } => and(initial_witness, lhs, rhs, output), + BlackBoxFuncCall::XOR { lhs, rhs, output } => xor(initial_witness, lhs, rhs, output), + BlackBoxFuncCall::RANGE { input } => solve_range_opcode(initial_witness, input), + BlackBoxFuncCall::SHA256 { inputs, outputs } => solve_generic_256_hash_opcode( + initial_witness, + inputs, + None, + outputs, + sha256, + bb_func.get_black_box_func(), + ), + BlackBoxFuncCall::Blake2s { inputs, outputs } => solve_generic_256_hash_opcode( + initial_witness, + inputs, + None, + outputs, + blake2s, + bb_func.get_black_box_func(), + ), + BlackBoxFuncCall::Keccak256 { inputs, outputs } => solve_generic_256_hash_opcode( + initial_witness, + inputs, + None, + outputs, + keccak256, + bb_func.get_black_box_func(), + ), + BlackBoxFuncCall::Keccak256VariableLength { inputs, var_message_size, outputs } => { + solve_generic_256_hash_opcode( + initial_witness, + inputs, + Some(var_message_size), + outputs, + keccak256, + bb_func.get_black_box_func(), + ) + } + BlackBoxFuncCall::HashToField128Security { inputs, output } => { + solve_hash_to_field(initial_witness, inputs, output) + } + BlackBoxFuncCall::SchnorrVerify { + public_key_x, + public_key_y, + signature, + message, + output, + } => schnorr_verify( + backend, + initial_witness, + *public_key_x, + *public_key_y, + signature, + message, + *output, + ), + BlackBoxFuncCall::Pedersen { inputs, domain_separator, outputs } => { + pedersen(backend, initial_witness, inputs, *domain_separator, *outputs) + } + BlackBoxFuncCall::EcdsaSecp256k1 { + public_key_x, + public_key_y, + signature, + hashed_message: message, + output, + } => secp256k1_prehashed( + initial_witness, + public_key_x, + public_key_y, + signature, + message, + *output, + ), + BlackBoxFuncCall::EcdsaSecp256r1 { + public_key_x, + public_key_y, + signature, + hashed_message: message, + output, + } => secp256r1_prehashed( + initial_witness, + public_key_x, + public_key_y, + signature, + message, + *output, + ), + BlackBoxFuncCall::FixedBaseScalarMul { low, high, outputs } => { + fixed_base_scalar_mul(backend, initial_witness, *low, *high, *outputs) + } + BlackBoxFuncCall::RecursiveAggregation { output_aggregation_object, .. } => { + // Solve the output of the recursive aggregation to zero to prevent missing assignment errors + // The correct value will be computed by the backend + for witness in output_aggregation_object { + insert_value(witness, FieldElement::zero(), initial_witness)?; + } + Ok(()) + } + } +} diff --git a/acvm-repo/acvm/src/pwg/blackbox/pedersen.rs b/acvm-repo/acvm/src/pwg/blackbox/pedersen.rs new file mode 100644 index 00000000000..44b4c91dc63 --- /dev/null +++ b/acvm-repo/acvm/src/pwg/blackbox/pedersen.rs @@ -0,0 +1,28 @@ +use acir::{ + circuit::opcodes::FunctionInput, + native_types::{Witness, WitnessMap}, +}; + +use crate::{ + pwg::{insert_value, witness_to_value, OpcodeResolutionError}, + BlackBoxFunctionSolver, +}; + +pub(super) fn pedersen( + backend: &impl BlackBoxFunctionSolver, + initial_witness: &mut WitnessMap, + inputs: &[FunctionInput], + domain_separator: u32, + outputs: (Witness, Witness), +) -> Result<(), OpcodeResolutionError> { + let scalars: Result, _> = + inputs.iter().map(|input| witness_to_value(initial_witness, input.witness)).collect(); + let scalars: Vec<_> = scalars?.into_iter().cloned().collect(); + + let (res_x, res_y) = backend.pedersen(&scalars, domain_separator)?; + + insert_value(&outputs.0, res_x, initial_witness)?; + insert_value(&outputs.1, res_y, initial_witness)?; + + Ok(()) +} diff --git a/acvm-repo/acvm/src/pwg/blackbox/range.rs b/acvm-repo/acvm/src/pwg/blackbox/range.rs new file mode 100644 index 00000000000..2c2b96cd753 --- /dev/null +++ b/acvm-repo/acvm/src/pwg/blackbox/range.rs @@ -0,0 +1,18 @@ +use crate::{ + pwg::{witness_to_value, ErrorLocation}, + OpcodeResolutionError, +}; +use acir::{circuit::opcodes::FunctionInput, native_types::WitnessMap}; + +pub(super) fn solve_range_opcode( + initial_witness: &mut WitnessMap, + input: &FunctionInput, +) -> Result<(), OpcodeResolutionError> { + let w_value = witness_to_value(initial_witness, input.witness)?; + if w_value.num_bits() > input.num_bits { + return Err(OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: ErrorLocation::Unresolved, + }); + } + Ok(()) +} diff --git a/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs b/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs new file mode 100644 index 00000000000..8f0df8378ad --- /dev/null +++ b/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs @@ -0,0 +1,91 @@ +use acir::{ + circuit::opcodes::FunctionInput, + native_types::{Witness, WitnessMap}, + FieldElement, +}; +use acvm_blackbox_solver::{ecdsa_secp256k1_verify, ecdsa_secp256r1_verify}; + +use crate::{pwg::insert_value, OpcodeResolutionError}; + +use super::to_u8_vec; + +pub(crate) fn secp256k1_prehashed( + initial_witness: &mut WitnessMap, + public_key_x_inputs: &[FunctionInput], + public_key_y_inputs: &[FunctionInput], + signature_inputs: &[FunctionInput], + hashed_message_inputs: &[FunctionInput], + output: Witness, +) -> Result<(), OpcodeResolutionError> { + let hashed_message = to_u8_vec(initial_witness, hashed_message_inputs)?; + + // These errors should never be emitted in practice as they would imply malformed ACIR generation. + let pub_key_x: [u8; 32] = + to_u8_vec(initial_witness, public_key_x_inputs)?.try_into().map_err(|_| { + OpcodeResolutionError::BlackBoxFunctionFailed( + acir::BlackBoxFunc::EcdsaSecp256k1, + format!("expected pubkey_x size 32 but received {}", public_key_x_inputs.len()), + ) + })?; + + let pub_key_y: [u8; 32] = + to_u8_vec(initial_witness, public_key_y_inputs)?.try_into().map_err(|_| { + OpcodeResolutionError::BlackBoxFunctionFailed( + acir::BlackBoxFunc::EcdsaSecp256k1, + format!("expected pubkey_y size 32 but received {}", public_key_y_inputs.len()), + ) + })?; + + let signature: [u8; 64] = + to_u8_vec(initial_witness, signature_inputs)?.try_into().map_err(|_| { + OpcodeResolutionError::BlackBoxFunctionFailed( + acir::BlackBoxFunc::EcdsaSecp256k1, + format!("expected signature size 64 but received {}", signature_inputs.len()), + ) + })?; + + let is_valid = ecdsa_secp256k1_verify(&hashed_message, &pub_key_x, &pub_key_y, &signature)?; + + insert_value(&output, FieldElement::from(is_valid), initial_witness)?; + Ok(()) +} + +pub(crate) fn secp256r1_prehashed( + initial_witness: &mut WitnessMap, + public_key_x_inputs: &[FunctionInput], + public_key_y_inputs: &[FunctionInput], + signature_inputs: &[FunctionInput], + hashed_message_inputs: &[FunctionInput], + output: Witness, +) -> Result<(), OpcodeResolutionError> { + let hashed_message = to_u8_vec(initial_witness, hashed_message_inputs)?; + + let pub_key_x: [u8; 32] = + to_u8_vec(initial_witness, public_key_x_inputs)?.try_into().map_err(|_| { + OpcodeResolutionError::BlackBoxFunctionFailed( + acir::BlackBoxFunc::EcdsaSecp256r1, + format!("expected pubkey_x size 32 but received {}", public_key_x_inputs.len()), + ) + })?; + + let pub_key_y: [u8; 32] = + to_u8_vec(initial_witness, public_key_y_inputs)?.try_into().map_err(|_| { + OpcodeResolutionError::BlackBoxFunctionFailed( + acir::BlackBoxFunc::EcdsaSecp256r1, + format!("expected pubkey_y size 32 but received {}", public_key_y_inputs.len()), + ) + })?; + + let signature: [u8; 64] = + to_u8_vec(initial_witness, signature_inputs)?.try_into().map_err(|_| { + OpcodeResolutionError::BlackBoxFunctionFailed( + acir::BlackBoxFunc::EcdsaSecp256r1, + format!("expected signature size 64 but received {}", signature_inputs.len()), + ) + })?; + + let is_valid = ecdsa_secp256r1_verify(&hashed_message, &pub_key_x, &pub_key_y, &signature)?; + + insert_value(&output, FieldElement::from(is_valid), initial_witness)?; + Ok(()) +} diff --git a/acvm-repo/acvm/src/pwg/blackbox/signature/mod.rs b/acvm-repo/acvm/src/pwg/blackbox/signature/mod.rs new file mode 100644 index 00000000000..0e28a63ff68 --- /dev/null +++ b/acvm-repo/acvm/src/pwg/blackbox/signature/mod.rs @@ -0,0 +1,21 @@ +use acir::{circuit::opcodes::FunctionInput, native_types::WitnessMap}; + +use crate::pwg::{witness_to_value, OpcodeResolutionError}; + +fn to_u8_vec( + initial_witness: &WitnessMap, + inputs: &[FunctionInput], +) -> Result, OpcodeResolutionError> { + let mut result = Vec::with_capacity(inputs.len()); + for input in inputs { + let witness_value_bytes = witness_to_value(initial_witness, input.witness)?.to_be_bytes(); + let byte = witness_value_bytes + .last() + .expect("Field element must be represented by non-zero amount of bytes"); + result.push(*byte); + } + Ok(result) +} + +pub(super) mod ecdsa; +pub(super) mod schnorr; diff --git a/acvm-repo/acvm/src/pwg/blackbox/signature/schnorr.rs b/acvm-repo/acvm/src/pwg/blackbox/signature/schnorr.rs new file mode 100644 index 00000000000..7f5381cee91 --- /dev/null +++ b/acvm-repo/acvm/src/pwg/blackbox/signature/schnorr.rs @@ -0,0 +1,35 @@ +use super::to_u8_vec; +use crate::{ + pwg::{insert_value, witness_to_value, OpcodeResolutionError}, + BlackBoxFunctionSolver, +}; +use acir::{ + circuit::opcodes::FunctionInput, + native_types::{Witness, WitnessMap}, + FieldElement, +}; + +#[allow(clippy::too_many_arguments)] +pub(crate) fn schnorr_verify( + backend: &impl BlackBoxFunctionSolver, + initial_witness: &mut WitnessMap, + public_key_x: FunctionInput, + public_key_y: FunctionInput, + signature: &[FunctionInput], + message: &[FunctionInput], + output: Witness, +) -> Result<(), OpcodeResolutionError> { + let public_key_x: &FieldElement = witness_to_value(initial_witness, public_key_x.witness)?; + let public_key_y: &FieldElement = witness_to_value(initial_witness, public_key_y.witness)?; + + let signature = to_u8_vec(initial_witness, signature)?; + + let message = to_u8_vec(initial_witness, message)?; + + let valid_signature = + backend.schnorr_verify(public_key_x, public_key_y, &signature, &message)?; + + insert_value(&output, FieldElement::from(valid_signature), initial_witness)?; + + Ok(()) +} diff --git a/acvm-repo/acvm/src/pwg/brillig.rs b/acvm-repo/acvm/src/pwg/brillig.rs new file mode 100644 index 00000000000..8662dab71e1 --- /dev/null +++ b/acvm-repo/acvm/src/pwg/brillig.rs @@ -0,0 +1,163 @@ +use acir::{ + brillig::{RegisterIndex, Value}, + circuit::{ + brillig::{Brillig, BrilligInputs, BrilligOutputs}, + OpcodeLocation, + }, + native_types::WitnessMap, + FieldElement, +}; +use acvm_blackbox_solver::BlackBoxFunctionSolver; +use brillig_vm::{Registers, VMStatus, VM}; + +use crate::{pwg::OpcodeNotSolvable, OpcodeResolutionError}; + +use super::{get_value, insert_value}; + +pub(super) struct BrilligSolver; + +impl BrilligSolver { + pub(super) fn solve( + initial_witness: &mut WitnessMap, + brillig: &Brillig, + bb_solver: &B, + acir_index: usize, + ) -> Result, OpcodeResolutionError> { + // If the predicate is `None`, then we simply return the value 1 + // If the predicate is `Some` but we cannot find a value, then we return stalled + let pred_value = match &brillig.predicate { + Some(pred) => get_value(pred, initial_witness), + None => Ok(FieldElement::one()), + }?; + + // A zero predicate indicates the oracle should be skipped, and its outputs zeroed. + if pred_value.is_zero() { + Self::zero_out_brillig_outputs(initial_witness, brillig)?; + return Ok(None); + } + + // Set input values + let mut input_register_values: Vec = Vec::new(); + let mut input_memory: Vec = Vec::new(); + // Each input represents an expression or array of expressions to evaluate. + // Iterate over each input and evaluate the expression(s) associated with it. + // Push the results into registers and/or memory. + // If a certain expression is not solvable, we stall the ACVM and do not proceed with Brillig VM execution. + for input in &brillig.inputs { + match input { + BrilligInputs::Single(expr) => match get_value(expr, initial_witness) { + Ok(value) => input_register_values.push(value.into()), + Err(_) => { + return Err(OpcodeResolutionError::OpcodeNotSolvable( + OpcodeNotSolvable::ExpressionHasTooManyUnknowns(expr.clone()), + )) + } + }, + BrilligInputs::Array(expr_arr) => { + // Attempt to fetch all array input values + let memory_pointer = input_memory.len(); + for expr in expr_arr.iter() { + match get_value(expr, initial_witness) { + Ok(value) => input_memory.push(value.into()), + Err(_) => { + return Err(OpcodeResolutionError::OpcodeNotSolvable( + OpcodeNotSolvable::ExpressionHasTooManyUnknowns(expr.clone()), + )) + } + } + } + + // Push value of the array pointer as a register + input_register_values.push(Value::from(memory_pointer)); + } + } + } + + // Instantiate a Brillig VM given the solved input registers and memory + // along with the Brillig bytecode, and any present foreign call results. + let input_registers = Registers::load(input_register_values); + let mut vm = VM::new( + input_registers, + input_memory, + brillig.bytecode.clone(), + brillig.foreign_call_results.clone(), + bb_solver, + ); + + // Run the Brillig VM on these inputs, bytecode, etc! + let vm_status = vm.process_opcodes(); + + // Check the status of the Brillig VM. + // It may be finished, in-progress, failed, or may be waiting for results of a foreign call. + // Return the "resolution" to the caller who may choose to make subsequent calls + // (when it gets foreign call results for example). + match vm_status { + VMStatus::Finished => { + for (i, output) in brillig.outputs.iter().enumerate() { + let register_value = vm.get_registers().get(RegisterIndex::from(i)); + match output { + BrilligOutputs::Simple(witness) => { + insert_value(witness, register_value.to_field(), initial_witness)?; + } + BrilligOutputs::Array(witness_arr) => { + // Treat the register value as a pointer to memory + for (i, witness) in witness_arr.iter().enumerate() { + let value = &vm.get_memory()[register_value.to_usize() + i]; + insert_value(witness, value.to_field(), initial_witness)?; + } + } + } + } + Ok(None) + } + VMStatus::InProgress => unreachable!("Brillig VM has not completed execution"), + VMStatus::Failure { message, call_stack } => { + Err(OpcodeResolutionError::BrilligFunctionFailed { + message, + call_stack: call_stack + .iter() + .map(|brillig_index| OpcodeLocation::Brillig { + acir_index, + brillig_index: *brillig_index, + }) + .collect(), + }) + } + VMStatus::ForeignCallWait { function, inputs } => { + Ok(Some(ForeignCallWaitInfo { function, inputs })) + } + } + } + + /// Assigns the zero value to all outputs of the given [`Brillig`] bytecode. + fn zero_out_brillig_outputs( + initial_witness: &mut WitnessMap, + brillig: &Brillig, + ) -> Result<(), OpcodeResolutionError> { + for output in &brillig.outputs { + match output { + BrilligOutputs::Simple(witness) => { + insert_value(witness, FieldElement::zero(), initial_witness)? + } + BrilligOutputs::Array(witness_arr) => { + for witness in witness_arr { + insert_value(witness, FieldElement::zero(), initial_witness)? + } + } + } + } + Ok(()) + } +} + +/// Encapsulates a request from a Brillig VM process that encounters a [foreign call opcode][acir::brillig_vm::Opcode::ForeignCall] +/// where the result of the foreign call has not yet been provided. +/// +/// The caller must resolve this opcode externally based upon the information in the request. +#[derive(Debug, PartialEq, Clone)] +pub struct ForeignCallWaitInfo { + /// An identifier interpreted by the caller process + pub function: String, + /// Resolved inputs to a foreign call computed in the previous steps of a Brillig VM process + pub inputs: Vec>, +} diff --git a/acvm-repo/acvm/src/pwg/directives/mod.rs b/acvm-repo/acvm/src/pwg/directives/mod.rs new file mode 100644 index 00000000000..d7dbb3edaf2 --- /dev/null +++ b/acvm-repo/acvm/src/pwg/directives/mod.rs @@ -0,0 +1,150 @@ +use std::cmp::Ordering; + +use acir::{ + circuit::directives::{Directive, QuotientDirective}, + native_types::WitnessMap, + FieldElement, +}; +use num_bigint::BigUint; +use num_traits::Zero; + +use crate::OpcodeResolutionError; + +use super::{get_value, insert_value, ErrorLocation}; + +mod sorting; + +/// Attempts to solve the [`Directive`] opcode `directive`. +/// If successful, `initial_witness` will be mutated to contain the new witness assignment. +/// +/// Returns `Ok(OpcodeResolution)` to signal whether the directive was successful solved. +/// +/// Returns `Err(OpcodeResolutionError)` if a circuit constraint is unsatisfied. +pub(super) fn solve_directives( + initial_witness: &mut WitnessMap, + directive: &Directive, +) -> Result<(), OpcodeResolutionError> { + match directive { + Directive::Quotient(QuotientDirective { a, b, q, r, predicate }) => { + let val_a = get_value(a, initial_witness)?; + let val_b = get_value(b, initial_witness)?; + let int_a = BigUint::from_bytes_be(&val_a.to_be_bytes()); + let int_b = BigUint::from_bytes_be(&val_b.to_be_bytes()); + + // If the predicate is `None`, then we simply return the value 1 + // If the predicate is `Some` but we cannot find a value, then we return unresolved + let pred_value = match predicate { + Some(pred) => get_value(pred, initial_witness)?, + None => FieldElement::one(), + }; + + let (int_r, int_q) = if pred_value.is_zero() || int_b.is_zero() { + (BigUint::zero(), BigUint::zero()) + } else { + (&int_a % &int_b, &int_a / &int_b) + }; + + insert_value( + q, + FieldElement::from_be_bytes_reduce(&int_q.to_bytes_be()), + initial_witness, + )?; + insert_value( + r, + FieldElement::from_be_bytes_reduce(&int_r.to_bytes_be()), + initial_witness, + )?; + + Ok(()) + } + Directive::ToLeRadix { a, b, radix } => { + let value_a = get_value(a, initial_witness)?; + let big_integer = BigUint::from_bytes_be(&value_a.to_be_bytes()); + + // Decompose the integer into its radix digits in little endian form. + let decomposed_integer = big_integer.to_radix_le(*radix); + + if b.len() < decomposed_integer.len() { + return Err(OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: ErrorLocation::Unresolved, + }); + } + + for (i, witness) in b.iter().enumerate() { + // Fetch the `i'th` digit from the decomposed integer list + // and convert it to a field element. + // If it is not available, which can happen when the decomposed integer + // list is shorter than the witness list, we return 0. + let value = match decomposed_integer.get(i) { + Some(digit) => FieldElement::from_be_bytes_reduce(&[*digit]), + None => FieldElement::zero(), + }; + + insert_value(witness, value, initial_witness)? + } + + Ok(()) + } + Directive::PermutationSort { inputs: a, tuple, bits, sort_by } => { + let mut val_a = Vec::new(); + let mut base = Vec::new(); + for (i, element) in a.iter().enumerate() { + assert_eq!(element.len(), *tuple as usize); + let mut element_val = Vec::with_capacity(*tuple as usize + 1); + for e in element { + element_val.push(get_value(e, initial_witness)?); + } + let field_i = FieldElement::from(i as i128); + element_val.push(field_i); + base.push(field_i); + val_a.push(element_val); + } + val_a.sort_by(|a, b| { + for i in sort_by { + let int_a = BigUint::from_bytes_be(&a[*i as usize].to_be_bytes()); + let int_b = BigUint::from_bytes_be(&b[*i as usize].to_be_bytes()); + let cmp = int_a.cmp(&int_b); + if cmp != Ordering::Equal { + return cmp; + } + } + Ordering::Equal + }); + let b = val_a.iter().map(|a| *a.last().unwrap()).collect(); + let control = sorting::route(base, b); + for (w, value) in bits.iter().zip(control) { + let value = if value { FieldElement::one() } else { FieldElement::zero() }; + insert_value(w, value, initial_witness)?; + } + Ok(()) + } + } +} + +#[cfg(test)] +mod tests { + use acir::{ + circuit::directives::{Directive, QuotientDirective}, + native_types::{Expression, Witness, WitnessMap}, + FieldElement, + }; + + use super::solve_directives; + + #[test] + fn divisor_is_zero() { + let quotient_directive = QuotientDirective { + a: Expression::zero(), + b: Expression::zero(), + q: Witness(0), + r: Witness(0), + predicate: Some(Expression::one()), + }; + + let mut witness_map = WitnessMap::new(); + witness_map.insert(Witness(0), FieldElement::zero()); + + solve_directives(&mut witness_map, &Directive::Quotient(quotient_directive)) + .expect("expected 0/0 to return 0"); + } +} diff --git a/acvm-repo/acvm/src/pwg/directives/sorting.rs b/acvm-repo/acvm/src/pwg/directives/sorting.rs new file mode 100644 index 00000000000..5ff43320226 --- /dev/null +++ b/acvm-repo/acvm/src/pwg/directives/sorting.rs @@ -0,0 +1,396 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use acir::FieldElement; + +// A sorting network is a graph of connected switches +// It is defined recursively so here we only keep track of the outer layer of switches +struct SortingNetwork { + n: usize, // size of the network + x_inputs: Vec, // inputs of the network + y_inputs: Vec, // outputs of the network + x_values: BTreeMap, // map for matching a y value with a x value + y_values: BTreeMap, // map for matching a x value with a y value + inner_x: Vec, // positions after the switch_x + inner_y: Vec, // positions after the sub-networks, and before the switch_y + switch_x: Vec, // outer switches for the inputs + switch_y: Vec, // outer switches for the outputs + free: BTreeSet, // outer switches available for looping +} + +impl SortingNetwork { + fn new(n: usize) -> SortingNetwork { + let free_len = (n - 1) / 2; + let mut free = BTreeSet::new(); + for i in 0..free_len { + free.insert(i); + } + SortingNetwork { + n, + x_inputs: Vec::with_capacity(n), + y_inputs: Vec::with_capacity(n), + x_values: BTreeMap::new(), + y_values: BTreeMap::new(), + inner_x: Vec::with_capacity(n), + inner_y: Vec::with_capacity(n), + switch_x: Vec::with_capacity(n / 2), + switch_y: Vec::with_capacity(free_len), + free, + } + } + + fn init(&mut self, inputs: Vec, outputs: Vec) { + let n = self.n; + assert_eq!(inputs.len(), outputs.len()); + assert_eq!(inputs.len(), n); + + self.x_inputs = inputs; + self.y_inputs = outputs; + for i in 0..self.n { + self.x_values.insert(self.x_inputs[i], i); + self.y_values.insert(self.y_inputs[i], i); + } + self.switch_x = vec![false; n / 2]; + self.switch_y = vec![false; (n - 1) / 2]; + self.inner_x = vec![FieldElement::zero(); n]; + self.inner_y = vec![FieldElement::zero(); n]; + + //Route the single wires so we do not need to handle this case later on + self.inner_y[n - 1] = self.y_inputs[n - 1]; + if n % 2 == 0 { + self.inner_y[n / 2 - 1] = self.y_inputs[n - 2]; + } else { + self.inner_x[n - 1] = self.x_inputs[n - 1]; + } + } + + //route a wire from outputs to its value in the inputs + fn route_out_wire(&mut self, y: usize, sub: bool) -> usize { + // sub <- y + if self.is_single_y(y) { + assert!(sub); + } else { + let port = y % 2 != 0; + let s1 = sub ^ port; + let inner = self.compute_inner(y, s1); + self.configure_y(y, s1, inner); + } + // x <- sub + let x = self.x_values.remove(&self.y_inputs[y]).unwrap(); + if !self.is_single_x(x) { + let port2 = x % 2 != 0; + let s2 = sub ^ port2; + let inner = self.compute_inner(x, s2); + self.configure_x(x, s2, inner); + } + x + } + + //route a wire from inputs to its value in the outputs + fn route_in_wire(&mut self, x: usize, sub: bool) -> usize { + // x -> sub + assert!(!self.is_single_x(x)); + let port = x % 2 != 0; + let s1 = sub ^ port; + let inner = self.compute_inner(x, s1); + self.configure_x(x, s1, inner); + + // sub -> y + let y = self.y_values.remove(&self.x_inputs[x]).unwrap(); + if !self.is_single_y(y) { + let port = y % 2 != 0; + let s2 = sub ^ port; + let inner = self.compute_inner(y, s2); + self.configure_y(y, s2, inner); + } + y + } + + //update the computed switch and inner values for an input wire + fn configure_x(&mut self, x: usize, switch: bool, inner: usize) { + self.inner_x[inner] = self.x_inputs[x]; + self.switch_x[x / 2] = switch; + } + + //update the computed switch and inner values for an output wire + fn configure_y(&mut self, y: usize, switch: bool, inner: usize) { + self.inner_y[inner] = self.y_inputs[y]; + self.switch_y[y / 2] = switch; + } + + // returns the other wire belonging to the same switch + fn sibling(index: usize) -> usize { + index + 1 - 2 * (index % 2) + } + + // returns a free switch + fn take(&mut self) -> Option { + self.free.first().copied() + } + + fn is_single_x(&self, a: usize) -> bool { + let n = self.x_inputs.len(); + n % 2 == 1 && a == n - 1 + } + + fn is_single_y(&mut self, a: usize) -> bool { + let n = self.x_inputs.len(); + a >= n - 2 + n % 2 + } + + // compute the inner position of idx through its switch + fn compute_inner(&self, idx: usize, switch: bool) -> usize { + if switch ^ (idx % 2 == 1) { + idx / 2 + self.n / 2 + } else { + idx / 2 + } + } + + fn new_start(&mut self) -> (Option, usize) { + let next = self.take(); + if let Some(switch) = next { + (next, 2 * switch) + } else { + (None, 0) + } + } +} + +// Computes the control bits of the sorting network which transform inputs into outputs +// implementation is based on https://www.mdpi.com/2227-7080/10/1/16 +pub(super) fn route(inputs: Vec, outputs: Vec) -> Vec { + assert_eq!(inputs.len(), outputs.len()); + match inputs.len() { + 0 => Vec::new(), + 1 => { + assert_eq!(inputs[0], outputs[0]); + Vec::new() + } + 2 => { + if inputs[0] == outputs[0] { + assert_eq!(inputs[1], outputs[1]); + vec![false] + } else { + assert_eq!(inputs[1], outputs[0]); + assert_eq!(inputs[0], outputs[1]); + vec![true] + } + } + _ => { + let n = inputs.len(); + + let mut result; + let n1 = n / 2; + let in_sub1; + let out_sub1; + let in_sub2; + let out_sub2; + + // process the outer layer in a code block so that the intermediate data is cleared before recursion + { + let mut network = SortingNetwork::new(n); + network.init(inputs, outputs); + + //We start with the last single wire + let mut out_idx = n - 1; + let mut start_sub = true; //it is connected to the lower inner network + let mut switch = None; + let mut start = None; + + while !network.free.is_empty() { + // the processed switch is no more available + if let Some(free_switch) = switch { + network.free.remove(&free_switch); + } + + // connect the output wire to its matching input + let in_idx = network.route_out_wire(out_idx, start_sub); + if network.is_single_x(in_idx) { + start_sub = !start_sub; //We need to restart, but did not complete the loop so we switch the sub network + (start, out_idx) = network.new_start(); + switch = start; + continue; + } + + // loop from the sibling + let next = SortingNetwork::sibling(in_idx); + // connect the input wire to its matching output, using the other sub-network + out_idx = network.route_in_wire(next, !start_sub); + switch = Some(out_idx / 2); + if start == switch || network.is_single_y(out_idx) { + //loop is complete, need a fresh start + (start, out_idx) = network.new_start(); + switch = start; + } else { + // we loop back from the sibling + out_idx = SortingNetwork::sibling(out_idx); + } + } + //All the wires are connected, we can now route the sub-networks + result = network.switch_x; + result.extend(network.switch_y); + in_sub1 = network.inner_x[0..n1].to_vec(); + in_sub2 = network.inner_x[n1..].to_vec(); + out_sub1 = network.inner_y[0..n1].to_vec(); + out_sub2 = network.inner_y[n1..].to_vec(); + } + let s1 = route(in_sub1, out_sub1); + result.extend(s1); + let s2 = route(in_sub2, out_sub2); + result.extend(s2); + result + } + } +} + +#[cfg(test)] +mod tests { + // Silence `unused_crate_dependencies` warning + use paste as _; + use proptest as _; + + use super::route; + use acir::FieldElement; + use rand::prelude::*; + + fn execute_network(config: Vec, inputs: Vec) -> Vec { + let n = inputs.len(); + if n == 1 { + return inputs; + } + let mut in1 = Vec::new(); + let mut in2 = Vec::new(); + //layer 1: + for i in 0..n / 2 { + if config[i] { + in1.push(inputs[2 * i + 1]); + in2.push(inputs[2 * i]); + } else { + in1.push(inputs[2 * i]); + in2.push(inputs[2 * i + 1]); + } + } + if n % 2 == 1 { + in2.push(*inputs.last().unwrap()); + } + let n2 = n / 2 + (n - 1) / 2; + let n3 = n2 + switch_nb(n / 2); + let mut result = Vec::new(); + let out1 = execute_network(config[n2..n3].to_vec(), in1); + let out2 = execute_network(config[n3..].to_vec(), in2); + //last layer: + for i in 0..(n - 1) / 2 { + if config[n / 2 + i] { + result.push(out2[i]); + result.push(out1[i]); + } else { + result.push(out1[i]); + result.push(out2[i]); + } + } + if n % 2 == 0 { + result.push(*out1.last().unwrap()); + result.push(*out2.last().unwrap()); + } else { + result.push(*out2.last().unwrap()) + } + result + } + + // returns the number of switches in the network + fn switch_nb(n: usize) -> usize { + let mut s = 0; + for i in 0..n { + s += f64::from((i + 1) as u32).log2().ceil() as usize; + } + s + } + + #[test] + fn test_route() { + //basic tests + let a = vec![ + FieldElement::from(1_i128), + FieldElement::from(2_i128), + FieldElement::from(3_i128), + ]; + let b = vec![ + FieldElement::from(1_i128), + FieldElement::from(2_i128), + FieldElement::from(3_i128), + ]; + let c = route(a, b); + assert_eq!(c, vec![false, false, false]); + + let a = vec![ + FieldElement::from(1_i128), + FieldElement::from(2_i128), + FieldElement::from(3_i128), + ]; + let b = vec![ + FieldElement::from(1_i128), + FieldElement::from(3_i128), + FieldElement::from(2_i128), + ]; + let c = route(a, b); + assert_eq!(c, vec![false, false, true]); + + let a = vec![ + FieldElement::from(1_i128), + FieldElement::from(2_i128), + FieldElement::from(3_i128), + ]; + let b = vec![ + FieldElement::from(3_i128), + FieldElement::from(2_i128), + FieldElement::from(1_i128), + ]; + let c = route(a, b); + assert_eq!(c, vec![true, true, true]); + + let a = vec![ + FieldElement::from(0_i128), + FieldElement::from(1_i128), + FieldElement::from(2_i128), + FieldElement::from(3_i128), + ]; + let b = vec![ + FieldElement::from(2_i128), + FieldElement::from(3_i128), + FieldElement::from(0_i128), + FieldElement::from(1_i128), + ]; + let c = route(a, b); + assert_eq!(c, vec![false, true, true, true, true]); + + let a = vec![ + FieldElement::from(0_i128), + FieldElement::from(1_i128), + FieldElement::from(2_i128), + FieldElement::from(3_i128), + FieldElement::from(4_i128), + ]; + let b = vec![ + FieldElement::from(0_i128), + FieldElement::from(3_i128), + FieldElement::from(4_i128), + FieldElement::from(2_i128), + FieldElement::from(1_i128), + ]; + let c = route(a, b); + assert_eq!(c, vec![false, false, false, true, false, true, false, true]); + + // random tests + for i in 2..50 { + let mut a = vec![FieldElement::zero()]; + for j in 0..i - 1 { + a.push(a[j] + FieldElement::one()); + } + let mut rng = rand::thread_rng(); + let mut b = a.clone(); + b.shuffle(&mut rng); + let c = route(a.clone(), b.clone()); + assert_eq!(b, execute_network(c, a)); + } + } +} diff --git a/acvm-repo/acvm/src/pwg/memory_op.rs b/acvm-repo/acvm/src/pwg/memory_op.rs new file mode 100644 index 00000000000..42951dfa3c1 --- /dev/null +++ b/acvm-repo/acvm/src/pwg/memory_op.rs @@ -0,0 +1,259 @@ +use std::collections::HashMap; + +use acir::{ + circuit::opcodes::MemOp, + native_types::{Expression, Witness, WitnessMap}, + FieldElement, +}; + +use super::{arithmetic::ArithmeticSolver, get_value, insert_value, witness_to_value}; +use super::{ErrorLocation, OpcodeResolutionError}; + +type MemoryIndex = u32; + +/// Maintains the state for solving [`MemoryInit`][`acir::circuit::Opcode::MemoryInit`] and [`MemoryOp`][`acir::circuit::Opcode::MemoryOp`] opcodes. +#[derive(Default)] +pub(super) struct MemoryOpSolver { + block_value: HashMap, + block_len: u32, +} + +impl MemoryOpSolver { + fn write_memory_index( + &mut self, + index: MemoryIndex, + value: FieldElement, + ) -> Result<(), OpcodeResolutionError> { + if index >= self.block_len { + return Err(OpcodeResolutionError::IndexOutOfBounds { + opcode_location: ErrorLocation::Unresolved, + index, + array_size: self.block_len, + }); + } + self.block_value.insert(index, value); + Ok(()) + } + + fn read_memory_index(&self, index: MemoryIndex) -> Result { + self.block_value.get(&index).copied().ok_or(OpcodeResolutionError::IndexOutOfBounds { + opcode_location: ErrorLocation::Unresolved, + index, + array_size: self.block_len, + }) + } + + /// Set the block_value from a MemoryInit opcode + pub(crate) fn init( + &mut self, + init: &[Witness], + initial_witness: &WitnessMap, + ) -> Result<(), OpcodeResolutionError> { + self.block_len = init.len() as u32; + for (memory_index, witness) in init.iter().enumerate() { + self.write_memory_index( + memory_index as MemoryIndex, + *witness_to_value(initial_witness, *witness)?, + )?; + } + Ok(()) + } + + pub(crate) fn solve_memory_op( + &mut self, + op: &MemOp, + initial_witness: &mut WitnessMap, + predicate: &Option, + ) -> Result<(), OpcodeResolutionError> { + let operation = get_value(&op.operation, initial_witness)?; + + // Find the memory index associated with this memory operation. + let index = get_value(&op.index, initial_witness)?; + let memory_index = index.try_to_u64().unwrap() as MemoryIndex; + + // Calculate the value associated with this memory operation. + // + // In read operations, this corresponds to the witness index at which the value from memory will be written. + // In write operations, this corresponds to the expression which will be written to memory. + let value = ArithmeticSolver::evaluate(&op.value, initial_witness); + + // `operation == 0` implies a read operation. (`operation == 1` implies write operation). + let is_read_operation = operation.is_zero(); + + // If the predicate is `None`, then we simply return the value 1 + let pred_value = match predicate { + Some(pred) => get_value(pred, initial_witness), + None => Ok(FieldElement::one()), + }?; + + if is_read_operation { + // `value_read = arr[memory_index]` + // + // This is the value that we want to read into; i.e. copy from the memory block + // into this value. + let value_read_witness = value.to_witness().expect( + "Memory must be read into a specified witness index, encountered an Expression", + ); + + // A zero predicate indicates that we should skip the read operation + // and zero out the operation's output. + let value_in_array = if pred_value.is_zero() { + FieldElement::zero() + } else { + self.read_memory_index(memory_index)? + }; + insert_value(&value_read_witness, value_in_array, initial_witness) + } else { + // `arr[memory_index] = value_write` + // + // This is the value that we want to write into; i.e. copy from `value_write` + // into the memory block. + let value_write = value; + + // A zero predicate indicates that we should skip the write operation. + if pred_value.is_zero() { + // We only want to write to already initialized memory. + // Do nothing if the predicate is zero. + Ok(()) + } else { + let value_to_write = get_value(&value_write, initial_witness)?; + self.write_memory_index(memory_index, value_to_write) + } + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use acir::{ + circuit::opcodes::MemOp, + native_types::{Expression, Witness, WitnessMap}, + FieldElement, + }; + + use super::MemoryOpSolver; + + #[test] + fn test_solver() { + let mut initial_witness = WitnessMap::from(BTreeMap::from_iter([ + (Witness(1), FieldElement::from(1u128)), + (Witness(2), FieldElement::from(1u128)), + (Witness(3), FieldElement::from(2u128)), + ])); + + let init = vec![Witness(1), Witness(2)]; + + let trace = vec![ + MemOp::write_to_mem_index(FieldElement::from(1u128).into(), Witness(3).into()), + MemOp::read_at_mem_index(FieldElement::one().into(), Witness(4)), + ]; + + let mut block_solver = MemoryOpSolver::default(); + block_solver.init(&init, &initial_witness).unwrap(); + + for op in trace { + block_solver.solve_memory_op(&op, &mut initial_witness, &None).unwrap(); + } + + assert_eq!(initial_witness[&Witness(4)], FieldElement::from(2u128)); + } + + #[test] + fn test_index_out_of_bounds() { + let mut initial_witness = WitnessMap::from(BTreeMap::from_iter([ + (Witness(1), FieldElement::from(1u128)), + (Witness(2), FieldElement::from(1u128)), + (Witness(3), FieldElement::from(2u128)), + ])); + + let init = vec![Witness(1), Witness(2)]; + + let invalid_trace = vec![ + MemOp::write_to_mem_index(FieldElement::from(1u128).into(), Witness(3).into()), + MemOp::read_at_mem_index(FieldElement::from(2u128).into(), Witness(4)), + ]; + let mut block_solver = MemoryOpSolver::default(); + block_solver.init(&init, &initial_witness).unwrap(); + let mut err = None; + for op in invalid_trace { + if err.is_none() { + err = block_solver.solve_memory_op(&op, &mut initial_witness, &None).err(); + } + } + + assert!(matches!( + err, + Some(crate::pwg::OpcodeResolutionError::IndexOutOfBounds { + opcode_location: _, + index: 2, + array_size: 2 + }) + )); + } + + #[test] + fn test_predicate_on_read() { + let mut initial_witness = WitnessMap::from(BTreeMap::from_iter([ + (Witness(1), FieldElement::from(1u128)), + (Witness(2), FieldElement::from(1u128)), + (Witness(3), FieldElement::from(2u128)), + ])); + + let init = vec![Witness(1), Witness(2)]; + + let invalid_trace = vec![ + MemOp::write_to_mem_index(FieldElement::from(1u128).into(), Witness(3).into()), + MemOp::read_at_mem_index(FieldElement::from(2u128).into(), Witness(4)), + ]; + let mut block_solver = MemoryOpSolver::default(); + block_solver.init(&init, &initial_witness).unwrap(); + let mut err = None; + for op in invalid_trace { + if err.is_none() { + err = block_solver + .solve_memory_op(&op, &mut initial_witness, &Some(Expression::zero())) + .err(); + } + } + + // Should have no index out of bounds error where predicate is zero + assert_eq!(err, None); + // The result of a read under a zero predicate should be zero + assert_eq!(initial_witness[&Witness(4)], FieldElement::from(0u128)); + } + + #[test] + fn test_predicate_on_write() { + let mut initial_witness = WitnessMap::from(BTreeMap::from_iter([ + (Witness(1), FieldElement::from(1u128)), + (Witness(2), FieldElement::from(1u128)), + (Witness(3), FieldElement::from(2u128)), + ])); + + let init = vec![Witness(1), Witness(2)]; + + let invalid_trace = vec![ + MemOp::write_to_mem_index(FieldElement::from(2u128).into(), Witness(3).into()), + MemOp::read_at_mem_index(FieldElement::from(0u128).into(), Witness(4)), + MemOp::read_at_mem_index(FieldElement::from(1u128).into(), Witness(5)), + ]; + let mut block_solver = MemoryOpSolver::default(); + block_solver.init(&init, &initial_witness).unwrap(); + let mut err = None; + for op in invalid_trace { + if err.is_none() { + err = block_solver + .solve_memory_op(&op, &mut initial_witness, &Some(Expression::zero())) + .err(); + } + } + + // Should have no index out of bounds error where predicate is zero + assert_eq!(err, None); + // The memory under a zero predicate should be zeroed out + assert_eq!(initial_witness[&Witness(4)], FieldElement::from(0u128)); + assert_eq!(initial_witness[&Witness(5)], FieldElement::from(0u128)); + } +} diff --git a/acvm-repo/acvm/src/pwg/mod.rs b/acvm-repo/acvm/src/pwg/mod.rs new file mode 100644 index 00000000000..3fcf1088225 --- /dev/null +++ b/acvm-repo/acvm/src/pwg/mod.rs @@ -0,0 +1,399 @@ +// Re-usable methods that backends can use to implement their PWG + +use std::collections::HashMap; + +use acir::{ + brillig::ForeignCallResult, + circuit::{opcodes::BlockId, Opcode, OpcodeLocation}, + native_types::{Expression, Witness, WitnessMap}, + BlackBoxFunc, FieldElement, +}; +use acvm_blackbox_solver::BlackBoxResolutionError; + +use self::{ + arithmetic::ArithmeticSolver, brillig::BrilligSolver, directives::solve_directives, + memory_op::MemoryOpSolver, +}; +use crate::{BlackBoxFunctionSolver, Language}; + +use thiserror::Error; + +// arithmetic +pub(crate) mod arithmetic; +// Brillig bytecode +mod brillig; +// Directives +mod directives; +// black box functions +mod blackbox; +mod memory_op; + +pub use brillig::ForeignCallWaitInfo; + +#[derive(Debug, Clone, PartialEq)] +pub enum ACVMStatus { + /// All opcodes have been solved. + Solved, + + /// The ACVM is in the process of executing the circuit. + InProgress, + + /// The ACVM has encountered an irrecoverable error while executing the circuit and can not progress. + /// Most commonly this will be due to an unsatisfied constraint due to invalid inputs to the circuit. + Failure(OpcodeResolutionError), + + /// The ACVM has encountered a request for a Brillig [foreign call][acir::brillig_vm::Opcode::ForeignCall] + /// to retrieve information from outside of the ACVM. The result of the foreign call must be passed back + /// to the ACVM using [`ACVM::resolve_pending_foreign_call`]. + /// + /// Once this is done, the ACVM can be restarted to solve the remaining opcodes. + RequiresForeignCall(ForeignCallWaitInfo), +} + +impl std::fmt::Display for ACVMStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ACVMStatus::Solved => write!(f, "Solved"), + ACVMStatus::InProgress => write!(f, "In progress"), + ACVMStatus::Failure(_) => write!(f, "Execution failure"), + ACVMStatus::RequiresForeignCall(_) => write!(f, "Waiting on foreign call"), + } + } +} + +// This enum represents the different cases in which an +// opcode can be unsolvable. +// The most common being that one of its input has not been +// assigned a value. +// +// TODO: ExpressionHasTooManyUnknowns is specific for arithmetic expressions +// TODO: we could have a error enum for arithmetic failure cases in that module +// TODO that can be converted into an OpcodeNotSolvable or OpcodeResolutionError enum +#[derive(Clone, PartialEq, Eq, Debug, Error)] +pub enum OpcodeNotSolvable { + #[error("missing assignment for witness index {0}")] + MissingAssignment(u32), + #[error("expression has too many unknowns {0}")] + ExpressionHasTooManyUnknowns(Expression), +} + +/// Allows to point to a specific opcode as cause in errors. +/// Some errors don't have a specific opcode associated with them, or are created without one and added later. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum ErrorLocation { + #[default] + Unresolved, + Resolved(OpcodeLocation), +} + +impl std::fmt::Display for ErrorLocation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ErrorLocation::Unresolved => write!(f, "unresolved"), + ErrorLocation::Resolved(location) => { + write!(f, "{location}") + } + } + } +} + +#[derive(Clone, PartialEq, Eq, Debug, Error)] +pub enum OpcodeResolutionError { + #[error("Cannot solve opcode: {0}")] + OpcodeNotSolvable(#[from] OpcodeNotSolvable), + #[error("Backend does not currently support the {0} opcode. ACVM does not currently have a fallback for this opcode.")] + UnsupportedBlackBoxFunc(BlackBoxFunc), + #[error("Cannot satisfy constraint")] + UnsatisfiedConstrain { opcode_location: ErrorLocation }, + #[error("Index out of bounds, array has size {array_size:?}, but index was {index:?}")] + IndexOutOfBounds { opcode_location: ErrorLocation, index: u32, array_size: u32 }, + #[error("Failed to solve blackbox function: {0}, reason: {1}")] + BlackBoxFunctionFailed(BlackBoxFunc, String), + #[error("Failed to solve brillig function, reason: {message}")] + BrilligFunctionFailed { message: String, call_stack: Vec }, +} + +impl From for OpcodeResolutionError { + fn from(value: BlackBoxResolutionError) -> Self { + match value { + BlackBoxResolutionError::Failed(func, reason) => { + OpcodeResolutionError::BlackBoxFunctionFailed(func, reason) + } + BlackBoxResolutionError::Unsupported(func) => { + OpcodeResolutionError::UnsupportedBlackBoxFunc(func) + } + } + } +} + +pub struct ACVM<'backend, B: BlackBoxFunctionSolver> { + status: ACVMStatus, + + backend: &'backend B, + + /// Stores the solver for memory operations acting on blocks of memory disambiguated by [block][`BlockId`]. + block_solvers: HashMap, + + /// A list of opcodes which are to be executed by the ACVM. + opcodes: Vec, + /// Index of the next opcode to be executed. + instruction_pointer: usize, + + witness_map: WitnessMap, +} + +impl<'backend, B: BlackBoxFunctionSolver> ACVM<'backend, B> { + pub fn new(backend: &'backend B, opcodes: Vec, initial_witness: WitnessMap) -> Self { + let status = if opcodes.is_empty() { ACVMStatus::Solved } else { ACVMStatus::InProgress }; + ACVM { + status, + backend, + block_solvers: HashMap::default(), + opcodes, + instruction_pointer: 0, + witness_map: initial_witness, + } + } + + /// Returns a reference to the current state of the ACVM's [`WitnessMap`]. + /// + /// Once execution has completed, the witness map can be extracted using [`ACVM::finalize`] + pub fn witness_map(&self) -> &WitnessMap { + &self.witness_map + } + + /// Returns a slice containing the opcodes of the circuit being executed. + pub fn opcodes(&self) -> &[Opcode] { + &self.opcodes + } + + /// Returns the index of the current opcode to be executed. + pub fn instruction_pointer(&self) -> usize { + self.instruction_pointer + } + + /// Finalize the ACVM execution, returning the resulting [`WitnessMap`]. + pub fn finalize(self) -> WitnessMap { + if self.status != ACVMStatus::Solved { + panic!("ACVM execution is not complete: ({})", self.status); + } + self.witness_map + } + + /// Updates the current status of the VM. + /// Returns the given status. + fn status(&mut self, status: ACVMStatus) -> ACVMStatus { + self.status = status.clone(); + status + } + + /// Sets the VM status to [ACVMStatus::Failure] using the provided `error`. + /// Returns the new status. + fn fail(&mut self, error: OpcodeResolutionError) -> ACVMStatus { + self.status(ACVMStatus::Failure(error)) + } + + /// Sets the status of the VM to `RequiresForeignCall`. + /// Indicating that the VM is now waiting for a foreign call to be resolved. + fn wait_for_foreign_call(&mut self, foreign_call: ForeignCallWaitInfo) -> ACVMStatus { + self.status(ACVMStatus::RequiresForeignCall(foreign_call)) + } + + /// Return a reference to the arguments for the next pending foreign call, if one exists. + pub fn get_pending_foreign_call(&self) -> Option<&ForeignCallWaitInfo> { + if let ACVMStatus::RequiresForeignCall(foreign_call) = &self.status { + Some(foreign_call) + } else { + None + } + } + + /// Resolves a foreign call's [result][acir::brillig_vm::ForeignCallResult] using a result calculated outside of the ACVM. + /// + /// The ACVM can then be restarted to solve the remaining Brillig VM process as well as the remaining ACIR opcodes. + pub fn resolve_pending_foreign_call(&mut self, foreign_call_result: ForeignCallResult) { + if !matches!(self.status, ACVMStatus::RequiresForeignCall(_)) { + panic!("ACVM is not expecting a foreign call response as no call was made"); + } + + // We want to inject the foreign call result into the brillig opcode which initiated the call. + let opcode = &mut self.opcodes[self.instruction_pointer]; + let Opcode::Brillig(brillig) = opcode else { + unreachable!("ACVM can only enter `RequiresForeignCall` state on a Brillig opcode"); + }; + brillig.foreign_call_results.push(foreign_call_result); + + // Now that the foreign call has been resolved then we can resume execution. + self.status(ACVMStatus::InProgress); + } + + /// Executes the ACVM's circuit until execution halts. + /// + /// Execution can halt due to three reasons: + /// 1. All opcodes have been executed successfully. + /// 2. The circuit has been found to be unsatisfiable. + /// 2. A Brillig [foreign call][`ForeignCallWaitInfo`] has been encountered and must be resolved. + pub fn solve(&mut self) -> ACVMStatus { + while self.status == ACVMStatus::InProgress { + self.solve_opcode(); + } + self.status.clone() + } + + pub fn solve_opcode(&mut self) -> ACVMStatus { + let opcode = &self.opcodes[self.instruction_pointer]; + + let resolution = match opcode { + Opcode::Arithmetic(expr) => ArithmeticSolver::solve(&mut self.witness_map, expr), + Opcode::BlackBoxFuncCall(bb_func) => { + blackbox::solve(self.backend, &mut self.witness_map, bb_func) + } + Opcode::Directive(directive) => solve_directives(&mut self.witness_map, directive), + Opcode::MemoryInit { block_id, init } => { + let solver = self.block_solvers.entry(*block_id).or_default(); + solver.init(init, &self.witness_map) + } + Opcode::MemoryOp { block_id, op, predicate } => { + let solver = self.block_solvers.entry(*block_id).or_default(); + solver.solve_memory_op(op, &mut self.witness_map, predicate) + } + Opcode::Brillig(brillig) => { + match BrilligSolver::solve( + &mut self.witness_map, + brillig, + self.backend, + self.instruction_pointer, + ) { + Ok(Some(foreign_call)) => return self.wait_for_foreign_call(foreign_call), + res => res.map(|_| ()), + } + } + }; + match resolution { + Ok(()) => { + self.instruction_pointer += 1; + if self.instruction_pointer == self.opcodes.len() { + self.status(ACVMStatus::Solved) + } else { + self.status(ACVMStatus::InProgress) + } + } + Err(mut error) => { + match &mut error { + // If we have an index out of bounds or an unsatisfied constraint, the opcode label will be unresolved + // because the solvers do not have knowledge of this information. + // We resolve, by setting this to the corresponding opcode that we just attempted to solve. + OpcodeResolutionError::IndexOutOfBounds { + opcode_location: opcode_index, + .. + } + | OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: opcode_index, + } => { + *opcode_index = ErrorLocation::Resolved(OpcodeLocation::Acir( + self.instruction_pointer(), + )); + } + // All other errors are thrown normally. + _ => (), + }; + self.fail(error) + } + } + } +} + +// Returns the concrete value for a particular witness +// If the witness has no assignment, then +// an error is returned +pub fn witness_to_value( + initial_witness: &WitnessMap, + witness: Witness, +) -> Result<&FieldElement, OpcodeResolutionError> { + match initial_witness.get(&witness) { + Some(value) => Ok(value), + None => Err(OpcodeNotSolvable::MissingAssignment(witness.0).into()), + } +} + +// TODO: There is an issue open to decide on whether we need to get values from Expressions +// TODO versus just getting values from Witness +pub fn get_value( + expr: &Expression, + initial_witness: &WitnessMap, +) -> Result { + let expr = ArithmeticSolver::evaluate(expr, initial_witness); + match expr.to_const() { + Some(value) => Ok(value), + None => Err(OpcodeResolutionError::OpcodeNotSolvable( + OpcodeNotSolvable::MissingAssignment(any_witness_from_expression(&expr).unwrap().0), + )), + } +} + +/// Inserts `value` into the initial witness map under the index `witness`. +/// +/// Returns an error if there was already a value in the map +/// which does not match the value that one is about to insert +pub fn insert_value( + witness: &Witness, + value_to_insert: FieldElement, + initial_witness: &mut WitnessMap, +) -> Result<(), OpcodeResolutionError> { + let optional_old_value = initial_witness.insert(*witness, value_to_insert); + + let old_value = match optional_old_value { + Some(old_value) => old_value, + None => return Ok(()), + }; + + if old_value != value_to_insert { + return Err(OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: ErrorLocation::Unresolved, + }); + } + + Ok(()) +} + +// Returns one witness belonging to an expression, in no relevant order +// Returns None if the expression is const +// The function is used during partial witness generation to report unsolved witness +fn any_witness_from_expression(expr: &Expression) -> Option { + if expr.linear_combinations.is_empty() { + if expr.mul_terms.is_empty() { + None + } else { + Some(expr.mul_terms[0].1) + } + } else { + Some(expr.linear_combinations[0].1) + } +} + +#[deprecated( + note = "For backwards compatibility, this method allows you to derive _sensible_ defaults for opcode support based on the np language. \n Backends should simply specify what they support." +)] +// This is set to match the previous functionality that we had +// Where we could deduce what opcodes were supported +// by knowing the np complete language +pub fn default_is_opcode_supported(language: Language) -> fn(&Opcode) -> bool { + // R1CS does not support any of the opcode except Arithmetic by default. + // The compiler will replace those that it can -- ie range, xor, and + fn r1cs_is_supported(opcode: &Opcode) -> bool { + matches!(opcode, Opcode::Arithmetic(_)) + } + + // PLONK supports most of the opcodes by default + // The ones which are not supported, the acvm compiler will + // attempt to transform into supported opcodes. If these are also not available + // then a compiler error will be emitted. + fn plonk_is_supported(_opcode: &Opcode) -> bool { + true + } + + match language { + Language::R1CS => r1cs_is_supported, + Language::PLONKCSat { .. } => plonk_is_supported, + } +} diff --git a/acvm-repo/acvm/tests/solver.rs b/acvm-repo/acvm/tests/solver.rs new file mode 100644 index 00000000000..ca0ca99ba07 --- /dev/null +++ b/acvm-repo/acvm/tests/solver.rs @@ -0,0 +1,648 @@ +use std::collections::BTreeMap; + +use acir::{ + brillig::{BinaryFieldOp, Opcode as BrilligOpcode, RegisterIndex, RegisterOrMemory, Value}, + circuit::{ + brillig::{Brillig, BrilligInputs, BrilligOutputs}, + opcodes::{BlockId, MemOp}, + Opcode, OpcodeLocation, + }, + native_types::{Expression, Witness, WitnessMap}, + FieldElement, +}; + +use acvm::{ + pwg::{ACVMStatus, ErrorLocation, ForeignCallWaitInfo, OpcodeResolutionError, ACVM}, + BlackBoxFunctionSolver, +}; +use acvm_blackbox_solver::BlackBoxResolutionError; + +pub(crate) struct StubbedBackend; + +impl BlackBoxFunctionSolver for StubbedBackend { + fn schnorr_verify( + &self, + _public_key_x: &FieldElement, + _public_key_y: &FieldElement, + _signature: &[u8], + _message: &[u8], + ) -> Result { + panic!("Path not trodden by this test") + } + fn pedersen( + &self, + _inputs: &[FieldElement], + _domain_separator: u32, + ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { + panic!("Path not trodden by this test") + } + fn fixed_base_scalar_mul( + &self, + _low: &FieldElement, + _high: &FieldElement, + ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { + panic!("Path not trodden by this test") + } +} + +// Reenable these test cases once we move the brillig implementation of inversion down into the acvm stdlib. + +#[test] +#[ignore] +fn inversion_brillig_oracle_equivalence() { + // Opcodes below describe the following: + // fn main(x : Field, y : pub Field) { + // let z = x + y; + // assert( 1/z == Oracle("inverse", x + y) ); + // } + // Also performs an unrelated equality check + // just for the sake of testing multiple brillig opcodes. + let fe_0 = FieldElement::zero(); + let fe_1 = FieldElement::one(); + let w_x = Witness(1); + let w_y = Witness(2); + let w_oracle = Witness(3); + let w_z = Witness(4); + let w_z_inverse = Witness(5); + let w_x_plus_y = Witness(6); + let w_equal_res = Witness(7); + + let equal_opcode = BrilligOpcode::BinaryFieldOp { + op: BinaryFieldOp::Equals, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(2), + }; + + let brillig_data = Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + // Input Register 0 + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], + q_c: fe_0, + }), + BrilligInputs::Single(Expression::default()), // Input Register 1 + ], + // This tells the BrilligSolver which witnesses its output registers correspond to + outputs: vec![ + BrilligOutputs::Simple(w_x_plus_y), // Output Register 0 - from input + BrilligOutputs::Simple(w_oracle), // Output Register 1 + BrilligOutputs::Simple(w_equal_res), // Output Register 2 + ], + // stack of foreign call/oracle resolutions, starts empty + foreign_call_results: vec![], + bytecode: vec![ + equal_opcode, + // Oracles are named 'foreign calls' in brillig + BrilligOpcode::ForeignCall { + function: "invert".into(), + destinations: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(1))], + inputs: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(0))], + }, + ], + predicate: None, + }; + + let opcodes = vec![ + Opcode::Brillig(brillig_data), + Opcode::Arithmetic(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], + q_c: fe_0, + }), + // Opcode::Directive(Directive::Invert { x: w_z, result: w_z_inverse }), + Opcode::Arithmetic(Expression { + mul_terms: vec![(fe_1, w_z, w_z_inverse)], + linear_combinations: vec![], + q_c: -fe_1, + }), + Opcode::Arithmetic(Expression { + mul_terms: vec![], + linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)], + q_c: fe_0, + }), + ]; + + let witness_assignments = BTreeMap::from([ + (Witness(1), FieldElement::from(2u128)), + (Witness(2), FieldElement::from(3u128)), + ]) + .into(); + + let mut acvm = ACVM::new(&StubbedBackend, opcodes, witness_assignments); + // use the partial witness generation solver with our acir program + let solver_status = acvm.solve(); + + assert!( + matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), + "should require foreign call response" + ); + assert_eq!(acvm.instruction_pointer(), 0, "brillig should have been removed"); + + let foreign_call_wait_info: &ForeignCallWaitInfo = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); + assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + + // As caller of VM, need to resolve foreign calls + let foreign_call_result = Value::from(foreign_call_wait_info.inputs[0][0].to_field().inverse()); + // Alter Brillig oracle opcode with foreign call resolution + acvm.resolve_pending_foreign_call(foreign_call_result.into()); + + // After filling data request, continue solving + let solver_status = acvm.solve(); + assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + + // ACVM should be able to be finalized in `Solved` state. + acvm.finalize(); +} + +#[test] +#[ignore] +fn double_inversion_brillig_oracle() { + // Opcodes below describe the following: + // fn main(x : Field, y : pub Field) { + // let z = x + y; + // let ij = i + j; + // assert( 1/z == Oracle("inverse", x + y) ); + // assert( 1/ij == Oracle("inverse", i + j) ); + // } + // Also performs an unrelated equality check + // just for the sake of testing multiple brillig opcodes. + let fe_0 = FieldElement::zero(); + let fe_1 = FieldElement::one(); + let w_x = Witness(1); + let w_y = Witness(2); + let w_oracle = Witness(3); + let w_z = Witness(4); + let w_z_inverse = Witness(5); + let w_x_plus_y = Witness(6); + let w_equal_res = Witness(7); + let w_i = Witness(8); + let w_j = Witness(9); + let w_ij_oracle = Witness(10); + let w_i_plus_j = Witness(11); + + let equal_opcode = BrilligOpcode::BinaryFieldOp { + op: BinaryFieldOp::Equals, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(4), + }; + + let brillig_data = Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + // Input Register 0 + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], + q_c: fe_0, + }), + BrilligInputs::Single(Expression::default()), // Input Register 1 + BrilligInputs::Single(Expression { + // Input Register 2 + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_i), (fe_1, w_j)], + q_c: fe_0, + }), + ], + outputs: vec![ + BrilligOutputs::Simple(w_x_plus_y), // Output Register 0 - from input + BrilligOutputs::Simple(w_oracle), // Output Register 1 + BrilligOutputs::Simple(w_i_plus_j), // Output Register 2 - from input + BrilligOutputs::Simple(w_ij_oracle), // Output Register 3 + BrilligOutputs::Simple(w_equal_res), // Output Register 4 + ], + // stack of foreign call/oracle resolutions, starts empty + foreign_call_results: vec![], + bytecode: vec![ + equal_opcode, + // Oracles are named 'foreign calls' in brillig + BrilligOpcode::ForeignCall { + function: "invert".into(), + destinations: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(1))], + inputs: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(0))], + }, + BrilligOpcode::ForeignCall { + function: "invert".into(), + destinations: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(3))], + inputs: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(2))], + }, + ], + predicate: None, + }; + + let opcodes = vec![ + Opcode::Brillig(brillig_data), + Opcode::Arithmetic(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], + q_c: fe_0, + }), + // Opcode::Directive(Directive::Invert { x: w_z, result: w_z_inverse }), + Opcode::Arithmetic(Expression { + mul_terms: vec![(fe_1, w_z, w_z_inverse)], + linear_combinations: vec![], + q_c: -fe_1, + }), + Opcode::Arithmetic(Expression { + mul_terms: vec![], + linear_combinations: vec![(-fe_1, w_oracle), (fe_1, w_z_inverse)], + q_c: fe_0, + }), + ]; + + let witness_assignments = BTreeMap::from([ + (Witness(1), FieldElement::from(2u128)), + (Witness(2), FieldElement::from(3u128)), + (Witness(8), FieldElement::from(5u128)), + (Witness(9), FieldElement::from(10u128)), + ]) + .into(); + + let mut acvm = ACVM::new(&StubbedBackend, opcodes, witness_assignments); + + // use the partial witness generation solver with our acir program + let solver_status = acvm.solve(); + assert!( + matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), + "should require foreign call response" + ); + assert_eq!(acvm.instruction_pointer(), 0, "should stall on brillig"); + + let foreign_call_wait_info: &ForeignCallWaitInfo = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); + assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + + let x_plus_y_inverse = Value::from(foreign_call_wait_info.inputs[0][0].to_field().inverse()); + + // Resolve Brillig foreign call + acvm.resolve_pending_foreign_call(x_plus_y_inverse.into()); + + // After filling data request, continue solving + let solver_status = acvm.solve(); + assert!( + matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), + "should require foreign call response" + ); + assert_eq!(acvm.instruction_pointer(), 0, "should stall on brillig"); + + let foreign_call_wait_info = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); + assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + + let i_plus_j_inverse = Value::from(foreign_call_wait_info.inputs[0][0].to_field().inverse()); + assert_ne!(x_plus_y_inverse, i_plus_j_inverse); + + // Alter Brillig oracle opcode + acvm.resolve_pending_foreign_call(i_plus_j_inverse.into()); + + // After filling data request, continue solving + let solver_status = acvm.solve(); + assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + + // ACVM should be able to be finalized in `Solved` state. + acvm.finalize(); +} + +#[test] +fn oracle_dependent_execution() { + // This test ensures that we properly track the list of opcodes which still need to be resolved + // across any brillig foreign calls we may have to perform. + // + // Opcodes below describe the following: + // fn main(x : Field, y : pub Field) { + // assert(x == y); + // let x_inv = Oracle("inverse", x); + // let y_inv = Oracle("inverse", y); + // + // assert(x_inv == y_inv); + // } + // Also performs an unrelated equality check + // just for the sake of testing multiple brillig opcodes. + let fe_0 = FieldElement::zero(); + let fe_1 = FieldElement::one(); + let w_x = Witness(1); + let w_y = Witness(2); + let w_x_inv = Witness(3); + let w_y_inv = Witness(4); + + let brillig_data = Brillig { + inputs: vec![ + BrilligInputs::Single(w_x.into()), // Input Register 0 + BrilligInputs::Single(Expression::default()), // Input Register 1 + BrilligInputs::Single(w_y.into()), // Input Register 2, + ], + outputs: vec![ + BrilligOutputs::Simple(w_x), // Output Register 0 - from input + BrilligOutputs::Simple(w_y_inv), // Output Register 1 + BrilligOutputs::Simple(w_y), // Output Register 2 - from input + BrilligOutputs::Simple(w_y_inv), // Output Register 3 + ], + // stack of foreign call/oracle resolutions, starts empty + foreign_call_results: vec![], + bytecode: vec![ + // Oracles are named 'foreign calls' in brillig + BrilligOpcode::ForeignCall { + function: "invert".into(), + destinations: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(1))], + inputs: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(0))], + }, + BrilligOpcode::ForeignCall { + function: "invert".into(), + destinations: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(3))], + inputs: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(2))], + }, + ], + predicate: None, + }; + + // This equality check can be executed immediately before resolving any foreign calls. + let equality_check = Expression { + mul_terms: vec![], + linear_combinations: vec![(-fe_1, w_x), (fe_1, w_y)], + q_c: fe_0, + }; + + // This equality check relies on the outputs of the Brillig call. + // It then cannot be solved until the foreign calls are resolved. + let inverse_equality_check = Expression { + mul_terms: vec![], + linear_combinations: vec![(-fe_1, w_x_inv), (fe_1, w_y_inv)], + q_c: fe_0, + }; + + let opcodes = vec![ + Opcode::Arithmetic(equality_check), + Opcode::Brillig(brillig_data), + Opcode::Arithmetic(inverse_equality_check), + ]; + + let witness_assignments = + BTreeMap::from([(w_x, FieldElement::from(2u128)), (w_y, FieldElement::from(2u128))]).into(); + + let mut acvm = ACVM::new(&StubbedBackend, opcodes, witness_assignments); + + // use the partial witness generation solver with our acir program + let solver_status = acvm.solve(); + assert!( + matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), + "should require foreign call response" + ); + assert_eq!(acvm.instruction_pointer(), 1, "should stall on brillig"); + + let foreign_call_wait_info: &ForeignCallWaitInfo = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); + assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + + // Resolve Brillig foreign call + let x_inverse = Value::from(foreign_call_wait_info.inputs[0][0].to_field().inverse()); + acvm.resolve_pending_foreign_call(x_inverse.into()); + + // After filling data request, continue solving + let solver_status = acvm.solve(); + assert!( + matches!(solver_status, ACVMStatus::RequiresForeignCall(_)), + "should require foreign call response" + ); + assert_eq!(acvm.instruction_pointer(), 1, "should stall on brillig"); + + let foreign_call_wait_info: &ForeignCallWaitInfo = + acvm.get_pending_foreign_call().expect("should have a brillig foreign call request"); + assert_eq!(foreign_call_wait_info.inputs.len(), 1, "Should be waiting for a single input"); + + // Resolve Brillig foreign call + let y_inverse = Value::from(foreign_call_wait_info.inputs[0][0].to_field().inverse()); + acvm.resolve_pending_foreign_call(y_inverse.into()); + + // We've resolved all the brillig foreign calls so we should be able to complete execution now. + + // After filling data request, continue solving + let solver_status = acvm.solve(); + assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + + // ACVM should be able to be finalized in `Solved` state. + acvm.finalize(); +} + +#[test] +fn brillig_oracle_predicate() { + let fe_0 = FieldElement::zero(); + let fe_1 = FieldElement::one(); + let w_x = Witness(1); + let w_y = Witness(2); + let w_oracle = Witness(3); + let w_x_plus_y = Witness(4); + let w_equal_res = Witness(5); + let w_lt_res = Witness(6); + + let equal_opcode = BrilligOpcode::BinaryFieldOp { + op: BinaryFieldOp::Equals, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(2), + }; + + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y)], + q_c: fe_0, + }), + BrilligInputs::Single(Expression::default()), + ], + outputs: vec![ + BrilligOutputs::Simple(w_x_plus_y), + BrilligOutputs::Simple(w_oracle), + BrilligOutputs::Simple(w_equal_res), + BrilligOutputs::Simple(w_lt_res), + ], + bytecode: vec![ + equal_opcode, + // Oracles are named 'foreign calls' in brillig + BrilligOpcode::ForeignCall { + function: "invert".into(), + destinations: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(1))], + inputs: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(0))], + }, + ], + predicate: Some(Expression::default()), + // oracle results + foreign_call_results: vec![], + }); + + let opcodes = vec![brillig_opcode]; + + let witness_assignments = BTreeMap::from([ + (Witness(1), FieldElement::from(2u128)), + (Witness(2), FieldElement::from(3u128)), + ]) + .into(); + + let mut acvm = ACVM::new(&StubbedBackend, opcodes, witness_assignments); + let solver_status = acvm.solve(); + assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + + // ACVM should be able to be finalized in `Solved` state. + acvm.finalize(); +} +#[test] +fn unsatisfied_opcode_resolved() { + let a = Witness(0); + let b = Witness(1); + let c = Witness(2); + let d = Witness(3); + + // a = b + c + d; + let opcode_a = Expression { + mul_terms: vec![], + linear_combinations: vec![ + (FieldElement::one(), a), + (-FieldElement::one(), b), + (-FieldElement::one(), c), + (-FieldElement::one(), d), + ], + q_c: FieldElement::zero(), + }; + + let mut values = WitnessMap::new(); + values.insert(a, FieldElement::from(4_i128)); + values.insert(b, FieldElement::from(2_i128)); + values.insert(c, FieldElement::from(1_i128)); + values.insert(d, FieldElement::from(2_i128)); + + let opcodes = vec![Opcode::Arithmetic(opcode_a)]; + let mut acvm = ACVM::new(&StubbedBackend, opcodes, values); + let solver_status = acvm.solve(); + assert_eq!( + solver_status, + ACVMStatus::Failure(OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: ErrorLocation::Resolved(OpcodeLocation::Acir(0)), + }), + "The first opcode is not satisfiable, expected an error indicating this" + ); +} + +#[test] +fn unsatisfied_opcode_resolved_brillig() { + let a = Witness(0); + let b = Witness(1); + let c = Witness(2); + let d = Witness(3); + + let fe_1 = FieldElement::one(); + let fe_0 = FieldElement::zero(); + let w_x = Witness(4); + let w_y = Witness(5); + let w_result = Witness(6); + + let equal_opcode = BrilligOpcode::BinaryFieldOp { + op: BinaryFieldOp::Equals, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(2), + }; + // Jump pass the trap if the values are equal, else + // jump to the trap + let location_of_stop = 3; + + let jmp_if_opcode = + BrilligOpcode::JumpIf { condition: RegisterIndex::from(2), location: location_of_stop }; + + let trap_opcode = BrilligOpcode::Trap; + let stop_opcode = BrilligOpcode::Stop; + + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x)], + q_c: fe_0, + }), + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_y)], + q_c: fe_0, + }), + ], + outputs: vec![BrilligOutputs::Simple(w_result)], + bytecode: vec![equal_opcode, jmp_if_opcode, trap_opcode, stop_opcode], + predicate: Some(Expression::one()), + // oracle results + foreign_call_results: vec![], + }); + + let opcode_a = Expression { + mul_terms: vec![], + linear_combinations: vec![ + (FieldElement::one(), a), + (-FieldElement::one(), b), + (-FieldElement::one(), c), + (-FieldElement::one(), d), + ], + q_c: FieldElement::zero(), + }; + + let mut values = WitnessMap::new(); + values.insert(a, FieldElement::from(4_i128)); + values.insert(b, FieldElement::from(2_i128)); + values.insert(c, FieldElement::from(1_i128)); + values.insert(d, FieldElement::from(2_i128)); + values.insert(w_x, FieldElement::from(0_i128)); + values.insert(w_y, FieldElement::from(1_i128)); + values.insert(w_result, FieldElement::from(0_i128)); + + let opcodes = vec![brillig_opcode, Opcode::Arithmetic(opcode_a)]; + + let mut acvm = ACVM::new(&StubbedBackend, opcodes, values); + let solver_status = acvm.solve(); + assert_eq!( + solver_status, + ACVMStatus::Failure(OpcodeResolutionError::BrilligFunctionFailed { + message: "explicit trap hit in brillig".to_string(), + call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }] + }), + "The first opcode is not satisfiable, expected an error indicating this" + ); +} + +#[test] +fn memory_operations() { + let initial_witness = WitnessMap::from(BTreeMap::from_iter([ + (Witness(1), FieldElement::from(1u128)), + (Witness(2), FieldElement::from(2u128)), + (Witness(3), FieldElement::from(3u128)), + (Witness(4), FieldElement::from(4u128)), + (Witness(5), FieldElement::from(5u128)), + (Witness(6), FieldElement::from(4u128)), + ])); + + let block_id = BlockId(0); + + let init = Opcode::MemoryInit { block_id, init: (1..6).map(Witness).collect() }; + + let read_op = Opcode::MemoryOp { + block_id, + op: MemOp::read_at_mem_index(Witness(6).into(), Witness(7)), + predicate: None, + }; + + let expression = Opcode::Arithmetic(Expression { + mul_terms: Vec::new(), + linear_combinations: vec![ + (FieldElement::one(), Witness(7)), + (-FieldElement::one(), Witness(8)), + ], + q_c: FieldElement::one(), + }); + + let opcodes = vec![init, read_op, expression]; + + let mut acvm = ACVM::new(&StubbedBackend, opcodes, initial_witness); + let solver_status = acvm.solve(); + assert_eq!(solver_status, ACVMStatus::Solved); + let witness_map = acvm.finalize(); + + assert_eq!(witness_map[&Witness(8)], FieldElement::from(6u128)); +} diff --git a/acvm-repo/acvm/tests/stdlib.rs b/acvm-repo/acvm/tests/stdlib.rs new file mode 100644 index 00000000000..309130d3992 --- /dev/null +++ b/acvm-repo/acvm/tests/stdlib.rs @@ -0,0 +1,354 @@ +#![cfg(feature = "testing")] +mod solver; +use crate::solver::StubbedBackend; +use acir::{ + circuit::{ + opcodes::{BlackBoxFuncCall, FunctionInput}, + Circuit, Opcode, + }, + native_types::{Expression, Witness}, + FieldElement, +}; +use acvm::{ + compiler::compile, + pwg::{ACVMStatus, ACVM}, + Language, +}; +use acvm_blackbox_solver::{blake2s, hash_to_field_128_security, keccak256, sha256}; +use paste::paste; +use proptest::prelude::*; +use std::collections::{BTreeMap, BTreeSet}; +use stdlib::blackbox_fallbacks::{UInt32, UInt64, UInt8}; + +test_uint!(test_uint8, UInt8, u8, 8); +test_uint!(test_uint32, UInt32, u32, 32); +test_uint!(test_uint64, UInt64, u64, 64); + +#[macro_export] +macro_rules! test_uint { + ( + $name:tt, + $uint:ident, + $u:ident, + $size:expr + ) => { + paste! { + test_uint_inner!( + [<$name _rol>], + [<$name _ror>], + [<$name _euclidean_division>], + [<$name _add>], + [<$name _sub>], + [<$name _left_shift>], + [<$name _right_shift>], + [<$name _less_than>], + $uint, + $u, + $size + ); + } + }; +} + +#[macro_export] +macro_rules! test_uint_inner { + ( + $rol:tt, + $ror:tt, + $euclidean_division:tt, + $add:tt, + $sub:tt, + $left_shift:tt, + $right_shift:tt, + $less_than:tt, + $uint: ident, + $u: ident, + $size: expr + ) => { + proptest! { + #[test] + fn $rol(x in 0..$u::MAX, y in 0..32_u32) { + let fe = FieldElement::from(x as u128); + let w = Witness(1); + let result = x.rotate_left(y); + let uint = $uint::new(w); + let (w, extra_opcodes, _) = uint.rol(y, 2); + let witness_assignments = BTreeMap::from([(Witness(1), fe)]).into(); + let mut acvm = ACVM::new(&StubbedBackend, extra_opcodes, witness_assignments); + let solver_status = acvm.solve(); + + prop_assert_eq!(acvm.witness_map().get(&w.get_inner()).unwrap(), &FieldElement::from(result as u128)); + prop_assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + } + + #[test] + fn $ror(x in 0..$u::MAX, y in 0..32_u32) { + let fe = FieldElement::from(x as u128); + let w = Witness(1); + let result = x.rotate_right(y); + let uint = $uint::new(w); + let (w, extra_opcodes, _) = uint.ror(y, 2); + let witness_assignments = BTreeMap::from([(Witness(1), fe)]).into(); + let mut acvm = ACVM::new(&StubbedBackend, extra_opcodes, witness_assignments); + let solver_status = acvm.solve(); + + prop_assert_eq!(acvm.witness_map().get(&w.get_inner()).unwrap(), &FieldElement::from(result as u128)); + prop_assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + } + + #[test] + fn $euclidean_division(x in 0..$u::MAX, y in 1 + ..$u::MAX) { + let lhs = FieldElement::from(x as u128); + let rhs = FieldElement::from(y as u128); + let w1 = Witness(1); + let w2 = Witness(2); + let q = x.div_euclid(y); + let r = x.rem_euclid(y); + let u32_1 = $uint::new(w1); + let u32_2 = $uint::new(w2); + let (q_w, r_w, extra_opcodes, _) = $uint::euclidean_division(&u32_1, &u32_2, 3); + let witness_assignments = BTreeMap::from([(Witness(1), lhs),(Witness(2), rhs)]).into(); + let mut acvm = ACVM::new(&StubbedBackend, extra_opcodes, witness_assignments); + let solver_status = acvm.solve(); + + prop_assert_eq!(acvm.witness_map().get(&q_w.get_inner()).unwrap(), &FieldElement::from(q as u128)); + prop_assert_eq!(acvm.witness_map().get(&r_w.get_inner()).unwrap(), &FieldElement::from(r as u128)); + prop_assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + } + + #[test] + fn $add(x in 0..$u::MAX, y in 0..$u::MAX, z in 0..$u::MAX) { + let lhs = FieldElement::from(x as u128); + let rhs = FieldElement::from(y as u128); + let rhs_z = FieldElement::from(z as u128); + let result = FieldElement::from(((x as u128).wrapping_add(y as u128) % (1_u128 << $size)).wrapping_add(z as u128) % (1_u128 << $size)); + let w1 = Witness(1); + let w2 = Witness(2); + let w3 = Witness(3); + let u32_1 = $uint::new(w1); + let u32_2 = $uint::new(w2); + let u32_3 = $uint::new(w3); + let mut opcodes = Vec::new(); + let (w, extra_opcodes, num_witness) = u32_1.add(&u32_2, 4); + opcodes.extend(extra_opcodes); + let (w2, extra_opcodes, _) = w.add(&u32_3, num_witness); + opcodes.extend(extra_opcodes); + let witness_assignments = BTreeMap::from([(Witness(1), lhs), (Witness(2), rhs), (Witness(3), rhs_z)]).into(); + let mut acvm = ACVM::new(&StubbedBackend, opcodes, witness_assignments); + let solver_status = acvm.solve(); + + prop_assert_eq!(acvm.witness_map().get(&w2.get_inner()).unwrap(), &result); + prop_assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + } + + #[test] + fn $sub(x in 0..$u::MAX, y in 0..$u::MAX, z in 0..$u::MAX) { + let lhs = FieldElement::from(x as u128); + let rhs = FieldElement::from(y as u128); + let rhs_z = FieldElement::from(z as u128); + let result = FieldElement::from(((x as u128).wrapping_sub(y as u128) % (1_u128 << $size)).wrapping_sub(z as u128) % (1_u128 << $size)); + let w1 = Witness(1); + let w2 = Witness(2); + let w3 = Witness(3); + let u32_1 = $uint::new(w1); + let u32_2 = $uint::new(w2); + let u32_3 = $uint::new(w3); + let mut opcodes = Vec::new(); + let (w, extra_opcodes, num_witness) = u32_1.sub(&u32_2, 4); + opcodes.extend(extra_opcodes); + let (w2, extra_opcodes, _) = w.sub(&u32_3, num_witness); + opcodes.extend(extra_opcodes); + let witness_assignments = BTreeMap::from([(Witness(1), lhs), (Witness(2), rhs), (Witness(3), rhs_z)]).into(); + let mut acvm = ACVM::new(&StubbedBackend, opcodes, witness_assignments); + let solver_status = acvm.solve(); + + prop_assert_eq!(acvm.witness_map().get(&w2.get_inner()).unwrap(), &result); + prop_assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + } + + #[test] + fn $left_shift(x in 0..$u::MAX, y in 0..32_u32) { + let lhs = FieldElement::from(x as u128); + let w1 = Witness(1); + let result = x.overflowing_shl(y).0; + let u32_1 = $uint::new(w1); + let (w, extra_opcodes, _) = u32_1.leftshift(y, 2); + let witness_assignments = BTreeMap::from([(Witness(1), lhs)]).into(); + let mut acvm = ACVM::new(&StubbedBackend, extra_opcodes, witness_assignments); + let solver_status = acvm.solve(); + + prop_assert_eq!(acvm.witness_map().get(&w.get_inner()).unwrap(), &FieldElement::from(result as u128)); + prop_assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + } + + #[test] + fn $right_shift(x in 0..$u::MAX, y in 0..32_u32) { + let lhs = FieldElement::from(x as u128); + let w1 = Witness(1); + let result = x.overflowing_shr(y).0; + let u32_1 = $uint::new(w1); + let (w, extra_opcodes, _) = u32_1.rightshift(y, 2); + let witness_assignments = BTreeMap::from([(Witness(1), lhs)]).into(); + let mut acvm = ACVM::new(&StubbedBackend, extra_opcodes, witness_assignments); + let solver_status = acvm.solve(); + + prop_assert_eq!(acvm.witness_map().get(&w.get_inner()).unwrap(), &FieldElement::from(result as u128)); + prop_assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + } + + #[test] + fn $less_than(x in 0..$u::MAX, y in 0..$u::MAX) { + let lhs = FieldElement::from(x as u128); + let rhs = FieldElement::from(y as u128); + let w1 = Witness(1); + let w2 = Witness(2); + let result = x < y; + let u32_1 = $uint::new(w1); + let u32_2 = $uint::new(w2); + let (w, extra_opcodes, _) = u32_1.less_than_comparison(&u32_2, 3); + let witness_assignments = BTreeMap::from([(Witness(1), lhs), (Witness(2), rhs)]).into(); + let mut acvm = ACVM::new(&StubbedBackend, extra_opcodes, witness_assignments); + let solver_status = acvm.solve(); + + prop_assert_eq!(acvm.witness_map().get(&w.get_inner()).unwrap(), &FieldElement::from(result as u128)); + prop_assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + } + } + }; +} + +test_hashes!(test_sha256, sha256, SHA256, does_not_support_sha256); +test_hashes!(test_blake2s, blake2s, Blake2s, does_not_support_blake2s); +test_hashes!(test_keccak, keccak256, Keccak256, does_not_support_keccak); + +fn does_not_support_sha256(opcode: &Opcode) -> bool { + !matches!(opcode, Opcode::BlackBoxFuncCall(BlackBoxFuncCall::SHA256 { .. })) +} +fn does_not_support_blake2s(opcode: &Opcode) -> bool { + !matches!(opcode, Opcode::BlackBoxFuncCall(BlackBoxFuncCall::Blake2s { .. })) +} +fn does_not_support_keccak(opcode: &Opcode) -> bool { + !matches!(opcode, Opcode::BlackBoxFuncCall(BlackBoxFuncCall::Keccak256 { .. })) +} + +#[macro_export] +macro_rules! test_hashes { + ( + $name:ident, + $hasher:ident, + $opcode:ident, + $opcode_support: ident + ) => { + proptest! { + #![proptest_config(ProptestConfig::with_cases(3))] + #[test] + fn $name(input_values in proptest::collection::vec(0..u8::MAX, 1..50)) { + let mut opcodes = Vec::new(); + let mut witness_assignments = BTreeMap::new(); + let mut input_witnesses: Vec = Vec::new(); + let mut correct_result_witnesses: Vec = Vec::new(); + let mut output_witnesses: Vec = Vec::new(); + + // prepare test data + let mut counter = 0; + let output = $hasher(&input_values).unwrap(); + for inp_v in input_values { + counter += 1; + let function_input = FunctionInput { witness: Witness(counter), num_bits: 8 }; + input_witnesses.push(function_input); + witness_assignments.insert(Witness(counter), FieldElement::from(inp_v as u128)); + } + + for o_v in output { + counter += 1; + correct_result_witnesses.push(Witness(counter)); + witness_assignments.insert(Witness(counter), FieldElement::from(o_v as u128)); + } + + for _ in 0..32 { + counter += 1; + output_witnesses.push(Witness(counter)); + } + let blackbox = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::$opcode { inputs: input_witnesses, outputs: output_witnesses.clone() }); + opcodes.push(blackbox); + + // constrain the output to be the same as the hasher + for i in 0..correct_result_witnesses.len() { + let mut output_constraint = Expression::from(correct_result_witnesses[i]); + output_constraint.push_addition_term(-FieldElement::one(), output_witnesses[i]); + opcodes.push(Opcode::Arithmetic(output_constraint)); + } + + // compile circuit + let circuit = Circuit { + current_witness_index: witness_assignments.len() as u32 + 32, + opcodes, + private_parameters: BTreeSet::new(), // This is not correct but is unused in this test. + ..Circuit::default() + }; + let circuit = compile(circuit, Language::PLONKCSat{ width: 3 }, $opcode_support).unwrap().0; + + // solve witnesses + let mut acvm = ACVM::new(&StubbedBackend, circuit.opcodes, witness_assignments.into()); + let solver_status = acvm.solve(); + + prop_assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + } + } + }; +} + +fn does_not_support_hash_to_field(opcode: &Opcode) -> bool { + !matches!(opcode, Opcode::BlackBoxFuncCall(BlackBoxFuncCall::HashToField128Security { .. })) +} + +proptest! { + #![proptest_config(ProptestConfig::with_cases(3))] + #[test] + fn test_hash_to_field(input_values in proptest::collection::vec(0..u8::MAX, 1..50)) { + let mut opcodes = Vec::new(); + let mut witness_assignments = BTreeMap::new(); + let mut input_witnesses: Vec = Vec::new(); + + // prepare test data + let mut counter = 0; + let output = hash_to_field_128_security(&input_values).unwrap(); + for inp_v in input_values { + counter += 1; + let function_input = FunctionInput { witness: Witness(counter), num_bits: 8 }; + input_witnesses.push(function_input); + witness_assignments.insert(Witness(counter), FieldElement::from(inp_v as u128)); + } + + counter += 1; + let correct_result_witnesses: Witness = Witness(counter); + witness_assignments.insert(Witness(counter), output); + + counter += 1; + let output_witness: Witness = Witness(counter); + + let blackbox = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::HashToField128Security { inputs: input_witnesses, output: output_witness }); + opcodes.push(blackbox); + + // constrain the output to be the same as the hasher + let mut output_constraint = Expression::from(correct_result_witnesses); + output_constraint.push_addition_term(-FieldElement::one(), output_witness); + opcodes.push(Opcode::Arithmetic(output_constraint)); + + // compile circuit + let circuit = Circuit { + current_witness_index: witness_assignments.len() as u32 + 1, + opcodes, + private_parameters: BTreeSet::new(), // This is not correct but is unused in this test. + ..Circuit::default() + }; + let circuit = compile(circuit, Language::PLONKCSat{ width: 3 }, does_not_support_hash_to_field).unwrap().0; + + // solve witnesses + let mut acvm = ACVM::new(&StubbedBackend, circuit.opcodes, witness_assignments.into()); + let solver_status = acvm.solve(); + + prop_assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); + } +} diff --git a/acvm-repo/acvm_js/.cargo/config.toml b/acvm-repo/acvm_js/.cargo/config.toml new file mode 100644 index 00000000000..85c748284e9 --- /dev/null +++ b/acvm-repo/acvm_js/.cargo/config.toml @@ -0,0 +1,5 @@ +[build] +target = "wasm32-unknown-unknown" + +[target.wasm32-unknown-unknown] +runner = 'wasm-bindgen-test-runner' diff --git a/acvm-repo/acvm_js/.eslintignore b/acvm-repo/acvm_js/.eslintignore new file mode 100644 index 00000000000..200ae222150 --- /dev/null +++ b/acvm-repo/acvm_js/.eslintignore @@ -0,0 +1,2 @@ +node_modules +pkg \ No newline at end of file diff --git a/acvm-repo/acvm_js/.eslintrc.js b/acvm-repo/acvm_js/.eslintrc.js new file mode 100644 index 00000000000..33335c2a877 --- /dev/null +++ b/acvm-repo/acvm_js/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ["../../.eslintrc.js"], +}; diff --git a/acvm-repo/acvm_js/.gitignore b/acvm-repo/acvm_js/.gitignore new file mode 100644 index 00000000000..f4cb6ac4779 --- /dev/null +++ b/acvm-repo/acvm_js/.gitignore @@ -0,0 +1,7 @@ +/target +node_modules + +# Build outputs +result +nodejs +web diff --git a/acvm-repo/acvm_js/.mocharc.json b/acvm-repo/acvm_js/.mocharc.json new file mode 100644 index 00000000000..27273835070 --- /dev/null +++ b/acvm-repo/acvm_js/.mocharc.json @@ -0,0 +1,5 @@ +{ + "extension": ["ts"], + "spec": "test/node/**/*.test.ts", + "require": "ts-node/register" +} \ No newline at end of file diff --git a/acvm-repo/acvm_js/CHANGELOG.md b/acvm-repo/acvm_js/CHANGELOG.md new file mode 100644 index 00000000000..0cca1480477 --- /dev/null +++ b/acvm-repo/acvm_js/CHANGELOG.md @@ -0,0 +1,150 @@ +# Changelog + +## [0.27.0](https://github.com/noir-lang/acvm/compare/acvm_js-v0.26.1...acvm_js-v0.27.0) (2023-09-19) + + +### ⚠ BREAKING CHANGES + +* Separate barretenberg solver from generic blackbox solver code ([#554](https://github.com/noir-lang/acvm/issues/554)) + +### Miscellaneous Chores + +* Separate barretenberg solver from generic blackbox solver code ([#554](https://github.com/noir-lang/acvm/issues/554)) ([a4b9772](https://github.com/noir-lang/acvm/commit/a4b97722a0892fe379ff075e6080675adafdce0e)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acvm bumped from 0.26.1 to 0.27.0 + * barretenberg_blackbox_solver bumped from 0.26.1 to 0.27.0 + +## [0.26.1](https://github.com/noir-lang/acvm/compare/acvm_js-v0.26.0...acvm_js-v0.26.1) (2023-09-12) + + +### Bug Fixes + +* Implements handling of the high limb during fixed base scalar multiplication ([#535](https://github.com/noir-lang/acvm/issues/535)) ([551504a](https://github.com/noir-lang/acvm/commit/551504aa572d3f9d56b5576d25ce1211296ee488)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acvm bumped from 0.26.0 to 0.26.1 + +## [0.26.0](https://github.com/noir-lang/acvm/compare/acvm_js-v0.25.0...acvm_js-v0.26.0) (2023-09-07) + + +### ⚠ BREAKING CHANGES + +* Add a low and high limb to scalar mul opcode ([#532](https://github.com/noir-lang/acvm/issues/532)) + +### Miscellaneous Chores + +* Add a low and high limb to scalar mul opcode ([#532](https://github.com/noir-lang/acvm/issues/532)) ([b054f66](https://github.com/noir-lang/acvm/commit/b054f66be9c73d4e02dbecdab80874a907f19242)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acvm bumped from 0.25.0 to 0.26.0 + +## [0.25.0](https://github.com/noir-lang/acvm/compare/acvm_js-v0.24.1...acvm_js-v0.25.0) (2023-09-04) + + +### ⚠ BREAKING CHANGES + +* Provide runtime callstacks for brillig failures and return errors in acvm_js ([#523](https://github.com/noir-lang/acvm/issues/523)) + +### Features + +* Provide runtime callstacks for brillig failures and return errors in acvm_js ([#523](https://github.com/noir-lang/acvm/issues/523)) ([7ab7cff](https://github.com/noir-lang/acvm/commit/7ab7cff48a9aba61a97fad2a759fc8e55740b098)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acvm bumped from 0.24.1 to 0.25.0 + +## [0.24.1](https://github.com/noir-lang/acvm/compare/acvm_js-v0.24.0...acvm_js-v0.24.1) (2023-09-03) + + +### Bug Fixes + +* Add WASI 20 `_initialize` call to `acvm_backend.wasm` binary ([#518](https://github.com/noir-lang/acvm/issues/518)) ([ec6ab0c](https://github.com/noir-lang/acvm/commit/ec6ab0c6fb2753209abe1e03a449873e255ffd76)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acvm bumped from 0.24.0 to 0.24.1 + +## [0.24.0](https://github.com/noir-lang/acvm/compare/acvm_js-v0.23.0...acvm_js-v0.24.0) (2023-08-31) + + +### ⚠ BREAKING CHANGES + +* **acir:** Remove unused `Directive` opcodes ([#510](https://github.com/noir-lang/acvm/issues/510)) +* **acir:** Add predicate to MemoryOp ([#503](https://github.com/noir-lang/acvm/issues/503)) +* Assertion messages embedded in the circuit ([#484](https://github.com/noir-lang/acvm/issues/484)) + +### Features + +* **acir:** Add predicate to MemoryOp ([#503](https://github.com/noir-lang/acvm/issues/503)) ([ca9eebe](https://github.com/noir-lang/acvm/commit/ca9eebe34e61adabf97318c8ccaf60c8a424aafd)) +* Assertion messages embedded in the circuit ([#484](https://github.com/noir-lang/acvm/issues/484)) ([06b97c5](https://github.com/noir-lang/acvm/commit/06b97c51041e16651cf8b2be8bc18214e276c6c9)) + + +### Miscellaneous Chores + +* **acir:** Remove unused `Directive` opcodes ([#510](https://github.com/noir-lang/acvm/issues/510)) ([cfd8cbf](https://github.com/noir-lang/acvm/commit/cfd8cbf58307511ac0cc9106c299695c2ca779de)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acvm bumped from 0.23.0 to 0.24.0 + +## [0.23.0](https://github.com/noir-lang/acvm/compare/acvm_js-v0.22.0...acvm_js-v0.23.0) (2023-08-30) + + +### ⚠ BREAKING CHANGES + +* **acvm:** Remove `BlackBoxFunctionSolver` from `Backend` trait ([#494](https://github.com/noir-lang/acvm/issues/494)) +* **acvm:** Pass `BlackBoxFunctionSolver` to `ACVM` by reference + +### Features + +* **acvm_js:** Add `execute_circuit_with_black_box_solver` to prevent reinitialization of `BlackBoxFunctionSolver` ([3877e0e](https://github.com/noir-lang/acvm/commit/3877e0e438a8d0e5545a4da7210767dec05c342f)) +* Expose a `BlackBoxFunctionSolver` containing a barretenberg wasm from `blackbox_solver` ([#494](https://github.com/noir-lang/acvm/issues/494)) ([a1d4b71](https://github.com/noir-lang/acvm/commit/a1d4b71256dfbf1e883e770dd9c45479235aa860)) + + +### Miscellaneous Chores + +* **acvm:** Pass `BlackBoxFunctionSolver` to `ACVM` by reference ([3877e0e](https://github.com/noir-lang/acvm/commit/3877e0e438a8d0e5545a4da7210767dec05c342f)) +* **acvm:** Remove `BlackBoxFunctionSolver` from `Backend` trait ([#494](https://github.com/noir-lang/acvm/issues/494)) ([a1d4b71](https://github.com/noir-lang/acvm/commit/a1d4b71256dfbf1e883e770dd9c45479235aa860)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acvm bumped from 0.22.0 to 0.23.0 + +## [0.22.0](https://github.com/noir-lang/acvm/compare/acvm_js-v0.21.0...acvm_js-v0.22.0) (2023-08-18) + + +### Bug Fixes + +* Empty commit to trigger release-please ([e8f0748](https://github.com/noir-lang/acvm/commit/e8f0748042ef505d59ab63266d3c36c5358ee30d)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acvm bumped from 0.21.0 to 0.22.0 diff --git a/acvm-repo/acvm_js/Cargo.toml b/acvm-repo/acvm_js/Cargo.toml new file mode 100644 index 00000000000..fd828864285 --- /dev/null +++ b/acvm-repo/acvm_js/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "acvm_js" +description = "Typescript wrapper around the ACVM allowing execution of ACIR code" +# x-release-please-start-version +version = "0.27.4" +# x-release-please-end +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib"] + +[dependencies] +cfg-if = "1.0.0" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +acvm = { path = "../acvm", default-features = false } +barretenberg_blackbox_solver = { path = "../barretenberg_blackbox_solver", default-features = false } +wasm-bindgen = { version = "0.2.86", features = ["serde-serialize"] } +wasm-bindgen-futures = "0.4.36" +serde = { version = "1.0.136", features = ["derive"] } +log = "0.4.17" +wasm-logger = "0.2.0" +console_error_panic_hook = "0.1.7" +gloo-utils = { version = "0.1", features = ["serde"] } +js-sys = "0.3.62" +const-str = "0.5.5" + +[build-dependencies] +build-data = "0.1.3" +pkg-config = "0.3" + +[dev-dependencies] +wasm-bindgen-test = "0.3.36" + +[features] +default = ["bn254"] +bn254 = ["acvm/bn254", "barretenberg_blackbox_solver/bn254"] +bls12_381 = ["acvm/bls12_381", "barretenberg_blackbox_solver/bls12_381"] diff --git a/acvm-repo/acvm_js/README.md b/acvm-repo/acvm_js/README.md new file mode 100644 index 00000000000..2e59ca08cc9 --- /dev/null +++ b/acvm-repo/acvm_js/README.md @@ -0,0 +1,17 @@ +# acvm_js + +The `acvm_js` package enables users to execute an ACIR program, i.e. generating an initial witness from a set of inputs and calculating a partial witness. This partial witness can then be used to create a proof of execution using an ACVM backend. + +## Dependencies + +In order to build the wasm package, the following must be installed: + +- [jq](https://github.com/stedolan/jq) + +## Build + +The wasm package can be built using the command below: + +```bash +./build.sh +``` \ No newline at end of file diff --git a/acvm-repo/acvm_js/build.rs b/acvm-repo/acvm_js/build.rs new file mode 100644 index 00000000000..3f6f9c115a6 --- /dev/null +++ b/acvm-repo/acvm_js/build.rs @@ -0,0 +1,11 @@ +const GIT_COMMIT: &&str = &"GIT_COMMIT"; + +fn main() -> Result<(), String> { + if std::env::var(GIT_COMMIT).is_err() { + build_data::set_GIT_COMMIT(); + build_data::set_GIT_DIRTY(); + build_data::no_debug_rebuilds(); + } + + Ok(()) +} diff --git a/acvm-repo/acvm_js/build.sh b/acvm-repo/acvm_js/build.sh new file mode 100755 index 00000000000..37f2fd0a5a9 --- /dev/null +++ b/acvm-repo/acvm_js/build.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +function require_command { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Error: $1 is required but not installed." >&2 + exit 1 + fi +} +function check_installed { + if ! command -v "$1" >/dev/null 2>&1; then + echo "$1 is not installed. Please install it." >&2 + return 1 + fi + return 0 +} +function run_or_fail { + "$@" + local status=$? + if [ $status -ne 0 ]; then + echo "Command '$*' failed with exit code $status" >&2 + exit $status + fi +} + +require_command jq +require_command cargo +require_command wasm-bindgen +check_installed wasm-opt + +self_path=$(dirname "$(readlink -f "$0")") +export pname=$(cargo read-manifest | jq -r '.name') +export CARGO_TARGET_DIR=$self_path/target + +rm -rf $self_path/outputs >/dev/null 2>&1 +rm -rf $self_path/result >/dev/null 2>&1 + +if [ -v out ]; then + echo "Will install package to $out (defined outside installPhase.sh script)" +else + export out="$self_path/outputs/out" + echo "Will install package to $out" +fi + +run_or_fail $self_path/buildPhaseCargoCommand.sh +run_or_fail $self_path/installPhase.sh + +ln -s $out $self_path/result diff --git a/acvm-repo/acvm_js/buildPhaseCargoCommand.sh b/acvm-repo/acvm_js/buildPhaseCargoCommand.sh new file mode 100755 index 00000000000..6c710bc938f --- /dev/null +++ b/acvm-repo/acvm_js/buildPhaseCargoCommand.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +function run_or_fail { + "$@" + local status=$? + if [ $status -ne 0 ]; then + echo "Command '$*' failed with exit code $status" >&2 + exit $status + fi +} +function run_if_available { + if command -v "$1" >/dev/null 2>&1; then + "$@" + else + echo "$1 is not installed. Please install it to use this feature." >&2 + fi +} + +export self_path=$(dirname "$(readlink -f "$0")") + + +NODE_DIR=$self_path/nodejs/ +BROWSER_DIR=$self_path/web/ + +# Clear out the existing build artifacts as these aren't automatically removed by wasm-pack. +if [ -d ./pkg/ ]; then + rm -r $NODE_DIR + rm -r $BROWSER_DIR +fi + +TARGET=wasm32-unknown-unknown +WASM_BINARY=$CARGO_TARGET_DIR/$TARGET/release/${pname}.wasm + +NODE_WASM=${NODE_DIR}/${pname}_bg.wasm +BROWSER_WASM=${BROWSER_DIR}/${pname}_bg.wasm + +# Build the new wasm package +run_or_fail cargo build --lib --release --target $TARGET --package ${pname} +run_or_fail wasm-bindgen $WASM_BINARY --out-dir $NODE_DIR --typescript --target nodejs +run_or_fail wasm-bindgen $WASM_BINARY --out-dir $BROWSER_DIR --typescript --target web +run_if_available wasm-opt $NODE_WASM -o $NODE_WASM -O +run_if_available wasm-opt $BROWSER_WASM -o $BROWSER_WASM -O diff --git a/acvm-repo/acvm_js/installPhase.sh b/acvm-repo/acvm_js/installPhase.sh new file mode 100755 index 00000000000..34ddb8155e1 --- /dev/null +++ b/acvm-repo/acvm_js/installPhase.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +export self_path=$(dirname "$(readlink -f "$0")") + +export out_path=$out/acvm_js + +mkdir -p $out_path +cp $self_path/README.md $out_path/ +cp $self_path/package.json $out_path/ +cp -r $self_path/nodejs $out_path/ +cp -r $self_path/web $out_path/ diff --git a/acvm-repo/acvm_js/package.json b/acvm-repo/acvm_js/package.json new file mode 100644 index 00000000000..ba2b80748d2 --- /dev/null +++ b/acvm-repo/acvm_js/package.json @@ -0,0 +1,48 @@ +{ + "name": "@noir-lang/acvm_js", + "version": "0.27.4", + "private": true, + "repository": { + "type": "git", + "url": "https://github.com/noir-lang/acvm.git" + }, + "publishConfig": { + "access": "public" + }, + "collaborators": [ + "The Noir Team " + ], + "license": "MIT", + "main": "./nodejs/acvm_js.js", + "types": "./web/acvm_js.d.ts", + "module": "./web/acvm_js.js", + "files": [ + "nodejs", + "web", + "package.json" + ], + "sideEffects": false, + "packageManager": "yarn@3.5.1", + "scripts": { + "build": "bash ./build.sh", + "test": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha", + "test:browser": "web-test-runner", + "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0", + "clean": "chmod u+w web nodejs && rm -rf web nodejs" + }, + "devDependencies": { + "@esm-bundle/chai": "^4.3.4-fix.0", + "@typescript-eslint/eslint-plugin": "^5.59.5", + "@typescript-eslint/parser": "^5.59.5", + "@web/dev-server-esbuild": "^0.3.6", + "@web/test-runner": "^0.15.3", + "@web/test-runner-playwright": "^0.10.0", + "chai": "^4.3.7", + "eslint": "^8.40.0", + "eslint-plugin-prettier": "^5.0.0", + "mocha": "^10.2.0", + "prettier": "3.0.3", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + } +} diff --git a/acvm-repo/acvm_js/src/build_info.rs b/acvm-repo/acvm_js/src/build_info.rs new file mode 100644 index 00000000000..d4b05708ad1 --- /dev/null +++ b/acvm-repo/acvm_js/src/build_info.rs @@ -0,0 +1,46 @@ +use gloo_utils::format::JsValueSerdeExt; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(typescript_custom_section)] +const LOG_LEVEL: &'static str = r#" +/** +* @typedef {Object} BuildInfo - Information about how the installed package was built +* @property {string} gitHash - The hash of the git commit from which the package was built. +* @property {string} version - The version of the package at the built git commit. +* @property {boolean} dirty - Whether the package contained uncommitted changes when built. + */ +export type BuildInfo = { + gitHash: string; + version: string; + dirty: string; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "BuildInfo")] + pub type JsBuildInfo; +} + +#[derive(Serialize, Deserialize)] +struct BuildInfo { + #[serde(rename = "gitHash")] + git_hash: &'static str, + version: &'static str, + dirty: bool, +} + +const BUILD_INFO: BuildInfo = BuildInfo { + git_hash: env!("GIT_COMMIT"), + version: env!("CARGO_PKG_VERSION"), + dirty: const_str::equal!(env!("GIT_DIRTY"), "true"), +}; + +/// Returns the `BuildInfo` object containing information about how the installed package was built. +/// @returns {BuildInfo} - Information on how the installed package was built. +#[wasm_bindgen(js_name = buildInfo, skip_jsdoc)] +pub fn build_info() -> JsBuildInfo { + console_error_panic_hook::set_once(); + ::from_serde(&BUILD_INFO).unwrap().into() +} diff --git a/acvm-repo/acvm_js/src/compression.rs b/acvm-repo/acvm_js/src/compression.rs new file mode 100644 index 00000000000..fedaa514bf0 --- /dev/null +++ b/acvm-repo/acvm_js/src/compression.rs @@ -0,0 +1,34 @@ +use acvm::acir::native_types::WitnessMap; +use js_sys::JsString; +use wasm_bindgen::prelude::wasm_bindgen; + +use crate::JsWitnessMap; + +/// Compresses a `WitnessMap` into the binary format outputted by Nargo. +/// +/// @param {Uint8Array} compressed_witness - A witness map. +/// @returns {WitnessMap} A compressed witness map +#[wasm_bindgen(js_name = compressWitness, skip_jsdoc)] +pub fn compress_witness(witness_map: JsWitnessMap) -> Result, JsString> { + console_error_panic_hook::set_once(); + + let witness_map = WitnessMap::from(witness_map); + let compressed_witness_map: Vec = + Vec::::try_from(witness_map).map_err(|err| err.to_string())?; + + Ok(compressed_witness_map) +} + +/// Decompresses a compressed witness as outputted by Nargo into a `WitnessMap`. +/// +/// @param {Uint8Array} compressed_witness - A compressed witness. +/// @returns {WitnessMap} The decompressed witness map. +#[wasm_bindgen(js_name = decompressWitness, skip_jsdoc)] +pub fn decompress_witness(compressed_witness: Vec) -> Result { + console_error_panic_hook::set_once(); + + let witness_map = + WitnessMap::try_from(compressed_witness.as_slice()).map_err(|err| err.to_string())?; + + Ok(witness_map.into()) +} diff --git a/acvm-repo/acvm_js/src/execute.rs b/acvm-repo/acvm_js/src/execute.rs new file mode 100644 index 00000000000..e623aff42bd --- /dev/null +++ b/acvm-repo/acvm_js/src/execute.rs @@ -0,0 +1,131 @@ +use acvm::{ + acir::circuit::{Circuit, OpcodeLocation}, + pwg::{ACVMStatus, ErrorLocation, OpcodeResolutionError, ACVM}, +}; +#[allow(deprecated)] +use barretenberg_blackbox_solver::BarretenbergSolver; + +use js_sys::Error; +use wasm_bindgen::prelude::wasm_bindgen; + +use crate::{ + foreign_call::{resolve_brillig, ForeignCallHandler}, + JsExecutionError, JsWitnessMap, +}; + +#[wasm_bindgen] +#[allow(deprecated)] +pub struct WasmBlackBoxFunctionSolver(BarretenbergSolver); + +impl WasmBlackBoxFunctionSolver { + async fn initialize() -> WasmBlackBoxFunctionSolver { + #[allow(deprecated)] + WasmBlackBoxFunctionSolver(BarretenbergSolver::initialize().await) + } +} + +#[wasm_bindgen(js_name = "createBlackBoxSolver")] +pub async fn create_black_box_solver() -> WasmBlackBoxFunctionSolver { + WasmBlackBoxFunctionSolver::initialize().await +} + +/// Executes an ACIR circuit to generate the solved witness from the initial witness. +/// +/// @param {Uint8Array} circuit - A serialized representation of an ACIR circuit +/// @param {WitnessMap} initial_witness - The initial witness map defining all of the inputs to `circuit`.. +/// @param {ForeignCallHandler} foreign_call_handler - A callback to process any foreign calls from the circuit. +/// @returns {WitnessMap} The solved witness calculated by executing the circuit on the provided inputs. +#[wasm_bindgen(js_name = executeCircuit, skip_jsdoc)] +pub async fn execute_circuit( + circuit: Vec, + initial_witness: JsWitnessMap, + foreign_call_handler: ForeignCallHandler, +) -> Result { + console_error_panic_hook::set_once(); + + let solver = WasmBlackBoxFunctionSolver::initialize().await; + + execute_circuit_with_black_box_solver(&solver, circuit, initial_witness, foreign_call_handler) + .await +} + +/// Executes an ACIR circuit to generate the solved witness from the initial witness. +/// +/// @param {&WasmBlackBoxFunctionSolver} solver - A black box solver. +/// @param {Uint8Array} circuit - A serialized representation of an ACIR circuit +/// @param {WitnessMap} initial_witness - The initial witness map defining all of the inputs to `circuit`.. +/// @param {ForeignCallHandler} foreign_call_handler - A callback to process any foreign calls from the circuit. +/// @returns {WitnessMap} The solved witness calculated by executing the circuit on the provided inputs. +#[wasm_bindgen(js_name = executeCircuitWithBlackBoxSolver, skip_jsdoc)] +pub async fn execute_circuit_with_black_box_solver( + solver: &WasmBlackBoxFunctionSolver, + circuit: Vec, + initial_witness: JsWitnessMap, + foreign_call_handler: ForeignCallHandler, +) -> Result { + console_error_panic_hook::set_once(); + let circuit: Circuit = Circuit::read(&*circuit).expect("Failed to deserialize circuit"); + + let mut acvm = ACVM::new(&solver.0, circuit.opcodes, initial_witness.into()); + + loop { + let solver_status = acvm.solve(); + + match solver_status { + ACVMStatus::Solved => break, + ACVMStatus::InProgress => { + unreachable!("Execution should not stop while in `InProgress` state.") + } + ACVMStatus::Failure(error) => { + let (assert_message, call_stack) = match &error { + OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: ErrorLocation::Resolved(opcode_location), + } + | OpcodeResolutionError::IndexOutOfBounds { + opcode_location: ErrorLocation::Resolved(opcode_location), + .. + } => ( + get_assert_message(&circuit.assert_messages, opcode_location), + Some(vec![*opcode_location]), + ), + OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => { + let failing_opcode = + call_stack.last().expect("Brillig error call stacks cannot be empty"); + ( + get_assert_message(&circuit.assert_messages, failing_opcode), + Some(call_stack.clone()), + ) + } + _ => (None, None), + }; + + let error_string = match &assert_message { + Some(assert_message) => format!("Assertion failed: {}", assert_message), + None => error.to_string(), + }; + + return Err(JsExecutionError::new(error_string.into(), call_stack).into()); + } + ACVMStatus::RequiresForeignCall(foreign_call) => { + let result = resolve_brillig(&foreign_call_handler, &foreign_call).await?; + + acvm.resolve_pending_foreign_call(result); + } + } + } + + let witness_map = acvm.finalize(); + Ok(witness_map.into()) +} + +// Searches the slice for `opcode_location`. +// This is functionality equivalent to .get on a map. +fn get_assert_message( + assert_messages: &[(OpcodeLocation, String)], + opcode_location: &OpcodeLocation, +) -> Option { + assert_messages + .iter() + .find(|(loc, _)| loc == opcode_location) + .map(|(_, message)| message.clone()) +} diff --git a/acvm-repo/acvm_js/src/foreign_call/inputs.rs b/acvm-repo/acvm_js/src/foreign_call/inputs.rs new file mode 100644 index 00000000000..1238f9b1cb2 --- /dev/null +++ b/acvm-repo/acvm_js/src/foreign_call/inputs.rs @@ -0,0 +1,17 @@ +use acvm::brillig_vm::brillig::Value; + +use crate::js_witness_map::field_element_to_js_string; + +pub(super) fn encode_foreign_call_inputs(foreign_call_inputs: &[Vec]) -> js_sys::Array { + let inputs = js_sys::Array::default(); + for input in foreign_call_inputs { + let input_array = js_sys::Array::default(); + for value in input { + let hex_js_string = field_element_to_js_string(&value.to_field()); + input_array.push(&hex_js_string); + } + inputs.push(&input_array); + } + + inputs +} diff --git a/acvm-repo/acvm_js/src/foreign_call/mod.rs b/acvm-repo/acvm_js/src/foreign_call/mod.rs new file mode 100644 index 00000000000..9ccaf733f84 --- /dev/null +++ b/acvm-repo/acvm_js/src/foreign_call/mod.rs @@ -0,0 +1,74 @@ +use acvm::{brillig_vm::brillig::ForeignCallResult, pwg::ForeignCallWaitInfo}; + +use js_sys::{Error, JsString}; +use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; + +mod inputs; +mod outputs; + +#[wasm_bindgen(typescript_custom_section)] +const FOREIGN_CALL_HANDLER: &'static str = r#" +export type ForeignCallInput = string[] +export type ForeignCallOutput = string | string[] + +/** +* A callback which performs an foreign call and returns the response. +* @callback ForeignCallHandler +* @param {string} name - The identifier for the type of foreign call being performed. +* @param {string[][]} inputs - An array of hex encoded inputs to the foreign call. +* @returns {Promise} outputs - An array of hex encoded outputs containing the results of the foreign call. +*/ +export type ForeignCallHandler = (name: string, inputs: ForeignCallInput[]) => Promise; +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = js_sys::Function, typescript_type = "ForeignCallHandler")] + pub type ForeignCallHandler; +} + +pub(super) async fn resolve_brillig( + foreign_call_callback: &ForeignCallHandler, + foreign_call_wait_info: &ForeignCallWaitInfo, +) -> Result { + // Prepare to call + let name = JsString::from(foreign_call_wait_info.function.clone()); + let inputs = inputs::encode_foreign_call_inputs(&foreign_call_wait_info.inputs); + + // Perform foreign call + let outputs = perform_foreign_call(foreign_call_callback, name, inputs).await?; + + // The Brillig VM checks that the number of return values from + // the foreign call is valid so we don't need to do it here. + outputs::decode_foreign_call_result(outputs).map_err(|message| Error::new(&message)) +} + +async fn perform_foreign_call( + foreign_call_handler: &ForeignCallHandler, + name: JsString, + inputs: js_sys::Array, +) -> Result { + // Call and await + let this = JsValue::null(); + let ret_js_val = foreign_call_handler + .call2(&this, &name, &inputs) + .map_err(|err| wrap_js_error("Error calling `foreign_call_callback`", &err))?; + let ret_js_prom: js_sys::Promise = ret_js_val.into(); + let ret_future: wasm_bindgen_futures::JsFuture = ret_js_prom.into(); + let js_resolution = ret_future + .await + .map_err(|err| wrap_js_error("Error awaiting `foreign_call_handler`", &err))?; + + // Check that result conforms to expected shape. + if !js_resolution.is_array() { + return Err(Error::new("Expected `foreign_call_handler` to return an array")); + } + + Ok(js_sys::Array::from(&js_resolution)) +} + +fn wrap_js_error(message: &str, err: &JsValue) -> Error { + let new_error = Error::new(message); + new_error.set_cause(err); + new_error +} diff --git a/acvm-repo/acvm_js/src/foreign_call/outputs.rs b/acvm-repo/acvm_js/src/foreign_call/outputs.rs new file mode 100644 index 00000000000..eea686b9369 --- /dev/null +++ b/acvm-repo/acvm_js/src/foreign_call/outputs.rs @@ -0,0 +1,31 @@ +use acvm::brillig_vm::brillig::{ForeignCallOutput, ForeignCallResult, Value}; +use wasm_bindgen::JsValue; + +use crate::js_witness_map::js_value_to_field_element; + +fn decode_foreign_call_output(output: JsValue) -> Result { + if output.is_string() { + let value = Value::from(js_value_to_field_element(output)?); + Ok(ForeignCallOutput::Single(value)) + } else if output.is_array() { + let output = js_sys::Array::from(&output); + + let mut values: Vec = Vec::with_capacity(output.length() as usize); + for elem in output.iter() { + values.push(Value::from(js_value_to_field_element(elem)?)) + } + Ok(ForeignCallOutput::Array(values)) + } else { + return Err("Non-string-or-array element in foreign_call_handler return".into()); + } +} + +pub(super) fn decode_foreign_call_result( + js_array: js_sys::Array, +) -> Result { + let mut values: Vec = Vec::with_capacity(js_array.length() as usize); + for elem in js_array.iter() { + values.push(decode_foreign_call_output(elem)?); + } + Ok(ForeignCallResult { values }) +} diff --git a/acvm-repo/acvm_js/src/js_execution_error.rs b/acvm-repo/acvm_js/src/js_execution_error.rs new file mode 100644 index 00000000000..d91a9425f7e --- /dev/null +++ b/acvm-repo/acvm_js/src/js_execution_error.rs @@ -0,0 +1,52 @@ +use acvm::acir::circuit::OpcodeLocation; +use js_sys::{Array, Error, JsString, Reflect}; +use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; + +#[wasm_bindgen(typescript_custom_section)] +const EXECUTION_ERROR: &'static str = r#" +export type ExecutionError = Error & { + callStack?: string[]; +}; +"#; + +/// JsExecutionError is a raw js error. +/// It'd be ideal that execution error was a subclass of Error, but for that we'd need to use JS snippets or a js module. +/// Currently JS snippets don't work with a nodejs target. And a module would be too much for just a custom error type. +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Error, js_name = "ExecutionError", typescript_type = "ExecutionError")] + #[derive(Clone, Debug, PartialEq, Eq)] + pub type JsExecutionError; + + #[wasm_bindgen(constructor, js_class = "Error")] + fn constructor(message: JsString) -> JsExecutionError; +} + +impl JsExecutionError { + /// Creates a new execution error with the given call stack. + /// Call stacks won't be optional in the future, after removing ErrorLocation in ACVM. + pub fn new(message: String, call_stack: Option>) -> Self { + let mut error = JsExecutionError::constructor(JsString::from(message)); + let js_call_stack = match call_stack { + Some(call_stack) => { + let js_array = Array::new(); + for loc in call_stack { + js_array.push(&JsValue::from(format!("{}", loc))); + } + js_array.into() + } + None => JsValue::UNDEFINED, + }; + + error.set_property("callStack", js_call_stack); + + error + } + + fn set_property(&mut self, property: &str, value: JsValue) { + assert!( + Reflect::set(self, &JsValue::from(property), &value).expect("Errors should be objects"), + "Errors should be writable" + ); + } +} diff --git a/acvm-repo/acvm_js/src/js_witness_map.rs b/acvm-repo/acvm_js/src/js_witness_map.rs new file mode 100644 index 00000000000..481b8caaa2d --- /dev/null +++ b/acvm-repo/acvm_js/src/js_witness_map.rs @@ -0,0 +1,112 @@ +use acvm::{ + acir::native_types::{Witness, WitnessMap}, + FieldElement, +}; +use js_sys::{JsString, Map}; +use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; + +#[wasm_bindgen(typescript_custom_section)] +const WITNESS_MAP: &'static str = r#" +// Map from witness index to hex string value of witness. +export type WitnessMap = Map; +"#; + +// WitnessMap +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Map, js_name = "WitnessMap", typescript_type = "WitnessMap")] + #[derive(Clone, Debug, PartialEq, Eq)] + pub type JsWitnessMap; + + #[wasm_bindgen(constructor, js_class = "Map")] + pub fn new() -> JsWitnessMap; + +} + +impl Default for JsWitnessMap { + fn default() -> Self { + Self::new() + } +} + +impl From for JsWitnessMap { + fn from(witness_map: WitnessMap) -> Self { + let js_map = JsWitnessMap::new(); + for (key, value) in witness_map { + js_map.set( + &js_sys::Number::from(key.witness_index()), + &field_element_to_js_string(&value), + ); + } + js_map + } +} + +impl From for WitnessMap { + fn from(js_map: JsWitnessMap) -> Self { + let mut witness_map = WitnessMap::new(); + js_map.for_each(&mut |value, key| { + let witness_index = Witness(key.as_f64().unwrap() as u32); + let witness_value = js_value_to_field_element(value).unwrap(); + witness_map.insert(witness_index, witness_value); + }); + witness_map + } +} + +pub(crate) fn js_value_to_field_element(js_value: JsValue) -> Result { + let hex_str = js_value.as_string().ok_or("failed to parse field element from non-string")?; + + FieldElement::from_hex(&hex_str) + .ok_or_else(|| format!("Invalid hex string: '{}'", hex_str).into()) +} + +pub(crate) fn field_element_to_js_string(field_element: &FieldElement) -> JsString { + // This currently maps `0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000` + // to the bigint `-1n`. This fails when converting back to a `FieldElement`. + // js_sys::BigInt::from_str(&value.to_hex()).unwrap() + + format!("0x{}", field_element.to_hex()).into() +} + +#[cfg(test)] +mod test { + use wasm_bindgen_test::wasm_bindgen_test as test; + + use std::collections::BTreeMap; + + use acvm::{ + acir::native_types::{Witness, WitnessMap}, + FieldElement, + }; + use wasm_bindgen::JsValue; + + use crate::JsWitnessMap; + + #[test] + fn test_witness_map_to_js() { + let witness_map = BTreeMap::from([ + (Witness(1), FieldElement::one()), + (Witness(2), FieldElement::zero()), + (Witness(3), -FieldElement::one()), + ]); + let witness_map = WitnessMap::from(witness_map); + + let js_map = JsWitnessMap::from(witness_map); + + assert_eq!( + js_map.get(&JsValue::from(1)), + JsValue::from_str("0x0000000000000000000000000000000000000000000000000000000000000001") + ); + assert_eq!( + js_map.get(&JsValue::from(2)), + JsValue::from_str("0x0000000000000000000000000000000000000000000000000000000000000000") + ); + assert_eq!( + js_map.get(&JsValue::from(3)), + // Equal to 21888242871839275222246405745257275088548364400416034343698204186575808495616, + // which is field modulus - 1: https://docs.rs/ark-bn254/latest/ark_bn254/ + JsValue::from_str("0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000") + ); + } +} diff --git a/acvm-repo/acvm_js/src/lib.rs b/acvm-repo/acvm_js/src/lib.rs new file mode 100644 index 00000000000..6914c0de540 --- /dev/null +++ b/acvm-repo/acvm_js/src/lib.rs @@ -0,0 +1,27 @@ +// #![warn(unused_crate_dependencies, unused_extern_crates)] +#![warn(unreachable_pub)] + +// TODO: Absence of per package targets +// https://doc.rust-lang.org/cargo/reference/unstable.html#per-package-target +// otherwise could be reorganized to make this file more pretty. + +cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + mod build_info; + mod compression; + mod execute; + mod foreign_call; + mod js_witness_map; + mod logging; + mod public_witness; + mod js_execution_error; + + pub use build_info::build_info; + pub use compression::{compress_witness, decompress_witness}; + pub use execute::{execute_circuit, execute_circuit_with_black_box_solver, create_black_box_solver}; + pub use js_witness_map::JsWitnessMap; + pub use logging::{init_log_level, LogLevel}; + pub use public_witness::{get_public_parameters_witness, get_public_witness, get_return_witness}; + pub use js_execution_error::JsExecutionError; + } +} diff --git a/acvm-repo/acvm_js/src/logging.rs b/acvm-repo/acvm_js/src/logging.rs new file mode 100644 index 00000000000..d939c5f8367 --- /dev/null +++ b/acvm-repo/acvm_js/src/logging.rs @@ -0,0 +1,31 @@ +use js_sys::JsString; +use log::Level; +use std::str::FromStr; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(typescript_custom_section)] +const LOG_LEVEL: &'static str = r#" +export type LogLevel = "OFF" | "ERROR" | "WARN" | "INFO" | "DEBUG" | "TRACE"; +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = JsString, typescript_type = "LogLevel")] + pub type LogLevel; +} + +/// Sets the package's logging level. +/// +/// @param {LogLevel} level - The maximum level of logging to be emitted. +#[wasm_bindgen(js_name = initLogLevel, skip_jsdoc)] +pub fn init_log_level(level: LogLevel) { + // Set the static variable from Rust + use std::sync::Once; + + let log_level = level.as_string().unwrap(); + let log_level = Level::from_str(&log_level).unwrap_or(Level::Error); + static SET_HOOK: Once = Once::new(); + SET_HOOK.call_once(|| { + wasm_logger::init(wasm_logger::Config::new(log_level)); + }); +} diff --git a/acvm-repo/acvm_js/src/public_witness.rs b/acvm-repo/acvm_js/src/public_witness.rs new file mode 100644 index 00000000000..46e4b788772 --- /dev/null +++ b/acvm-repo/acvm_js/src/public_witness.rs @@ -0,0 +1,80 @@ +use acvm::acir::{ + circuit::Circuit, + native_types::{Witness, WitnessMap}, +}; +use js_sys::JsString; +use wasm_bindgen::prelude::wasm_bindgen; + +use crate::JsWitnessMap; + +fn extract_indices(witness_map: &WitnessMap, indices: Vec) -> Result { + let mut extracted_witness_map = WitnessMap::new(); + for witness in indices { + let witness_value = witness_map.get(&witness).ok_or(format!( + "Failed to extract witness {} from witness map. Witness not found.", + witness.0 + ))?; + extracted_witness_map.insert(witness, *witness_value); + } + Ok(extracted_witness_map) +} + +/// Extracts a `WitnessMap` containing the witness indices corresponding to the circuit's return values. +/// +/// @param {Uint8Array} circuit - A serialized representation of an ACIR circuit +/// @param {WitnessMap} witness_map - The completed witness map after executing the circuit. +/// @returns {WitnessMap} A witness map containing the circuit's return values. +#[wasm_bindgen(js_name = getReturnWitness)] +pub fn get_return_witness( + circuit: Vec, + witness_map: JsWitnessMap, +) -> Result { + console_error_panic_hook::set_once(); + let circuit: Circuit = Circuit::read(&*circuit).expect("Failed to deserialize circuit"); + let witness_map = WitnessMap::from(witness_map); + + let return_witness = + extract_indices(&witness_map, circuit.return_values.0.into_iter().collect())?; + + Ok(JsWitnessMap::from(return_witness)) +} + +/// Extracts a `WitnessMap` containing the witness indices corresponding to the circuit's public parameters. +/// +/// @param {Uint8Array} circuit - A serialized representation of an ACIR circuit +/// @param {WitnessMap} witness_map - The completed witness map after executing the circuit. +/// @returns {WitnessMap} A witness map containing the circuit's public parameters. +#[wasm_bindgen(js_name = getPublicParametersWitness)] +pub fn get_public_parameters_witness( + circuit: Vec, + solved_witness: JsWitnessMap, +) -> Result { + console_error_panic_hook::set_once(); + let circuit: Circuit = Circuit::read(&*circuit).expect("Failed to deserialize circuit"); + let witness_map = WitnessMap::from(solved_witness); + + let public_params_witness = + extract_indices(&witness_map, circuit.public_parameters.0.into_iter().collect())?; + + Ok(JsWitnessMap::from(public_params_witness)) +} + +/// Extracts a `WitnessMap` containing the witness indices corresponding to the circuit's public inputs. +/// +/// @param {Uint8Array} circuit - A serialized representation of an ACIR circuit +/// @param {WitnessMap} witness_map - The completed witness map after executing the circuit. +/// @returns {WitnessMap} A witness map containing the circuit's public inputs. +#[wasm_bindgen(js_name = getPublicWitness)] +pub fn get_public_witness( + circuit: Vec, + solved_witness: JsWitnessMap, +) -> Result { + console_error_panic_hook::set_once(); + let circuit: Circuit = Circuit::read(&*circuit).expect("Failed to deserialize circuit"); + let witness_map = WitnessMap::from(solved_witness); + + let public_witness = + extract_indices(&witness_map, circuit.public_inputs().0.into_iter().collect())?; + + Ok(JsWitnessMap::from(public_witness)) +} diff --git a/acvm-repo/acvm_js/test/browser/execute_circuit.test.ts b/acvm-repo/acvm_js/test/browser/execute_circuit.test.ts new file mode 100644 index 00000000000..601deffc79e --- /dev/null +++ b/acvm-repo/acvm_js/test/browser/execute_circuit.test.ts @@ -0,0 +1,211 @@ +import { expect } from "@esm-bundle/chai"; +import initACVM, { + createBlackBoxSolver, + executeCircuit, + executeCircuitWithBlackBoxSolver, + WasmBlackBoxFunctionSolver, + WitnessMap, + initLogLevel, + ForeignCallHandler, +} from "@noir-lang/acvm_js"; + +beforeEach(async () => { + await initACVM(); + + initLogLevel("INFO"); +}); + +it("successfully executes circuit and extracts return value", async () => { + const { bytecode, initialWitnessMap, resultWitness, expectedResult } = + await import("../shared/addition"); + + const solvedWitness: WitnessMap = await executeCircuit( + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + + // Solved witness should be consistent with initial witness + initialWitnessMap.forEach((value, key) => { + expect(solvedWitness.get(key) as string).to.be.eq(value); + }); + + // Solved witness should contain expected return value + expect(solvedWitness.get(resultWitness)).to.be.eq(expectedResult); +}); + +it("successfully processes simple brillig foreign call opcodes", async () => { + const { + bytecode, + initialWitnessMap, + expectedWitnessMap, + oracleResponse, + oracleCallName, + oracleCallInputs, + } = await import("../shared/foreign_call"); + + let observedName = ""; + let observedInputs: string[][] = []; + const foreignCallHandler: ForeignCallHandler = async ( + name: string, + inputs: string[][], + ) => { + // Throwing inside the oracle callback causes a timeout so we log the observed values + // and defer the check against expected values until after the execution is complete. + observedName = name; + observedInputs = inputs; + + return oracleResponse; + }; + + const solved_witness: WitnessMap = await executeCircuit( + bytecode, + initialWitnessMap, + foreignCallHandler, + ); + + // Check that expected values were passed to oracle callback. + expect(observedName).to.be.eq(oracleCallName); + expect(observedInputs).to.be.deep.eq(oracleCallInputs); + + // If incorrect value is written into circuit then execution should halt due to unsatisfied constraint in + // arithmetic opcode. Nevertheless, check that returned value was inserted correctly. + expect(solved_witness).to.be.deep.eq(expectedWitnessMap); +}); + +it("successfully processes complex brillig foreign call opcodes", async () => { + const { + bytecode, + initialWitnessMap, + expectedWitnessMap, + oracleResponse, + oracleCallName, + oracleCallInputs, + } = await import("../shared/complex_foreign_call"); + + let observedName = ""; + let observedInputs: string[][] = []; + const foreignCallHandler: ForeignCallHandler = async ( + name: string, + inputs: string[][], + ) => { + // Throwing inside the oracle callback causes a timeout so we log the observed values + // and defer the check against expected values until after the execution is complete. + observedName = name; + observedInputs = inputs; + + return oracleResponse; + }; + + const solved_witness: WitnessMap = await executeCircuit( + bytecode, + initialWitnessMap, + foreignCallHandler, + ); + + // Check that expected values were passed to oracle callback. + expect(observedName).to.be.eq(oracleCallName); + expect(observedInputs).to.be.deep.eq(oracleCallInputs); + + // If incorrect value is written into circuit then execution should halt due to unsatisfied constraint in + // arithmetic opcode. Nevertheless, check that returned value was inserted correctly. + expect(solved_witness).to.be.deep.eq(expectedWitnessMap); +}); + +it("successfully executes a Pedersen opcode", async function () { + const { bytecode, initialWitnessMap, expectedWitnessMap } = await import( + "../shared/pedersen" + ); + + const solvedWitness: WitnessMap = await executeCircuit( + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + + expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); +}); + +it("successfully executes a FixedBaseScalarMul opcode", async () => { + const { bytecode, initialWitnessMap, expectedWitnessMap } = await import( + "../shared/fixed_base_scalar_mul" + ); + + const solvedWitness: WitnessMap = await executeCircuit( + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + + expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); +}); + +it("successfully executes a SchnorrVerify opcode", async () => { + const { bytecode, initialWitnessMap, expectedWitnessMap } = await import( + "../shared/schnorr_verify" + ); + + const solvedWitness: WitnessMap = await executeCircuit( + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + + expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); +}); + +it("successfully executes a MemoryOp opcode", async () => { + const { bytecode, initialWitnessMap, expectedWitnessMap } = await import( + "../shared/memory_op" + ); + + const solvedWitness: WitnessMap = await executeCircuit( + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + + expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); +}); + +it("successfully executes two circuits with same backend", async function () { + // chose pedersen op here because it is the one with slow initialization + // that led to the decision to pull backend initialization into a separate + // function/wasmbind + const solver: WasmBlackBoxFunctionSolver = await createBlackBoxSolver(); + + const { bytecode, initialWitnessMap, expectedWitnessMap } = await import( + "../shared/pedersen" + ); + + const solvedWitness0: WitnessMap = await executeCircuitWithBlackBoxSolver( + solver, + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + + expect(solvedWitness0).to.be.deep.eq(expectedWitnessMap); + + const solvedWitness1: WitnessMap = await executeCircuitWithBlackBoxSolver( + solver, + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + expect(solvedWitness1).to.be.deep.eq(expectedWitnessMap); +}); diff --git a/acvm-repo/acvm_js/test/browser/witness_conversion.test.ts b/acvm-repo/acvm_js/test/browser/witness_conversion.test.ts new file mode 100644 index 00000000000..67c6c998923 --- /dev/null +++ b/acvm-repo/acvm_js/test/browser/witness_conversion.test.ts @@ -0,0 +1,25 @@ +import { expect } from "@esm-bundle/chai"; +import initACVM, { + compressWitness, + decompressWitness, +} from "@noir-lang/acvm_js"; +import { + expectedCompressedWitnessMap, + expectedWitnessMap, +} from "../shared/witness_compression"; + +beforeEach(async () => { + await initACVM(); +}); + +it("successfully compresses the witness", async () => { + const compressedWitnessMap = compressWitness(expectedWitnessMap); + + expect(compressedWitnessMap).to.be.deep.eq(expectedCompressedWitnessMap); +}); + +it("successfully decompresses the witness", async () => { + const witnessMap = decompressWitness(expectedCompressedWitnessMap); + + expect(witnessMap).to.be.deep.eq(expectedWitnessMap); +}); diff --git a/acvm-repo/acvm_js/test/node/build_info.test.ts b/acvm-repo/acvm_js/test/node/build_info.test.ts new file mode 100644 index 00000000000..fcbdd9e45b7 --- /dev/null +++ b/acvm-repo/acvm_js/test/node/build_info.test.ts @@ -0,0 +1,17 @@ +import { expect } from "chai"; +import { BuildInfo, buildInfo } from "@noir-lang/acvm_js"; +import child_process from "child_process"; +import pkg from "../../package.json"; + +it("returns the correct build into", () => { + const info: BuildInfo = buildInfo(); + + // TODO: enforce that `package.json` and `Cargo.toml` are consistent. + expect(info.version).to.be.eq(pkg.version); + + const revision = child_process + .execSync("git rev-parse HEAD") + .toString() + .trim(); + expect(info.gitHash).to.be.eq(revision); +}); diff --git a/acvm-repo/acvm_js/test/node/execute_circuit.test.ts b/acvm-repo/acvm_js/test/node/execute_circuit.test.ts new file mode 100644 index 00000000000..a9807b48aa7 --- /dev/null +++ b/acvm-repo/acvm_js/test/node/execute_circuit.test.ts @@ -0,0 +1,233 @@ +import { expect } from "chai"; +import { + createBlackBoxSolver, + executeCircuit, + executeCircuitWithBlackBoxSolver, + WasmBlackBoxFunctionSolver, + WitnessMap, + ForeignCallHandler, +} from "@noir-lang/acvm_js"; + +it("successfully executes circuit and extracts return value", async () => { + const { bytecode, initialWitnessMap, resultWitness, expectedResult } = + await import("../shared/addition"); + + const solvedWitness: WitnessMap = await executeCircuit( + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + + // Solved witness should be consistent with initial witness + initialWitnessMap.forEach((value, key) => { + expect(solvedWitness.get(key) as string).to.be.eq(value); + }); + + // Solved witness should contain expected return value + expect(solvedWitness.get(resultWitness)).to.be.eq(expectedResult); +}); + +it("successfully processes simple brillig foreign call opcodes", async () => { + const { + bytecode, + initialWitnessMap, + expectedWitnessMap, + oracleResponse, + oracleCallName, + oracleCallInputs, + } = await import("../shared/foreign_call"); + + let observedName = ""; + let observedInputs: string[][] = []; + const foreignCallHandler: ForeignCallHandler = async ( + name: string, + inputs: string[][], + ) => { + // Throwing inside the oracle callback causes a timeout so we log the observed values + // and defer the check against expected values until after the execution is complete. + observedName = name; + observedInputs = inputs; + + return oracleResponse; + }; + + const solved_witness: WitnessMap = await executeCircuit( + bytecode, + initialWitnessMap, + foreignCallHandler, + ); + + // Check that expected values were passed to oracle callback. + expect(observedName).to.be.eq(oracleCallName); + expect(observedInputs).to.be.deep.eq(oracleCallInputs); + + // If incorrect value is written into circuit then execution should halt due to unsatisfied constraint in + // arithmetic opcode. Nevertheless, check that returned value was inserted correctly. + expect(solved_witness).to.be.deep.eq(expectedWitnessMap); +}); + +it("successfully processes complex brillig foreign call opcodes", async () => { + const { + bytecode, + initialWitnessMap, + expectedWitnessMap, + oracleResponse, + oracleCallName, + oracleCallInputs, + } = await import("../shared/complex_foreign_call"); + + let observedName = ""; + let observedInputs: string[][] = []; + const foreignCallHandler: ForeignCallHandler = async ( + name: string, + inputs: string[][], + ) => { + // Throwing inside the oracle callback causes a timeout so we log the observed values + // and defer the check against expected values until after the execution is complete. + observedName = name; + observedInputs = inputs; + + return oracleResponse; + }; + + const solved_witness: WitnessMap = await executeCircuit( + bytecode, + initialWitnessMap, + foreignCallHandler, + ); + + // Check that expected values were passed to oracle callback. + expect(observedName).to.be.eq(oracleCallName); + expect(observedInputs).to.be.deep.eq(oracleCallInputs); + + // If incorrect value is written into circuit then execution should halt due to unsatisfied constraint in + // arithmetic opcode. Nevertheless, check that returned value was inserted correctly. + expect(solved_witness).to.be.deep.eq(expectedWitnessMap); +}); + +it("successfully executes a Pedersen opcode", async function () { + this.timeout(10000); + const { bytecode, initialWitnessMap, expectedWitnessMap } = await import( + "../shared/pedersen" + ); + + const solvedWitness: WitnessMap = await executeCircuit( + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + + expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); +}); + +it("successfully executes a FixedBaseScalarMul opcode", async () => { + const { bytecode, initialWitnessMap, expectedWitnessMap } = await import( + "../shared/fixed_base_scalar_mul" + ); + + const solvedWitness: WitnessMap = await executeCircuit( + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + + expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); +}); + +it("successfully executes a SchnorrVerify opcode", async () => { + const { bytecode, initialWitnessMap, expectedWitnessMap } = await import( + "../shared/schnorr_verify" + ); + + const solvedWitness: WitnessMap = await executeCircuit( + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + + expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); +}); + +it("successfully executes a MemoryOp opcode", async () => { + const { bytecode, initialWitnessMap, expectedWitnessMap } = await import( + "../shared/memory_op" + ); + + const solvedWitness: WitnessMap = await executeCircuit( + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + + expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); +}); + +it("successfully executes two circuits with same backend", async function () { + this.timeout(10000); + + // chose pedersen op here because it is the one with slow initialization + // that led to the decision to pull backend initialization into a separate + // function/wasmbind + const solver: WasmBlackBoxFunctionSolver = await createBlackBoxSolver(); + + const { bytecode, initialWitnessMap, expectedWitnessMap } = await import( + "../shared/pedersen" + ); + + const solvedWitness0 = await executeCircuitWithBlackBoxSolver( + solver, + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + + const solvedWitness1 = await executeCircuitWithBlackBoxSolver( + solver, + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + + expect(solvedWitness0).to.be.deep.eq(expectedWitnessMap); + expect(solvedWitness1).to.be.deep.eq(expectedWitnessMap); +}); + +it("successfully executes 500 circuits with same backend", async function () { + this.timeout(100000); + + // chose pedersen op here because it is the one with slow initialization + // that led to the decision to pull backend initialization into a separate + // function/wasmbind + const solver: WasmBlackBoxFunctionSolver = await createBlackBoxSolver(); + + const { bytecode, initialWitnessMap, expectedWitnessMap } = await import( + "../shared/pedersen" + ); + + for (let i = 0; i < 500; i++) { + const solvedWitness = await executeCircuitWithBlackBoxSolver( + solver, + bytecode, + initialWitnessMap, + () => { + throw Error("unexpected oracle"); + }, + ); + + expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); + } +}); diff --git a/acvm-repo/acvm_js/test/node/witness_conversion.test.ts b/acvm-repo/acvm_js/test/node/witness_conversion.test.ts new file mode 100644 index 00000000000..86a716c6d70 --- /dev/null +++ b/acvm-repo/acvm_js/test/node/witness_conversion.test.ts @@ -0,0 +1,18 @@ +import { expect } from "chai"; +import { compressWitness, decompressWitness } from "@noir-lang/acvm_js"; +import { + expectedCompressedWitnessMap, + expectedWitnessMap, +} from "../shared/witness_compression"; + +it("successfully compresses the witness", () => { + const compressedWitnessMap = compressWitness(expectedWitnessMap); + + expect(compressedWitnessMap).to.be.deep.eq(expectedCompressedWitnessMap); +}); + +it("successfully decompresses the witness", () => { + const witnessMap = decompressWitness(expectedCompressedWitnessMap); + + expect(witnessMap).to.be.deep.eq(expectedWitnessMap); +}); diff --git a/acvm-repo/acvm_js/test/shared/addition.ts b/acvm-repo/acvm_js/test/shared/addition.ts new file mode 100644 index 00000000000..02b7d8457a8 --- /dev/null +++ b/acvm-repo/acvm_js/test/shared/addition.ts @@ -0,0 +1,21 @@ +import { WitnessMap } from "@noir-lang/acvm_js"; + +// See `addition_circuit` integration test in `acir/tests/test_program_serialization.rs`. +export const bytecode = Uint8Array.from([ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 144, 187, 13, 192, 32, 12, 68, 249, + 100, 32, 27, 219, 96, 119, 89, 37, 40, 176, 255, 8, 17, 18, 5, 74, 202, 240, + 154, 235, 158, 238, 238, 112, 206, 121, 247, 37, 206, 60, 103, 194, 63, 208, + 111, 116, 133, 197, 69, 144, 153, 91, 73, 13, 9, 47, 72, 86, 85, 128, 165, + 102, 69, 69, 81, 185, 147, 18, 53, 101, 45, 86, 173, 128, 33, 83, 195, 46, 70, + 125, 202, 226, 190, 94, 16, 166, 103, 108, 13, 203, 151, 254, 245, 233, 224, + 1, 1, 52, 166, 127, 120, 1, 0, 0, +]); + +export const initialWitnessMap: WitnessMap = new Map([ + [1, "0x0000000000000000000000000000000000000000000000000000000000000001"], + [2, "0x0000000000000000000000000000000000000000000000000000000000000002"], +]); + +export const resultWitness = 3; +export const expectedResult = + "0x0000000000000000000000000000000000000000000000000000000000000003"; diff --git a/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts b/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts new file mode 100644 index 00000000000..5fb6b2559e1 --- /dev/null +++ b/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts @@ -0,0 +1,49 @@ +import { WitnessMap } from "@noir-lang/acvm_js"; + +// See `complex_brillig_foreign_call` integration test in `acir/tests/test_program_serialization.rs`. +export const bytecode = Uint8Array.from([ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 83, 219, 10, 128, 48, 8, 245, 210, + 101, 159, 179, 254, 160, 127, 137, 222, 138, 122, 236, 243, 27, 228, 64, 44, + 232, 33, 7, 237, 128, 56, 157, 147, 131, 103, 6, 0, 64, 184, 192, 201, 72, + 206, 40, 177, 70, 174, 27, 197, 199, 111, 24, 208, 175, 87, 44, 197, 145, 42, + 224, 200, 5, 56, 230, 255, 240, 83, 189, 61, 117, 113, 157, 31, 63, 236, 79, + 147, 172, 77, 214, 73, 220, 139, 15, 106, 214, 168, 114, 249, 126, 218, 214, + 125, 153, 15, 54, 37, 90, 26, 155, 39, 227, 95, 223, 232, 230, 4, 247, 157, + 215, 56, 1, 153, 86, 63, 138, 44, 4, 0, 0, +]); +export const initialWitnessMap: WitnessMap = new Map([ + [1, "0x0000000000000000000000000000000000000000000000000000000000000001"], + [2, "0x0000000000000000000000000000000000000000000000000000000000000002"], + [3, "0x0000000000000000000000000000000000000000000000000000000000000003"], +]); + +export const oracleCallName = "complex"; +export const oracleCallInputs = [ + [ + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000003", + ], + ["0x0000000000000000000000000000000000000000000000000000000000000006"], +]; + +export const oracleResponse = [ + [ + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x000000000000000000000000000000000000000000000000000000000000000c", + ], + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x000000000000000000000000000000000000000000000000000000000000000c", +]; + +export const expectedWitnessMap = new Map([ + [1, "0x0000000000000000000000000000000000000000000000000000000000000001"], + [2, "0x0000000000000000000000000000000000000000000000000000000000000002"], + [3, "0x0000000000000000000000000000000000000000000000000000000000000003"], + [4, "0x0000000000000000000000000000000000000000000000000000000000000002"], + [5, "0x0000000000000000000000000000000000000000000000000000000000000006"], + [6, "0x000000000000000000000000000000000000000000000000000000000000000c"], + [7, "0x0000000000000000000000000000000000000000000000000000000000000006"], + [8, "0x000000000000000000000000000000000000000000000000000000000000000c"], +]); diff --git a/acvm-repo/acvm_js/test/shared/fixed_base_scalar_mul.ts b/acvm-repo/acvm_js/test/shared/fixed_base_scalar_mul.ts new file mode 100644 index 00000000000..4240b424999 --- /dev/null +++ b/acvm-repo/acvm_js/test/shared/fixed_base_scalar_mul.ts @@ -0,0 +1,18 @@ +// See `fixed_base_scalar_mul_circuit` integration test in `acir/tests/test_program_serialization.rs`. +export const bytecode = Uint8Array.from([ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 77, 138, 91, 10, 0, 48, 12, 194, 178, 215, + 207, 78, 189, 163, 175, 165, 10, 21, 36, 10, 57, 192, 160, 146, 188, 226, 139, + 78, 113, 69, 183, 190, 61, 111, 218, 182, 231, 124, 68, 185, 243, 207, 92, 0, + 0, 0, +]); +export const initialWitnessMap = new Map([ + [1, "0x0000000000000000000000000000000000000000000000000000000000000001"], + [2, "0x0000000000000000000000000000000000000000000000000000000000000000"], +]); + +export const expectedWitnessMap = new Map([ + [1, "0x0000000000000000000000000000000000000000000000000000000000000001"], + [2, "0x0000000000000000000000000000000000000000000000000000000000000000"], + [3, "0x0000000000000000000000000000000000000000000000000000000000000001"], + [4, "0x0000000000000002cf135e7506a45d632d270d45f1181294833fc48d823f272c"], +]); diff --git a/acvm-repo/acvm_js/test/shared/foreign_call.ts b/acvm-repo/acvm_js/test/shared/foreign_call.ts new file mode 100644 index 00000000000..615f705f064 --- /dev/null +++ b/acvm-repo/acvm_js/test/shared/foreign_call.ts @@ -0,0 +1,27 @@ +import { WitnessMap } from "@noir-lang/acvm_js"; + +// See `simple_brillig_foreign_call` integration test in `acir/tests/test_program_serialization.rs`. +export const bytecode = Uint8Array.from([ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 143, 81, 10, 0, 16, 16, 68, 199, 42, + 57, 14, 55, 112, 25, 31, 126, 124, 72, 206, 79, 161, 86, 225, 135, 87, 219, + 78, 187, 53, 205, 104, 0, 2, 29, 201, 52, 103, 222, 220, 216, 230, 13, 43, + 254, 121, 25, 158, 151, 54, 153, 117, 27, 53, 116, 136, 197, 167, 124, 107, + 184, 64, 236, 73, 56, 83, 1, 18, 139, 122, 157, 67, 1, 0, 0, +]); +export const initialWitnessMap: WitnessMap = new Map([ + [1, "0x0000000000000000000000000000000000000000000000000000000000000005"], +]); + +export const oracleCallName = "invert"; +export const oracleCallInputs = [ + ["0x0000000000000000000000000000000000000000000000000000000000000005"], +]; + +export const oracleResponse = [ + "0x135b52945a13d9aa49b9b57c33cd568ba9ae5ce9ca4a2d06e7f3fbd4c6666667", +]; + +export const expectedWitnessMap = new Map([ + [1, "0x0000000000000000000000000000000000000000000000000000000000000005"], + [2, "0x135b52945a13d9aa49b9b57c33cd568ba9ae5ce9ca4a2d06e7f3fbd4c6666667"], +]); diff --git a/acvm-repo/acvm_js/test/shared/memory_op.ts b/acvm-repo/acvm_js/test/shared/memory_op.ts new file mode 100644 index 00000000000..ffb37df3404 --- /dev/null +++ b/acvm-repo/acvm_js/test/shared/memory_op.ts @@ -0,0 +1,21 @@ +// See `memory_op_circuit` integration test in `acir/tests/test_program_serialization.rs`. +export const bytecode = Uint8Array.from([ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 146, 49, 14, 0, 32, 8, 3, 139, 192, + 127, 240, 7, 254, 255, 85, 198, 136, 9, 131, 155, 48, 216, 165, 76, 77, 57, + 80, 0, 140, 45, 117, 111, 238, 228, 179, 224, 174, 225, 110, 111, 234, 213, + 185, 148, 156, 203, 121, 89, 86, 13, 215, 126, 131, 43, 153, 187, 115, 40, + 185, 62, 153, 3, 136, 83, 60, 30, 96, 2, 12, 235, 225, 124, 14, 3, 0, 0, +]); + +export const initialWitnessMap = new Map([ + [1, "0x0000000000000000000000000000000000000000000000000000000000000001"], + [2, "0x0000000000000000000000000000000000000000000000000000000000000001"], + [3, "0x0000000000000000000000000000000000000000000000000000000000000002"], +]); + +export const expectedWitnessMap = new Map([ + [1, "0x0000000000000000000000000000000000000000000000000000000000000001"], + [2, "0x0000000000000000000000000000000000000000000000000000000000000001"], + [3, "0x0000000000000000000000000000000000000000000000000000000000000002"], + [4, "0x0000000000000000000000000000000000000000000000000000000000000002"], +]); diff --git a/acvm-repo/acvm_js/test/shared/pedersen.ts b/acvm-repo/acvm_js/test/shared/pedersen.ts new file mode 100644 index 00000000000..16e5ded5eaf --- /dev/null +++ b/acvm-repo/acvm_js/test/shared/pedersen.ts @@ -0,0 +1,16 @@ +// See `pedersen_circuit` integration test in `acir/tests/test_program_serialization.rs`. +export const bytecode = Uint8Array.from([ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 93, 138, 9, 10, 0, 64, 8, 2, 103, 15, 250, + 255, 139, 163, 162, 130, 72, 16, 149, 241, 3, 135, 84, 164, 172, 173, 213, + 175, 251, 45, 198, 96, 243, 211, 50, 152, 67, 220, 211, 92, 0, 0, 0, +]); + +export const initialWitnessMap = new Map([ + [1, "0x0000000000000000000000000000000000000000000000000000000000000001"], +]); + +export const expectedWitnessMap = new Map([ + [1, "0x0000000000000000000000000000000000000000000000000000000000000001"], + [2, "0x09489945604c9686e698cb69d7bd6fc0cdb02e9faae3e1a433f1c342c1a5ecc4"], + [3, "0x24f50d25508b4dfb1e8a834e39565f646e217b24cb3a475c2e4991d1bb07a9d8"], +]); diff --git a/acvm-repo/acvm_js/test/shared/schnorr_verify.ts b/acvm-repo/acvm_js/test/shared/schnorr_verify.ts new file mode 100644 index 00000000000..de9e61e757c --- /dev/null +++ b/acvm-repo/acvm_js/test/shared/schnorr_verify.ts @@ -0,0 +1,105 @@ +// See `schnorr_verify_circuit` integration test in `acir/tests/test_program_serialization.rs`. +export const bytecode = Uint8Array.from([ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 77, 210, 87, 78, 2, 1, 20, 134, 209, 177, + 247, 222, 123, 71, 68, 68, 68, 68, 68, 68, 68, 68, 68, 221, 133, 251, 95, 130, + 145, 27, 206, 36, 78, 50, 57, 16, 94, 200, 253, 191, 159, 36, 73, 134, 146, + 193, 19, 142, 241, 183, 255, 14, 179, 233, 247, 145, 254, 59, 217, 127, 71, + 57, 198, 113, 78, 48, 125, 167, 56, 205, 25, 206, 114, 142, 243, 92, 224, 34, + 151, 184, 204, 21, 174, 114, 141, 235, 220, 224, 38, 183, 184, 205, 29, 238, + 114, 143, 251, 60, 224, 33, 143, 120, 204, 19, 158, 242, 140, 25, 158, 51, + 203, 11, 230, 120, 201, 60, 175, 88, 224, 53, 139, 188, 97, 137, 183, 44, 243, + 142, 21, 222, 179, 202, 7, 214, 248, 200, 58, 159, 216, 224, 51, 155, 124, 97, + 235, 223, 142, 241, 188, 250, 222, 230, 27, 59, 124, 103, 151, 31, 236, 241, + 147, 95, 252, 246, 57, 158, 104, 47, 186, 139, 214, 162, 179, 104, 44, 250, + 74, 219, 154, 242, 63, 162, 165, 232, 40, 26, 138, 126, 162, 157, 232, 38, + 154, 137, 94, 162, 149, 232, 36, 26, 137, 62, 162, 141, 232, 34, 154, 136, 30, + 162, 133, 232, 32, 26, 136, 253, 99, 251, 195, 100, 176, 121, 236, 29, 91, + 159, 218, 56, 99, 219, 172, 77, 115, 182, 204, 219, 176, 96, 187, 162, 205, + 74, 182, 42, 219, 168, 98, 155, 170, 77, 106, 182, 168, 219, 160, 225, 246, + 77, 55, 111, 185, 113, 219, 109, 59, 110, 218, 117, 203, 158, 27, 166, 55, 75, + 239, 150, 184, 101, 250, 252, 1, 19, 89, 159, 101, 220, 3, 0, 0, +]); + +export const initialWitnessMap = new Map([ + [1, "0x17cbd3ed3151ccfd170efe1d54280a6a4822640bf5c369908ad74ea21518a9c5"], + [2, "0x0e0456e3795c1a31f20035b741cd6158929eeccd320d299cfcac962865a6bc74"], + [3, "0x0000000000000000000000000000000000000000000000000000000000000005"], + [4, "0x00000000000000000000000000000000000000000000000000000000000000ca"], + [5, "0x000000000000000000000000000000000000000000000000000000000000001f"], + [6, "0x0000000000000000000000000000000000000000000000000000000000000092"], + [7, "0x0000000000000000000000000000000000000000000000000000000000000051"], + [8, "0x00000000000000000000000000000000000000000000000000000000000000f2"], + [9, "0x00000000000000000000000000000000000000000000000000000000000000f6"], + [10, "0x0000000000000000000000000000000000000000000000000000000000000045"], + [11, "0x000000000000000000000000000000000000000000000000000000000000002b"], + [12, "0x000000000000000000000000000000000000000000000000000000000000006b"], + [13, "0x00000000000000000000000000000000000000000000000000000000000000f9"], + [14, "0x0000000000000000000000000000000000000000000000000000000000000099"], + [15, "0x00000000000000000000000000000000000000000000000000000000000000c6"], + [16, "0x000000000000000000000000000000000000000000000000000000000000002c"], + [17, "0x000000000000000000000000000000000000000000000000000000000000000e"], + [18, "0x000000000000000000000000000000000000000000000000000000000000006f"], + [19, "0x00000000000000000000000000000000000000000000000000000000000000bf"], + [20, "0x0000000000000000000000000000000000000000000000000000000000000079"], + [21, "0x0000000000000000000000000000000000000000000000000000000000000089"], + [22, "0x00000000000000000000000000000000000000000000000000000000000000a6"], + [23, "0x00000000000000000000000000000000000000000000000000000000000000a0"], + [24, "0x0000000000000000000000000000000000000000000000000000000000000067"], + [25, "0x0000000000000000000000000000000000000000000000000000000000000012"], + [26, "0x00000000000000000000000000000000000000000000000000000000000000b5"], + [27, "0x00000000000000000000000000000000000000000000000000000000000000f3"], + [28, "0x00000000000000000000000000000000000000000000000000000000000000e9"], + [29, "0x00000000000000000000000000000000000000000000000000000000000000e2"], + [30, "0x000000000000000000000000000000000000000000000000000000000000005f"], + [31, "0x0000000000000000000000000000000000000000000000000000000000000043"], + [32, "0x0000000000000000000000000000000000000000000000000000000000000010"], + [33, "0x0000000000000000000000000000000000000000000000000000000000000025"], + [34, "0x0000000000000000000000000000000000000000000000000000000000000080"], + [35, "0x0000000000000000000000000000000000000000000000000000000000000055"], + [36, "0x000000000000000000000000000000000000000000000000000000000000004c"], + [37, "0x0000000000000000000000000000000000000000000000000000000000000013"], + [38, "0x00000000000000000000000000000000000000000000000000000000000000fd"], + [39, "0x000000000000000000000000000000000000000000000000000000000000001e"], + [40, "0x000000000000000000000000000000000000000000000000000000000000004d"], + [41, "0x00000000000000000000000000000000000000000000000000000000000000c0"], + [42, "0x0000000000000000000000000000000000000000000000000000000000000035"], + [43, "0x000000000000000000000000000000000000000000000000000000000000008a"], + [44, "0x00000000000000000000000000000000000000000000000000000000000000cd"], + [45, "0x0000000000000000000000000000000000000000000000000000000000000045"], + [46, "0x0000000000000000000000000000000000000000000000000000000000000021"], + [47, "0x00000000000000000000000000000000000000000000000000000000000000ec"], + [48, "0x00000000000000000000000000000000000000000000000000000000000000a3"], + [49, "0x0000000000000000000000000000000000000000000000000000000000000053"], + [50, "0x00000000000000000000000000000000000000000000000000000000000000c2"], + [51, "0x0000000000000000000000000000000000000000000000000000000000000054"], + [52, "0x0000000000000000000000000000000000000000000000000000000000000089"], + [53, "0x00000000000000000000000000000000000000000000000000000000000000b8"], + [54, "0x00000000000000000000000000000000000000000000000000000000000000dd"], + [55, "0x00000000000000000000000000000000000000000000000000000000000000b0"], + [56, "0x0000000000000000000000000000000000000000000000000000000000000079"], + [57, "0x00000000000000000000000000000000000000000000000000000000000000b3"], + [58, "0x000000000000000000000000000000000000000000000000000000000000001b"], + [59, "0x000000000000000000000000000000000000000000000000000000000000003f"], + [60, "0x0000000000000000000000000000000000000000000000000000000000000046"], + [61, "0x0000000000000000000000000000000000000000000000000000000000000036"], + [62, "0x0000000000000000000000000000000000000000000000000000000000000010"], + [63, "0x00000000000000000000000000000000000000000000000000000000000000b0"], + [64, "0x00000000000000000000000000000000000000000000000000000000000000fa"], + [65, "0x0000000000000000000000000000000000000000000000000000000000000027"], + [66, "0x00000000000000000000000000000000000000000000000000000000000000ef"], + [67, "0x0000000000000000000000000000000000000000000000000000000000000000"], + [68, "0x0000000000000000000000000000000000000000000000000000000000000001"], + [69, "0x0000000000000000000000000000000000000000000000000000000000000002"], + [70, "0x0000000000000000000000000000000000000000000000000000000000000003"], + [71, "0x0000000000000000000000000000000000000000000000000000000000000004"], + [72, "0x0000000000000000000000000000000000000000000000000000000000000005"], + [73, "0x0000000000000000000000000000000000000000000000000000000000000006"], + [74, "0x0000000000000000000000000000000000000000000000000000000000000007"], + [75, "0x0000000000000000000000000000000000000000000000000000000000000008"], + [76, "0x0000000000000000000000000000000000000000000000000000000000000009"], +]); + +export const expectedWitnessMap = new Map(initialWitnessMap).set( + 77, + "0x0000000000000000000000000000000000000000000000000000000000000001", +); diff --git a/acvm-repo/acvm_js/test/shared/witness_compression.ts b/acvm-repo/acvm_js/test/shared/witness_compression.ts new file mode 100644 index 00000000000..8885364bf6d --- /dev/null +++ b/acvm-repo/acvm_js/test/shared/witness_compression.ts @@ -0,0 +1,24 @@ +// Solved witness for noir program (x = 1, y = 2) +// +// fn main(x : Field, y : pub Field) -> pub Field { +// assert(x != y); +// x + y +// } + +export const expectedCompressedWitnessMap = Uint8Array.from([ + 31, 139, 8, 0, 0, 0, 0, 0, 2, 255, 173, 208, 187, 13, 128, 48, 12, 4, 80, 190, + 153, 199, 142, 237, 196, 238, 88, 133, 8, 103, 255, 17, 64, 34, 5, 61, 62, + 233, 164, 171, 94, 113, 105, 122, 51, 63, 61, 198, 134, 127, 193, 37, 206, + 202, 235, 199, 34, 40, 204, 94, 179, 35, 225, 9, 217, 154, 10, 176, 180, 162, + 168, 40, 42, 87, 86, 34, 87, 214, 106, 205, 42, 24, 50, 57, 118, 49, 234, 3, + 219, 2, 173, 61, 240, 175, 20, 103, 209, 13, 151, 252, 77, 33, 208, 1, 0, 0, +]); + +export const expectedWitnessMap = new Map([ + [1, "0x0000000000000000000000000000000000000000000000000000000000000001"], + [2, "0x0000000000000000000000000000000000000000000000000000000000000002"], + [3, "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000"], + [4, "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000"], + [5, "0x0000000000000000000000000000000000000000000000000000000000000001"], + [6, "0x0000000000000000000000000000000000000000000000000000000000000003"], +]); diff --git a/acvm-repo/acvm_js/test/types.ts b/acvm-repo/acvm_js/test/types.ts new file mode 100644 index 00000000000..3a72544b8d0 --- /dev/null +++ b/acvm-repo/acvm_js/test/types.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type DecodedInputs = { inputs: Record; return_value: any }; diff --git a/acvm-repo/acvm_js/tsconfig.json b/acvm-repo/acvm_js/tsconfig.json new file mode 100644 index 00000000000..eef2ad84833 --- /dev/null +++ b/acvm-repo/acvm_js/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "outDir": "lib", + "target": "ESNext", + "module": "ESNext", + "strict": true, + "experimentalDecorators": true, + "esModuleInterop": true, + "noImplicitAny": true, + "removeComments": false, + "preserveConstEnums": true, + "sourceMap": true, + "resolveJsonModule": true, + "importHelpers": true + } +} \ No newline at end of file diff --git a/acvm-repo/acvm_js/web-test-runner.config.mjs b/acvm-repo/acvm_js/web-test-runner.config.mjs new file mode 100644 index 00000000000..eb5eb73beb9 --- /dev/null +++ b/acvm-repo/acvm_js/web-test-runner.config.mjs @@ -0,0 +1,25 @@ +import { esbuildPlugin } from "@web/dev-server-esbuild"; +import { playwrightLauncher } from "@web/test-runner-playwright"; + +export default { + browsers: [ + playwrightLauncher({ product: "chromium" }), + playwrightLauncher({ product: "webkit" }), + // Firefox requires 40s to perform a Pedersen hash so we recommend using either + // a Chromium- or Webkit-based browser + // playwrightLauncher({ product: "firefox" }), + ], + plugins: [ + esbuildPlugin({ + ts: true, + }), + ], + files: ["test/browser/**/*.test.ts"], + nodeResolve: true, + testFramework: { + config: { + ui: "bdd", + timeout: 40000, + }, + }, +}; diff --git a/acvm-repo/barretenberg_blackbox_solver/CHANGELOG.md b/acvm-repo/barretenberg_blackbox_solver/CHANGELOG.md new file mode 100644 index 00000000000..5714bb405da --- /dev/null +++ b/acvm-repo/barretenberg_blackbox_solver/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +## [0.27.0](https://github.com/noir-lang/acvm/compare/barretenberg_blackbox_solver-v0.26.1...barretenberg_blackbox_solver-v0.27.0) (2023-09-19) + + +### ⚠ BREAKING CHANGES + +* Separate barretenberg solver from generic blackbox solver code ([#554](https://github.com/noir-lang/acvm/issues/554)) + +### Bug Fixes + +* Empty commit to trigger release-please ([e8f0748](https://github.com/noir-lang/acvm/commit/e8f0748042ef505d59ab63266d3c36c5358ee30d)) + + +### Miscellaneous Chores + +* Separate barretenberg solver from generic blackbox solver code ([#554](https://github.com/noir-lang/acvm/issues/554)) ([a4b9772](https://github.com/noir-lang/acvm/commit/a4b97722a0892fe379ff075e6080675adafdce0e)) diff --git a/acvm-repo/barretenberg_blackbox_solver/Cargo.toml b/acvm-repo/barretenberg_blackbox_solver/Cargo.toml new file mode 100644 index 00000000000..bbbd6649c47 --- /dev/null +++ b/acvm-repo/barretenberg_blackbox_solver/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "barretenberg_blackbox_solver" +description = "A wrapper around a barretenberg WASM binary to execute black box functions for which there is no rust implementation" +# x-release-please-start-version +version = "0.27.4" +# x-release-please-end +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +acir.workspace = true +acvm_blackbox_solver.workspace = true +thiserror.workspace = true +hex.workspace = true +num-bigint.workspace = true + +rust-embed = { version = "6.6.0", features = [ + "debug-embed", + "interpolate-folder-path", + "include-exclude", +] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasmer = { version = "3.3", default-features = false, features = [ + "js-default", +] } + +getrandom = { version = "0.2", features = ["js"] } +wasm-bindgen-futures = "0.4.36" +js-sys = "0.3.62" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +getrandom = "0.2" +wasmer = "3.3" + +[build-dependencies] +pkg-config = "0.3" +tar = "~0.4.15" +flate2 = "~1.0.1" +reqwest = { version = "0.11.16", default-features = false, features = [ + "rustls-tls", + "blocking", +] } + +[features] +default = ["bn254"] +bn254 = ["acir/bn254"] +bls12_381 = ["acir/bls12_381"] diff --git a/acvm-repo/barretenberg_blackbox_solver/build.rs b/acvm-repo/barretenberg_blackbox_solver/build.rs new file mode 100644 index 00000000000..39db930b9d9 --- /dev/null +++ b/acvm-repo/barretenberg_blackbox_solver/build.rs @@ -0,0 +1,61 @@ +use std::{ + fs::File, + io::{Cursor, Read}, + path::{Path, PathBuf}, +}; + +const BARRETENBERG_ARCHIVE: &str = "BARRETENBERG_ARCHIVE"; +const BARRETENBERG_BIN_DIR: &str = "BARRETENBERG_BIN_DIR"; + +const BARRETENBERG_ARCHIVE_FALLBACK: &str = "https://github.com/AztecProtocol/barretenberg/releases/download/barretenberg-v0.5.0/acvm_backend.wasm.tar.gz"; +// const ARCHIVE_SHA256: &str = "1xpycikqlvsjcryi3hkbc4mwmmdz7zshw6f76vyf1qssq53asyfx"; + +fn unpack_wasm(archive_path: &Path, target_dir: &Path) -> Result<(), String> { + if archive_path.exists() && archive_path.is_file() { + let archive = File::open(archive_path).map_err(|_| "Could not read archive")?; + unpack_archive(archive, target_dir); + + Ok(()) + } else { + Err(format!("Unable to locate {BARRETENBERG_ARCHIVE} - Please set the BARRETENBERG_BIN_DIR env var to the directory where it exists, or ensure it's located at {}", archive_path.display())) + } +} + +fn unpack_archive(archive: T, target_dir: &Path) { + use flate2::read::GzDecoder; + use tar::Archive; + + let gz_decoder = GzDecoder::new(archive); + let mut archive = Archive::new(gz_decoder); + + archive.unpack(target_dir).unwrap(); +} + +/// Try to download the specified URL into a buffer which is returned. +fn download_binary_from_url(url: &str) -> Result>, String> { + let response = reqwest::blocking::get(url).map_err(|error| error.to_string())?; + + let bytes = response.bytes().unwrap(); + Ok(Cursor::new(bytes.to_vec())) +} + +fn main() -> Result<(), String> { + let out_dir = std::env::var("OUT_DIR").unwrap(); + + match std::env::var(BARRETENBERG_ARCHIVE) { + Ok(archive_path) => { + unpack_wasm(&PathBuf::from(archive_path), &PathBuf::from(&out_dir))?; + println!("cargo:rustc-env={BARRETENBERG_BIN_DIR}={out_dir}"); + Ok(()) + } + Err(_) => { + let wasm_bytes = download_binary_from_url(BARRETENBERG_ARCHIVE_FALLBACK) + .expect("download should succeed"); + + unpack_archive(wasm_bytes, &PathBuf::from(&out_dir)); + println!("cargo:rustc-env={BARRETENBERG_BIN_DIR}={out_dir}"); + + Ok(()) + } + } +} diff --git a/acvm-repo/barretenberg_blackbox_solver/src/lib.rs b/acvm-repo/barretenberg_blackbox_solver/src/lib.rs new file mode 100644 index 00000000000..db384b60105 --- /dev/null +++ b/acvm-repo/barretenberg_blackbox_solver/src/lib.rs @@ -0,0 +1,81 @@ +use acir::{BlackBoxFunc, FieldElement}; +use acvm_blackbox_solver::{BlackBoxFunctionSolver, BlackBoxResolutionError}; + +mod wasm; + +use wasm::Barretenberg; + +use self::wasm::{Pedersen, ScalarMul, SchnorrSig}; + +#[deprecated = "The `BarretenbergSolver` is a temporary solution and will be removed in future."] +pub struct BarretenbergSolver { + blackbox_vendor: Barretenberg, +} + +#[allow(deprecated)] +impl BarretenbergSolver { + #[cfg(target_arch = "wasm32")] + pub async fn initialize() -> BarretenbergSolver { + let blackbox_vendor = Barretenberg::initialize().await; + BarretenbergSolver { blackbox_vendor } + } + + #[cfg(not(target_arch = "wasm32"))] + pub fn new() -> BarretenbergSolver { + let blackbox_vendor = Barretenberg::new(); + BarretenbergSolver { blackbox_vendor } + } +} + +#[cfg(not(target_arch = "wasm32"))] +#[allow(deprecated)] +impl Default for BarretenbergSolver { + fn default() -> Self { + Self::new() + } +} + +#[allow(deprecated)] +impl BlackBoxFunctionSolver for BarretenbergSolver { + fn schnorr_verify( + &self, + public_key_x: &FieldElement, + public_key_y: &FieldElement, + signature: &[u8], + message: &[u8], + ) -> Result { + let pub_key_bytes: Vec = + public_key_x.to_be_bytes().iter().copied().chain(public_key_y.to_be_bytes()).collect(); + + let pub_key: [u8; 64] = pub_key_bytes.try_into().unwrap(); + let sig_s: [u8; 32] = signature[0..32].try_into().unwrap(); + let sig_e: [u8; 32] = signature[32..64].try_into().unwrap(); + + #[allow(deprecated)] + self.blackbox_vendor.verify_signature(pub_key, sig_s, sig_e, message).map_err(|err| { + BlackBoxResolutionError::Failed(BlackBoxFunc::SchnorrVerify, err.to_string()) + }) + } + + fn pedersen( + &self, + inputs: &[FieldElement], + domain_separator: u32, + ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { + #[allow(deprecated)] + self.blackbox_vendor + .encrypt(inputs.to_vec(), domain_separator) + .map_err(|err| BlackBoxResolutionError::Failed(BlackBoxFunc::Pedersen, err.to_string())) + } + + fn fixed_base_scalar_mul( + &self, + low: &FieldElement, + high: &FieldElement, + ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { + #[allow(deprecated)] + self.blackbox_vendor.fixed_base(low, high).map_err(|err| { + BlackBoxResolutionError::Failed(BlackBoxFunc::FixedBaseScalarMul, err.to_string()) + }) + } +} diff --git a/acvm-repo/barretenberg_blackbox_solver/src/wasm/barretenberg_structures.rs b/acvm-repo/barretenberg_blackbox_solver/src/wasm/barretenberg_structures.rs new file mode 100644 index 00000000000..302ffa8af9b --- /dev/null +++ b/acvm-repo/barretenberg_blackbox_solver/src/wasm/barretenberg_structures.rs @@ -0,0 +1,25 @@ +use acir::FieldElement; + +#[derive(Debug, Default)] +pub(crate) struct Assignments(Vec); + +impl Assignments { + pub(crate) fn to_bytes(&self) -> Vec { + let mut buffer = Vec::new(); + + let witness_len = self.0.len() as u32; + buffer.extend_from_slice(&witness_len.to_be_bytes()); + + for assignment in self.0.iter() { + buffer.extend_from_slice(&assignment.to_be_bytes()); + } + + buffer + } +} + +impl From> for Assignments { + fn from(w: Vec) -> Assignments { + Assignments(w) + } +} diff --git a/acvm-repo/barretenberg_blackbox_solver/src/wasm/mod.rs b/acvm-repo/barretenberg_blackbox_solver/src/wasm/mod.rs new file mode 100644 index 00000000000..03d9712dde2 --- /dev/null +++ b/acvm-repo/barretenberg_blackbox_solver/src/wasm/mod.rs @@ -0,0 +1,363 @@ +//! ACVM execution is independent of the proving backend against which the ACIR code is being proven. +//! However there are currently a few opcodes for which there is currently no rust implementation so we must +//! use the C++ implementations included in Aztec Lab's Barretenberg library. +//! +//! As [`acvm`] includes rust implementations for these opcodes, this module can be removed. + +mod barretenberg_structures; +mod pedersen; +mod scalar_mul; +mod schnorr; + +use barretenberg_structures::Assignments; + +pub(crate) use pedersen::Pedersen; +pub(crate) use scalar_mul::ScalarMul; +pub(crate) use schnorr::SchnorrSig; + +/// The number of bytes necessary to store a `FieldElement`. +const FIELD_BYTES: usize = 32; + +#[derive(Debug, thiserror::Error)] +pub(crate) enum Error { + #[error(transparent)] + FromFeature(#[from] FeatureError), +} + +#[derive(Debug, thiserror::Error)] +pub(crate) enum FeatureError { + #[error("Trying to call {name} resulted in an error")] + FunctionCallFailed { name: String, source: wasmer::RuntimeError }, + #[error("Could not find function export named {name}")] + InvalidExport { name: String, source: wasmer::ExportError }, + #[error("No value available when value was expected")] + NoValue, + #[error("Value expected to be i32")] + InvalidI32, + #[error("Value {scalar_as_hex} is not a valid grumpkin scalar")] + InvalidGrumpkinScalar { scalar_as_hex: String }, + #[error("Limb {limb_as_hex} is not less than 2^128")] + InvalidGrumpkinScalarLimb { limb_as_hex: String }, + #[error("Could not convert value {value} from i32 to u32")] + InvalidU32 { value: i32, source: std::num::TryFromIntError }, + #[error("Could not convert value {value} from i32 to usize")] + InvalidUsize { value: i32, source: std::num::TryFromIntError }, + #[error("Value expected to be 0 or 1 representing a boolean")] + InvalidBool, +} +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub(crate) struct BackendError(#[from] Error); + +impl From for BackendError { + fn from(value: FeatureError) -> Self { + value.into() + } +} + +#[derive(Debug)] +pub(crate) struct Barretenberg { + store: std::cell::RefCell, + memory: wasmer::Memory, + instance: wasmer::Instance, +} + +use std::cell::RefCell; + +use wasmer::{ + imports, Function, FunctionEnv, FunctionEnvMut, Imports, Instance, Memory, MemoryType, Store, + Value, WasmPtr, +}; + +/// The number of bytes necessary to represent a pointer to memory inside the wasm. +// pub(super) const POINTER_BYTES: usize = 4; + +/// The Barretenberg WASM gives us 1024 bytes of scratch space which we can use without +/// needing to allocate/free it ourselves. This can be useful for when we need to pass in several small variables +/// when calling functions on the wasm, however it's important to not overrun this scratch space as otherwise +/// the written data will begin to corrupt the stack. +/// +/// Using this scratch space isn't particularly safe if we have multiple threads interacting with the wasm however, +/// each thread could write to the same pointer address simultaneously. +pub(super) const WASM_SCRATCH_BYTES: usize = 1024; + +/// Embed the Barretenberg WASM file +#[derive(rust_embed::RustEmbed)] +#[folder = "$BARRETENBERG_BIN_DIR"] +#[include = "acvm_backend.wasm"] +struct Wasm; + +impl Barretenberg { + #[cfg(not(target_arch = "wasm32"))] + pub(crate) fn new() -> Barretenberg { + let (instance, memory, store) = instance_load(); + let barretenberg = Barretenberg { memory, instance, store: RefCell::new(store) }; + barretenberg.call_wasi_initialize(); + barretenberg + } + + #[cfg(target_arch = "wasm32")] + pub(crate) async fn initialize() -> Barretenberg { + let (instance, memory, store) = instance_load().await; + let barretenberg = Barretenberg { memory, instance, store: RefCell::new(store) }; + barretenberg.call_wasi_initialize(); + barretenberg + } + /// Call initialization function for WASI, to initialize all of the appropriate + /// globals. + fn call_wasi_initialize(&self) { + self.call_multiple("_initialize", vec![]) + .expect("expected call to WASI initialization function to not fail"); + } +} + +/// A wrapper around the arguments or return value from a WASM call. +/// Notice, `Option` is used because not every call returns a value, +/// some calls are simply made to free a pointer or manipulate the heap. +#[derive(Debug, Clone)] +pub(crate) struct WASMValue(Option); + +impl From for WASMValue { + fn from(value: usize) -> Self { + WASMValue(Some(Value::I32(value as i32))) + } +} + +impl From for WASMValue { + fn from(value: u32) -> Self { + WASMValue(Some(Value::I32(value as i32))) + } +} + +impl From for WASMValue { + fn from(value: i32) -> Self { + WASMValue(Some(Value::I32(value))) + } +} + +impl From for WASMValue { + fn from(value: Value) -> Self { + WASMValue(Some(value)) + } +} + +impl TryFrom for bool { + type Error = FeatureError; + + fn try_from(value: WASMValue) -> Result { + match value.try_into()? { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(FeatureError::InvalidBool), + } + } +} + +impl TryFrom for usize { + type Error = FeatureError; + + fn try_from(value: WASMValue) -> Result { + let value: i32 = value.try_into()?; + value.try_into().map_err(|source| FeatureError::InvalidUsize { value, source }) + } +} + +impl TryFrom for u32 { + type Error = FeatureError; + + fn try_from(value: WASMValue) -> Result { + let value = value.try_into()?; + u32::try_from(value).map_err(|source| FeatureError::InvalidU32 { value, source }) + } +} + +impl TryFrom for i32 { + type Error = FeatureError; + + fn try_from(value: WASMValue) -> Result { + value.0.map_or(Err(FeatureError::NoValue), |val| val.i32().ok_or(FeatureError::InvalidI32)) + } +} + +impl TryFrom for Value { + type Error = FeatureError; + + fn try_from(value: WASMValue) -> Result { + value.0.ok_or(FeatureError::NoValue) + } +} + +impl Barretenberg { + /// Transfer bytes to WASM heap + // TODO: Consider making this Result-returning + pub(crate) fn transfer_to_heap(&self, data: &[u8], offset: usize) { + let memory = &self.memory; + let store = self.store.borrow(); + let memory_view = memory.view(&store); + + memory_view.write(offset as u64, data).unwrap() + } + + // TODO: Consider making this Result-returning + pub(crate) fn read_memory(&self, start: usize) -> [u8; SIZE] { + self.read_memory_variable_length(start, SIZE) + .try_into() + .expect("Read memory should be of the specified length") + } + + // TODO: Consider making this Result-returning + pub(crate) fn read_memory_variable_length(&self, offset: usize, length: usize) -> Vec { + let memory = &self.memory; + let store = &self.store.borrow(); + let memory_view = memory.view(&store); + + let mut buf = vec![0; length]; + + memory_view.read(offset as u64, &mut buf).unwrap(); + buf + } + + pub(crate) fn call(&self, name: &str, param: &WASMValue) -> Result { + self.call_multiple(name, vec![param]) + } + + pub(crate) fn call_multiple( + &self, + name: &str, + params: Vec<&WASMValue>, + ) -> Result { + // We take in a reference to values, since they do not implement Copy. + // We then clone them inside of this function, so that the API does not have a bunch of Clones everywhere + + let mut args: Vec = vec![]; + for param in params.into_iter().cloned() { + args.push(param.try_into()?) + } + let func = self + .instance + .exports + .get_function(name) + .map_err(|source| FeatureError::InvalidExport { name: name.to_string(), source })?; + let boxed_value = func.call(&mut self.store.borrow_mut(), &args).map_err(|source| { + FeatureError::FunctionCallFailed { name: name.to_string(), source } + })?; + let option_value = boxed_value.first().cloned(); + + Ok(WASMValue(option_value)) + } + + /// Creates a pointer and allocates the bytes that the pointer references to, to the heap + pub(crate) fn allocate(&self, bytes: &[u8]) -> Result { + let ptr: i32 = self.call("bbmalloc", &bytes.len().into())?.try_into()?; + + let i32_bytes = ptr.to_be_bytes(); + let u32_bytes = u32::from_be_bytes(i32_bytes); + + self.transfer_to_heap(bytes, u32_bytes as usize); + Ok(ptr.into()) + } +} + +fn init_memory_and_state() -> (Memory, Store, Imports) { + let mut store = Store::default(); + + let mem_type = MemoryType::new(18, Some(65536), false); + let memory = Memory::new(&mut store, mem_type).unwrap(); + + let function_env = FunctionEnv::new(&mut store, memory.clone()); + let custom_imports = imports! { + "env" => { + "logstr" => Function::new_typed_with_env( + &mut store, + &function_env, + logstr, + ), + "memory" => memory.clone(), + }, + "wasi_snapshot_preview1" => { + "proc_exit" => Function::new_typed(&mut store, proc_exit), + "random_get" => Function::new_typed_with_env( + &mut store, + &function_env, + random_get + ), + }, + }; + + (memory, store, custom_imports) +} + +#[cfg(not(target_arch = "wasm32"))] +fn instance_load() -> (Instance, Memory, Store) { + use wasmer::Module; + + let (memory, mut store, custom_imports) = init_memory_and_state(); + + let module = Module::new(&store, Wasm::get("acvm_backend.wasm").unwrap().data).unwrap(); + + (Instance::new(&mut store, &module, &custom_imports).unwrap(), memory, store) +} + +#[cfg(target_arch = "wasm32")] +async fn instance_load() -> (Instance, Memory, Store) { + use js_sys::WebAssembly::{self}; + use wasmer::AsJs; + + let (memory, mut store, custom_imports) = init_memory_and_state(); + + let wasm_binary = Wasm::get("acvm_backend.wasm").unwrap().data; + + let js_bytes = unsafe { js_sys::Uint8Array::view(&wasm_binary) }; + let js_module_promise = WebAssembly::compile(&js_bytes); + let js_module: js_sys::WebAssembly::Module = + wasm_bindgen_futures::JsFuture::from(js_module_promise).await.unwrap().into(); + + let js_instance_promise = + WebAssembly::instantiate_module(&js_module, &custom_imports.as_jsvalue(&store).into()); + let js_instance = wasm_bindgen_futures::JsFuture::from(js_instance_promise).await.unwrap(); + let module: wasmer::Module = (js_module, wasm_binary).into(); + let instance: wasmer::Instance = Instance::from_jsvalue(&mut store, &module, &js_instance) + .map_err(|_| "Error while creating BlackBox Functions vendor instance") + .unwrap(); + + (instance, memory, store) +} + +fn logstr(mut env: FunctionEnvMut, ptr: i32) { + let (memory, store) = env.data_and_store_mut(); + let memory_view = memory.view(&store); + + let log_str_wasm_ptr: WasmPtr = WasmPtr::new(ptr as u32); + + match log_str_wasm_ptr.read_utf8_string_with_nul(&memory_view) { + Ok(log_string) => println!("{log_string}"), + Err(err) => println!("Error while reading log string from memory: {err}"), + }; +} + +// Based on https://github.com/wasmerio/wasmer/blob/2.3.0/lib/wasi/src/syscalls/mod.rs#L2537 +fn random_get(mut env: FunctionEnvMut, buf_ptr: i32, buf_len: i32) -> i32 { + let mut u8_buffer = vec![0; buf_len as usize]; + let res = getrandom::getrandom(&mut u8_buffer); + match res { + Ok(()) => { + let (memory, store) = env.data_and_store_mut(); + let memory_view = memory.view(&store); + match memory_view.write(buf_ptr as u64, u8_buffer.as_mut_slice()) { + Ok(_) => { + 0_i32 // __WASI_ESUCCESS + } + Err(_) => { + 29_i32 // __WASI_EIO + } + } + } + Err(_) => { + 29_i32 // __WASI_EIO + } + } +} + +fn proc_exit(_: i32) { + unimplemented!("proc_exit is not implemented") +} diff --git a/acvm-repo/barretenberg_blackbox_solver/src/wasm/pedersen.rs b/acvm-repo/barretenberg_blackbox_solver/src/wasm/pedersen.rs new file mode 100644 index 00000000000..05f3f014e64 --- /dev/null +++ b/acvm-repo/barretenberg_blackbox_solver/src/wasm/pedersen.rs @@ -0,0 +1,54 @@ +use acir::FieldElement; + +use super::{Assignments, Barretenberg, Error, FIELD_BYTES}; + +pub(crate) trait Pedersen { + fn encrypt( + &self, + inputs: Vec, + hash_index: u32, + ) -> Result<(FieldElement, FieldElement), Error>; +} + +impl Pedersen for Barretenberg { + fn encrypt( + &self, + inputs: Vec, + hash_index: u32, + ) -> Result<(FieldElement, FieldElement), Error> { + let input_buf = Assignments::from(inputs).to_bytes(); + let input_ptr = self.allocate(&input_buf)?; + let result_ptr: usize = 0; + + self.call_multiple( + "pedersen_plookup_commit_with_hash_index", + vec![&input_ptr, &result_ptr.into(), &hash_index.into()], + )?; + + let result_bytes: [u8; 2 * FIELD_BYTES] = self.read_memory(result_ptr); + let (point_x_bytes, point_y_bytes) = result_bytes.split_at(FIELD_BYTES); + + let point_x = FieldElement::from_be_bytes_reduce(point_x_bytes); + let point_y = FieldElement::from_be_bytes_reduce(point_y_bytes); + + Ok((point_x, point_y)) + } +} + +#[test] +fn pedersen_hash_to_point() -> Result<(), Error> { + let barretenberg = Barretenberg::new(); + let (x, y) = barretenberg.encrypt(vec![FieldElement::zero(), FieldElement::one()], 0)?; + let expected_x = FieldElement::from_hex( + "0x0c5e1ddecd49de44ed5e5798d3f6fb7c71fe3d37f5bee8664cf88a445b5ba0af", + ) + .unwrap(); + let expected_y = FieldElement::from_hex( + "0x230294a041e26fe80b827c2ef5cb8784642bbaa83842da2714d62b1f3c4f9752", + ) + .unwrap(); + + assert_eq!(expected_x.to_hex(), x.to_hex()); + assert_eq!(expected_y.to_hex(), y.to_hex()); + Ok(()) +} diff --git a/acvm-repo/barretenberg_blackbox_solver/src/wasm/scalar_mul.rs b/acvm-repo/barretenberg_blackbox_solver/src/wasm/scalar_mul.rs new file mode 100644 index 00000000000..71e1701bc30 --- /dev/null +++ b/acvm-repo/barretenberg_blackbox_solver/src/wasm/scalar_mul.rs @@ -0,0 +1,98 @@ +use acir::FieldElement; +use num_bigint::BigUint; + +use crate::wasm::FeatureError; + +use super::{Barretenberg, Error, FIELD_BYTES}; + +pub(crate) trait ScalarMul { + fn fixed_base( + &self, + low: &FieldElement, + high: &FieldElement, + ) -> Result<(FieldElement, FieldElement), Error>; +} + +impl ScalarMul for Barretenberg { + fn fixed_base( + &self, + low: &FieldElement, + high: &FieldElement, + ) -> Result<(FieldElement, FieldElement), Error> { + let lhs_ptr: usize = 0; + let result_ptr: usize = lhs_ptr + FIELD_BYTES; + + let low: u128 = low.try_into_u128().ok_or_else(|| { + Error::FromFeature(FeatureError::InvalidGrumpkinScalarLimb { + limb_as_hex: low.to_hex(), + }) + })?; + + let high: u128 = high.try_into_u128().ok_or_else(|| { + Error::FromFeature(FeatureError::InvalidGrumpkinScalarLimb { + limb_as_hex: high.to_hex(), + }) + })?; + + let mut bytes = high.to_be_bytes().to_vec(); + bytes.extend_from_slice(&low.to_be_bytes()); + + // Check if this is smaller than the grumpkin modulus + let grumpkin_integer = BigUint::from_bytes_be(&bytes); + let grumpkin_modulus = BigUint::from_bytes_be(&[ + 48, 100, 78, 114, 225, 49, 160, 41, 184, 80, 69, 182, 129, 129, 88, 93, 151, 129, 106, + 145, 104, 113, 202, 141, 60, 32, 140, 22, 216, 124, 253, 71, + ]); + + if grumpkin_integer >= grumpkin_modulus { + return Err(Error::FromFeature(FeatureError::InvalidGrumpkinScalar { + scalar_as_hex: hex::encode(grumpkin_integer.to_bytes_be()), + })); + } + + self.transfer_to_heap(&bytes, lhs_ptr); + self.call_multiple("compute_public_key", vec![&lhs_ptr.into(), &result_ptr.into()])?; + + let result_bytes: [u8; 2 * FIELD_BYTES] = self.read_memory(result_ptr); + let (pubkey_x_bytes, pubkey_y_bytes) = result_bytes.split_at(FIELD_BYTES); + + assert!(pubkey_x_bytes.len() == FIELD_BYTES); + assert!(pubkey_y_bytes.len() == FIELD_BYTES); + + let pubkey_x = FieldElement::from_be_bytes_reduce(pubkey_x_bytes); + let pubkey_y = FieldElement::from_be_bytes_reduce(pubkey_y_bytes); + Ok((pubkey_x, pubkey_y)) + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn smoke_test() -> Result<(), Error> { + let barretenberg = Barretenberg::new(); + let input = FieldElement::one(); + + let res = barretenberg.fixed_base(&input, &FieldElement::zero())?; + let x = "0000000000000000000000000000000000000000000000000000000000000001"; + let y = "0000000000000002cf135e7506a45d632d270d45f1181294833fc48d823f272c"; + + assert_eq!(x, res.0.to_hex()); + assert_eq!(y, res.1.to_hex()); + Ok(()) + } + #[test] + fn low_high_smoke_test() -> Result<(), Error> { + let barretenberg = Barretenberg::new(); + let low = FieldElement::one(); + let high = FieldElement::from(2u128); + + let res = barretenberg.fixed_base(&low, &high)?; + let x = "0702ab9c7038eeecc179b4f209991bcb68c7cb05bf4c532d804ccac36199c9a9"; + let y = "23f10e9e43a3ae8d75d24154e796aae12ae7af546716e8f81a2564f1b5814130"; + + assert_eq!(x, res.0.to_hex()); + assert_eq!(y, res.1.to_hex()); + Ok(()) + } +} diff --git a/acvm-repo/barretenberg_blackbox_solver/src/wasm/schnorr.rs b/acvm-repo/barretenberg_blackbox_solver/src/wasm/schnorr.rs new file mode 100644 index 00000000000..18c4f04ef64 --- /dev/null +++ b/acvm-repo/barretenberg_blackbox_solver/src/wasm/schnorr.rs @@ -0,0 +1,104 @@ +use super::{Barretenberg, Error, FIELD_BYTES, WASM_SCRATCH_BYTES}; + +pub(crate) trait SchnorrSig { + fn construct_signature( + &self, + message: &[u8], + private_key: [u8; 32], + ) -> Result<([u8; 32], [u8; 32]), Error>; + fn construct_public_key(&self, private_key: [u8; 32]) -> Result<[u8; 64], Error>; + fn verify_signature( + &self, + pub_key: [u8; 64], + sig_s: [u8; 32], + sig_e: [u8; 32], + message: &[u8], + ) -> Result; +} + +impl SchnorrSig for Barretenberg { + fn construct_signature( + &self, + message: &[u8], + private_key: [u8; 32], + ) -> Result<([u8; 32], [u8; 32]), Error> { + let sig_s_ptr: usize = 0; + let sig_e_ptr: usize = sig_s_ptr + FIELD_BYTES; + let private_key_ptr: usize = sig_e_ptr + FIELD_BYTES; + let message_ptr: usize = private_key_ptr + private_key.len(); + assert!( + message_ptr + message.len() < WASM_SCRATCH_BYTES, + "Message overran wasm scratch space" + ); + + self.transfer_to_heap(&private_key, private_key_ptr); + self.transfer_to_heap(message, message_ptr); + self.call_multiple( + "construct_signature", + vec![ + &message_ptr.into(), + &message.len().into(), + &private_key_ptr.into(), + &sig_s_ptr.into(), + &sig_e_ptr.into(), + ], + )?; + + let sig_s: [u8; FIELD_BYTES] = self.read_memory(sig_s_ptr); + let sig_e: [u8; FIELD_BYTES] = self.read_memory(sig_e_ptr); + + Ok((sig_s, sig_e)) + } + + #[allow(dead_code)] + fn construct_public_key(&self, private_key: [u8; 32]) -> Result<[u8; 64], Error> { + let private_key_ptr: usize = 0; + let result_ptr: usize = private_key_ptr + FIELD_BYTES; + + self.transfer_to_heap(&private_key, private_key_ptr); + + self.call_multiple( + "compute_public_key", + vec![&private_key_ptr.into(), &result_ptr.into()], + )?; + + Ok(self.read_memory(result_ptr)) + } + + fn verify_signature( + &self, + pub_key: [u8; 64], + sig_s: [u8; 32], + sig_e: [u8; 32], + message: &[u8], + ) -> Result { + let public_key_ptr: usize = 0; + let sig_s_ptr: usize = public_key_ptr + pub_key.len(); + let sig_e_ptr: usize = sig_s_ptr + sig_s.len(); + let message_ptr: usize = sig_e_ptr + sig_e.len(); + assert!( + message_ptr + message.len() < WASM_SCRATCH_BYTES, + "Message overran wasm scratch space" + ); + + self.transfer_to_heap(&pub_key, public_key_ptr); + self.transfer_to_heap(&sig_s, sig_s_ptr); + self.transfer_to_heap(&sig_e, sig_e_ptr); + self.transfer_to_heap(message, message_ptr); + + let verified = self.call_multiple( + "verify_signature", + vec![ + &message_ptr.into(), + &message.len().into(), + &public_key_ptr.into(), + &sig_s_ptr.into(), + &sig_e_ptr.into(), + ], + )?; + + // Note, currently for Barretenberg plonk, if the signature fails + // then the whole circuit fails. + Ok(verified.try_into()?) + } +} diff --git a/acvm-repo/blackbox_solver/CHANGELOG.md b/acvm-repo/blackbox_solver/CHANGELOG.md new file mode 100644 index 00000000000..48774ce122f --- /dev/null +++ b/acvm-repo/blackbox_solver/CHANGELOG.md @@ -0,0 +1,144 @@ +# Changelog + +## [0.27.0](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.26.1...acvm_blackbox_solver-v0.27.0) (2023-09-19) + + +### ⚠ BREAKING CHANGES + +* Separate barretenberg solver from generic blackbox solver code ([#554](https://github.com/noir-lang/acvm/issues/554)) + +### Bug Fixes + +* use the exact version for the hex crate ([#546](https://github.com/noir-lang/acvm/issues/546)) ([2a546e5](https://github.com/noir-lang/acvm/commit/2a546e5b5cc9f39737ad81f8e96d58313a74eced)) + + +### Miscellaneous Chores + +* Separate barretenberg solver from generic blackbox solver code ([#554](https://github.com/noir-lang/acvm/issues/554)) ([a4b9772](https://github.com/noir-lang/acvm/commit/a4b97722a0892fe379ff075e6080675adafdce0e)) + +## [0.26.1](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.26.0...acvm_blackbox_solver-v0.26.1) (2023-09-12) + + +### Bug Fixes + +* Implements handling of the high limb during fixed base scalar multiplication ([#535](https://github.com/noir-lang/acvm/issues/535)) ([551504a](https://github.com/noir-lang/acvm/commit/551504aa572d3f9d56b5576d25ce1211296ee488)) + +## [0.26.0](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.25.0...acvm_blackbox_solver-v0.26.0) (2023-09-07) + + +### ⚠ BREAKING CHANGES + +* Add a low and high limb to scalar mul opcode ([#532](https://github.com/noir-lang/acvm/issues/532)) + +### Miscellaneous Chores + +* Add a low and high limb to scalar mul opcode ([#532](https://github.com/noir-lang/acvm/issues/532)) ([b054f66](https://github.com/noir-lang/acvm/commit/b054f66be9c73d4e02dbecdab80874a907f19242)) + +## [0.25.0](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.24.1...acvm_blackbox_solver-v0.25.0) (2023-09-04) + + +### Miscellaneous Chores + +* **acvm_blackbox_solver:** Synchronize acvm versions + +## [0.24.1](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.24.0...acvm_blackbox_solver-v0.24.1) (2023-09-03) + + +### Bug Fixes + +* Add WASI 20 `_initialize` call to `acvm_backend.wasm` binary ([#518](https://github.com/noir-lang/acvm/issues/518)) ([ec6ab0c](https://github.com/noir-lang/acvm/commit/ec6ab0c6fb2753209abe1e03a449873e255ffd76)) + +## [0.24.0](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.23.0...acvm_blackbox_solver-v0.24.0) (2023-08-31) + + +### Miscellaneous Chores + +* **acvm_blackbox_solver:** Synchronize acvm versions + +## [0.23.0](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.22.0...acvm_blackbox_solver-v0.23.0) (2023-08-30) + + +### ⚠ BREAKING CHANGES + +* **acvm:** Remove `BlackBoxFunctionSolver` from `Backend` trait ([#494](https://github.com/noir-lang/acvm/issues/494)) + +### Features + +* Expose a `BlackBoxFunctionSolver` containing a barretenberg wasm from `blackbox_solver` ([#494](https://github.com/noir-lang/acvm/issues/494)) ([a1d4b71](https://github.com/noir-lang/acvm/commit/a1d4b71256dfbf1e883e770dd9c45479235aa860)) + + +### Miscellaneous Chores + +* **acvm:** Remove `BlackBoxFunctionSolver` from `Backend` trait ([#494](https://github.com/noir-lang/acvm/issues/494)) ([a1d4b71](https://github.com/noir-lang/acvm/commit/a1d4b71256dfbf1e883e770dd9c45479235aa860)) + +## [0.22.0](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.21.0...acvm_blackbox_solver-v0.22.0) (2023-08-18) + + +### Miscellaneous Chores + +* **acvm_blackbox_solver:** Synchronize acvm versions + +## [0.21.0](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.20.1...acvm_blackbox_solver-v0.21.0) (2023-07-26) + + +### Miscellaneous Chores + +* **acvm_blackbox_solver:** Synchronize acvm versions + +## [0.20.1](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.20.0...acvm_blackbox_solver-v0.20.1) (2023-07-26) + + +### Miscellaneous Chores + +* **acvm_blackbox_solver:** Synchronize acvm versions + +## [0.20.0](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.19.1...acvm_blackbox_solver-v0.20.0) (2023-07-20) + + +### Miscellaneous Chores + +* **acvm_blackbox_solver:** Synchronize acvm versions + +## [0.19.1](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.19.0...acvm_blackbox_solver-v0.19.1) (2023-07-17) + + +### Miscellaneous Chores + +* **acvm_blackbox_solver:** Synchronize acvm versions + +## [0.19.0](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.18.2...acvm_blackbox_solver-v0.19.0) (2023-07-15) + + +### Miscellaneous Chores + +* **acvm_blackbox_solver:** Synchronize acvm versions + +## [0.18.2](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.18.1...acvm_blackbox_solver-v0.18.2) (2023-07-12) + + +### Miscellaneous Chores + +* **acvm_blackbox_solver:** Synchronize acvm versions + +## [0.18.1](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.18.0...acvm_blackbox_solver-v0.18.1) (2023-07-12) + + +### Miscellaneous Chores + +* **acvm_blackbox_solver:** Synchronize acvm versions + +## [0.18.0](https://github.com/noir-lang/acvm/compare/acvm_blackbox_solver-v0.17.0...acvm_blackbox_solver-v0.18.0) (2023-07-12) + + +### ⚠ BREAKING CHANGES + +* add backend-solvable blackboxes to brillig & unify implementations ([#422](https://github.com/noir-lang/acvm/issues/422)) + +### Features + +* add backend-solvable blackboxes to brillig & unify implementations ([#422](https://github.com/noir-lang/acvm/issues/422)) ([093342e](https://github.com/noir-lang/acvm/commit/093342ea9481a311fa71343b8b7a22774788838a)) + + +### Bug Fixes + +* Empty commit to trigger release-please ([e8f0748](https://github.com/noir-lang/acvm/commit/e8f0748042ef505d59ab63266d3c36c5358ee30d)) diff --git a/acvm-repo/blackbox_solver/Cargo.toml b/acvm-repo/blackbox_solver/Cargo.toml new file mode 100644 index 00000000000..f92c75e78d4 --- /dev/null +++ b/acvm-repo/blackbox_solver/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "acvm_blackbox_solver" +description = "A solver for the blackbox functions found in ACIR and Brillig" +# x-release-please-start-version +version = "0.27.4" +# x-release-please-end +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +acir.workspace = true +thiserror.workspace = true + +blake2 = "0.10.6" +sha2 = "0.10.6" +sha3 = "0.10.6" +k256 = { version = "0.11.0", features = [ + "ecdsa", + "ecdsa-core", + "sha256", + "digest", + "arithmetic", +] } +p256 = { version = "0.11.0", features = [ + "ecdsa", + "ecdsa-core", + "sha256", + "digest", + "arithmetic", +] } + +[features] +default = ["bn254"] +bn254 = ["acir/bn254"] +bls12_381 = ["acir/bls12_381"] diff --git a/acvm-repo/blackbox_solver/src/lib.rs b/acvm-repo/blackbox_solver/src/lib.rs new file mode 100644 index 00000000000..c7f99404257 --- /dev/null +++ b/acvm-repo/blackbox_solver/src/lib.rs @@ -0,0 +1,400 @@ +#![warn(unused_crate_dependencies)] +#![warn(unreachable_pub)] + +//! This crate provides the implementation of BlackBox functions of ACIR and Brillig. +//! For functions that are backend-dependent, it provides a Trait [BlackBoxFunctionSolver] that must be implemented by the backend. +//! For functions that have a reference implementation, such as [keccak256], this crate exports the reference implementation directly. + +use acir::{BlackBoxFunc, FieldElement}; +use blake2::digest::generic_array::GenericArray; +use blake2::{Blake2s256, Digest}; +use sha2::Sha256; +use sha3::Keccak256; +use thiserror::Error; + +#[derive(Clone, PartialEq, Eq, Debug, Error)] +pub enum BlackBoxResolutionError { + #[error("unsupported blackbox function: {0}")] + Unsupported(BlackBoxFunc), + #[error("failed to solve blackbox function: {0}, reason: {1}")] + Failed(BlackBoxFunc, String), +} + +/// This component will generate outputs for Blackbox function calls where the underlying [`acir::BlackBoxFunc`] +/// doesn't have a canonical Rust implementation. +/// +/// Returns an [`BlackBoxResolutionError`] if the backend does not support the given [`acir::BlackBoxFunc`]. +pub trait BlackBoxFunctionSolver { + fn schnorr_verify( + &self, + public_key_x: &FieldElement, + public_key_y: &FieldElement, + signature: &[u8], + message: &[u8], + ) -> Result; + fn pedersen( + &self, + inputs: &[FieldElement], + domain_separator: u32, + ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError>; + fn fixed_base_scalar_mul( + &self, + low: &FieldElement, + high: &FieldElement, + ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError>; +} + +pub fn sha256(inputs: &[u8]) -> Result<[u8; 32], BlackBoxResolutionError> { + generic_hash_256::(inputs) + .map_err(|err| BlackBoxResolutionError::Failed(BlackBoxFunc::SHA256, err)) +} + +pub fn blake2s(inputs: &[u8]) -> Result<[u8; 32], BlackBoxResolutionError> { + generic_hash_256::(inputs) + .map_err(|err| BlackBoxResolutionError::Failed(BlackBoxFunc::Blake2s, err)) +} + +pub fn keccak256(inputs: &[u8]) -> Result<[u8; 32], BlackBoxResolutionError> { + generic_hash_256::(inputs) + .map_err(|err| BlackBoxResolutionError::Failed(BlackBoxFunc::Keccak256, err)) +} + +pub fn hash_to_field_128_security(inputs: &[u8]) -> Result { + generic_hash_to_field::(inputs) + .map_err(|err| BlackBoxResolutionError::Failed(BlackBoxFunc::HashToField128Security, err)) +} + +pub fn ecdsa_secp256k1_verify( + hashed_msg: &[u8], + public_key_x: &[u8; 32], + public_key_y: &[u8; 32], + signature: &[u8; 64], +) -> Result { + Ok(verify_secp256k1_ecdsa_signature(hashed_msg, public_key_x, public_key_y, signature)) +} + +pub fn ecdsa_secp256r1_verify( + hashed_msg: &[u8], + public_key_x: &[u8; 32], + public_key_y: &[u8; 32], + signature: &[u8; 64], +) -> Result { + Ok(verify_secp256r1_ecdsa_signature(hashed_msg, public_key_x, public_key_y, signature)) +} + +/// Does a generic hash of the inputs returning the resulting 32 bytes separately. +fn generic_hash_256(message: &[u8]) -> Result<[u8; 32], String> { + let output_bytes: [u8; 32] = + D::digest(message).as_slice().try_into().map_err(|_| "digest should be 256 bits")?; + + Ok(output_bytes) +} + +/// Does a generic hash of the entire inputs converting the resulting hash into a single output field. +fn generic_hash_to_field(message: &[u8]) -> Result { + let output_bytes: [u8; 32] = + D::digest(message).as_slice().try_into().map_err(|_| "digest should be 256 bits")?; + + Ok(FieldElement::from_be_bytes_reduce(&output_bytes)) +} + +fn verify_secp256k1_ecdsa_signature( + hashed_msg: &[u8], + public_key_x_bytes: &[u8; 32], + public_key_y_bytes: &[u8; 32], + signature: &[u8; 64], +) -> bool { + use k256::elliptic_curve::sec1::FromEncodedPoint; + use k256::elliptic_curve::PrimeField; + + use k256::{ecdsa::Signature, Scalar}; + use k256::{ + elliptic_curve::{ + sec1::{Coordinates, ToEncodedPoint}, + IsHigh, + }, + AffinePoint, EncodedPoint, ProjectivePoint, PublicKey, + }; + // Convert the inputs into k256 data structures + + let Ok(signature) = Signature::try_from(signature.as_slice()) else { + // Signature `r` and `s` are forbidden from being zero. + return false; + }; + + let point = EncodedPoint::from_affine_coordinates( + public_key_x_bytes.into(), + public_key_y_bytes.into(), + true, + ); + + let pubkey = PublicKey::from_encoded_point(&point); + let pubkey = if pubkey.is_some().into() { + pubkey.unwrap() + } else { + // Public key must sit on the Secp256k1 curve. + return false; + }; + + // Note: This is incorrect as it will panic if `hashed_msg >= k256::Secp256k1::ORDER`. + // In this scenario we should just take the leftmost bits from `hashed_msg` up to the group order length. + let z = Scalar::from_repr(*GenericArray::from_slice(hashed_msg)).unwrap(); + + // Finished converting bytes into data structures + + let r = signature.r(); + let s = signature.s(); + + // Ensure signature is "low S" normalized ala BIP 0062 + if s.is_high().into() { + return false; + } + + let s_inv = s.invert().unwrap(); + let u1 = z * s_inv; + let u2 = *r * s_inv; + + #[allow(non_snake_case)] + let R: AffinePoint = ((ProjectivePoint::GENERATOR * u1) + + (ProjectivePoint::from(*pubkey.as_affine()) * u2)) + .to_affine(); + + match R.to_encoded_point(false).coordinates() { + Coordinates::Uncompressed { x, y: _ } => Scalar::from_repr(*x).unwrap().eq(&r), + _ => unreachable!("Point is uncompressed"), + } +} + +fn verify_secp256r1_ecdsa_signature( + hashed_msg: &[u8], + public_key_x_bytes: &[u8; 32], + public_key_y_bytes: &[u8; 32], + signature: &[u8; 64], +) -> bool { + use p256::elliptic_curve::sec1::FromEncodedPoint; + use p256::elliptic_curve::PrimeField; + + use p256::{ecdsa::Signature, Scalar}; + use p256::{ + elliptic_curve::{ + sec1::{Coordinates, ToEncodedPoint}, + IsHigh, + }, + AffinePoint, EncodedPoint, ProjectivePoint, PublicKey, + }; + + // Convert the inputs into k256 data structures + + let Ok(signature) = Signature::try_from(signature.as_slice()) else { + // Signature `r` and `s` are forbidden from being zero. + return false; + }; + + let point = EncodedPoint::from_affine_coordinates( + public_key_x_bytes.into(), + public_key_y_bytes.into(), + true, + ); + + let pubkey = PublicKey::from_encoded_point(&point); + let pubkey = if pubkey.is_some().into() { + pubkey.unwrap() + } else { + // Public key must sit on the Secp256r1 curve. + return false; + }; + + // Note: This is incorrect as it will panic if `hashed_msg >= p256::NistP256::ORDER`. + // In this scenario we should just take the leftmost bits from `hashed_msg` up to the group order length. + let z = Scalar::from_repr(*GenericArray::from_slice(hashed_msg)).unwrap(); + + // Finished converting bytes into data structures + + let r = signature.r(); + let s = signature.s(); + + // Ensure signature is "low S" normalized ala BIP 0062 + if s.is_high().into() { + return false; + } + + let s_inv = s.invert().unwrap(); + let u1 = z * s_inv; + let u2 = *r * s_inv; + + #[allow(non_snake_case)] + let R: AffinePoint = ((ProjectivePoint::GENERATOR * u1) + + (ProjectivePoint::from(*pubkey.as_affine()) * u2)) + .to_affine(); + + match R.to_encoded_point(false).coordinates() { + Coordinates::Uncompressed { x, y: _ } => Scalar::from_repr(*x).unwrap().eq(&r), + _ => unreachable!("Point is uncompressed"), + } +} + +#[cfg(test)] +mod secp256k1_tests { + use super::verify_secp256k1_ecdsa_signature; + + // 0x3a73f4123a5cd2121f21cd7e8d358835476949d035d9c2da6806b4633ac8c1e2, + const HASHED_MESSAGE: [u8; 32] = [ + 0x3a, 0x73, 0xf4, 0x12, 0x3a, 0x5c, 0xd2, 0x12, 0x1f, 0x21, 0xcd, 0x7e, 0x8d, 0x35, 0x88, + 0x35, 0x47, 0x69, 0x49, 0xd0, 0x35, 0xd9, 0xc2, 0xda, 0x68, 0x06, 0xb4, 0x63, 0x3a, 0xc8, + 0xc1, 0xe2, + ]; + // 0xa0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7 + const PUB_KEY_X: [u8; 32] = [ + 0xa0, 0x43, 0x4d, 0x9e, 0x47, 0xf3, 0xc8, 0x62, 0x35, 0x47, 0x7c, 0x7b, 0x1a, 0xe6, 0xae, + 0x5d, 0x34, 0x42, 0xd4, 0x9b, 0x19, 0x43, 0xc2, 0xb7, 0x52, 0xa6, 0x8e, 0x2a, 0x47, 0xe2, + 0x47, 0xc7, + ]; + // 0x893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7 + const PUB_KEY_Y: [u8; 32] = [ + 0x89, 0x3a, 0xba, 0x42, 0x54, 0x19, 0xbc, 0x27, 0xa3, 0xb6, 0xc7, 0xe6, 0x93, 0xa2, 0x4c, + 0x69, 0x6f, 0x79, 0x4c, 0x2e, 0xd8, 0x77, 0xa1, 0x59, 0x3c, 0xbe, 0xe5, 0x3b, 0x03, 0x73, + 0x68, 0xd7, + ]; + // 0xe5081c80ab427dc370346f4a0e31aa2bad8d9798c38061db9ae55a4e8df454fd28119894344e71b78770cc931d61f480ecbb0b89d6eb69690161e49a715fcd55 + const SIGNATURE: [u8; 64] = [ + 0xe5, 0x08, 0x1c, 0x80, 0xab, 0x42, 0x7d, 0xc3, 0x70, 0x34, 0x6f, 0x4a, 0x0e, 0x31, 0xaa, + 0x2b, 0xad, 0x8d, 0x97, 0x98, 0xc3, 0x80, 0x61, 0xdb, 0x9a, 0xe5, 0x5a, 0x4e, 0x8d, 0xf4, + 0x54, 0xfd, 0x28, 0x11, 0x98, 0x94, 0x34, 0x4e, 0x71, 0xb7, 0x87, 0x70, 0xcc, 0x93, 0x1d, + 0x61, 0xf4, 0x80, 0xec, 0xbb, 0x0b, 0x89, 0xd6, 0xeb, 0x69, 0x69, 0x01, 0x61, 0xe4, 0x9a, + 0x71, 0x5f, 0xcd, 0x55, + ]; + + #[test] + fn verifies_valid_signature_with_low_s_value() { + let valid = + verify_secp256k1_ecdsa_signature(&HASHED_MESSAGE, &PUB_KEY_X, &PUB_KEY_Y, &SIGNATURE); + + assert!(valid) + } + + #[test] + fn rejects_invalid_signature() { + // This signature is invalid as ECDSA specifies that `r` and `s` must be non-zero. + let invalid_signature: [u8; 64] = [0x00; 64]; + + let valid = verify_secp256k1_ecdsa_signature( + &HASHED_MESSAGE, + &PUB_KEY_X, + &PUB_KEY_Y, + &invalid_signature, + ); + assert!(!valid); + } + + #[test] + fn rejects_invalid_public_key() { + let invalid_pub_key_x: [u8; 32] = [0xff; 32]; + let invalid_pub_key_y: [u8; 32] = [0xff; 32]; + + let valid = verify_secp256k1_ecdsa_signature( + &HASHED_MESSAGE, + &invalid_pub_key_x, + &invalid_pub_key_y, + &SIGNATURE, + ); + + assert!(!valid) + } + + #[test] + #[ignore = "ECDSA verification does not currently handle long hashes correctly"] + fn trims_overly_long_hashes_to_correct_length() { + let mut long_hashed_message = HASHED_MESSAGE.to_vec(); + long_hashed_message.push(0xff); + + let valid = verify_secp256k1_ecdsa_signature( + &long_hashed_message, + &PUB_KEY_X, + &PUB_KEY_Y, + &SIGNATURE, + ); + + assert!(valid) + } +} + +#[cfg(test)] +mod secp256r1_tests { + use super::verify_secp256r1_ecdsa_signature; + + // 0x54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad + const HASHED_MESSAGE: [u8; 32] = [ + 84, 112, 91, 163, 186, 175, 219, 223, 186, 140, 95, 154, 112, 247, 168, 155, 238, 152, 217, + 6, 181, 62, 49, 7, 77, 167, 186, 236, 220, 13, 169, 173, + ]; + // 0x550f471003f3df97c3df506ac797f6721fb1a1fb7b8f6f83d224498a65c88e24 + const PUB_KEY_X: [u8; 32] = [ + 85, 15, 71, 16, 3, 243, 223, 151, 195, 223, 80, 106, 199, 151, 246, 114, 31, 177, 161, 251, + 123, 143, 111, 131, 210, 36, 73, 138, 101, 200, 142, 36, + ]; + // 0x136093d7012e509a73715cbd0b00a3cc0ff4b5c01b3ffa196ab1fb327036b8e6 + const PUB_KEY_Y: [u8; 32] = [ + 19, 96, 147, 215, 1, 46, 80, 154, 115, 113, 92, 189, 11, 0, 163, 204, 15, 244, 181, 192, + 27, 63, 250, 25, 106, 177, 251, 50, 112, 54, 184, 230, + ]; + // 0x2c70a8d084b62bfc5ce03641caf9f72ad4da8c81bfe6ec9487bb5e1bef62a13218ad9ee29eaf351fdc50f1520c425e9b908a07278b43b0ec7b872778c14e0784 + const SIGNATURE: [u8; 64] = [ + 44, 112, 168, 208, 132, 182, 43, 252, 92, 224, 54, 65, 202, 249, 247, 42, 212, 218, 140, + 129, 191, 230, 236, 148, 135, 187, 94, 27, 239, 98, 161, 50, 24, 173, 158, 226, 158, 175, + 53, 31, 220, 80, 241, 82, 12, 66, 94, 155, 144, 138, 7, 39, 139, 67, 176, 236, 123, 135, + 39, 120, 193, 78, 7, 132, + ]; + + #[test] + fn verifies_valid_signature_with_low_s_value() { + let valid = + verify_secp256r1_ecdsa_signature(&HASHED_MESSAGE, &PUB_KEY_X, &PUB_KEY_Y, &SIGNATURE); + + assert!(valid) + } + + #[test] + fn rejects_invalid_signature() { + // This signature is invalid as ECDSA specifies that `r` and `s` must be non-zero. + let invalid_signature: [u8; 64] = [0x00; 64]; + + let valid = verify_secp256r1_ecdsa_signature( + &HASHED_MESSAGE, + &PUB_KEY_X, + &PUB_KEY_Y, + &invalid_signature, + ); + assert!(!valid); + } + + #[test] + fn rejects_invalid_public_key() { + let invalid_pub_key_x: [u8; 32] = [0xff; 32]; + let invalid_pub_key_y: [u8; 32] = [0xff; 32]; + + let valid = verify_secp256r1_ecdsa_signature( + &HASHED_MESSAGE, + &invalid_pub_key_x, + &invalid_pub_key_y, + &SIGNATURE, + ); + + assert!(!valid) + } + + #[test] + #[ignore = "ECDSA verification does not currently handle long hashes correctly"] + fn trims_overly_long_hashes_to_correct_length() { + let mut long_hashed_message = HASHED_MESSAGE.to_vec(); + long_hashed_message.push(0xff); + + let valid = verify_secp256r1_ecdsa_signature( + &long_hashed_message, + &PUB_KEY_X, + &PUB_KEY_Y, + &SIGNATURE, + ); + + assert!(valid) + } +} diff --git a/acvm-repo/brillig/CHANGELOG.md b/acvm-repo/brillig/CHANGELOG.md new file mode 100644 index 00000000000..f9b5307d534 --- /dev/null +++ b/acvm-repo/brillig/CHANGELOG.md @@ -0,0 +1,127 @@ +# Changelog + +## [0.27.0](https://github.com/noir-lang/acvm/compare/brillig-v0.26.1...brillig-v0.27.0) (2023-09-19) + + +### Miscellaneous Chores + +* **brillig:** Synchronize acvm versions + +## [0.26.1](https://github.com/noir-lang/acvm/compare/brillig-v0.26.0...brillig-v0.26.1) (2023-09-12) + + +### Miscellaneous Chores + +* **brillig:** Synchronize acvm versions + +## [0.26.0](https://github.com/noir-lang/acvm/compare/brillig-v0.25.0...brillig-v0.26.0) (2023-09-07) + + +### ⚠ BREAKING CHANGES + +* Add a low and high limb to scalar mul opcode ([#532](https://github.com/noir-lang/acvm/issues/532)) + +### Miscellaneous Chores + +* Add a low and high limb to scalar mul opcode ([#532](https://github.com/noir-lang/acvm/issues/532)) ([b054f66](https://github.com/noir-lang/acvm/commit/b054f66be9c73d4e02dbecdab80874a907f19242)) + +## [0.25.0](https://github.com/noir-lang/acvm/compare/brillig-v0.24.1...brillig-v0.25.0) (2023-09-04) + + +### Miscellaneous Chores + +* **brillig:** Synchronize acvm versions + +## [0.24.1](https://github.com/noir-lang/acvm/compare/brillig-v0.24.0...brillig-v0.24.1) (2023-09-03) + + +### Miscellaneous Chores + +* **brillig:** Synchronize acvm versions + +## [0.24.0](https://github.com/noir-lang/acvm/compare/brillig-v0.23.0...brillig-v0.24.0) (2023-08-31) + + +### Miscellaneous Chores + +* **brillig:** Synchronize acvm versions + +## [0.23.0](https://github.com/noir-lang/acvm/compare/brillig-v0.22.0...brillig-v0.23.0) (2023-08-30) + + +### Miscellaneous Chores + +* **brillig:** Synchronize acvm versions + +## [0.22.0](https://github.com/noir-lang/acvm/compare/brillig-v0.21.0...brillig-v0.22.0) (2023-08-18) + + +### Miscellaneous Chores + +* **brillig:** Synchronize acvm versions + +## [0.21.0](https://github.com/noir-lang/acvm/compare/brillig-v0.20.1...brillig-v0.21.0) (2023-07-26) + + +### Miscellaneous Chores + +* **brillig:** Synchronize acvm versions + +## [0.20.1](https://github.com/noir-lang/acvm/compare/brillig-v0.20.0...brillig-v0.20.1) (2023-07-26) + + +### Miscellaneous Chores + +* **brillig:** Synchronize acvm versions + +## [0.20.0](https://github.com/noir-lang/acvm/compare/brillig-v0.19.1...brillig-v0.20.0) (2023-07-20) + + +### Miscellaneous Chores + +* **brillig:** Synchronize acvm versions + +## [0.19.1](https://github.com/noir-lang/acvm/compare/brillig-v0.19.0...brillig-v0.19.1) (2023-07-17) + + +### Miscellaneous Chores + +* **brillig:** Synchronize acvm versions + +## [0.19.0](https://github.com/noir-lang/acvm/compare/brillig-v0.18.2...brillig-v0.19.0) (2023-07-15) + + +### Miscellaneous Chores + +* **brillig:** Synchronize acvm versions + +## [0.18.2](https://github.com/noir-lang/acvm/compare/brillig-v0.18.1...brillig-v0.18.2) (2023-07-12) + + +### Miscellaneous Chores + +* **brillig:** Synchronize acvm versions + +## [0.18.1](https://github.com/noir-lang/acvm/compare/brillig-v0.18.0...brillig-v0.18.1) (2023-07-12) + + +### Miscellaneous Chores + +* **brillig:** Synchronize acvm versions + +## [0.18.0](https://github.com/noir-lang/acvm/compare/brillig-v0.17.0...brillig-v0.18.0) (2023-07-12) + + +### ⚠ BREAKING CHANGES + +* add backend-solvable blackboxes to brillig & unify implementations ([#422](https://github.com/noir-lang/acvm/issues/422)) + +### Features + +* add backend-solvable blackboxes to brillig & unify implementations ([#422](https://github.com/noir-lang/acvm/issues/422)) ([093342e](https://github.com/noir-lang/acvm/commit/093342ea9481a311fa71343b8b7a22774788838a)) +* derive PartialOrd, Ord, and Hash on RegisterIndex ([#425](https://github.com/noir-lang/acvm/issues/425)) ([7f6b0dc](https://github.com/noir-lang/acvm/commit/7f6b0dc138c4e11d2b5847f0c9603979cc43493a)) + + +### Bug Fixes + +* Empty commit to trigger release-please ([e8f0748](https://github.com/noir-lang/acvm/commit/e8f0748042ef505d59ab63266d3c36c5358ee30d)) diff --git a/acvm-repo/brillig/Cargo.toml b/acvm-repo/brillig/Cargo.toml new file mode 100644 index 00000000000..a0fa1f52281 --- /dev/null +++ b/acvm-repo/brillig/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "brillig" +description = "Brillig is the bytecode ACIR uses for non-determinism." +# x-release-please-start-version +version = "0.27.4" +# x-release-please-end +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +acir_field.workspace = true +serde.workspace = true + +[features] +default = ["bn254"] +bn254 = ["acir_field/bn254"] +bls12_381 = ["acir_field/bls12_381"] diff --git a/acvm-repo/brillig/src/black_box.rs b/acvm-repo/brillig/src/black_box.rs new file mode 100644 index 00000000000..be858a43cee --- /dev/null +++ b/acvm-repo/brillig/src/black_box.rs @@ -0,0 +1,48 @@ +use crate::{opcodes::HeapVector, HeapArray, RegisterIndex}; +use serde::{Deserialize, Serialize}; + +/// These opcodes provide an equivalent of ACIR blackbox functions. +/// They are implemented as native functions in the VM. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum BlackBoxOp { + /// Calculates the SHA256 hash of the inputs. + Sha256 { message: HeapVector, output: HeapArray }, + /// Calculates the Blake2s hash of the inputs. + Blake2s { message: HeapVector, output: HeapArray }, + /// Calculates the Keccak256 hash of the inputs. + Keccak256 { message: HeapVector, output: HeapArray }, + /// Hashes a set of inputs and applies the field modulus to the result + /// to return a value which can be represented as a [`FieldElement`][acir_field::FieldElement] + /// + /// This is implemented using the `Blake2s` hash function. + /// The "128" in the name specifies that this function should have 128 bits of security. + HashToField128Security { message: HeapVector, output: RegisterIndex }, + /// Verifies a ECDSA signature over the secp256k1 curve. + EcdsaSecp256k1 { + hashed_msg: HeapVector, + public_key_x: HeapArray, + public_key_y: HeapArray, + signature: HeapArray, + result: RegisterIndex, + }, + /// Verifies a ECDSA signature over the secp256r1 curve. + EcdsaSecp256r1 { + hashed_msg: HeapVector, + public_key_x: HeapArray, + public_key_y: HeapArray, + signature: HeapArray, + result: RegisterIndex, + }, + /// Verifies a Schnorr signature over a curve which is "pairing friendly" with the curve on which the Brillig bytecode is defined. + SchnorrVerify { + public_key_x: RegisterIndex, + public_key_y: RegisterIndex, + message: HeapVector, + signature: HeapVector, + result: RegisterIndex, + }, + /// Calculates a Pedersen commitment to the inputs. + Pedersen { inputs: HeapVector, domain_separator: RegisterIndex, output: HeapArray }, + /// Performs scalar multiplication over the embedded curve. + FixedBaseScalarMul { low: RegisterIndex, high: RegisterIndex, result: HeapArray }, +} diff --git a/acvm-repo/brillig/src/foreign_call.rs b/acvm-repo/brillig/src/foreign_call.rs new file mode 100644 index 00000000000..3c041d00d02 --- /dev/null +++ b/acvm-repo/brillig/src/foreign_call.rs @@ -0,0 +1,34 @@ +use crate::value::Value; +use serde::{Deserialize, Serialize}; + +/// Single output of a [foreign call][crate::Opcode::ForeignCall]. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] +pub enum ForeignCallOutput { + Single(Value), + Array(Vec), +} + +/// Represents the full output of a [foreign call][crate::Opcode::ForeignCall]. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] +pub struct ForeignCallResult { + /// Resolved output values of the foreign call. + pub values: Vec, +} + +impl From for ForeignCallResult { + fn from(value: Value) -> Self { + ForeignCallResult { values: vec![ForeignCallOutput::Single(value)] } + } +} + +impl From> for ForeignCallResult { + fn from(values: Vec) -> Self { + ForeignCallResult { values: vec![ForeignCallOutput::Array(values)] } + } +} + +impl From> for ForeignCallResult { + fn from(values: Vec) -> Self { + ForeignCallResult { values } + } +} diff --git a/acvm-repo/brillig/src/lib.rs b/acvm-repo/brillig/src/lib.rs new file mode 100644 index 00000000000..a91a3484323 --- /dev/null +++ b/acvm-repo/brillig/src/lib.rs @@ -0,0 +1,23 @@ +#![warn(unused_crate_dependencies)] +#![warn(unreachable_pub)] + +//! The Brillig bytecode is distinct from regular [ACIR][acir] in that it does not generate constraints. +//! This is a generalization over the fixed directives that exists within in the ACVM. +//! +//! [acir]: https://crates.io/crates/acir +//! [acvm]: https://crates.io/crates/acvm +//! [brillig_vm]: https://crates.io/crates/brillig_vm + +mod black_box; +mod foreign_call; +mod opcodes; +mod value; + +pub use black_box::BlackBoxOp; +pub use foreign_call::{ForeignCallOutput, ForeignCallResult}; +pub use opcodes::{ + BinaryFieldOp, BinaryIntOp, HeapArray, HeapVector, RegisterIndex, RegisterOrMemory, +}; +pub use opcodes::{Label, Opcode}; +pub use value::Typ; +pub use value::Value; diff --git a/acvm-repo/brillig/src/opcodes.rs b/acvm-repo/brillig/src/opcodes.rs new file mode 100644 index 00000000000..7170ac24e48 --- /dev/null +++ b/acvm-repo/brillig/src/opcodes.rs @@ -0,0 +1,193 @@ +use crate::{black_box::BlackBoxOp, Value}; +use serde::{Deserialize, Serialize}; + +pub type Label = usize; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct RegisterIndex(pub usize); + +/// `RegisterIndex` refers to the index in VM register space. +impl RegisterIndex { + pub fn to_usize(self) -> usize { + self.0 + } +} + +impl From for RegisterIndex { + fn from(value: usize) -> Self { + RegisterIndex(value) + } +} + +/// A fixed-sized array starting from a Brillig register memory location. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy)] +pub struct HeapArray { + pub pointer: RegisterIndex, + pub size: usize, +} + +/// A register-sized vector passed starting from a Brillig register memory location and with a register-held size +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy)] +pub struct HeapVector { + pub pointer: RegisterIndex, + pub size: RegisterIndex, +} + +/// Lays out various ways an external foreign call's input and output data may be interpreted inside Brillig. +/// This data can either be an individual register value or memory. +/// +/// While we are usually agnostic to how memory is passed within Brillig, +/// this needs to be encoded somehow when dealing with an external system. +/// For simplicity, the extra type information is given right in the ForeignCall instructions. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy)] +pub enum RegisterOrMemory { + /// A single register value passed to or from an external call + /// It is an 'immediate' value - used without dereferencing memory. + /// For a foreign call input, the value is read directly from the register. + /// For a foreign call output, the value is written directly to the register. + RegisterIndex(RegisterIndex), + /// An array passed to or from an external call + /// In the case of a foreign call input, the array is read from this Brillig memory location + usize more cells. + /// In the case of a foreign call output, the array is written to this Brillig memory location with the usize being here just as a sanity check for the size write. + HeapArray(HeapArray), + /// A vector passed to or from an external call + /// In the case of a foreign call input, the vector is read from this Brillig memory location + as many cells as the 2nd register indicates. + /// In the case of a foreign call output, the vector is written to this Brillig memory location and as 'size' cells, with size being stored in the second register. + HeapVector(HeapVector), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Opcode { + /// Takes the fields in registers `lhs` and `rhs` + /// Performs the specified binary operation + /// and stores the value in the `result` register. + BinaryFieldOp { + destination: RegisterIndex, + op: BinaryFieldOp, + lhs: RegisterIndex, + rhs: RegisterIndex, + }, + /// Takes the `bit_size` size integers in registers `lhs` and `rhs` + /// Performs the specified binary operation + /// and stores the value in the `result` register. + BinaryIntOp { + destination: RegisterIndex, + op: BinaryIntOp, + bit_size: u32, + lhs: RegisterIndex, + rhs: RegisterIndex, + }, + JumpIfNot { + condition: RegisterIndex, + location: Label, + }, + /// Sets the program counter to the value located at `destination` + /// If the value at `condition` is non-zero + JumpIf { + condition: RegisterIndex, + location: Label, + }, + /// Sets the program counter to the label. + Jump { + location: Label, + }, + /// We don't support dynamic jumps or calls + /// See https://github.com/ethereum/aleth/issues/3404 for reasoning + Call { + location: Label, + }, + Const { + destination: RegisterIndex, + value: Value, + }, + Return, + /// Used to get data from an outside source. + /// Also referred to as an Oracle. However, we don't use that name as + /// this is intended for things like state tree reads, and shouldn't be confused + /// with e.g. blockchain price oracles. + ForeignCall { + /// Interpreted by caller context, ie this will have different meanings depending on + /// who the caller is. + function: String, + /// Destination registers (may be single values or memory pointers). + destinations: Vec, + /// Input registers (may be single values or memory pointers). + inputs: Vec, + }, + Mov { + destination: RegisterIndex, + source: RegisterIndex, + }, + Load { + destination: RegisterIndex, + source_pointer: RegisterIndex, + }, + Store { + destination_pointer: RegisterIndex, + source: RegisterIndex, + }, + BlackBox(BlackBoxOp), + /// Used to denote execution failure + Trap, + /// Stop execution + Stop, +} + +impl Opcode { + pub fn name(&self) -> &'static str { + match self { + Opcode::BinaryFieldOp { .. } => "binary_field_op", + Opcode::BinaryIntOp { .. } => "binary_int_op", + Opcode::JumpIfNot { .. } => "jmp_if_not", + Opcode::JumpIf { .. } => "jmp_if", + Opcode::Jump { .. } => "jmp", + Opcode::Call { .. } => "call", + Opcode::Const { .. } => "const", + Opcode::Return => "return", + Opcode::ForeignCall { .. } => "foreign_call", + Opcode::Mov { .. } => "mov", + Opcode::Load { .. } => "load", + Opcode::Store { .. } => "store", + Opcode::BlackBox(_) => "black_box", + Opcode::Trap => "trap", + Opcode::Stop => "stop", + } + } +} + +/// Binary fixed-length field expressions +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum BinaryFieldOp { + Add, + Sub, + Mul, + Div, + /// (==) equal + Equals, +} + +/// Binary fixed-length integer expressions +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum BinaryIntOp { + Add, + Sub, + Mul, + SignedDiv, + UnsignedDiv, + /// (==) equal + Equals, + /// (<) Field less than + LessThan, + /// (<=) field less or equal + LessThanEquals, + /// (&) Bitwise AND + And, + /// (|) Bitwise OR + Or, + /// (^) Bitwise XOR + Xor, + /// (<<) Shift left + Shl, + /// (>>) Shift right + Shr, +} diff --git a/acvm-repo/brillig/src/value.rs b/acvm-repo/brillig/src/value.rs new file mode 100644 index 00000000000..73a7d897eb7 --- /dev/null +++ b/acvm-repo/brillig/src/value.rs @@ -0,0 +1,103 @@ +use acir_field::FieldElement; +use serde::{Deserialize, Serialize}; +use std::ops::{Add, Div, Mul, Neg, Sub}; + +/// Types of values allowed in the VM +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)] +pub enum Typ { + Field, + Unsigned { bit_size: u32 }, + Signed { bit_size: u32 }, +} + +/// `Value` represents the base descriptor for a value in the VM. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct Value { + inner: FieldElement, +} + +impl Value { + /// Returns `true` if the `Value` represents `zero` + pub fn is_zero(&self) -> bool { + self.inner.is_zero() + } + + /// Converts `Value` into a `FieldElement`. + pub fn to_field(&self) -> FieldElement { + self.inner + } + + /// Converts `Value` into a `u128`. + // TODO: Check what happens if `Value` cannot fit into a u128 + pub fn to_u128(&self) -> u128 { + self.to_field().to_u128() + } + + /// Converts `Value` into a u64 and then casts it into a usize. + /// Panics: If `Value` cannot fit into a u64 or `Value` does + //// not fit into a usize. + pub fn to_usize(&self) -> usize { + usize::try_from(self.inner.try_to_u64().expect("register does not fit into u64")) + .expect("register does not fit into usize") + } +} + +impl From for Value { + fn from(value: usize) -> Self { + Value { inner: FieldElement::from(value as u128) } + } +} + +impl From for Value { + fn from(value: u128) -> Self { + Value { inner: FieldElement::from(value) } + } +} + +impl From for Value { + fn from(value: FieldElement) -> Self { + Value { inner: value } + } +} + +impl From for Value { + fn from(value: bool) -> Self { + Value { inner: FieldElement::from(value) } + } +} + +impl Add for Value { + type Output = Value; + + fn add(self, rhs: Self) -> Self::Output { + Value { inner: self.inner + rhs.inner } + } +} +impl Sub for Value { + type Output = Value; + + fn sub(self, rhs: Self) -> Self::Output { + Value { inner: self.inner - rhs.inner } + } +} +impl Mul for Value { + type Output = Value; + + fn mul(self, rhs: Self) -> Self::Output { + Value { inner: self.inner * rhs.inner } + } +} +impl Div for Value { + type Output = Value; + + fn div(self, rhs: Self) -> Self::Output { + Value { inner: self.inner / rhs.inner } + } +} +impl Neg for Value { + type Output = Value; + + fn neg(self) -> Self::Output { + Value { inner: -self.inner } + } +} diff --git a/acvm-repo/brillig_vm/CHANGELOG.md b/acvm-repo/brillig_vm/CHANGELOG.md new file mode 100644 index 00000000000..4607fdf4da5 --- /dev/null +++ b/acvm-repo/brillig_vm/CHANGELOG.md @@ -0,0 +1,220 @@ +# Changelog + +## [0.27.0](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.26.1...brillig_vm-v0.27.0) (2023-09-19) + + +### ⚠ BREAKING CHANGES + +* Separate barretenberg solver from generic blackbox solver code ([#554](https://github.com/noir-lang/acvm/issues/554)) + +### Miscellaneous Chores + +* Separate barretenberg solver from generic blackbox solver code ([#554](https://github.com/noir-lang/acvm/issues/554)) ([a4b9772](https://github.com/noir-lang/acvm/commit/a4b97722a0892fe379ff075e6080675adafdce0e)) + +## [0.26.1](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.26.0...brillig_vm-v0.26.1) (2023-09-12) + + +### Miscellaneous Chores + +* **brillig_vm:** Synchronize acvm versions + +## [0.26.0](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.25.0...brillig_vm-v0.26.0) (2023-09-07) + + +### ⚠ BREAKING CHANGES + +* Add a low and high limb to scalar mul opcode ([#532](https://github.com/noir-lang/acvm/issues/532)) + +### Miscellaneous Chores + +* Add a low and high limb to scalar mul opcode ([#532](https://github.com/noir-lang/acvm/issues/532)) ([b054f66](https://github.com/noir-lang/acvm/commit/b054f66be9c73d4e02dbecdab80874a907f19242)) + +## [0.25.0](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.24.1...brillig_vm-v0.25.0) (2023-09-04) + + +### ⚠ BREAKING CHANGES + +* Provide runtime callstacks for brillig failures and return errors in acvm_js ([#523](https://github.com/noir-lang/acvm/issues/523)) + +### Features + +* Provide runtime callstacks for brillig failures and return errors in acvm_js ([#523](https://github.com/noir-lang/acvm/issues/523)) ([7ab7cff](https://github.com/noir-lang/acvm/commit/7ab7cff48a9aba61a97fad2a759fc8e55740b098)) + +## [0.24.1](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.24.0...brillig_vm-v0.24.1) (2023-09-03) + + +### Miscellaneous Chores + +* **brillig_vm:** Synchronize acvm versions + +## [0.24.0](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.23.0...brillig_vm-v0.24.0) (2023-08-31) + + +### Miscellaneous Chores + +* **brillig_vm:** Synchronize acvm versions + +## [0.23.0](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.22.0...brillig_vm-v0.23.0) (2023-08-30) + + +### Miscellaneous Chores + +* **brillig_vm:** Synchronize acvm versions + +## [0.22.0](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.21.0...brillig_vm-v0.22.0) (2023-08-18) + + +### Miscellaneous Chores + +* **brillig_vm:** Synchronize acvm versions + +## [0.21.0](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.20.1...brillig_vm-v0.21.0) (2023-07-26) + + +### Miscellaneous Chores + +* **brillig_vm:** Synchronize acvm versions + +## [0.20.1](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.20.0...brillig_vm-v0.20.1) (2023-07-26) + + +### Miscellaneous Chores + +* **brillig_vm:** Synchronize acvm versions + +## [0.20.0](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.19.1...brillig_vm-v0.20.0) (2023-07-20) + + +### Features + +* **brillig:** Support integers which fit inside a `FieldElement` ([#403](https://github.com/noir-lang/acvm/issues/403)) ([f992412](https://github.com/noir-lang/acvm/commit/f992412617ade875fa26fe3a2cc3c06dbcad503b)) + +## [0.19.1](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.19.0...brillig_vm-v0.19.1) (2023-07-17) + + +### Miscellaneous Chores + +* **brillig_vm:** Synchronize acvm versions + +## [0.19.0](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.18.2...brillig_vm-v0.19.0) (2023-07-15) + + +### Miscellaneous Chores + +* **brillig_vm:** Synchronize acvm versions + +## [0.18.2](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.18.1...brillig_vm-v0.18.2) (2023-07-12) + + +### Miscellaneous Chores + +* **brillig_vm:** Synchronize acvm versions + +## [0.18.1](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.18.0...brillig_vm-v0.18.1) (2023-07-12) + + +### Miscellaneous Chores + +* **brillig_vm:** Synchronize acvm versions + +## [0.18.0](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.17.0...brillig_vm-v0.18.0) (2023-07-12) + + +### ⚠ BREAKING CHANGES + +* add backend-solvable blackboxes to brillig & unify implementations ([#422](https://github.com/noir-lang/acvm/issues/422)) + +### Features + +* add backend-solvable blackboxes to brillig & unify implementations ([#422](https://github.com/noir-lang/acvm/issues/422)) ([093342e](https://github.com/noir-lang/acvm/commit/093342ea9481a311fa71343b8b7a22774788838a)) + +## [0.17.0](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.16.0...brillig_vm-v0.17.0) (2023-07-07) + + +### ⚠ BREAKING CHANGES + +* **acir:** add `EcdsaSecp256r1` blackbox function ([#408](https://github.com/noir-lang/acvm/issues/408)) + +### Features + +* **acir:** add `EcdsaSecp256r1` blackbox function ([#408](https://github.com/noir-lang/acvm/issues/408)) ([9895817](https://github.com/noir-lang/acvm/commit/98958170c9fa9b4731e33b31cb494a72bb90549e)) + +## [0.16.0](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.15.1...brillig_vm-v0.16.0) (2023-07-06) + + +### Features + +* **brillig:** implemented first blackbox functions ([#401](https://github.com/noir-lang/acvm/issues/401)) ([62d40f7](https://github.com/noir-lang/acvm/commit/62d40f7c03cd1102f615b8d565f82496962db637)) + +## [0.15.1](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.15.0...brillig_vm-v0.15.1) (2023-06-20) + + +### Features + +* **brillig:** Allow dynamic-size foreign calls ([#370](https://github.com/noir-lang/acvm/issues/370)) ([5ba0349](https://github.com/noir-lang/acvm/commit/5ba0349420cc1b20113cb5e96490a0808a769757)) + + +### Bug Fixes + +* **brillig:** remove register initialization check ([#392](https://github.com/noir-lang/acvm/issues/392)) ([1a53143](https://github.com/noir-lang/acvm/commit/1a531438b5c1ab7ce8c4bd599dda3515bdd5cfcd)) + +## [0.15.0](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.14.2...brillig_vm-v0.15.0) (2023-06-15) + + +### ⚠ BREAKING CHANGES + +* **brillig:** Accept multiple inputs/outputs for foreign calls ([#367](https://github.com/noir-lang/acvm/issues/367)) + +### Features + +* Add method to generate updated `Brillig` opcode from `UnresolvedBrilligCall` ([#363](https://github.com/noir-lang/acvm/issues/363)) ([fda5dbe](https://github.com/noir-lang/acvm/commit/fda5dbe57c28dc4bc28dfd8fe0a4a8ba29635393)) +* **brillig:** Accept multiple inputs/outputs for foreign calls ([#367](https://github.com/noir-lang/acvm/issues/367)) ([78d62b2](https://github.com/noir-lang/acvm/commit/78d62b2d7c1c8b884e1f3fe7983e6e5029700e70)) +* **brillig:** Set `VMStatus` to `Failure` rather than panicking on invalid foreign call response ([#375](https://github.com/noir-lang/acvm/issues/375)) ([c49d82c](https://github.com/noir-lang/acvm/commit/c49d82c99c73c60e264585ed201af2b6a2b7ee0f)) + + +### Bug Fixes + +* **brillig:** Correct signed division implementation ([#356](https://github.com/noir-lang/acvm/issues/356)) ([4eefda0](https://github.com/noir-lang/acvm/commit/4eefda01e7b371035314f77631df4687608b4782)) +* **brillig:** Explicitly wrap on arithmetic operations ([#365](https://github.com/noir-lang/acvm/issues/365)) ([c0544a9](https://github.com/noir-lang/acvm/commit/c0544a99930d3c8d534376c8f8a91645a39aecf8)) + +## [0.14.2](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.14.1...brillig_vm-v0.14.2) (2023-06-08) + + +### Bug Fixes + +* **brillig:** expand memory with zeroes on store ([#350](https://github.com/noir-lang/acvm/issues/350)) ([4d2dadd](https://github.com/noir-lang/acvm/commit/4d2dadd3acd9dc25f0feae865b74cbaea7250f3d)) + +## [0.14.1](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.14.0...brillig_vm-v0.14.1) (2023-06-07) + + +### Miscellaneous Chores + +* **brillig_vm:** Synchronize acvm versions + +## [0.14.0](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.13.3...brillig_vm-v0.14.0) (2023-06-06) + + +### Miscellaneous Chores + +* **brillig_vm:** Synchronize acvm versions + +## [0.13.3](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.13.2...brillig_vm-v0.13.3) (2023-06-05) + + +### Bug Fixes + +* Empty commit to trigger release-please ([e8f0748](https://github.com/noir-lang/acvm/commit/e8f0748042ef505d59ab63266d3c36c5358ee30d)) + +## [0.13.2](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.13.1...brillig_vm-v0.13.2) (2023-06-02) + + +### Miscellaneous Chores + +* **brillig_vm:** Synchronize acvm versions + +## [0.13.1](https://github.com/noir-lang/acvm/compare/brillig_vm-v0.1.1...brillig_vm-v0.13.1) (2023-06-01) + + +### Bug Fixes + +* **brillig:** Proper error handling for Brillig failures ([#329](https://github.com/noir-lang/acvm/issues/329)) ([cffa110](https://github.com/noir-lang/acvm/commit/cffa110c8df30ee3dd8b635d38b17b1fcd54b03e)) diff --git a/acvm-repo/brillig_vm/Cargo.toml b/acvm-repo/brillig_vm/Cargo.toml new file mode 100644 index 00000000000..b406e57a23b --- /dev/null +++ b/acvm-repo/brillig_vm/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "brillig_vm" +description = "The virtual machine that processes Brillig bytecode, used to introduce non-determinism to the ACVM" +# x-release-please-start-version +version = "0.27.4" +# x-release-please-end +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +acir.workspace = true +acvm_blackbox_solver.workspace = true +num-bigint.workspace = true +num-traits.workspace = true + +[features] +default = ["bn254"] +bn254 = ["acir/bn254"] +bls12_381 = ["acir/bls12_381"] diff --git a/acvm-repo/brillig_vm/src/arithmetic.rs b/acvm-repo/brillig_vm/src/arithmetic.rs new file mode 100644 index 00000000000..51ab8660452 --- /dev/null +++ b/acvm-repo/brillig_vm/src/arithmetic.rs @@ -0,0 +1,235 @@ +use acir::brillig::{BinaryFieldOp, BinaryIntOp}; +use acir::FieldElement; +use num_bigint::{BigInt, BigUint}; +use num_traits::{One, ToPrimitive, Zero}; + +/// Evaluate a binary operation on two FieldElements and return the result as a FieldElement. +pub(crate) fn evaluate_binary_field_op( + op: &BinaryFieldOp, + a: FieldElement, + b: FieldElement, +) -> FieldElement { + match op { + // Perform addition, subtraction, multiplication, and division based on the BinaryOp variant. + BinaryFieldOp::Add => a + b, + BinaryFieldOp::Sub => a - b, + BinaryFieldOp::Mul => a * b, + BinaryFieldOp::Div => a / b, + BinaryFieldOp::Equals => (a == b).into(), + } +} + +/// Evaluate a binary operation on two unsigned big integers with a given bit size and return the result as a big integer. +pub(crate) fn evaluate_binary_bigint_op( + op: &BinaryIntOp, + a: BigUint, + b: BigUint, + bit_size: u32, +) -> BigUint { + let bit_modulo = &(BigUint::one() << bit_size); + match op { + // Perform addition, subtraction, and multiplication, applying a modulo operation to keep the result within the bit size. + BinaryIntOp::Add => (a + b) % bit_modulo, + BinaryIntOp::Sub => (bit_modulo + a - b) % bit_modulo, + BinaryIntOp::Mul => (a * b) % bit_modulo, + // Perform unsigned division using the modulo operation on a and b. + BinaryIntOp::UnsignedDiv => (a % bit_modulo) / (b % bit_modulo), + // Perform signed division by first converting a and b to signed integers and then back to unsigned after the operation. + BinaryIntOp::SignedDiv => { + let signed_div = to_big_signed(a, bit_size) / to_big_signed(b, bit_size); + to_big_unsigned(signed_div, bit_size) + } + // Perform a == operation, returning 0 or 1 + BinaryIntOp::Equals => { + if (a % bit_modulo) == (b % bit_modulo) { + BigUint::one() + } else { + BigUint::zero() + } + } + // Perform a < operation, returning 0 or 1 + BinaryIntOp::LessThan => { + if (a % bit_modulo) < (b % bit_modulo) { + BigUint::one() + } else { + BigUint::zero() + } + } + // Perform a <= operation, returning 0 or 1 + BinaryIntOp::LessThanEquals => { + if (a % bit_modulo) <= (b % bit_modulo) { + BigUint::one() + } else { + BigUint::zero() + } + } + // Perform bitwise AND, OR, XOR, left shift, and right shift operations, applying a modulo operation to keep the result within the bit size. + BinaryIntOp::And => (a & b) % bit_modulo, + BinaryIntOp::Or => (a | b) % bit_modulo, + BinaryIntOp::Xor => (a ^ b) % bit_modulo, + BinaryIntOp::Shl => { + assert!(bit_size <= 128, "unsupported bit size for right shift"); + let b = b.to_u128().unwrap(); + (a << b) % bit_modulo + } + BinaryIntOp::Shr => { + assert!(bit_size <= 128, "unsupported bit size for right shift"); + let b = b.to_u128().unwrap(); + (a >> b) % bit_modulo + } + } +} + +fn to_big_signed(a: BigUint, bit_size: u32) -> BigInt { + let pow_2 = BigUint::from(2_u32).pow(bit_size - 1); + if a < pow_2 { + BigInt::from(a) + } else { + BigInt::from(a) - 2 * BigInt::from(pow_2) + } +} + +fn to_big_unsigned(a: BigInt, bit_size: u32) -> BigUint { + if a >= BigInt::zero() { + BigUint::from_bytes_le(&a.to_bytes_le().1) + } else { + BigUint::from(2_u32).pow(bit_size) - BigUint::from_bytes_le(&a.to_bytes_le().1) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + struct TestParams { + a: u128, + b: u128, + result: u128, + } + + fn evaluate_u128(op: &BinaryIntOp, a: u128, b: u128, bit_size: u32) -> u128 { + // Convert to big integers + let lhs_big = BigUint::from(a); + let rhs_big = BigUint::from(b); + let result_value = evaluate_binary_bigint_op(op, lhs_big, rhs_big, bit_size); + // Convert back to u128 + result_value.to_u128().unwrap() + } + + fn to_signed(a: u128, bit_size: u32) -> i128 { + assert!(bit_size < 128); + let pow_2 = 2_u128.pow(bit_size - 1); + if a < pow_2 { + a as i128 + } else { + (a.wrapping_sub(2 * pow_2)) as i128 + } + } + + fn to_unsigned(a: i128, bit_size: u32) -> u128 { + if a >= 0 { + a as u128 + } else { + (a + 2_i128.pow(bit_size)) as u128 + } + } + + fn to_negative(a: u128, bit_size: u32) -> u128 { + assert!(a > 0); + let two_pow = 2_u128.pow(bit_size); + two_pow - a + } + + fn evaluate_int_ops(test_params: Vec, op: BinaryIntOp, bit_size: u32) { + for test in test_params { + assert_eq!(evaluate_u128(&op, test.a, test.b, bit_size), test.result); + } + } + + #[test] + fn add_test() { + let bit_size = 4; + + let test_ops = vec![ + TestParams { a: 5, b: 10, result: 15 }, + TestParams { a: 10, b: 10, result: 4 }, + TestParams { a: 5, b: to_negative(3, bit_size), result: 2 }, + TestParams { a: to_negative(3, bit_size), b: 1, result: to_negative(2, bit_size) }, + TestParams { a: 5, b: to_negative(6, bit_size), result: to_negative(1, bit_size) }, + ]; + + evaluate_int_ops(test_ops, BinaryIntOp::Add, bit_size); + } + + #[test] + fn sub_test() { + let bit_size = 4; + + let test_ops = vec![ + TestParams { a: 5, b: 3, result: 2 }, + TestParams { a: 5, b: 10, result: to_negative(5, bit_size) }, + TestParams { a: 5, b: to_negative(3, bit_size), result: 8 }, + TestParams { a: to_negative(3, bit_size), b: 2, result: to_negative(5, bit_size) }, + TestParams { a: 14, b: to_negative(3, bit_size), result: 1 }, + ]; + + evaluate_int_ops(test_ops, BinaryIntOp::Sub, bit_size); + } + + #[test] + fn mul_test() { + let bit_size = 4; + + let test_ops = vec![ + TestParams { a: 5, b: 3, result: 15 }, + TestParams { a: 5, b: 10, result: 2 }, + TestParams { a: to_negative(1, bit_size), b: to_negative(5, bit_size), result: 5 }, + TestParams { a: to_negative(1, bit_size), b: 5, result: to_negative(5, bit_size) }, + TestParams { + a: to_negative(2, bit_size), + b: 7, + // negative 14 wraps to a 2 + result: to_negative(14, bit_size), + }, + ]; + + evaluate_int_ops(test_ops, BinaryIntOp::Mul, bit_size); + + let bit_size = 127; + let a = 2_u128.pow(bit_size) - 1; + let b = 3; + + // ( 2**(n-1) - 1 ) * 3 = 2*2**(n-1) - 2 + (2**(n-1) - 1) => wraps to (2**(n-1) - 1) - 2 + assert_eq!(evaluate_u128(&BinaryIntOp::Mul, a, b, bit_size), a - 2); + } + + #[test] + fn div_test() { + let bit_size = 4; + + let test_ops = + vec![TestParams { a: 5, b: 3, result: 1 }, TestParams { a: 5, b: 10, result: 0 }]; + + evaluate_int_ops(test_ops, BinaryIntOp::UnsignedDiv, bit_size); + } + + #[test] + fn to_signed_roundtrip() { + let bit_size = 32; + let minus_one = 2_u128.pow(bit_size) - 1; + assert_eq!(to_unsigned(to_signed(minus_one, bit_size), bit_size), minus_one); + } + + #[test] + fn signed_div_test() { + let bit_size = 32; + + let test_ops = vec![ + TestParams { a: 5, b: to_negative(10, bit_size), result: 0 }, + TestParams { a: 5, b: to_negative(1, bit_size), result: to_negative(5, bit_size) }, + TestParams { a: to_negative(5, bit_size), b: to_negative(1, bit_size), result: 5 }, + ]; + + evaluate_int_ops(test_ops, BinaryIntOp::SignedDiv, bit_size); + } +} diff --git a/acvm-repo/brillig_vm/src/black_box.rs b/acvm-repo/brillig_vm/src/black_box.rs new file mode 100644 index 00000000000..ada8a2f5993 --- /dev/null +++ b/acvm-repo/brillig_vm/src/black_box.rs @@ -0,0 +1,211 @@ +use acir::brillig::{BlackBoxOp, HeapArray, HeapVector, Value}; +use acir::{BlackBoxFunc, FieldElement}; +use acvm_blackbox_solver::{ + blake2s, ecdsa_secp256k1_verify, ecdsa_secp256r1_verify, hash_to_field_128_security, keccak256, + sha256, BlackBoxFunctionSolver, BlackBoxResolutionError, +}; + +use crate::{Memory, Registers}; + +fn read_heap_vector<'a>( + memory: &'a Memory, + registers: &Registers, + vector: &HeapVector, +) -> &'a [Value] { + memory + .read_slice(registers.get(vector.pointer).to_usize(), registers.get(vector.size).to_usize()) +} + +fn read_heap_array<'a>( + memory: &'a Memory, + registers: &Registers, + array: &HeapArray, +) -> &'a [Value] { + memory.read_slice(registers.get(array.pointer).to_usize(), array.size) +} + +/// Extracts the last byte of every value +fn to_u8_vec(inputs: &[Value]) -> Vec { + let mut result = Vec::with_capacity(inputs.len()); + for input in inputs { + let field_bytes = input.to_field().to_be_bytes(); + let byte = field_bytes.last().unwrap(); + result.push(*byte); + } + result +} + +fn to_value_vec(input: &[u8]) -> Vec { + input.iter().map(|x| Value::from(*x as usize)).collect() +} + +pub(crate) fn evaluate_black_box( + op: &BlackBoxOp, + solver: &Solver, + registers: &mut Registers, + memory: &mut Memory, +) -> Result<(), BlackBoxResolutionError> { + match op { + BlackBoxOp::Sha256 { message, output } => { + let message = to_u8_vec(read_heap_vector(memory, registers, message)); + let bytes = sha256(message.as_slice())?; + memory.write_slice(registers.get(output.pointer).to_usize(), &to_value_vec(&bytes)); + Ok(()) + } + BlackBoxOp::Blake2s { message, output } => { + let message = to_u8_vec(read_heap_vector(memory, registers, message)); + let bytes = blake2s(message.as_slice())?; + memory.write_slice(registers.get(output.pointer).to_usize(), &to_value_vec(&bytes)); + Ok(()) + } + BlackBoxOp::Keccak256 { message, output } => { + let message = to_u8_vec(read_heap_vector(memory, registers, message)); + let bytes = keccak256(message.as_slice())?; + memory.write_slice(registers.get(output.pointer).to_usize(), &to_value_vec(&bytes)); + Ok(()) + } + BlackBoxOp::HashToField128Security { message, output } => { + let field = hash_to_field_128_security(&to_u8_vec(read_heap_vector( + memory, registers, message, + )))?; + registers.set(*output, field.into()); + Ok(()) + } + BlackBoxOp::EcdsaSecp256k1 { + hashed_msg, + public_key_x, + public_key_y, + signature, + result: result_register, + } + | BlackBoxOp::EcdsaSecp256r1 { + hashed_msg, + public_key_x, + public_key_y, + signature, + result: result_register, + } => { + let bb_func = match op { + BlackBoxOp::EcdsaSecp256k1 { .. } => BlackBoxFunc::EcdsaSecp256k1, + BlackBoxOp::EcdsaSecp256r1 { .. } => BlackBoxFunc::EcdsaSecp256r1, + _ => unreachable!(), + }; + + let public_key_x: [u8; 32] = to_u8_vec(read_heap_array( + memory, + registers, + public_key_x, + )) + .try_into() + .map_err(|_| { + BlackBoxResolutionError::Failed(bb_func, "Invalid public key x length".to_string()) + })?; + let public_key_y: [u8; 32] = to_u8_vec(read_heap_array( + memory, + registers, + public_key_y, + )) + .try_into() + .map_err(|_| { + BlackBoxResolutionError::Failed(bb_func, "Invalid public key y length".to_string()) + })?; + let signature: [u8; 64] = to_u8_vec(read_heap_array(memory, registers, signature)) + .try_into() + .map_err(|_| { + BlackBoxResolutionError::Failed(bb_func, "Invalid signature length".to_string()) + })?; + + let hashed_msg = to_u8_vec(read_heap_vector(memory, registers, hashed_msg)); + + let result = match op { + BlackBoxOp::EcdsaSecp256k1 { .. } => { + ecdsa_secp256k1_verify(&hashed_msg, &public_key_x, &public_key_y, &signature)? + } + BlackBoxOp::EcdsaSecp256r1 { .. } => { + ecdsa_secp256r1_verify(&hashed_msg, &public_key_x, &public_key_y, &signature)? + } + _ => unreachable!(), + }; + + registers.set(*result_register, result.into()); + Ok(()) + } + BlackBoxOp::SchnorrVerify { public_key_x, public_key_y, message, signature, result } => { + let public_key_x = registers.get(*public_key_x).to_field(); + let public_key_y = registers.get(*public_key_y).to_field(); + let message: Vec = to_u8_vec(read_heap_vector(memory, registers, message)); + let signature: Vec = to_u8_vec(read_heap_vector(memory, registers, signature)); + let verified = + solver.schnorr_verify(&public_key_x, &public_key_y, &signature, &message)?; + registers.set(*result, verified.into()); + Ok(()) + } + BlackBoxOp::FixedBaseScalarMul { low, high, result } => { + let low = registers.get(*low).to_field(); + let high = registers.get(*high).to_field(); + let (x, y) = solver.fixed_base_scalar_mul(&low, &high)?; + memory.write_slice(registers.get(result.pointer).to_usize(), &[x.into(), y.into()]); + Ok(()) + } + BlackBoxOp::Pedersen { inputs, domain_separator, output } => { + let inputs: Vec = + read_heap_vector(memory, registers, inputs).iter().map(|x| x.to_field()).collect(); + let domain_separator: u32 = + registers.get(*domain_separator).to_u128().try_into().map_err(|_| { + BlackBoxResolutionError::Failed( + BlackBoxFunc::Pedersen, + "Invalid signature length".to_string(), + ) + })?; + let (x, y) = solver.pedersen(&inputs, domain_separator)?; + memory.write_slice(registers.get(output.pointer).to_usize(), &[x.into(), y.into()]); + Ok(()) + } + } +} + +#[cfg(test)] +mod test { + use acir::brillig::BlackBoxOp; + + use crate::{ + black_box::{evaluate_black_box, to_u8_vec, to_value_vec}, + DummyBlackBoxSolver, HeapArray, HeapVector, Memory, Registers, Value, + }; + + #[test] + fn sha256() { + let message: Vec = b"hello world".to_vec(); + let message_length = message.len(); + + let mut memory = Memory::from(vec![]); + let message_pointer = 0; + let result_pointer = message_pointer + message_length; + memory.write_slice(message_pointer, to_value_vec(&message).as_slice()); + + let mut registers = Registers { + inner: vec![ + Value::from(message_pointer), + Value::from(message_length), + Value::from(result_pointer), + ], + }; + + let op = BlackBoxOp::Sha256 { + message: HeapVector { pointer: 0.into(), size: 1.into() }, + output: HeapArray { pointer: 2.into(), size: 32 }, + }; + + evaluate_black_box(&op, &DummyBlackBoxSolver, &mut registers, &mut memory).unwrap(); + + let result = memory.read_slice(result_pointer, 32); + + assert_eq!( + to_u8_vec(result), + vec![ + 185, 77, 39, 185, 147, 77, 62, 8, 165, 46, 82, 215, 218, 125, 171, 250, 196, 132, + 239, 227, 122, 83, 128, 238, 144, 136, 247, 172, 226, 239, 205, 233 + ] + ); + } +} diff --git a/acvm-repo/brillig_vm/src/lib.rs b/acvm-repo/brillig_vm/src/lib.rs new file mode 100644 index 00000000000..082f0169ce5 --- /dev/null +++ b/acvm-repo/brillig_vm/src/lib.rs @@ -0,0 +1,1224 @@ +#![warn(unused_crate_dependencies)] +#![warn(unreachable_pub)] + +//! The Brillig VM is a specialized VM which allows the [ACVM][acvm] to perform custom non-determinism. +//! +//! Brillig bytecode is distinct from regular [ACIR][acir] in that it does not generate constraints. +//! This is a generalization over the fixed directives that exists within in the ACVM. +//! +//! [acir]: https://crates.io/crates/acir +//! [acvm]: https://crates.io/crates/acvm + +use acir::brillig::{ + BinaryFieldOp, BinaryIntOp, ForeignCallOutput, ForeignCallResult, HeapArray, HeapVector, + Opcode, RegisterIndex, RegisterOrMemory, Value, +}; +use acir::FieldElement; +// Re-export `brillig`. +pub use acir::brillig; + +mod arithmetic; +mod black_box; +mod memory; +mod registers; + +use acvm_blackbox_solver::{BlackBoxFunctionSolver, BlackBoxResolutionError}; +use arithmetic::{evaluate_binary_bigint_op, evaluate_binary_field_op}; +use black_box::evaluate_black_box; + +pub use memory::Memory; +use num_bigint::BigUint; +pub use registers::Registers; + +/// The error call stack contains the opcode indexes of the call stack at the time of failure, plus the index of the opcode that failed. +pub type ErrorCallStack = Vec; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum VMStatus { + Finished, + InProgress, + Failure { + message: String, + call_stack: ErrorCallStack, + }, + /// The VM process is not solvable as a [foreign call][Opcode::ForeignCall] has been + /// reached where the outputs are yet to be resolved. + /// + /// The caller should interpret the information returned to compute a [ForeignCallResult] + /// and update the Brillig process. The VM can then be restarted to fully solve the previously + /// unresolved foreign call as well as the remaining Brillig opcodes. + ForeignCallWait { + /// Interpreted by simulator context + function: String, + /// Input values + /// Each input is a list of values as an input can be either a single value or a memory pointer + inputs: Vec>, + }, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +/// VM encapsulates the state of the Brillig VM during execution. +pub struct VM<'bb_solver, B: BlackBoxFunctionSolver> { + /// Register storage + registers: Registers, + /// Instruction pointer + program_counter: usize, + /// A counter maintained throughout a Brillig process that determines + /// whether the caller has resolved the results of a [foreign call][Opcode::ForeignCall]. + foreign_call_counter: usize, + /// Represents the outputs of all foreign calls during a Brillig process + /// List is appended onto by the caller upon reaching a [VMStatus::ForeignCallWait] + foreign_call_results: Vec, + /// Executable opcodes + bytecode: Vec, + /// Status of the VM + status: VMStatus, + /// Memory of the VM + memory: Memory, + /// Call stack + call_stack: Vec, + /// The solver for blackbox functions + black_box_solver: &'bb_solver B, +} + +impl<'bb_solver, B: BlackBoxFunctionSolver> VM<'bb_solver, B> { + /// Constructs a new VM instance + pub fn new( + inputs: Registers, + memory: Vec, + bytecode: Vec, + foreign_call_results: Vec, + black_box_solver: &'bb_solver B, + ) -> Self { + Self { + registers: inputs, + program_counter: 0, + foreign_call_counter: 0, + foreign_call_results, + bytecode, + status: VMStatus::InProgress, + memory: memory.into(), + call_stack: Vec::new(), + black_box_solver, + } + } + + /// Updates the current status of the VM. + /// Returns the given status. + fn status(&mut self, status: VMStatus) -> VMStatus { + self.status = status.clone(); + status + } + + /// Sets the current status of the VM to Finished (completed execution). + fn finish(&mut self) -> VMStatus { + self.status(VMStatus::Finished) + } + + /// Sets the status of the VM to `ForeignCallWait`. + /// Indicating that the VM is now waiting for a foreign call to be resolved. + fn wait_for_foreign_call(&mut self, function: String, inputs: Vec>) -> VMStatus { + self.status(VMStatus::ForeignCallWait { function, inputs }) + } + + /// Sets the current status of the VM to `fail`. + /// Indicating that the VM encountered a `Trap` Opcode + /// or an invalid state. + fn fail(&mut self, message: String) -> VMStatus { + let mut error_stack: Vec<_> = + self.call_stack.iter().map(|value| value.to_usize()).collect(); + error_stack.push(self.program_counter); + self.status(VMStatus::Failure { call_stack: error_stack, message }); + self.status.clone() + } + + /// Loop over the bytecode and update the program counter + pub fn process_opcodes(&mut self) -> VMStatus { + while !matches!( + self.process_opcode(), + VMStatus::Finished | VMStatus::Failure { .. } | VMStatus::ForeignCallWait { .. } + ) {} + self.status.clone() + } + + /// Returns all of the registers in the VM. + pub fn get_registers(&self) -> &Registers { + &self.registers + } + + pub fn get_memory(&self) -> &Vec { + self.memory.values() + } + + /// Process a single opcode and modify the program counter. + pub fn process_opcode(&mut self) -> VMStatus { + let opcode = &self.bytecode[self.program_counter]; + match opcode { + Opcode::BinaryFieldOp { op, lhs, rhs, destination: result } => { + self.process_binary_field_op(*op, *lhs, *rhs, *result); + self.increment_program_counter() + } + Opcode::BinaryIntOp { op, bit_size, lhs, rhs, destination: result } => { + self.process_binary_int_op(*op, *bit_size, *lhs, *rhs, *result); + self.increment_program_counter() + } + Opcode::Jump { location: destination } => self.set_program_counter(*destination), + Opcode::JumpIf { condition, location: destination } => { + // Check if condition is true + // We use 0 to mean false and any other value to mean true + let condition_value = self.registers.get(*condition); + if !condition_value.is_zero() { + return self.set_program_counter(*destination); + } + self.increment_program_counter() + } + Opcode::JumpIfNot { condition, location: destination } => { + let condition_value = self.registers.get(*condition); + if condition_value.is_zero() { + return self.set_program_counter(*destination); + } + self.increment_program_counter() + } + Opcode::Return => { + if let Some(register) = self.call_stack.pop() { + self.set_program_counter(register.to_usize() + 1) + } else { + self.fail("return opcode hit, but callstack already empty".to_string()) + } + } + Opcode::ForeignCall { function, destinations, inputs } => { + if self.foreign_call_counter >= self.foreign_call_results.len() { + // When this opcode is called, it is possible that the results of a foreign call are + // not yet known (not enough entries in `foreign_call_results`). + // If that is the case, just resolve the inputs and pause the VM with a status + // (VMStatus::ForeignCallWait) that communicates the foreign function name and + // resolved inputs back to the caller. Once the caller pushes to `foreign_call_results`, + // they can then make another call to the VM that starts at this opcode + // but has the necessary results to proceed with execution. + let resolved_inputs = inputs + .iter() + .map(|input| self.get_register_value_or_memory_values(*input)) + .collect::>(); + return self.wait_for_foreign_call(function.clone(), resolved_inputs); + } + + let values = &self.foreign_call_results[self.foreign_call_counter].values; + + let mut invalid_foreign_call_result = false; + for (destination, output) in destinations.iter().zip(values) { + match destination { + RegisterOrMemory::RegisterIndex(value_index) => match output { + ForeignCallOutput::Single(value) => { + self.registers.set(*value_index, *value) + } + _ => unreachable!( + "Function result size does not match brillig bytecode (expected 1 result)" + ), + }, + RegisterOrMemory::HeapArray(HeapArray { pointer: pointer_index, size }) => { + match output { + ForeignCallOutput::Array(values) => { + if values.len() != *size { + invalid_foreign_call_result = true; + break; + } + // Convert the destination pointer to a usize + let destination = self.registers.get(*pointer_index).to_usize(); + // Write to our destination memory + self.memory.write_slice(destination, values); + } + _ => { + unreachable!("Function result size does not match brillig bytecode size") + } + } + } + RegisterOrMemory::HeapVector(HeapVector { pointer: pointer_index, size: size_index }) => { + match output { + ForeignCallOutput::Array(values) => { + // Set our size in the size register + self.registers.set(*size_index, Value::from(values.len())); + // Convert the destination pointer to a usize + let destination = self.registers.get(*pointer_index).to_usize(); + // Write to our destination memory + self.memory.write_slice(destination, values); + } + _ => { + unreachable!("Function result size does not match brillig bytecode size") + } + } + } + } + } + + // These checks must come after resolving the foreign call outputs as `fail` uses a mutable reference + if destinations.len() != values.len() { + self.fail(format!("{} output values were provided as a foreign call result for {} destination slots", values.len(), destinations.len())); + } + if invalid_foreign_call_result { + self.fail("Function result size does not match brillig bytecode".to_owned()); + } + + self.foreign_call_counter += 1; + self.increment_program_counter() + } + Opcode::Mov { destination: destination_register, source: source_register } => { + let source_value = self.registers.get(*source_register); + self.registers.set(*destination_register, source_value); + self.increment_program_counter() + } + Opcode::Trap => self.fail("explicit trap hit in brillig".to_string()), + Opcode::Stop => self.finish(), + Opcode::Load { destination: destination_register, source_pointer } => { + // Convert our source_pointer to a usize + let source = self.registers.get(*source_pointer); + // Use our usize source index to lookup the value in memory + let value = &self.memory.read(source.to_usize()); + self.registers.set(*destination_register, *value); + self.increment_program_counter() + } + Opcode::Store { destination_pointer, source: source_register } => { + // Convert our destination_pointer to a usize + let destination = self.registers.get(*destination_pointer).to_usize(); + // Use our usize destination index to set the value in memory + self.memory.write(destination, self.registers.get(*source_register)); + self.increment_program_counter() + } + Opcode::Call { location } => { + // Push a return location + self.call_stack.push(Value::from(self.program_counter)); + self.set_program_counter(*location) + } + Opcode::Const { destination, value } => { + self.registers.set(*destination, *value); + self.increment_program_counter() + } + Opcode::BlackBox(black_box_op) => { + match evaluate_black_box( + black_box_op, + self.black_box_solver, + &mut self.registers, + &mut self.memory, + ) { + Ok(()) => self.increment_program_counter(), + Err(e) => self.fail(e.to_string()), + } + } + } + } + + /// Returns the current value of the program counter. + pub fn program_counter(self) -> usize { + self.program_counter + } + + /// Increments the program counter by 1. + fn increment_program_counter(&mut self) -> VMStatus { + self.set_program_counter(self.program_counter + 1) + } + + /// Increments the program counter by `value`. + /// If the program counter no longer points to an opcode + /// in the bytecode, then the VMStatus reports halted. + fn set_program_counter(&mut self, value: usize) -> VMStatus { + assert!(self.program_counter < self.bytecode.len()); + self.program_counter = value; + if self.program_counter >= self.bytecode.len() { + self.status = VMStatus::Finished; + } + self.status.clone() + } + + fn get_register_value_or_memory_values(&self, input: RegisterOrMemory) -> Vec { + match input { + RegisterOrMemory::RegisterIndex(value_index) => { + vec![self.registers.get(value_index)] + } + RegisterOrMemory::HeapArray(HeapArray { pointer: pointer_index, size }) => { + let start = self.registers.get(pointer_index); + self.memory.read_slice(start.to_usize(), size).to_vec() + } + RegisterOrMemory::HeapVector(HeapVector { + pointer: pointer_index, + size: size_index, + }) => { + let start = self.registers.get(pointer_index); + let size = self.registers.get(size_index); + self.memory.read_slice(start.to_usize(), size.to_usize()).to_vec() + } + } + } + + /// Process a binary operation. + /// This method will not modify the program counter. + fn process_binary_field_op( + &mut self, + op: BinaryFieldOp, + lhs: RegisterIndex, + rhs: RegisterIndex, + result: RegisterIndex, + ) { + let lhs_value = self.registers.get(lhs); + let rhs_value = self.registers.get(rhs); + + let result_value = + evaluate_binary_field_op(&op, lhs_value.to_field(), rhs_value.to_field()); + + self.registers.set(result, result_value.into()) + } + + /// Process a binary operation. + /// This method will not modify the program counter. + fn process_binary_int_op( + &mut self, + op: BinaryIntOp, + bit_size: u32, + lhs: RegisterIndex, + rhs: RegisterIndex, + result: RegisterIndex, + ) { + let lhs_value = self.registers.get(lhs); + let rhs_value = self.registers.get(rhs); + + // Convert to big integers + let lhs_big = BigUint::from_bytes_be(&lhs_value.to_field().to_be_bytes()); + let rhs_big = BigUint::from_bytes_be(&rhs_value.to_field().to_be_bytes()); + let result_value = evaluate_binary_bigint_op(&op, lhs_big, rhs_big, bit_size); + // Convert back to field element + self.registers + .set(result, FieldElement::from_be_bytes_reduce(&result_value.to_bytes_be()).into()); + } +} + +pub(crate) struct DummyBlackBoxSolver; + +impl BlackBoxFunctionSolver for DummyBlackBoxSolver { + fn schnorr_verify( + &self, + _public_key_x: &FieldElement, + _public_key_y: &FieldElement, + _signature: &[u8], + _message: &[u8], + ) -> Result { + Ok(true) + } + fn pedersen( + &self, + _inputs: &[FieldElement], + _domain_separator: u32, + ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { + Ok((2_u128.into(), 3_u128.into())) + } + fn fixed_base_scalar_mul( + &self, + _low: &FieldElement, + _high: &FieldElement, + ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { + Ok((4_u128.into(), 5_u128.into())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn add_single_step_smoke() { + // Load values into registers and initialize the registers that + // will be used during bytecode processing + let input_registers = + Registers::load(vec![Value::from(1u128), Value::from(2u128), Value::from(0u128)]); + + // Add opcode to add the value in register `0` and `1` + // and place the output in register `2` + let opcode = Opcode::BinaryIntOp { + op: BinaryIntOp::Add, + bit_size: 2, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(2), + }; + + // Start VM + let mut vm = VM::new(input_registers, vec![], vec![opcode], vec![], &DummyBlackBoxSolver); + + // Process a single VM opcode + // + // After processing a single opcode, we should have + // the vm status as finished since there is only one opcode + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::Finished); + + // The register at index `2` should have the value of 3 since we had an + // add opcode + let VM { registers, .. } = vm; + let output_value = registers.get(RegisterIndex::from(2)); + + assert_eq!(output_value, Value::from(3u128)) + } + + #[test] + fn jmpif_opcode() { + let mut registers = vec![]; + let mut opcodes = vec![]; + + let lhs = { + registers.push(Value::from(2u128)); + RegisterIndex::from(registers.len() - 1) + }; + + let rhs = { + registers.push(Value::from(2u128)); + RegisterIndex::from(registers.len() - 1) + }; + + let destination = { + registers.push(Value::from(0u128)); + RegisterIndex::from(registers.len() - 1) + }; + + let equal_cmp_opcode = + Opcode::BinaryIntOp { op: BinaryIntOp::Equals, bit_size: 1, lhs, rhs, destination }; + opcodes.push(equal_cmp_opcode); + opcodes.push(Opcode::Jump { location: 2 }); + opcodes.push(Opcode::JumpIf { condition: RegisterIndex::from(2), location: 3 }); + + let mut vm = + VM::new(Registers::load(registers), vec![], opcodes, vec![], &DummyBlackBoxSolver); + + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + + let output_cmp_value = vm.registers.get(RegisterIndex::from(2)); + assert_eq!(output_cmp_value, Value::from(true)); + + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::Finished); + } + + #[test] + fn jmpifnot_opcode() { + let input_registers = + Registers::load(vec![Value::from(1u128), Value::from(2u128), Value::from(0u128)]); + + let trap_opcode = Opcode::Trap; + + let not_equal_cmp_opcode = Opcode::BinaryFieldOp { + op: BinaryFieldOp::Equals, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(2), + }; + + let jump_opcode = Opcode::Jump { location: 2 }; + + let jump_if_not_opcode = + Opcode::JumpIfNot { condition: RegisterIndex::from(2), location: 1 }; + + let add_opcode = Opcode::BinaryFieldOp { + op: BinaryFieldOp::Add, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(2), + }; + + let mut vm = VM::new( + input_registers, + vec![], + vec![jump_opcode, trap_opcode, not_equal_cmp_opcode, jump_if_not_opcode, add_opcode], + vec![], + &DummyBlackBoxSolver, + ); + + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + + let output_cmp_value = vm.registers.get(RegisterIndex::from(2)); + assert_eq!(output_cmp_value, Value::from(false)); + + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + + let status = vm.process_opcode(); + assert_eq!( + status, + VMStatus::Failure { + message: "explicit trap hit in brillig".to_string(), + call_stack: vec![1] + } + ); + + // The register at index `2` should have not changed as we jumped over the add opcode + let VM { registers, .. } = vm; + let output_value = registers.get(RegisterIndex::from(2)); + assert_eq!(output_value, Value::from(false)); + } + + #[test] + fn mov_opcode() { + let input_registers = + Registers::load(vec![Value::from(1u128), Value::from(2u128), Value::from(3u128)]); + + let mov_opcode = + Opcode::Mov { destination: RegisterIndex::from(2), source: RegisterIndex::from(0) }; + + let mut vm = + VM::new(input_registers, vec![], vec![mov_opcode], vec![], &DummyBlackBoxSolver); + + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::Finished); + + let VM { registers, .. } = vm; + + let destination_value = registers.get(RegisterIndex::from(2)); + assert_eq!(destination_value, Value::from(1u128)); + + let source_value = registers.get(RegisterIndex::from(0)); + assert_eq!(source_value, Value::from(1u128)); + } + + #[test] + fn cmp_binary_ops() { + let bit_size = 32; + let input_registers = Registers::load(vec![ + Value::from(2u128), + Value::from(2u128), + Value::from(0u128), + Value::from(5u128), + Value::from(6u128), + ]); + + let equal_opcode = Opcode::BinaryIntOp { + bit_size, + op: BinaryIntOp::Equals, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(2), + }; + + let not_equal_opcode = Opcode::BinaryIntOp { + bit_size, + op: BinaryIntOp::Equals, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(3), + destination: RegisterIndex::from(2), + }; + + let less_than_opcode = Opcode::BinaryIntOp { + bit_size, + op: BinaryIntOp::LessThan, + lhs: RegisterIndex::from(3), + rhs: RegisterIndex::from(4), + destination: RegisterIndex::from(2), + }; + + let less_than_equal_opcode = Opcode::BinaryIntOp { + bit_size, + op: BinaryIntOp::LessThanEquals, + lhs: RegisterIndex::from(3), + rhs: RegisterIndex::from(4), + destination: RegisterIndex::from(2), + }; + + let mut vm = VM::new( + input_registers, + vec![], + vec![equal_opcode, not_equal_opcode, less_than_opcode, less_than_equal_opcode], + vec![], + &DummyBlackBoxSolver, + ); + + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + + let output_eq_value = vm.registers.get(RegisterIndex::from(2)); + assert_eq!(output_eq_value, Value::from(true)); + + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + + let output_neq_value = vm.registers.get(RegisterIndex::from(2)); + assert_eq!(output_neq_value, Value::from(false)); + + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::InProgress); + + let lt_value = vm.registers.get(RegisterIndex::from(2)); + assert_eq!(lt_value, Value::from(true)); + + let status = vm.process_opcode(); + assert_eq!(status, VMStatus::Finished); + + let lte_value = vm.registers.get(RegisterIndex::from(2)); + assert_eq!(lte_value, Value::from(true)); + } + #[test] + fn store_opcode() { + /// Brillig code for the following: + /// let mut i = 0; + /// let len = memory.len(); + /// while i < len { + /// memory[i] = i as Value; + /// i += 1; + /// } + fn brillig_write_memory(memory: Vec) -> Vec { + let bit_size = 32; + let r_i = RegisterIndex::from(0); + let r_len = RegisterIndex::from(1); + let r_tmp = RegisterIndex::from(2); + let start = [ + // i = 0 + Opcode::Const { destination: r_i, value: 0u128.into() }, + // len = memory.len() (approximation) + Opcode::Const { destination: r_len, value: Value::from(memory.len() as u128) }, + ]; + let loop_body = [ + // *i = i + Opcode::Store { destination_pointer: r_i, source: r_i }, + // tmp = 1 + Opcode::Const { destination: r_tmp, value: 1u128.into() }, + // i = i + 1 (tmp) + Opcode::BinaryIntOp { + destination: r_i, + lhs: r_i, + op: BinaryIntOp::Add, + rhs: r_tmp, + bit_size, + }, + // tmp = i < len + Opcode::BinaryIntOp { + destination: r_tmp, + lhs: r_i, + op: BinaryIntOp::LessThan, + rhs: r_len, + bit_size, + }, + // if tmp != 0 goto loop_body + Opcode::JumpIf { condition: r_tmp, location: start.len() }, + ]; + let vm = brillig_execute_and_get_vm(memory, [&start[..], &loop_body[..]].concat()); + vm.get_memory().clone() + } + + let memory = brillig_write_memory(vec![Value::from(0u128); 5]); + let expected = vec![ + Value::from(0u128), + Value::from(1u128), + Value::from(2u128), + Value::from(3u128), + Value::from(4u128), + ]; + assert_eq!(memory, expected); + + let memory = brillig_write_memory(vec![Value::from(0u128); 1024]); + let expected: Vec = (0..1024).map(|i| Value::from(i as u128)).collect(); + assert_eq!(memory, expected); + } + + #[test] + fn load_opcode() { + /// Brillig code for the following: + /// let mut sum = 0; + /// let mut i = 0; + /// let len = memory.len(); + /// while i < len { + /// sum += memory[i]; + /// i += 1; + /// } + fn brillig_sum_memory(memory: Vec) -> Value { + let bit_size = 32; + let r_i = RegisterIndex::from(0); + let r_len = RegisterIndex::from(1); + let r_sum = RegisterIndex::from(2); + let r_tmp = RegisterIndex::from(3); + let start = [ + // sum = 0 + Opcode::Const { destination: r_sum, value: 0u128.into() }, + // i = 0 + Opcode::Const { destination: r_i, value: 0u128.into() }, + // len = array.len() (approximation) + Opcode::Const { destination: r_len, value: Value::from(memory.len() as u128) }, + ]; + let loop_body = [ + // tmp = *i + Opcode::Load { destination: r_tmp, source_pointer: r_i }, + // sum = sum + tmp + Opcode::BinaryIntOp { + destination: r_sum, + lhs: r_sum, + op: BinaryIntOp::Add, + rhs: r_tmp, + bit_size, + }, + // tmp = 1 + Opcode::Const { destination: r_tmp, value: 1u128.into() }, + // i = i + 1 (tmp) + Opcode::BinaryIntOp { + destination: r_i, + lhs: r_i, + op: BinaryIntOp::Add, + rhs: r_tmp, + bit_size, + }, + // tmp = i < len + Opcode::BinaryIntOp { + destination: r_tmp, + lhs: r_i, + op: BinaryIntOp::LessThan, + rhs: r_len, + bit_size, + }, + // if tmp != 0 goto loop_body + Opcode::JumpIf { condition: r_tmp, location: start.len() }, + ]; + let vm = brillig_execute_and_get_vm(memory, [&start[..], &loop_body[..]].concat()); + vm.registers.get(r_sum) + } + + assert_eq!( + brillig_sum_memory(vec![ + Value::from(1u128), + Value::from(2u128), + Value::from(3u128), + Value::from(4u128), + Value::from(5u128), + ]), + Value::from(15u128) + ); + assert_eq!(brillig_sum_memory(vec![Value::from(1u128); 1024]), Value::from(1024u128)); + } + + #[test] + fn call_and_return_opcodes() { + /// Brillig code for the following recursive function: + /// fn recursive_write(i: u128, len: u128) { + /// if len <= i { + /// return; + /// } + /// memory[i as usize] = i as Value; + /// recursive_write(memory, i + 1, len); + /// } + /// Note we represent a 100% in-register optimized form in brillig + fn brillig_recursive_write_memory(memory: Vec) -> Vec { + let bit_size = 32; + let r_i = RegisterIndex::from(0); + let r_len = RegisterIndex::from(1); + let r_tmp = RegisterIndex::from(2); + + let start = [ + // i = 0 + Opcode::Const { destination: r_i, value: 0u128.into() }, + // len = memory.len() (approximation) + Opcode::Const { destination: r_len, value: Value::from(memory.len() as u128) }, + // call recursive_fn + Opcode::Call { + location: 4, // Call after 'start' + }, + // end program by jumping to end + Opcode::Jump { location: 100 }, + ]; + + let recursive_fn = [ + // tmp = len <= i + Opcode::BinaryIntOp { + destination: r_tmp, + lhs: r_len, + op: BinaryIntOp::LessThanEquals, + rhs: r_i, + bit_size, + }, + // if !tmp, goto end + Opcode::JumpIf { + condition: r_tmp, + location: start.len() + 6, // 7 ops in recursive_fn, go to 'Return' + }, + // *i = i + Opcode::Store { destination_pointer: r_i, source: r_i }, + // tmp = 1 + Opcode::Const { destination: r_tmp, value: 1u128.into() }, + // i = i + 1 (tmp) + Opcode::BinaryIntOp { + destination: r_i, + lhs: r_i, + op: BinaryIntOp::Add, + rhs: r_tmp, + bit_size, + }, + // call recursive_fn + Opcode::Call { location: start.len() }, + Opcode::Return {}, + ]; + + let vm = brillig_execute_and_get_vm(memory, [&start[..], &recursive_fn[..]].concat()); + vm.get_memory().clone() + } + + let memory = brillig_recursive_write_memory(vec![Value::from(0u128); 5]); + let expected = vec![ + Value::from(0u128), + Value::from(1u128), + Value::from(2u128), + Value::from(3u128), + Value::from(4u128), + ]; + assert_eq!(memory, expected); + + let memory = brillig_recursive_write_memory(vec![Value::from(0u128); 1024]); + let expected: Vec = (0..1024).map(|i| Value::from(i as u128)).collect(); + assert_eq!(memory, expected); + } + + fn empty_registers() -> Registers { + Registers::load(vec![Value::from(0u128); 16]) + } + /// Helper to execute brillig code + fn brillig_execute_and_get_vm( + memory: Vec, + opcodes: Vec, + ) -> VM<'static, DummyBlackBoxSolver> { + let mut vm = VM::new(empty_registers(), memory, opcodes, vec![], &DummyBlackBoxSolver); + brillig_execute(&mut vm); + assert_eq!(vm.call_stack, vec![]); + vm + } + + fn brillig_execute(vm: &mut VM) { + loop { + let status = vm.process_opcode(); + if matches!(status, VMStatus::Finished | VMStatus::ForeignCallWait { .. }) { + break; + } + assert_eq!(status, VMStatus::InProgress) + } + } + + #[test] + fn foreign_call_opcode_register_result() { + let r_input = RegisterIndex::from(0); + let r_result = RegisterIndex::from(1); + + let double_program = vec![ + // Load input register with value 5 + Opcode::Const { destination: r_input, value: Value::from(5u128) }, + // Call foreign function "double" with the input register + Opcode::ForeignCall { + function: "double".into(), + destinations: vec![RegisterOrMemory::RegisterIndex(r_result)], + inputs: vec![RegisterOrMemory::RegisterIndex(r_input)], + }, + ]; + + let mut vm = brillig_execute_and_get_vm(vec![], double_program); + + // Check that VM is waiting + assert_eq!( + vm.status, + VMStatus::ForeignCallWait { + function: "double".into(), + inputs: vec![vec![Value::from(5u128)]] + } + ); + + // Push result we're waiting for + vm.foreign_call_results.push( + Value::from(10u128).into(), // Result of doubling 5u128 + ); + + // Resume VM + brillig_execute(&mut vm); + + // Check that VM finished once resumed + assert_eq!(vm.status, VMStatus::Finished); + + // Check result register + let result_value = vm.registers.get(r_result); + assert_eq!(result_value, Value::from(10u128)); + + // Ensure the foreign call counter has been incremented + assert_eq!(vm.foreign_call_counter, 1); + } + #[test] + fn foreign_call_opcode_memory_result() { + let r_input = RegisterIndex::from(0); + let r_output = RegisterIndex::from(1); + + // Define a simple 2x2 matrix in memory + let initial_matrix = + vec![Value::from(1u128), Value::from(2u128), Value::from(3u128), Value::from(4u128)]; + + // Transpose of the matrix (but arbitrary for this test, the 'correct value') + let expected_result = + vec![Value::from(1u128), Value::from(3u128), Value::from(2u128), Value::from(4u128)]; + + let invert_program = vec![ + // input = 0 + Opcode::Const { destination: r_input, value: Value::from(0u128) }, + // output = 0 + Opcode::Const { destination: r_output, value: Value::from(0u128) }, + // *output = matrix_2x2_transpose(*input) + Opcode::ForeignCall { + function: "matrix_2x2_transpose".into(), + destinations: vec![RegisterOrMemory::HeapArray(HeapArray { + pointer: r_output, + size: initial_matrix.len(), + })], + inputs: vec![RegisterOrMemory::HeapArray(HeapArray { + pointer: r_input, + size: initial_matrix.len(), + })], + }, + ]; + + let mut vm = brillig_execute_and_get_vm(initial_matrix.clone(), invert_program); + + // Check that VM is waiting + assert_eq!( + vm.status, + VMStatus::ForeignCallWait { + function: "matrix_2x2_transpose".into(), + inputs: vec![initial_matrix] + } + ); + + // Push result we're waiting for + vm.foreign_call_results.push(expected_result.clone().into()); + + // Resume VM + brillig_execute(&mut vm); + + // Check that VM finished once resumed + assert_eq!(vm.status, VMStatus::Finished); + + // Check result in memory + let result_values = vm.memory.read_slice(0, 4).to_vec(); + assert_eq!(result_values, expected_result); + + // Ensure the foreign call counter has been incremented + assert_eq!(vm.foreign_call_counter, 1); + } + + /// Calling a simple foreign call function that takes any string input, concatenates it with itself, and reverses the concatenation + #[test] + fn foreign_call_opcode_vector_input_and_output() { + let r_input_pointer = RegisterIndex::from(0); + let r_input_size = RegisterIndex::from(1); + // We need to pass a location of appropriate size + let r_output_pointer = RegisterIndex::from(2); + let r_output_size = RegisterIndex::from(3); + + // Our first string to use the identity function with + let input_string = + vec![Value::from(1u128), Value::from(2u128), Value::from(3u128), Value::from(4u128)]; + // Double the string (concatenate it with itself) + let mut output_string: Vec = + input_string.iter().cloned().chain(input_string.clone()).collect(); + // Reverse the concatenated string + output_string.reverse(); + + // First call: + let string_double_program = vec![ + // input_pointer = 0 + Opcode::Const { destination: r_input_pointer, value: Value::from(0u128) }, + // input_size = input_string.len() (constant here) + Opcode::Const { destination: r_input_size, value: Value::from(input_string.len()) }, + // output_pointer = 0 + input_size = input_size + Opcode::Const { destination: r_output_pointer, value: Value::from(input_string.len()) }, + // output_size = input_size * 2 + Opcode::Const { + destination: r_output_size, + value: Value::from(input_string.len() * 2), + }, + // output_pointer[0..output_size] = string_double(input_pointer[0...input_size]) + Opcode::ForeignCall { + function: "string_double".into(), + destinations: vec![RegisterOrMemory::HeapVector(HeapVector { + pointer: r_output_pointer, + size: r_output_size, + })], + inputs: vec![RegisterOrMemory::HeapVector(HeapVector { + pointer: r_input_pointer, + size: r_input_size, + })], + }, + ]; + + let mut vm = brillig_execute_and_get_vm(input_string.clone(), string_double_program); + + // Check that VM is waiting + assert_eq!( + vm.status, + VMStatus::ForeignCallWait { + function: "string_double".into(), + inputs: vec![input_string.clone()] + } + ); + + // Push result we're waiting for + vm.foreign_call_results.push(ForeignCallResult { + values: vec![ForeignCallOutput::Array(output_string.clone())], + }); + + // Resume VM + brillig_execute(&mut vm); + + // Check that VM finished once resumed + assert_eq!(vm.status, VMStatus::Finished); + + // Check result in memory + let result_values = vm.memory.read_slice(input_string.len(), output_string.len()).to_vec(); + assert_eq!(result_values, output_string); + + // Ensure the foreign call counter has been incremented + assert_eq!(vm.foreign_call_counter, 1); + } + + #[test] + fn foreign_call_opcode_memory_alloc_result() { + let r_input = RegisterIndex::from(0); + let r_output = RegisterIndex::from(1); + + // Define a simple 2x2 matrix in memory + let initial_matrix = + vec![Value::from(1u128), Value::from(2u128), Value::from(3u128), Value::from(4u128)]; + + // Transpose of the matrix (but arbitrary for this test, the 'correct value') + let expected_result = + vec![Value::from(1u128), Value::from(3u128), Value::from(2u128), Value::from(4u128)]; + + let invert_program = vec![ + // input = 0 + Opcode::Const { destination: r_input, value: Value::from(0u128) }, + // output = 0 + Opcode::Const { destination: r_output, value: Value::from(4u128) }, + // *output = matrix_2x2_transpose(*input) + Opcode::ForeignCall { + function: "matrix_2x2_transpose".into(), + destinations: vec![RegisterOrMemory::HeapArray(HeapArray { + pointer: r_output, + size: initial_matrix.len(), + })], + inputs: vec![RegisterOrMemory::HeapArray(HeapArray { + pointer: r_input, + size: initial_matrix.len(), + })], + }, + ]; + + let mut vm = brillig_execute_and_get_vm(initial_matrix.clone(), invert_program); + + // Check that VM is waiting + assert_eq!( + vm.status, + VMStatus::ForeignCallWait { + function: "matrix_2x2_transpose".into(), + inputs: vec![initial_matrix.clone()] + } + ); + + // Push result we're waiting for + vm.foreign_call_results.push(expected_result.clone().into()); + + // Resume VM + brillig_execute(&mut vm); + + // Check that VM finished once resumed + assert_eq!(vm.status, VMStatus::Finished); + + // Check initial memory still in place + let initial_values = vm.memory.read_slice(0, 4).to_vec(); + assert_eq!(initial_values, initial_matrix); + + // Check result in memory + let result_values = vm.memory.read_slice(4, 4).to_vec(); + assert_eq!(result_values, expected_result); + + // Ensure the foreign call counter has been incremented + assert_eq!(vm.foreign_call_counter, 1); + } + + #[test] + fn foreign_call_opcode_multiple_array_inputs_result() { + let r_input_a = RegisterIndex::from(0); + let r_input_b = RegisterIndex::from(1); + let r_output = RegisterIndex::from(2); + + // Define a simple 2x2 matrix in memory + let matrix_a = + vec![Value::from(1u128), Value::from(2u128), Value::from(3u128), Value::from(4u128)]; + + let matrix_b = vec![ + Value::from(10u128), + Value::from(11u128), + Value::from(12u128), + Value::from(13u128), + ]; + + // Transpose of the matrix (but arbitrary for this test, the 'correct value') + let expected_result = vec![ + Value::from(34u128), + Value::from(37u128), + Value::from(78u128), + Value::from(85u128), + ]; + + let matrix_mul_program = vec![ + // input = 0 + Opcode::Const { destination: r_input_a, value: Value::from(0u128) }, + // input = 0 + Opcode::Const { destination: r_input_b, value: Value::from(4u128) }, + // output = 0 + Opcode::Const { destination: r_output, value: Value::from(0u128) }, + // *output = matrix_2x2_transpose(*input) + Opcode::ForeignCall { + function: "matrix_2x2_transpose".into(), + destinations: vec![RegisterOrMemory::HeapArray(HeapArray { + pointer: r_output, + size: matrix_a.len(), + })], + inputs: vec![ + RegisterOrMemory::HeapArray(HeapArray { + pointer: r_input_a, + size: matrix_a.len(), + }), + RegisterOrMemory::HeapArray(HeapArray { + pointer: r_input_b, + size: matrix_b.len(), + }), + ], + }, + ]; + let mut initial_memory = matrix_a.clone(); + initial_memory.extend(matrix_b.clone()); + let mut vm = brillig_execute_and_get_vm(initial_memory, matrix_mul_program); + + // Check that VM is waiting + assert_eq!( + vm.status, + VMStatus::ForeignCallWait { + function: "matrix_2x2_transpose".into(), + inputs: vec![matrix_a, matrix_b] + } + ); + + // Push result we're waiting for + vm.foreign_call_results.push(expected_result.clone().into()); + + // Resume VM + brillig_execute(&mut vm); + + // Check that VM finished once resumed + assert_eq!(vm.status, VMStatus::Finished); + + // Check result in memory + let result_values = vm.memory.read_slice(0, 4).to_vec(); + assert_eq!(result_values, expected_result); + + // Ensure the foreign call counter has been incremented + assert_eq!(vm.foreign_call_counter, 1); + } +} diff --git a/acvm-repo/brillig_vm/src/memory.rs b/acvm-repo/brillig_vm/src/memory.rs new file mode 100644 index 00000000000..e2309537283 --- /dev/null +++ b/acvm-repo/brillig_vm/src/memory.rs @@ -0,0 +1,45 @@ +use crate::Value; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Memory { + // Memory is a vector of values. + // We grow the memory when values past the end are set, extending with 0s. + inner: Vec, +} + +impl From> for Memory { + fn from(values: Vec) -> Self { + Memory { inner: values } + } +} + +impl Memory { + /// Gets the value at pointer + pub fn read(&self, ptr: usize) -> Value { + self.inner[ptr] + } + + pub fn read_slice(&self, ptr: usize, len: usize) -> &[Value] { + &self.inner[ptr..ptr + len] + } + + /// Sets the value at pointer `ptr` to `value` + pub fn write(&mut self, ptr: usize, value: Value) { + self.write_slice(ptr, &[value]); + } + + /// Sets the values after pointer `ptr` to `values` + pub fn write_slice(&mut self, ptr: usize, values: &[Value]) { + // Calculate new memory size + let new_size = std::cmp::max(self.inner.len(), ptr + values.len()); + // Expand memory to new size with default values if needed + self.inner.resize(new_size, Value::from(0_usize)); + + self.inner[ptr..ptr + values.len()].copy_from_slice(values); + } + + /// Returns the values of the memory + pub fn values(&self) -> &Vec { + &self.inner + } +} diff --git a/acvm-repo/brillig_vm/src/registers.rs b/acvm-repo/brillig_vm/src/registers.rs new file mode 100644 index 00000000000..fcc596dd6c9 --- /dev/null +++ b/acvm-repo/brillig_vm/src/registers.rs @@ -0,0 +1,43 @@ +use acir::brillig::{RegisterIndex, Value}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Registers { + // Registers are a vector of values. + // We grow the register as registers past the end are set, extending with 0s. + pub inner: Vec, +} + +/// Aims to match a reasonable max register count for a SNARK prover. +/// As well, catches obvious erroneous use of registers. +/// This can be revisited if it proves not enough. +const MAX_REGISTERS: usize = 2_usize.pow(16); + +/// Registers will store field element values during the +/// duration of the execution of the bytecode. +impl Registers { + /// Create a Registers object initialized with definite values + pub fn load(values: Vec) -> Registers { + let inner = values.into_iter().collect(); + Self { inner } + } + + /// Gets the values at register with address `index` + pub fn get(&self, register_index: RegisterIndex) -> Value { + let index = register_index.to_usize(); + assert!(index < MAX_REGISTERS, "Reading register past maximum!"); + let value = self.inner.get(index); + match value { + Some(value) => *value, + None => 0u128.into(), + } + } + + /// Sets the value at register with address `index` to `value` + pub fn set(&mut self, RegisterIndex(index): RegisterIndex, value: Value) { + assert!(index < MAX_REGISTERS, "Writing register past maximum!"); + // if size isn't at least index + 1, resize + let new_register_size = std::cmp::max(index + 1, self.inner.len()); + self.inner.resize(new_register_size, 0u128.into()); + self.inner[index] = value; + } +} diff --git a/acvm-repo/stdlib/CHANGELOG.md b/acvm-repo/stdlib/CHANGELOG.md new file mode 100644 index 00000000000..bea80c95d1e --- /dev/null +++ b/acvm-repo/stdlib/CHANGELOG.md @@ -0,0 +1,350 @@ +# Changelog + +## [0.27.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.26.1...acvm_stdlib-v0.27.0) (2023-09-19) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.26.1](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.26.0...acvm_stdlib-v0.26.1) (2023-09-12) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.26.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.25.0...acvm_stdlib-v0.26.0) (2023-09-07) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.25.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.24.1...acvm_stdlib-v0.25.0) (2023-09-04) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.24.1](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.24.0...acvm_stdlib-v0.24.1) (2023-09-03) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.24.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.23.0...acvm_stdlib-v0.24.0) (2023-08-31) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.23.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.22.0...acvm_stdlib-v0.23.0) (2023-08-30) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.22.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.21.0...acvm_stdlib-v0.22.0) (2023-08-18) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.21.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.20.1...acvm_stdlib-v0.21.0) (2023-07-26) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.20.1](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.20.0...acvm_stdlib-v0.20.1) (2023-07-26) + + +### Features + +* add optimisations to fallback black box functions on booleans ([#446](https://github.com/noir-lang/acvm/issues/446)) ([2cfb2a8](https://github.com/noir-lang/acvm/commit/2cfb2a8cf911a81eedbd9da13ab2c616abd67f83)) +* **stdlib:** Add fallback implementation of `Keccak256` black box function ([#445](https://github.com/noir-lang/acvm/issues/445)) ([f7ebb03](https://github.com/noir-lang/acvm/commit/f7ebb03653c971f119700ff8126d9eb5ff01be0f)) + +## [0.20.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.19.1...acvm_stdlib-v0.20.0) (2023-07-20) + + +### Features + +* **stdlib:** Add fallback implementation of `HashToField128Security` black box function ([#435](https://github.com/noir-lang/acvm/issues/435)) ([ed40f22](https://github.com/noir-lang/acvm/commit/ed40f228529e888d1960bfa70cb92b277e24b37f)) + +## [0.19.1](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.19.0...acvm_stdlib-v0.19.1) (2023-07-17) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.19.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.18.2...acvm_stdlib-v0.19.0) (2023-07-15) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.18.2](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.18.1...acvm_stdlib-v0.18.2) (2023-07-12) + + +### Features + +* **stdlib:** Add fallback implementation of `Blake2s` black box function ([#424](https://github.com/noir-lang/acvm/issues/424)) ([982d940](https://github.com/noir-lang/acvm/commit/982d94087d46092ce7a5e94dbd7e732195f58e42)) + +## [0.18.1](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.18.0...acvm_stdlib-v0.18.1) (2023-07-12) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.18.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.17.0...acvm_stdlib-v0.18.0) (2023-07-12) + + +### Features + +* **stdlib:** Add fallback implementation of `SHA256` black box function ([#407](https://github.com/noir-lang/acvm/issues/407)) ([040369a](https://github.com/noir-lang/acvm/commit/040369adc8749fa5ec2edd255ff54c105c3140f5)) + +## [0.17.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.16.0...acvm_stdlib-v0.17.0) (2023-07-07) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.16.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.15.1...acvm_stdlib-v0.16.0) (2023-07-06) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.15.1](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.15.0...acvm_stdlib-v0.15.1) (2023-06-20) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.15.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.14.2...acvm_stdlib-v0.15.0) (2023-06-15) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.14.2](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.14.1...acvm_stdlib-v0.14.2) (2023-06-08) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.14.1](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.14.0...acvm_stdlib-v0.14.1) (2023-06-07) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.14.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.13.3...acvm_stdlib-v0.14.0) (2023-06-06) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.13.3](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.13.2...acvm_stdlib-v0.13.3) (2023-06-05) + + +### Bug Fixes + +* Empty commit to trigger release-please ([e8f0748](https://github.com/noir-lang/acvm/commit/e8f0748042ef505d59ab63266d3c36c5358ee30d)) + +## [0.13.2](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.13.1...acvm_stdlib-v0.13.2) (2023-06-02) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.13.1](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.13.0...acvm_stdlib-v0.13.1) (2023-06-01) + + +### Bug Fixes + +* **ci:** Correct typo to avoid `undefined` in changelogs ([#333](https://github.com/noir-lang/acvm/issues/333)) ([d3424c0](https://github.com/noir-lang/acvm/commit/d3424c04fd303c9cbe25d03118d8b358cbb84b83)) + +## [0.13.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.12.0...acvm_stdlib-v0.13.0) (2023-06-01) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.12.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.11.0...acvm_stdlib-v0.12.0) (2023-05-17) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.11.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.10.3...acvm_stdlib-v0.11.0) (2023-05-04) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.10.3](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.10.2...acvm_stdlib-v0.10.3) (2023-04-28) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.10.2](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.10.1...acvm_stdlib-v0.10.2) (2023-04-28) + + +### Bug Fixes + +* add default flag to `acvm_stdlib` ([#242](https://github.com/noir-lang/acvm/issues/242)) ([83b6fa8](https://github.com/noir-lang/acvm/commit/83b6fa8302569add7e3ac8481b2fd2a6a1ff3576)) + +## [0.10.1](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.10.0...acvm_stdlib-v0.10.1) (2023-04-28) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + +## [0.10.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.9.0...acvm_stdlib-v0.10.0) (2023-04-26) + + +### ⚠ BREAKING CHANGES + +* organise operator implementations for Expression ([#190](https://github.com/noir-lang/acvm/issues/190)) + +### Bug Fixes + +* prevent `bn254` feature flag always being enabled ([#225](https://github.com/noir-lang/acvm/issues/225)) ([82eee6a](https://github.com/noir-lang/acvm/commit/82eee6ab08ae480f04904ca8571fd88f4466c000)) + + +### Miscellaneous Chores + +* organise operator implementations for Expression ([#190](https://github.com/noir-lang/acvm/issues/190)) ([a619df6](https://github.com/noir-lang/acvm/commit/a619df614bbb9b2518b788b42a7553b069823a0f)) + +## [0.9.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.8.1...acvm_stdlib-v0.9.0) (2023-04-07) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acir bumped from 0.8.1 to 0.9.0 + +## [0.8.1](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.8.0...acvm_stdlib-v0.8.1) (2023-03-30) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acir bumped from 0.8.0 to 0.8.1 + +## [0.8.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.7.1...acvm_stdlib-v0.8.0) (2023-03-28) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acir bumped from 0.7.1 to 0.8.0 + +## [0.7.1](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.7.0...acvm_stdlib-v0.7.1) (2023-03-27) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acir bumped from 0.7.0 to 0.7.1 + +## [0.7.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.6.0...acvm_stdlib-v0.7.0) (2023-03-23) + + +### Miscellaneous Chores + +* **acvm_stdlib:** Synchronize acvm versions + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acir bumped from 0.6.0 to 0.7.0 + +## [0.6.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.5.0...acvm_stdlib-v0.6.0) (2023-03-03) + + +### ⚠ BREAKING CHANGES + +* **acir:** rename `term_addition` to `push_addition_term` +* **acir:** rename `term_multiplication` to `push_multiplication_term` ([#122](https://github.com/noir-lang/acvm/issues/122)) + +### Miscellaneous Chores + +* **acir:** rename `term_addition` to `push_addition_term` ([d389385](https://github.com/noir-lang/acvm/commit/d38938542851a97dc01727438391e6a65e44c689)) +* **acir:** rename `term_multiplication` to `push_multiplication_term` ([#122](https://github.com/noir-lang/acvm/issues/122)) ([d389385](https://github.com/noir-lang/acvm/commit/d38938542851a97dc01727438391e6a65e44c689)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acir bumped from 0.5.0 to 0.6.0 + +## [0.5.0](https://github.com/noir-lang/acvm/compare/acvm_stdlib-v0.4.1...acvm_stdlib-v0.5.0) (2023-02-22) + + +### ⚠ BREAKING CHANGES + +* refactor ToRadix to ToRadixLe and ToRadixBe ([#58](https://github.com/noir-lang/acvm/issues/58)) + +### Miscellaneous Chores + +* refactor ToRadix to ToRadixLe and ToRadixBe ([#58](https://github.com/noir-lang/acvm/issues/58)) ([2427a27](https://github.com/noir-lang/acvm/commit/2427a275048e598c6d651cce8348a4c55148f235)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * acir bumped from 0.4.1 to 0.5.0 diff --git a/acvm-repo/stdlib/Cargo.toml b/acvm-repo/stdlib/Cargo.toml new file mode 100644 index 00000000000..b397fe124e0 --- /dev/null +++ b/acvm-repo/stdlib/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "acvm_stdlib" +description = "The ACVM standard library." +# x-release-please-start-version +version = "0.27.4" +# x-release-please-end +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +acir.workspace = true + +[features] +default = ["bn254"] +bn254 = ["acir/bn254"] +bls12_381 = ["acir/bls12_381"] +testing = ["bn254"] diff --git a/acvm-repo/stdlib/src/blackbox_fallbacks/blake2s.rs b/acvm-repo/stdlib/src/blackbox_fallbacks/blake2s.rs new file mode 100644 index 00000000000..81ff320aac3 --- /dev/null +++ b/acvm-repo/stdlib/src/blackbox_fallbacks/blake2s.rs @@ -0,0 +1,468 @@ +//! Blake2s fallback function. +use super::{ + utils::{byte_decomposition, round_to_nearest_byte}, + UInt32, +}; +use acir::{ + circuit::Opcode, + native_types::{Expression, Witness}, + FieldElement, +}; +use std::vec; + +const BLAKE2S_BLOCKBYTES_USIZE: usize = 64; +const MSG_SCHEDULE_BLAKE2: [[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; +const INITIAL_H: [u32; 8] = [ + 0x6b08e647, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, +]; +const IV_VALUE: [u32; 8] = [ + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19, +]; + +pub fn blake2s( + inputs: Vec<(Expression, u32)>, + outputs: Vec, + mut num_witness: u32, +) -> (u32, Vec) { + let mut new_opcodes = Vec::new(); + let mut new_inputs = Vec::new(); + + // Decompose the input field elements into bytes and collect the resulting witnesses. + for (witness, num_bits) in inputs { + let num_bytes = round_to_nearest_byte(num_bits); + let (extra_opcodes, extra_inputs, updated_witness_counter) = + byte_decomposition(witness, num_bytes, num_witness); + new_opcodes.extend(extra_opcodes); + new_inputs.extend(extra_inputs); + num_witness = updated_witness_counter; + } + + let (result, num_witness, extra_opcodes) = create_blake2s_constraint(new_inputs, num_witness); + new_opcodes.extend(extra_opcodes); + + // constrain the outputs to be the same as the result of the circuit + for i in 0..outputs.len() { + let mut expr = Expression::from(outputs[i]); + expr.push_addition_term(-FieldElement::one(), result[i]); + new_opcodes.push(Opcode::Arithmetic(expr)); + } + (num_witness, new_opcodes) +} + +pub(crate) fn create_blake2s_constraint( + input: Vec, + num_witness: u32, +) -> (Vec, u32, Vec) { + let mut new_opcodes = Vec::new(); + + // prepare constants + let (mut blake2s_state, extra_opcodes, num_witness) = Blake2sState::init(num_witness); + new_opcodes.extend(extra_opcodes); + let (blake2s_constants, extra_opcodes, num_witness) = + Blake2sConstantsInCircuit::init(num_witness); + new_opcodes.extend(extra_opcodes); + let (blake2s_iv, extra_opcodes, mut num_witness) = Blake2sIV::init(num_witness); + new_opcodes.extend(extra_opcodes); + + let mut offset = 0; + let mut size = input.len(); + + while size > BLAKE2S_BLOCKBYTES_USIZE { + let (extra_opcodes, updated_witness_counter) = blake2s_increment_counter( + &mut blake2s_state, + &blake2s_constants.blake2s_blockbytes_uint32, + num_witness, + ); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, updated_witness_counter) = blake2s_compress( + &mut blake2s_state, + &blake2s_iv, + input.get(offset..offset + BLAKE2S_BLOCKBYTES_USIZE).unwrap(), + updated_witness_counter, + ); + new_opcodes.extend(extra_opcodes); + offset += BLAKE2S_BLOCKBYTES_USIZE; + size -= BLAKE2S_BLOCKBYTES_USIZE; + num_witness = updated_witness_counter; + } + + let (u32_max, extra_opcodes, mut num_witness) = UInt32::load_constant(u32::MAX, num_witness); + new_opcodes.extend(extra_opcodes); + blake2s_state.f[0] = u32_max; + + // pad final block + let mut final_block = input.get(offset..).unwrap().to_vec(); + for _ in 0..BLAKE2S_BLOCKBYTES_USIZE - final_block.len() { + let (pad, extra_opcodes, updated_witness_counter) = + UInt32::load_constant(0_u32, num_witness); + new_opcodes.extend(extra_opcodes); + final_block.push(pad.inner); + num_witness = updated_witness_counter; + } + + let (size_w, extra_opcodes, num_witness) = UInt32::load_constant(size as u32, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = + blake2s_increment_counter(&mut blake2s_state, &size_w, num_witness); + new_opcodes.extend(extra_opcodes); + + let (extra_opcodes, num_witness) = + blake2s_compress(&mut blake2s_state, &blake2s_iv, &final_block, num_witness); + new_opcodes.extend(extra_opcodes); + + // decompose the result bytes in u32 to u8 + let (extra_opcodes, mut byte1, num_witness) = + byte_decomposition(Expression::from(blake2s_state.h[0].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, mut byte2, num_witness) = + byte_decomposition(Expression::from(blake2s_state.h[1].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, mut byte3, num_witness) = + byte_decomposition(Expression::from(blake2s_state.h[2].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, mut byte4, num_witness) = + byte_decomposition(Expression::from(blake2s_state.h[3].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, mut byte5, num_witness) = + byte_decomposition(Expression::from(blake2s_state.h[4].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, mut byte6, num_witness) = + byte_decomposition(Expression::from(blake2s_state.h[5].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, mut byte7, num_witness) = + byte_decomposition(Expression::from(blake2s_state.h[6].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, mut byte8, num_witness) = + byte_decomposition(Expression::from(blake2s_state.h[7].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + + byte1.reverse(); + byte2.reverse(); + byte3.reverse(); + byte4.reverse(); + byte5.reverse(); + byte6.reverse(); + byte7.reverse(); + byte8.reverse(); + + let result = vec![byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8] + .into_iter() + .flatten() + .collect(); + + (result, num_witness, new_opcodes) +} + +fn blake2s_increment_counter( + state: &mut Blake2sState, + inc: &UInt32, + num_witness: u32, +) -> (Vec, u32) { + let mut new_opcodes = Vec::new(); + + // t0 + inc + let (state_t0, extra_opcodes, num_witness) = state.t[0].add(inc, num_witness); + new_opcodes.extend(extra_opcodes); + state.t[0] = state_t0; + + // t1 + (t0 < inc) + let (to_inc, extra_opcodes, num_witness) = state.t[0].less_than_comparison(inc, num_witness); + new_opcodes.extend(extra_opcodes); + let (state_t1, extra_opcodes, num_witness) = state.t[1].add(&to_inc, num_witness); + new_opcodes.extend(extra_opcodes); + state.t[1] = state_t1; + + (new_opcodes, num_witness) +} + +fn blake2s_compress( + state: &mut Blake2sState, + blake2s_iv: &Blake2sIV, + input: &[Witness], + mut num_witness: u32, +) -> (Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut m = Vec::new(); + let mut v = Vec::new(); + + for i in 0..16 { + let mut mi_bytes = input.get(i * 4..i * 4 + 4).unwrap().to_vec(); + mi_bytes.reverse(); + let (mi, extra_opcodes, updated_witness_counter) = + UInt32::from_witnesses(&mi_bytes, num_witness); + new_opcodes.extend(extra_opcodes); + m.push(mi[0]); + num_witness = updated_witness_counter + } + + for i in 0..8 { + v.push(state.h[i]); + } + + v.push(blake2s_iv.iv[0]); + v.push(blake2s_iv.iv[1]); + v.push(blake2s_iv.iv[2]); + v.push(blake2s_iv.iv[3]); + let (v12, extra_opcodes, num_witness) = state.t[0].xor(&blake2s_iv.iv[4], num_witness); + new_opcodes.extend(extra_opcodes); + v.push(v12); + let (v13, extra_opcodes, num_witness) = state.t[1].xor(&blake2s_iv.iv[5], num_witness); + new_opcodes.extend(extra_opcodes); + v.push(v13); + let (v14, extra_opcodes, num_witness) = state.f[0].xor(&blake2s_iv.iv[6], num_witness); + new_opcodes.extend(extra_opcodes); + v.push(v14); + let (v15, extra_opcodes, num_witness) = state.f[1].xor(&blake2s_iv.iv[7], num_witness); + new_opcodes.extend(extra_opcodes); + v.push(v15); + + let (extra_opcodes, num_witness) = blake2s_round(&mut v, &m, 0, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = blake2s_round(&mut v, &m, 1, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = blake2s_round(&mut v, &m, 2, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = blake2s_round(&mut v, &m, 3, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = blake2s_round(&mut v, &m, 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = blake2s_round(&mut v, &m, 5, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = blake2s_round(&mut v, &m, 6, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = blake2s_round(&mut v, &m, 7, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = blake2s_round(&mut v, &m, 8, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, mut num_witness) = blake2s_round(&mut v, &m, 9, num_witness); + new_opcodes.extend(extra_opcodes); + + for i in 0..8 { + let (a, extra_opcodes, updated_witness_counter) = state.h[i].xor(&v[i], num_witness); + new_opcodes.extend(extra_opcodes); + let (state_hi, extra_opcodes, updated_witness_counter) = + a.xor(&v[i + 8], updated_witness_counter); + new_opcodes.extend(extra_opcodes); + state.h[i] = state_hi; + num_witness = updated_witness_counter; + } + + (new_opcodes, num_witness) +} + +fn blake2s_round( + state: &mut [UInt32], + msg: &[UInt32], + round: usize, + num_witness: u32, +) -> (Vec, u32) { + let mut new_opcodes = Vec::new(); + let schedule = &MSG_SCHEDULE_BLAKE2[round]; + + // Mix the columns. + let (extra_opcodes, num_witness) = + g(state, 0, 4, 8, 12, msg[schedule[0]], msg[schedule[1]], num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = + g(state, 1, 5, 9, 13, msg[schedule[2]], msg[schedule[3]], num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = + g(state, 2, 6, 10, 14, msg[schedule[4]], msg[schedule[5]], num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = + g(state, 3, 7, 11, 15, msg[schedule[6]], msg[schedule[7]], num_witness); + new_opcodes.extend(extra_opcodes); + + // Mix the rows. + let (extra_opcodes, num_witness) = + g(state, 0, 5, 10, 15, msg[schedule[8]], msg[schedule[9]], num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = + g(state, 1, 6, 11, 12, msg[schedule[10]], msg[schedule[11]], num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = + g(state, 2, 7, 8, 13, msg[schedule[12]], msg[schedule[13]], num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, num_witness) = + g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]], num_witness); + new_opcodes.extend(extra_opcodes); + + (new_opcodes, num_witness) +} + +#[allow(clippy::too_many_arguments)] +fn g( + state: &mut [UInt32], + a: usize, + b: usize, + c: usize, + d: usize, + x: UInt32, + y: UInt32, + num_witness: u32, +) -> (Vec, u32) { + let mut new_opcodes = Vec::new(); + + // calculate state[a] as `state[a] + state[b] + x` + let (state_a_1, extra_opcodes, num_witness) = state[a].add(&state[b], num_witness); + new_opcodes.extend(extra_opcodes); + let (state_a, extra_opcodes, num_witness) = state_a_1.add(&x, num_witness); + new_opcodes.extend(extra_opcodes); + state[a] = state_a; + + // calculate state[d] as `(state[d] ^ state[a]).ror(16)` + let (state_d_1, extra_opcodes, num_witness) = state[d].xor(&state[a], num_witness); + new_opcodes.extend(extra_opcodes); + let (state_d, extra_opcodes, num_witness) = state_d_1.ror(16, num_witness); + new_opcodes.extend(extra_opcodes); + state[d] = state_d; + + // calculate state[c] as `state[c] + state[d]` + let (state_c, extra_opcodes, num_witness) = state[c].add(&state[d], num_witness); + new_opcodes.extend(extra_opcodes); + state[c] = state_c; + + // caclulate state[b] as `(state[b] ^ state[c]).ror(12)` + let (state_b_1, extra_opcodes, num_witness) = state[b].xor(&state[c], num_witness); + new_opcodes.extend(extra_opcodes); + let (state_b, extra_opcodes, num_witness) = state_b_1.ror(12, num_witness); + new_opcodes.extend(extra_opcodes); + state[b] = state_b; + + // calculate state[a] as `state[a] + state[b] + y` + let (state_a_1, extra_opcodes, num_witness) = state[a].add(&state[b], num_witness); + new_opcodes.extend(extra_opcodes); + let (state_a, extra_opcodes, num_witness) = state_a_1.add(&y, num_witness); + new_opcodes.extend(extra_opcodes); + state[a] = state_a; + + // calculate state[d] as `(state[d] ^ state[a]).ror(8)` + let (state_d_1, extra_opcodes, num_witness) = state[d].xor(&state[a], num_witness); + new_opcodes.extend(extra_opcodes); + let (state_d, extra_opcodes, num_witness) = state_d_1.ror(8, num_witness); + new_opcodes.extend(extra_opcodes); + state[d] = state_d; + + // calculate state[c] as `state[c] + state[d]` + let (state_c, extra_opcodes, num_witness) = state[c].add(&state[d], num_witness); + new_opcodes.extend(extra_opcodes); + state[c] = state_c; + + // caclulate state[b] as `(state[b] ^ state[c]).ror(7)` + let (state_b_1, extra_opcodes, num_witness) = state[b].xor(&state[c], num_witness); + new_opcodes.extend(extra_opcodes); + let (state_b, extra_opcodes, num_witness) = state_b_1.ror(7, num_witness); + new_opcodes.extend(extra_opcodes); + state[b] = state_b; + + (new_opcodes, num_witness) +} + +/// Blake2s state `h` `t` and `f` +#[derive(Debug)] +struct Blake2sState { + h: Vec, + t: Vec, + f: Vec, +} + +impl Blake2sState { + fn new(h: Vec, t: Vec, f: Vec) -> Self { + Blake2sState { h, t, f } + } + + /// Initialize internal state of Blake2s + fn init(mut num_witness: u32) -> (Blake2sState, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut h = Vec::new(); + let mut t = Vec::new(); + let mut f = Vec::new(); + + for init_h in INITIAL_H { + let (new_witness, extra_opcodes, updated_witness_counter) = + UInt32::load_constant(init_h, num_witness); + new_opcodes.extend(extra_opcodes); + h.push(new_witness); + num_witness = updated_witness_counter; + } + + for _ in 0..2 { + let (new_witness, extra_opcodes, updated_witness_counter) = + UInt32::load_constant(0_u32, num_witness); + new_opcodes.extend(extra_opcodes); + t.push(new_witness); + num_witness = updated_witness_counter; + } + + for _ in 0..2 { + let (new_witness, extra_opcodes, updated_witness_counter) = + UInt32::load_constant(0_u32, num_witness); + new_opcodes.extend(extra_opcodes); + f.push(new_witness); + num_witness = updated_witness_counter; + } + + let blake2s_state = Blake2sState::new(h, t, f); + + (blake2s_state, new_opcodes, num_witness) + } +} + +/// Blake2s IV (Initialization Vector) +struct Blake2sIV { + iv: Vec, +} + +impl Blake2sIV { + fn new(iv: Vec) -> Self { + Blake2sIV { iv } + } + + /// Initialize IV of Blake2s + fn init(mut num_witness: u32) -> (Blake2sIV, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut iv = Vec::new(); + + for iv_v in IV_VALUE { + let (new_witness, extra_opcodes, updated_witness_counter) = + UInt32::load_constant(iv_v, num_witness); + new_opcodes.extend(extra_opcodes); + iv.push(new_witness); + num_witness = updated_witness_counter; + } + + let blake2s_iv = Blake2sIV::new(iv); + + (blake2s_iv, new_opcodes, num_witness) + } +} + +struct Blake2sConstantsInCircuit { + blake2s_blockbytes_uint32: UInt32, +} + +impl Blake2sConstantsInCircuit { + fn new(blake2s_blockbytes_uint32: UInt32) -> Self { + Blake2sConstantsInCircuit { blake2s_blockbytes_uint32 } + } + + fn init(num_witness: u32) -> (Blake2sConstantsInCircuit, Vec, u32) { + let mut new_opcodes = Vec::new(); + let (blake2s_blockbytes_uint32, extra_opcodes, num_witness) = + UInt32::load_constant(64_u32, num_witness); + new_opcodes.extend(extra_opcodes); + + (Blake2sConstantsInCircuit::new(blake2s_blockbytes_uint32), new_opcodes, num_witness) + } +} diff --git a/acvm-repo/stdlib/src/blackbox_fallbacks/hash_to_field.rs b/acvm-repo/stdlib/src/blackbox_fallbacks/hash_to_field.rs new file mode 100644 index 00000000000..46e2de6f129 --- /dev/null +++ b/acvm-repo/stdlib/src/blackbox_fallbacks/hash_to_field.rs @@ -0,0 +1,170 @@ +//! HashToField128Security fallback function. +use super::{ + blake2s::create_blake2s_constraint, + utils::{byte_decomposition, round_to_nearest_byte}, + UInt32, +}; +use crate::helpers::VariableStore; +use acir::{ + brillig::{self, RegisterIndex}, + circuit::{ + brillig::{Brillig, BrilligInputs, BrilligOutputs}, + Opcode, + }, + native_types::{Expression, Witness}, + FieldElement, +}; + +pub fn hash_to_field( + inputs: Vec<(Expression, u32)>, + outputs: Witness, + mut num_witness: u32, +) -> (u32, Vec) { + let mut new_opcodes = Vec::new(); + let mut new_inputs = Vec::new(); + + // Decompose the input field elements into bytes and collect the resulting witnesses. + for (witness, num_bits) in inputs { + let num_bytes = round_to_nearest_byte(num_bits); + let (extra_opcodes, extra_inputs, updated_witness_counter) = + byte_decomposition(witness, num_bytes, num_witness); + new_opcodes.extend(extra_opcodes); + new_inputs.extend(extra_inputs); + num_witness = updated_witness_counter; + } + + let (result, num_witness, extra_opcodes) = create_blake2s_constraint(new_inputs, num_witness); + new_opcodes.extend(extra_opcodes); + + // transform bytes to a single field + let (result, extra_opcodes, num_witness) = field_from_be_bytes(&result, num_witness); + new_opcodes.extend(extra_opcodes); + + // constrain the outputs to be the same as the result of the circuit + let mut expr = Expression::from(outputs); + expr.push_addition_term(-FieldElement::one(), result); + new_opcodes.push(Opcode::Arithmetic(expr)); + (num_witness, new_opcodes) +} + +/// Convert bytes represented by [Witness]es to a single [FieldElement] +fn field_from_be_bytes(result: &[Witness], num_witness: u32) -> (Witness, Vec, u32) { + let mut new_opcodes = Vec::new(); + + // Load `0` and `256` using the load constant function from UInt32 + let (new_witness, extra_opcodes, num_witness) = UInt32::load_constant(0, num_witness); + let mut new_witness = new_witness.inner; + new_opcodes.extend(extra_opcodes); + let (const_256, extra_opcodes, mut num_witness) = UInt32::load_constant(256, num_witness); + let const_256 = const_256.inner; + new_opcodes.extend(extra_opcodes); + + // add byte and multiply 256 each round + for r in result.iter().take(result.len() - 1) { + let (updated_witness, extra_opcodes, updated_witness_counter) = + field_addition(&new_witness, r, num_witness); + new_opcodes.extend(extra_opcodes); + let (updated_witness, extra_opcodes, updated_witness_counter) = + field_mul(&updated_witness, &const_256, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + new_witness = updated_witness; + num_witness = updated_witness_counter; + } + + let (new_witness, extra_opcodes, num_witness) = + field_addition(&new_witness, &result[result.len() - 1], num_witness); + new_opcodes.extend(extra_opcodes); + + (new_witness, new_opcodes, num_witness) +} + +/// Caculate and constrain `self` + `rhs` as field +fn field_addition( + lhs: &Witness, + rhs: &Witness, + mut num_witness: u32, +) -> (Witness, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let new_witness = variables.new_variable(); + + // calculate `self` + `rhs` as field + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), *lhs)], + q_c: FieldElement::zero(), + }), + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), *rhs)], + q_c: FieldElement::zero(), + }), + ], + outputs: vec![BrilligOutputs::Simple(new_witness)], + foreign_call_results: vec![], + bytecode: vec![brillig::Opcode::BinaryFieldOp { + op: brillig::BinaryFieldOp::Add, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(0), + }], + predicate: None, + }); + new_opcodes.push(brillig_opcode); + let num_witness = variables.finalize(); + + // constrain addition + let mut add_expr = Expression::from(new_witness); + add_expr.push_addition_term(-FieldElement::one(), *lhs); + add_expr.push_addition_term(-FieldElement::one(), *rhs); + new_opcodes.push(Opcode::Arithmetic(add_expr)); + + (new_witness, new_opcodes, num_witness) +} + +/// Calculate and constrain `self` * `rhs` as field +pub(crate) fn field_mul( + lhs: &Witness, + rhs: &Witness, + mut num_witness: u32, +) -> (Witness, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let new_witness = variables.new_variable(); + + // calulate `self` * `rhs` with overflow + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), *lhs)], + q_c: FieldElement::zero(), + }), + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), *rhs)], + q_c: FieldElement::zero(), + }), + ], + outputs: vec![BrilligOutputs::Simple(new_witness)], + foreign_call_results: vec![], + bytecode: vec![brillig::Opcode::BinaryFieldOp { + op: brillig::BinaryFieldOp::Mul, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(0), + }], + predicate: None, + }); + new_opcodes.push(brillig_opcode); + let num_witness = variables.finalize(); + + // constrain mul + let mut mul_constraint = Expression::from(new_witness); + mul_constraint.push_multiplication_term(-FieldElement::one(), *lhs, *rhs); + new_opcodes.push(Opcode::Arithmetic(mul_constraint)); + + (new_witness, new_opcodes, num_witness) +} diff --git a/acvm-repo/stdlib/src/blackbox_fallbacks/keccak256.rs b/acvm-repo/stdlib/src/blackbox_fallbacks/keccak256.rs new file mode 100644 index 00000000000..d91db3dc2c6 --- /dev/null +++ b/acvm-repo/stdlib/src/blackbox_fallbacks/keccak256.rs @@ -0,0 +1,269 @@ +//! Keccak256 fallback function. +use super::{ + sha256::pad, + uint8::UInt8, + utils::{byte_decomposition, round_to_nearest_byte}, + UInt64, +}; +use acir::{ + circuit::Opcode, + native_types::{Expression, Witness}, + FieldElement, +}; + +const STATE_NUM_BYTES: usize = 200; +const BITS: usize = 256; +const WORD_SIZE: usize = 8; +const BLOCK_SIZE: usize = (1600 - BITS * 2) / WORD_SIZE; +const ROUND_CONSTANTS: [u64; 24] = [ + 1, + 0x8082, + 0x800000000000808a, + 0x8000000080008000, + 0x808b, + 0x80000001, + 0x8000000080008081, + 0x8000000000008009, + 0x8a, + 0x88, + 0x80008009, + 0x8000000a, + 0x8000808b, + 0x800000000000008b, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x800a, + 0x800000008000000a, + 0x8000000080008081, + 0x8000000000008080, + 0x80000001, + 0x8000000080008008, +]; +const RHO: [u32; 24] = + [1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44]; +const PI: [usize; 24] = + [10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1]; + +pub fn keccak256( + inputs: Vec<(Expression, u32)>, + outputs: Vec, + mut num_witness: u32, +) -> (u32, Vec) { + let mut new_opcodes = Vec::new(); + let mut new_inputs = Vec::new(); + + // Decompose the input field elements into bytes and collect the resulting witnesses. + for (witness, num_bits) in inputs { + let num_bytes = round_to_nearest_byte(num_bits); + let (extra_opcodes, extra_inputs, updated_witness_counter) = + byte_decomposition(witness, num_bytes, num_witness); + new_opcodes.extend(extra_opcodes); + new_inputs.extend(extra_inputs); + num_witness = updated_witness_counter; + } + + let (result, num_witness, extra_opcodes) = create_keccak_constraint(new_inputs, num_witness); + new_opcodes.extend(extra_opcodes); + + // constrain the outputs to be the same as the result of the circuit + for i in 0..outputs.len() { + let mut expr = Expression::from(outputs[i]); + expr.push_addition_term(-FieldElement::one(), result[i]); + new_opcodes.push(Opcode::Arithmetic(expr)); + } + (num_witness, new_opcodes) +} + +fn create_keccak_constraint( + input: Vec, + num_witness: u32, +) -> (Vec, u32, Vec) { + let mut new_opcodes = Vec::new(); + let num_blocks = input.len() / BLOCK_SIZE + 1; + + // pad keccak + let (input, extra_opcodes, mut num_witness) = pad_keccak(input, num_blocks, num_witness); + new_opcodes.extend(extra_opcodes); + + // prepare state + let mut state = Vec::with_capacity(200); + for _ in 0..STATE_NUM_BYTES { + let (zero, extra_opcodes, updated_witness_counter) = UInt8::load_constant(0, num_witness); + new_opcodes.extend(extra_opcodes); + state.push(zero); + num_witness = updated_witness_counter; + } + + // process block + for i in 0..num_blocks { + for j in 0..BLOCK_SIZE { + let (new_state, extra_opcodes, updated_witness_counter) = + state[j].xor(&UInt8::new(input[i * BLOCK_SIZE + j]), num_witness); + new_opcodes.extend(extra_opcodes); + state[j] = new_state; + num_witness = updated_witness_counter; + } + let (new_state, extra_opcodes, updated_witness_counter) = keccakf(state, num_witness); + new_opcodes.extend(extra_opcodes); + num_witness = updated_witness_counter; + state = new_state; + } + + let result: Vec = state[..32].iter().map(|x| x.inner).collect(); + (result, num_witness, new_opcodes) +} + +fn keccakf(state: Vec, num_witness: u32) -> (Vec, Vec, u32) { + let mut new_opcodes = Vec::new(); + + // turn state into UInt64 + let mut state_witnesses: Vec = Vec::new(); + for i in 0..state.len() / 8 { + for j in 0..8 { + state_witnesses.push(state[i * 8 + (7 - j)].inner); + } + } + let (mut state_u64, extra_opcodes, mut num_witness) = + UInt64::from_witnesses(&state_witnesses, num_witness); + new_opcodes.extend(extra_opcodes); + + // process round + for round_constant in ROUND_CONSTANTS { + let (new_state_u64, extra_opcodes, updated_witness_counter) = + keccak_round(state_u64, round_constant, num_witness); + state_u64 = new_state_u64; + new_opcodes.extend(extra_opcodes); + num_witness = updated_witness_counter; + } + + // turn state back to UInt8 + let state_u64_witnesses: Vec = state_u64.into_iter().map(|x| x.inner).collect(); + let mut state_u8 = Vec::with_capacity(state_u64_witnesses.len()); + for state_u64_witness in state_u64_witnesses { + let (extra_opcodes, mut u8s, updated_witness_counter) = + byte_decomposition(Expression::from(state_u64_witness), 8, num_witness); + new_opcodes.extend(extra_opcodes); + u8s.reverse(); + state_u8.push(u8s); + num_witness = updated_witness_counter; + } + + let state_u8: Vec = state_u8.into_iter().flatten().map(UInt8::new).collect(); + (state_u8, new_opcodes, num_witness) +} + +fn keccak_round( + mut a: Vec, + round_const: u64, + mut num_witness: u32, +) -> (Vec, Vec, u32) { + let mut new_opcodes = Vec::new(); + + // theta + let mut array = Vec::with_capacity(5); + for _ in 0..5 { + let (zero, extra_opcodes, updated_witness_counter) = UInt64::load_constant(0, num_witness); + array.push(zero); + new_opcodes.extend(extra_opcodes); + num_witness = updated_witness_counter; + } + for x in 0..5 { + for y_count in 0..5 { + let y = y_count * 5; + let (new_array_ele, extra_opcodes, updated_witness_counter) = + array[x].xor(&a[x + y], num_witness); + new_opcodes.extend(extra_opcodes); + num_witness = updated_witness_counter; + array[x] = new_array_ele; + } + } + for x in 0..5 { + for y_count in 0..5 { + let y = y_count * 5; + let (a_ele, extra_opcodes, updated_witness_counter) = + array[(x + 1) % 5].rol(1, num_witness); + new_opcodes.extend(extra_opcodes); + let (b_ele, extra_opcodes, updated_witness_counter) = + array[(x + 4) % 5].xor(&a_ele, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (new_array_ele, extra_opcodes, updated_witness_counter) = + a[x + y].xor(&b_ele, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + num_witness = updated_witness_counter; + a[x + y] = new_array_ele; + } + } + + // rho and pi + let mut last = a[1]; + for x in 0..24 { + array[0] = a[PI[x]]; + let (a_ele, extra_opcodes, updated_witness_counter) = last.rol(RHO[x], num_witness); + new_opcodes.extend(extra_opcodes); + a[PI[x]] = a_ele; + num_witness = updated_witness_counter; + last = array[0]; + } + + // chi + for y_step in 0..5 { + let y = y_step * 5; + + array[..5].copy_from_slice(&a[y..(5 + y)]); + + for x in 0..5 { + let (a_ele, extra_opcodes, updated_witness_counter) = + array[(x + 1) % 5].not(num_witness); + new_opcodes.extend(extra_opcodes); + let (b_ele, extra_opcodes, updated_witness_counter) = + a_ele.and(&array[(x + 2) % 5], updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (c_ele, extra_opcodes, updated_witness_counter) = + array[x].xor(&b_ele, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + + a[y + x] = c_ele; + num_witness = updated_witness_counter; + } + } + + // iota + let (rc, extra_opcodes, num_witness) = UInt64::load_constant(round_const, num_witness); + new_opcodes.extend(extra_opcodes); + let (a_ele, extra_opcodes, num_witness) = a[0].xor(&rc, num_witness); + new_opcodes.extend(extra_opcodes); + a[0] = a_ele; + + (a, new_opcodes, num_witness) +} + +fn pad_keccak( + mut input: Vec, + num_blocks: usize, + num_witness: u32, +) -> (Vec, Vec, u32) { + let mut new_opcodes = Vec::new(); + let total_len = BLOCK_SIZE * num_blocks; + + let (mut num_witness, pad_witness, extra_opcodes) = pad(0x01, 8, num_witness); + + new_opcodes.extend(extra_opcodes); + input.push(pad_witness); + for _ in 0..total_len - input.len() { + let (updated_witness_counter, pad_witness, extra_opcodes) = pad(0x00, 8, num_witness); + new_opcodes.extend(extra_opcodes); + input.push(pad_witness); + num_witness = updated_witness_counter; + } + + let (zero_x_80, extra_opcodes, num_witness) = UInt8::load_constant(0x80, num_witness); + new_opcodes.extend(extra_opcodes); + let (final_pad, extra_opcodes, num_witness) = + UInt8::new(input[total_len - 1]).xor(&zero_x_80, num_witness); + new_opcodes.extend(extra_opcodes); + input[total_len - 1] = final_pad.inner; + + (input, new_opcodes, num_witness) +} diff --git a/acvm-repo/stdlib/src/blackbox_fallbacks/logic_fallbacks.rs b/acvm-repo/stdlib/src/blackbox_fallbacks/logic_fallbacks.rs new file mode 100644 index 00000000000..fa8c1060a26 --- /dev/null +++ b/acvm-repo/stdlib/src/blackbox_fallbacks/logic_fallbacks.rs @@ -0,0 +1,127 @@ +use crate::{blackbox_fallbacks::utils::mul_with_witness, helpers::VariableStore}; + +use super::utils::{bit_decomposition, boolean_expr}; +use acir::{ + acir_field::FieldElement, + circuit::Opcode, + native_types::{Expression, Witness}, +}; + +// Range constraint +pub fn range(opcode: Expression, bit_size: u32, mut num_witness: u32) -> (u32, Vec) { + if bit_size == 1 { + let mut variables = VariableStore::new(&mut num_witness); + let bit_constraint = Opcode::Arithmetic(boolean_expr(&opcode, &mut variables)); + return (variables.finalize(), vec![bit_constraint]); + } + + let (new_opcodes, _, updated_witness_counter) = + bit_decomposition(opcode, bit_size, num_witness); + (updated_witness_counter, new_opcodes) +} + +/// Returns a set of opcodes which constrain `a & b == result` +/// +/// `a` and `b` are assumed to be constrained to fit within `bit_size` externally. +pub fn and( + a: Expression, + b: Expression, + result: Witness, + bit_size: u32, + mut num_witness: u32, +) -> (u32, Vec) { + if bit_size == 1 { + let mut variables = VariableStore::new(&mut num_witness); + + let mut and_expr = mul_with_witness(&a, &b, &mut variables); + and_expr.push_addition_term(-FieldElement::one(), result); + + return (variables.finalize(), vec![Opcode::Arithmetic(and_expr)]); + } + // Decompose the operands into bits + // + let (extra_opcodes_a, a_bits, updated_witness_counter) = + bit_decomposition(a, bit_size, num_witness); + + let (extra_opcodes_b, b_bits, updated_witness_counter) = + bit_decomposition(b, bit_size, updated_witness_counter); + + assert_eq!(a_bits.len(), b_bits.len()); + assert_eq!(a_bits.len(), bit_size as usize); + + let mut two_pow = FieldElement::one(); + let two = FieldElement::from(2_i128); + + // Build an expression that Multiplies each bit element-wise + // This gives the same truth table as the AND operation + // Additionally, we multiply by a power of 2 to build up the + // expected output; ie result = \sum 2^i x_i * y_i + let mut and_expr = Expression::default(); + for (a_bit, b_bit) in a_bits.into_iter().zip(b_bits) { + and_expr.push_multiplication_term(two_pow, a_bit, b_bit); + two_pow = two * two_pow; + } + and_expr.push_addition_term(-FieldElement::one(), result); + + and_expr.sort(); + + let mut new_opcodes = Vec::new(); + new_opcodes.extend(extra_opcodes_a); + new_opcodes.extend(extra_opcodes_b); + new_opcodes.push(Opcode::Arithmetic(and_expr)); + + (updated_witness_counter, new_opcodes) +} + +/// Returns a set of opcodes which constrain `a ^ b == result` +/// +/// `a` and `b` are assumed to be constrained to fit within `bit_size` externally. +pub fn xor( + a: Expression, + b: Expression, + result: Witness, + bit_size: u32, + mut num_witness: u32, +) -> (u32, Vec) { + if bit_size == 1 { + let mut variables = VariableStore::new(&mut num_witness); + + let product = mul_with_witness(&a, &b, &mut variables); + let mut xor_expr = &(&a + &b) - &product; + xor_expr.push_addition_term(-FieldElement::one(), result); + + return (variables.finalize(), vec![Opcode::Arithmetic(xor_expr)]); + } + + // Decompose the operands into bits + // + let (extra_opcodes_a, a_bits, updated_witness_counter) = + bit_decomposition(a, bit_size, num_witness); + let (extra_opcodes_b, b_bits, updated_witness_counter) = + bit_decomposition(b, bit_size, updated_witness_counter); + + assert_eq!(a_bits.len(), b_bits.len()); + assert_eq!(a_bits.len(), bit_size as usize); + + let mut two_pow = FieldElement::one(); + let two = FieldElement::from(2_i128); + + // Build an xor expression + // TODO: check this is the correct arithmetization + let mut xor_expr = Expression::default(); + for (a_bit, b_bit) in a_bits.into_iter().zip(b_bits) { + xor_expr.push_addition_term(two_pow, a_bit); + xor_expr.push_addition_term(two_pow, b_bit); + two_pow = two * two_pow; + xor_expr.push_multiplication_term(-two_pow, a_bit, b_bit); + } + xor_expr.push_addition_term(-FieldElement::one(), result); + + xor_expr.sort(); + let mut new_opcodes = Vec::new(); + new_opcodes.extend(extra_opcodes_a); + new_opcodes.extend(extra_opcodes_b); + new_opcodes.push(Opcode::Arithmetic(xor_expr)); + + (updated_witness_counter, new_opcodes) +} diff --git a/acvm-repo/stdlib/src/blackbox_fallbacks/mod.rs b/acvm-repo/stdlib/src/blackbox_fallbacks/mod.rs new file mode 100644 index 00000000000..d2ca3c50fa7 --- /dev/null +++ b/acvm-repo/stdlib/src/blackbox_fallbacks/mod.rs @@ -0,0 +1,19 @@ +mod blake2s; +mod hash_to_field; +mod keccak256; +mod logic_fallbacks; +mod sha256; +#[macro_use] +mod uint; +mod uint32; +mod uint64; +mod uint8; +mod utils; +pub use blake2s::blake2s; +pub use hash_to_field::hash_to_field; +pub use keccak256::keccak256; +pub use logic_fallbacks::{and, range, xor}; +pub use sha256::sha256; +pub use uint32::UInt32; +pub use uint64::UInt64; +pub use uint8::UInt8; diff --git a/acvm-repo/stdlib/src/blackbox_fallbacks/sha256.rs b/acvm-repo/stdlib/src/blackbox_fallbacks/sha256.rs new file mode 100644 index 00000000000..c9de55ffd0d --- /dev/null +++ b/acvm-repo/stdlib/src/blackbox_fallbacks/sha256.rs @@ -0,0 +1,378 @@ +//! Sha256 fallback function. +use super::uint32::UInt32; +use super::utils::{byte_decomposition, round_to_nearest_byte}; +use crate::helpers::VariableStore; +use acir::{ + brillig, + circuit::{ + brillig::{Brillig, BrilligInputs, BrilligOutputs}, + opcodes::{BlackBoxFuncCall, FunctionInput}, + Opcode, + }, + native_types::{Expression, Witness}, + FieldElement, +}; + +const INIT_CONSTANTS: [u32; 8] = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, +]; + +const ROUND_CONSTANTS: [u32; 64] = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, +]; + +pub fn sha256( + inputs: Vec<(Expression, u32)>, + outputs: Vec, + mut num_witness: u32, +) -> (u32, Vec) { + let mut new_opcodes = Vec::new(); + let mut new_inputs = Vec::new(); + let mut total_num_bytes = 0; + + // Decompose the input field elements into bytes and collect the resulting witnesses. + for (witness, num_bits) in inputs { + let num_bytes = round_to_nearest_byte(num_bits); + total_num_bytes += num_bytes; + let (extra_opcodes, extra_inputs, updated_witness_counter) = + byte_decomposition(witness, num_bytes, num_witness); + new_opcodes.extend(extra_opcodes); + new_inputs.extend(extra_inputs); + num_witness = updated_witness_counter; + } + + let (result, num_witness, extra_opcodes) = + create_sha256_constraint(new_inputs, total_num_bytes, num_witness); + new_opcodes.extend(extra_opcodes); + + // constrain the outputs to be the same as the result of the circuit + for i in 0..outputs.len() { + let mut expr = Expression::from(outputs[i]); + expr.push_addition_term(-FieldElement::one(), result[i]); + new_opcodes.push(Opcode::Arithmetic(expr)); + } + (num_witness, new_opcodes) +} + +fn create_sha256_constraint( + mut input: Vec, + total_num_bytes: u32, + num_witness: u32, +) -> (Vec, u32, Vec) { + let mut new_opcodes = Vec::new(); + + // pad the bytes according to sha256 padding rules + let message_bits = total_num_bytes * 8; + let (mut num_witness, pad_witness, extra_opcodes) = pad(128, 8, num_witness); + new_opcodes.extend(extra_opcodes); + input.push(pad_witness); + let bytes_per_block = 64; + let num_bytes = (input.len() + 8) as u32; + let num_blocks = num_bytes / bytes_per_block + ((num_bytes % bytes_per_block != 0) as u32); + let num_total_bytes = num_blocks * bytes_per_block; + for _ in num_bytes..num_total_bytes { + let (updated_witness_counter, pad_witness, extra_opcodes) = pad(0, 8, num_witness); + num_witness = updated_witness_counter; + new_opcodes.extend(extra_opcodes); + input.push(pad_witness); + } + let (num_witness, pad_witness, extra_opcodes) = pad(message_bits, 64, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, pad_witness, num_witness) = + byte_decomposition(pad_witness.into(), 8, num_witness); + new_opcodes.extend(extra_opcodes); + input.extend(pad_witness); + + // turn witness into u32 and load sha256 state + let (input, extra_opcodes, num_witness) = UInt32::from_witnesses(&input, num_witness); + new_opcodes.extend(extra_opcodes); + let (mut rolling_hash, extra_opcodes, num_witness) = prepare_state_constants(num_witness); + new_opcodes.extend(extra_opcodes); + let (round_constants, extra_opcodes, mut num_witness) = prepare_round_constants(num_witness); + new_opcodes.extend(extra_opcodes); + // split the input into blocks of size 16 + let input: Vec> = input.chunks(16).map(|block| block.to_vec()).collect(); + + // process sha256 blocks + for i in &input { + let (new_rolling_hash, extra_opcodes, updated_witness_counter) = + sha256_block(i, rolling_hash.clone(), round_constants.clone(), num_witness); + new_opcodes.extend(extra_opcodes); + num_witness = updated_witness_counter; + rolling_hash = new_rolling_hash; + } + + // decompose the result bytes in u32 to u8 + let (extra_opcodes, byte1, num_witness) = + byte_decomposition(Expression::from(rolling_hash[0].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, byte2, num_witness) = + byte_decomposition(Expression::from(rolling_hash[1].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, byte3, num_witness) = + byte_decomposition(Expression::from(rolling_hash[2].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, byte4, num_witness) = + byte_decomposition(Expression::from(rolling_hash[3].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, byte5, num_witness) = + byte_decomposition(Expression::from(rolling_hash[4].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, byte6, num_witness) = + byte_decomposition(Expression::from(rolling_hash[5].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, byte7, num_witness) = + byte_decomposition(Expression::from(rolling_hash[6].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + let (extra_opcodes, byte8, num_witness) = + byte_decomposition(Expression::from(rolling_hash[7].inner), 4, num_witness); + new_opcodes.extend(extra_opcodes); + + let result = vec![byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8] + .into_iter() + .flatten() + .collect(); + + (result, num_witness, new_opcodes) +} + +pub(crate) fn pad(number: u32, bit_size: u32, mut num_witness: u32) -> (u32, Witness, Vec) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let pad = variables.new_variable(); + + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![], + q_c: FieldElement::from(number as u128), + })], + outputs: vec![BrilligOutputs::Simple(pad)], + foreign_call_results: vec![], + bytecode: vec![brillig::Opcode::Stop], + predicate: None, + }); + new_opcodes.push(brillig_opcode); + + let range = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness: pad, num_bits: bit_size }, + }); + new_opcodes.push(range); + + (num_witness, pad, new_opcodes) +} + +fn sha256_block( + input: &[UInt32], + rolling_hash: Vec, + round_constants: Vec, + mut num_witness: u32, +) -> (Vec, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut w = Vec::new(); + w.extend(input.to_owned()); + + for i in 16..64 { + // calculate s0 `w[i - 15].ror(7) ^ w[i - 15].ror(18) ^ (w[i - 15] >> 3)` + let (a1, extra_opcodes, updated_witness_counter) = w[i - 15].ror(7, num_witness); + new_opcodes.extend(extra_opcodes); + let (a2, extra_opcodes, updated_witness_counter) = + w[i - 15].ror(18, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (a3, extra_opcodes, updated_witness_counter) = + w[i - 15].rightshift(3, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (a4, extra_opcodes, updated_witness_counter) = a1.xor(&a2, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (s0, extra_opcodes, updated_witness_counter) = a4.xor(&a3, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + + // calculate s1 `w[i - 2].ror(17) ^ w[i - 2].ror(19) ^ (w[i - 2] >> 10)` + let (b1, extra_opcodes, updated_witness_counter) = + w[i - 2].ror(17, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (b2, extra_opcodes, updated_witness_counter) = + w[i - 2].ror(19, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (b3, extra_opcodes, updated_witness_counter) = + w[i - 2].rightshift(10, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (b4, extra_opcodes, updated_witness_counter) = b1.xor(&b2, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (s1, extra_opcodes, updated_witness_counter) = b4.xor(&b3, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + + // calculate w[i] `w[i - 16] + w[i - 7] + s0 + s1` + let (c1, extra_opcodes, updated_witness_counter) = + w[i - 16].add(&w[i - 7], updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (c2, extra_opcodes, updated_witness_counter) = c1.add(&s0, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (c3, extra_opcodes, updated_witness_counter) = c2.add(&s1, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + w.push(c3); + num_witness = updated_witness_counter; + } + + let mut a = rolling_hash[0]; + let mut b = rolling_hash[1]; + let mut c = rolling_hash[2]; + let mut d = rolling_hash[3]; + let mut e = rolling_hash[4]; + let mut f = rolling_hash[5]; + let mut g = rolling_hash[6]; + let mut h = rolling_hash[7]; + + #[allow(non_snake_case)] + for i in 0..64 { + // calculate S1 `e.ror(6) ^ e.ror(11) ^ e.ror(25)` + let (a1, extra_opcodes, updated_witness_counter) = e.ror(6, num_witness); + new_opcodes.extend(extra_opcodes); + let (a2, extra_opcodes, updated_witness_counter) = e.ror(11, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (a3, extra_opcodes, updated_witness_counter) = e.ror(25, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (a4, extra_opcodes, updated_witness_counter) = a1.xor(&a2, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (S1, extra_opcodes, updated_witness_counter) = a4.xor(&a3, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + + // calculate ch `(e & f) + (~e & g)` + let (b1, extra_opcodes, updated_witness_counter) = e.and(&f, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (b2, extra_opcodes, updated_witness_counter) = e.not(updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (b3, extra_opcodes, updated_witness_counter) = b2.and(&g, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (ch, extra_opcodes, updated_witness_counter) = b1.add(&b3, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + + // caculate temp1 `h + S1 + ch + round_constants[i] + w[i]` + let (c1, extra_opcodes, updated_witness_counter) = h.add(&S1, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (c2, extra_opcodes, updated_witness_counter) = c1.add(&ch, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (c3, extra_opcodes, updated_witness_counter) = + c2.add(&round_constants[i], updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (temp1, extra_opcodes, updated_witness_counter) = + c3.add(&w[i], updated_witness_counter); + new_opcodes.extend(extra_opcodes); + + // calculate S0 `a.ror(2) ^ a.ror(13) ^ a.ror(22)` + let (d1, extra_opcodes, updated_witness_counter) = a.ror(2, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (d2, extra_opcodes, updated_witness_counter) = a.ror(13, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (d3, extra_opcodes, updated_witness_counter) = a.ror(22, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (d4, extra_opcodes, updated_witness_counter) = d1.xor(&d2, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (S0, extra_opcodes, updated_witness_counter) = d4.xor(&d3, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + + // calculate T0 `b & c` + let (T0, extra_opcodes, updated_witness_counter) = b.and(&c, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + + // calculate maj `(a & (b + c - (T0 + T0))) + T0` which is the same as `(a & b) ^ (a & c) ^ (b & c)` + let (e1, extra_opcodes, updated_witness_counter) = T0.add(&T0, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (e2, extra_opcodes, updated_witness_counter) = c.sub(&e1, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (e3, extra_opcodes, updated_witness_counter) = b.add(&e2, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (e4, extra_opcodes, updated_witness_counter) = a.and(&e3, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + let (maj, extra_opcodes, updated_witness_counter) = e4.add(&T0, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + + // calculate temp2 `S0 + maj` + let (temp2, extra_opcodes, updated_witness_counter) = S0.add(&maj, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + + h = g; + g = f; + f = e; + let (new_e, extra_opcodes, updated_witness_counter) = + d.add(&temp1, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + d = c; + c = b; + b = a; + let (new_a, extra_opcodes, updated_witness_counter) = + temp1.add(&temp2, updated_witness_counter); + new_opcodes.extend(extra_opcodes); + num_witness = updated_witness_counter; + a = new_a; + e = new_e; + } + + let mut output = Vec::new(); + let (output0, extra_opcodes, num_witness) = a.add(&rolling_hash[0], num_witness); + new_opcodes.extend(extra_opcodes); + let (output1, extra_opcodes, num_witness) = b.add(&rolling_hash[1], num_witness); + new_opcodes.extend(extra_opcodes); + let (output2, extra_opcodes, num_witness) = c.add(&rolling_hash[2], num_witness); + new_opcodes.extend(extra_opcodes); + let (output3, extra_opcodes, num_witness) = d.add(&rolling_hash[3], num_witness); + new_opcodes.extend(extra_opcodes); + let (output4, extra_opcodes, num_witness) = e.add(&rolling_hash[4], num_witness); + new_opcodes.extend(extra_opcodes); + let (output5, extra_opcodes, num_witness) = f.add(&rolling_hash[5], num_witness); + new_opcodes.extend(extra_opcodes); + let (output6, extra_opcodes, num_witness) = g.add(&rolling_hash[6], num_witness); + new_opcodes.extend(extra_opcodes); + let (output7, extra_opcodes, num_witness) = h.add(&rolling_hash[7], num_witness); + new_opcodes.extend(extra_opcodes); + + output.push(output0); + output.push(output1); + output.push(output2); + output.push(output3); + output.push(output4); + output.push(output5); + output.push(output6); + output.push(output7); + + (output, new_opcodes, num_witness) +} + +/// Load initial state constants of Sha256 +pub(crate) fn prepare_state_constants(mut num_witness: u32) -> (Vec, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut new_witnesses = Vec::new(); + + for i in INIT_CONSTANTS { + let (new_witness, extra_opcodes, updated_witness_counter) = + UInt32::load_constant(i, num_witness); + new_opcodes.extend(extra_opcodes); + new_witnesses.push(new_witness); + num_witness = updated_witness_counter; + } + + (new_witnesses, new_opcodes, num_witness) +} + +/// Load round constants of Sha256 +pub(crate) fn prepare_round_constants(mut num_witness: u32) -> (Vec, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut new_witnesses = Vec::new(); + + for i in ROUND_CONSTANTS { + let (new_witness, extra_opcodes, updated_witness_counter) = + UInt32::load_constant(i, num_witness); + new_opcodes.extend(extra_opcodes); + new_witnesses.push(new_witness); + num_witness = updated_witness_counter; + } + + (new_witnesses, new_opcodes, num_witness) +} diff --git a/acvm-repo/stdlib/src/blackbox_fallbacks/uint.rs b/acvm-repo/stdlib/src/blackbox_fallbacks/uint.rs new file mode 100644 index 00000000000..a65cf776e54 --- /dev/null +++ b/acvm-repo/stdlib/src/blackbox_fallbacks/uint.rs @@ -0,0 +1,656 @@ +#[macro_export] +macro_rules! impl_uint { + ( + $name:ident, + $type:ty, + $size:expr + ) => { + use acir::{ + brillig::{self, RegisterIndex}, + circuit::{ + brillig::{Brillig, BrilligInputs, BrilligOutputs}, + directives::QuotientDirective, + opcodes::{BlackBoxFuncCall, FunctionInput}, + Opcode, + }, + native_types::{Expression, Witness}, + FieldElement, + }; + use $crate::helpers::VariableStore; + + /// UInt contains a witness that points to a field element that represents a u32 integer + /// It has a inner field of type [Witness] that points to the field element and width = 32 + #[derive(Copy, Clone, Debug)] + pub struct $name { + pub(crate) inner: Witness, + width: u32, + } + + impl $name { + #[cfg(any(test, feature = "testing"))] + pub fn get_inner(&self) -> Witness { + self.inner + } + } + + impl $name { + /// Initialize A new [UInt] type with a [Witness] + pub fn new(witness: Witness) -> Self { + $name { inner: witness, width: $size } + } + + /// Get u(n) + 1 + pub(crate) fn get_max_plus_one( + &self, + mut num_witness: u32, + ) -> ($name, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let new_witness = variables.new_variable(); + + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![], + q_c: FieldElement::from(2_u128.pow(self.width)), + })], + outputs: vec![BrilligOutputs::Simple(new_witness)], + foreign_call_results: vec![], + bytecode: vec![brillig::Opcode::Stop], + predicate: None, + }); + new_opcodes.push(brillig_opcode); + let num_witness = variables.finalize(); + + ($name::new(new_witness), new_opcodes, num_witness) + } + + /// Load a constant into the circuit + pub(crate) fn load_constant( + constant: $type, + mut num_witness: u32, + ) -> ($name, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let new_witness = variables.new_variable(); + + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![], + q_c: FieldElement::from(constant as u128), + })], + outputs: vec![BrilligOutputs::Simple(new_witness)], + foreign_call_results: vec![], + bytecode: vec![brillig::Opcode::Stop], + predicate: None, + }); + new_opcodes.push(brillig_opcode); + let num_witness = variables.finalize(); + + ($name::new(new_witness), new_opcodes, num_witness) + } + + /// Returns the quotient and remainder such that lhs = rhs * quotient + remainder + // This should be the same as its equivalent in the Noir repo + pub fn euclidean_division( + lhs: &$name, + rhs: &$name, + mut num_witness: u32, + ) -> ($name, $name, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let q_witness = variables.new_variable(); + let r_witness = variables.new_variable(); + + // compute quotient using directive function + let quotient_opcode = Opcode::Directive( + acir::circuit::directives::Directive::Quotient(QuotientDirective { + a: lhs.inner.into(), + b: rhs.inner.into(), + q: q_witness, + r: r_witness, + predicate: None, + }), + ); + new_opcodes.push(quotient_opcode); + + // make sure r and q are in 32 bit range + let r_range_opcode = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness: r_witness, num_bits: lhs.width }, + }); + let q_range_opcode = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness: q_witness, num_bits: lhs.width }, + }); + new_opcodes.push(r_range_opcode); + new_opcodes.push(q_range_opcode); + let num_witness = variables.finalize(); + + // constrain r < rhs + let (rhs_sub_r, extra_opcodes, num_witness) = + rhs.sub_no_overflow(&$name::new(r_witness), num_witness); + new_opcodes.extend(extra_opcodes); + let rhs_sub_r_range_opcode = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness: rhs_sub_r.inner, num_bits: lhs.width }, + }); + new_opcodes.push(rhs_sub_r_range_opcode); + + // constrain lhs = rhs * quotient + remainder + let rhs_expr = Expression::from(rhs.inner); + let lhs_constraint = Expression::from(lhs.inner); + let rhs_constraint = &rhs_expr * &Expression::from(q_witness); + let rhs_constraint = &rhs_constraint.unwrap() + &Expression::from(r_witness); + let div_euclidean = &lhs_constraint - &rhs_constraint; + new_opcodes.push(Opcode::Arithmetic(div_euclidean)); + + ($name::new(q_witness), $name::new(r_witness), new_opcodes, num_witness) + } + + /// Rotate left `rotation` bits. `(x << rotation) | (x >> (width - rotation))` + // This should be the same as `u32.rotate_left(rotation)` in rust stdlib + pub fn rol(&self, rotation: u32, num_witness: u32) -> ($name, Vec, u32) { + let rotation = rotation % self.width; + let mut new_opcodes = Vec::new(); + let (right_shift, extra_opcodes, num_witness) = + self.rightshift(self.width - rotation, num_witness); + new_opcodes.extend(extra_opcodes); + let (left_shift, extra_opcodes, num_witness) = + self.leftshift(rotation, num_witness); + new_opcodes.extend(extra_opcodes); + let (result, extra_opcodes, num_witness) = left_shift.or(&right_shift, num_witness); + new_opcodes.extend(extra_opcodes); + + (result, new_opcodes, num_witness) + } + + /// Rotate right `rotation` bits. `(x >> rotation) | (x << (width - rotation))` + // This should be the same as `u32.rotate_right(rotation)` in rust stdlib + pub fn ror(&self, rotation: u32, num_witness: u32) -> ($name, Vec, u32) { + let rotation = rotation % self.width; + let mut new_opcodes = Vec::new(); + let (left_shift, extra_opcodes, num_witness) = + self.leftshift(self.width - rotation, num_witness); + new_opcodes.extend(extra_opcodes); + let (right_shift, extra_opcodes, num_witness) = + self.rightshift(rotation, num_witness); + new_opcodes.extend(extra_opcodes); + let (result, extra_opcodes, num_witness) = left_shift.or(&right_shift, num_witness); + new_opcodes.extend(extra_opcodes); + + (result, new_opcodes, num_witness) + } + + /// left shift by `bits` + pub fn leftshift(&self, bits: u32, num_witness: u32) -> ($name, Vec, u32) { + let bits = bits % self.width; + let mut new_opcodes = Vec::new(); + let two: $type = 2; + let (two_pow_rhs, extra_opcodes, num_witness) = + $name::load_constant(two.pow(bits), num_witness); + new_opcodes.extend(extra_opcodes); + let (left_shift, extra_opcodes, num_witness) = self.mul(&two_pow_rhs, num_witness); + new_opcodes.extend(extra_opcodes); + + (left_shift, new_opcodes, num_witness) + } + + /// right shift by `bits` + pub fn rightshift(&self, bits: u32, num_witness: u32) -> ($name, Vec, u32) { + let bits = bits % self.width; + let mut new_opcodes = Vec::new(); + let two: $type = 2; + let (two_pow_rhs, extra_opcodes, num_witness) = + $name::load_constant(two.pow(bits), num_witness); + new_opcodes.extend(extra_opcodes); + let (right_shift, _, extra_opcodes, num_witness) = + $name::euclidean_division(self, &two_pow_rhs, num_witness); + new_opcodes.extend(extra_opcodes); + + (right_shift, new_opcodes, num_witness) + } + + /// Caculate and constrain `self` + `rhs` + pub fn add(&self, rhs: &$name, mut num_witness: u32) -> ($name, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let new_witness = variables.new_variable(); + + // calculate `self` + `rhs` with overflow + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), self.inner)], + q_c: FieldElement::zero(), + }), + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), rhs.inner)], + q_c: FieldElement::zero(), + }), + ], + outputs: vec![BrilligOutputs::Simple(new_witness)], + foreign_call_results: vec![], + bytecode: vec![brillig::Opcode::BinaryIntOp { + op: brillig::BinaryIntOp::Add, + bit_size: 127, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(0), + }], + predicate: None, + }); + new_opcodes.push(brillig_opcode); + let num_witness = variables.finalize(); + + // constrain addition + let mut add_expr = Expression::from(new_witness); + add_expr.push_addition_term(-FieldElement::one(), self.inner); + add_expr.push_addition_term(-FieldElement::one(), rhs.inner); + new_opcodes.push(Opcode::Arithmetic(add_expr)); + + // mod 2^width to get final result as the remainder + let (two_pow_width, extra_opcodes, num_witness) = + self.get_max_plus_one(num_witness); + new_opcodes.extend(extra_opcodes); + let (_, add_mod, extra_opcodes, num_witness) = $name::euclidean_division( + &$name::new(new_witness), + &two_pow_width, + num_witness, + ); + new_opcodes.extend(extra_opcodes); + + (add_mod, new_opcodes, num_witness) + } + + /// Caculate and constrain `self` - `rhs` + pub fn sub(&self, rhs: &$name, mut num_witness: u32) -> ($name, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let new_witness = variables.new_variable(); + + // calculate 2^32 + self - rhs to avoid overflow + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), self.inner)], + q_c: FieldElement::zero(), + }), + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), rhs.inner)], + q_c: FieldElement::zero(), + }), + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![], + q_c: FieldElement::from(1_u128 << self.width), + }), + ], + outputs: vec![BrilligOutputs::Simple(new_witness)], + foreign_call_results: vec![], + bytecode: vec![ + brillig::Opcode::BinaryIntOp { + op: brillig::BinaryIntOp::Add, + bit_size: 127, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(2), + destination: RegisterIndex::from(0), + }, + brillig::Opcode::BinaryIntOp { + op: brillig::BinaryIntOp::Sub, + bit_size: 127, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(0), + }, + ], + predicate: None, + }); + new_opcodes.push(brillig_opcode); + let num_witness = variables.finalize(); + + // constrain subtraction + let mut sub_constraint = Expression::from(self.inner); + sub_constraint.push_addition_term(-FieldElement::one(), new_witness); + sub_constraint.push_addition_term(-FieldElement::one(), rhs.inner); + sub_constraint.q_c = FieldElement::from(1_u128 << self.width); + new_opcodes.push(Opcode::Arithmetic(sub_constraint)); + + // mod 2^width to get final result as the remainder + let (two_pow_width, extra_opcodes, num_witness) = + self.get_max_plus_one(num_witness); + new_opcodes.extend(extra_opcodes); + let (_, sub_mod, extra_opcodes, num_witness) = $name::euclidean_division( + &$name::new(new_witness), + &two_pow_width, + num_witness, + ); + new_opcodes.extend(extra_opcodes); + + (sub_mod, new_opcodes, num_witness) + } + + /// Calculate and constrain `self` - `rhs` - 1 without allowing overflow + /// This is a helper function to `euclidean_division` + // There is a `-1` because theres a case where rhs = 2^32 and remainder = 0 + pub(crate) fn sub_no_overflow( + &self, + rhs: &$name, + mut num_witness: u32, + ) -> ($name, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let new_witness = variables.new_variable(); + + // calculate self - rhs - 1 + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), self.inner)], + q_c: FieldElement::zero(), + }), + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), rhs.inner)], + q_c: FieldElement::zero(), + }), + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![], + q_c: FieldElement::one(), + }), + ], + outputs: vec![BrilligOutputs::Simple(new_witness)], + foreign_call_results: vec![], + bytecode: vec![ + brillig::Opcode::BinaryIntOp { + op: brillig::BinaryIntOp::Sub, + bit_size: 127, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(0), + }, + brillig::Opcode::BinaryIntOp { + op: brillig::BinaryIntOp::Sub, + bit_size: 127, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(2), + destination: RegisterIndex::from(0), + }, + ], + predicate: None, + }); + new_opcodes.push(brillig_opcode); + let num_witness = variables.finalize(); + + // constrain subtraction + let mut sub_constraint = Expression::from(self.inner); + sub_constraint.push_addition_term(-FieldElement::one(), new_witness); + sub_constraint.push_addition_term(-FieldElement::one(), rhs.inner); + sub_constraint.q_c = -FieldElement::one(); + new_opcodes.push(Opcode::Arithmetic(sub_constraint)); + + ($name::new(new_witness), new_opcodes, num_witness) + } + + /// Calculate and constrain `self` * `rhs` + pub(crate) fn mul( + &self, + rhs: &$name, + mut num_witness: u32, + ) -> ($name, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let new_witness = variables.new_variable(); + + // calulate `self` * `rhs` with overflow + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), self.inner)], + q_c: FieldElement::zero(), + }), + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), rhs.inner)], + q_c: FieldElement::zero(), + }), + ], + outputs: vec![BrilligOutputs::Simple(new_witness)], + foreign_call_results: vec![], + bytecode: vec![brillig::Opcode::BinaryFieldOp { + op: brillig::BinaryFieldOp::Mul, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(0), + }], + predicate: None, + }); + new_opcodes.push(brillig_opcode); + let num_witness = variables.finalize(); + + // constrain mul + let mut mul_constraint = Expression::from(new_witness); + mul_constraint.push_multiplication_term( + -FieldElement::one(), + self.inner, + rhs.inner, + ); + new_opcodes.push(Opcode::Arithmetic(mul_constraint)); + + // mod 2^width to get final result as the remainder + let (two_pow_rhs, extra_opcodes, num_witness) = self.get_max_plus_one(num_witness); + new_opcodes.extend(extra_opcodes); + let (_, mul_mod, extra_opcodes, num_witness) = + $name::euclidean_division(&$name::new(new_witness), &two_pow_rhs, num_witness); + new_opcodes.extend(extra_opcodes); + + (mul_mod, new_opcodes, num_witness) + } + + /// Calculate and constrain `self` and `rhs` + pub fn and(&self, rhs: &$name, mut num_witness: u32) -> ($name, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let new_witness = variables.new_variable(); + let num_witness = variables.finalize(); + let and_opcode = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::AND { + lhs: FunctionInput { witness: self.inner, num_bits: self.width }, + rhs: FunctionInput { witness: rhs.inner, num_bits: self.width }, + output: new_witness, + }); + new_opcodes.push(and_opcode); + + ($name::new(new_witness), new_opcodes, num_witness) + } + + /// Calculate and constrain `self` xor `rhs` + pub fn xor(&self, rhs: &$name, mut num_witness: u32) -> ($name, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let new_witness = variables.new_variable(); + let num_witness = variables.finalize(); + let xor_opcode = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::XOR { + lhs: FunctionInput { witness: self.inner, num_bits: self.width }, + rhs: FunctionInput { witness: rhs.inner, num_bits: self.width }, + output: new_witness, + }); + new_opcodes.push(xor_opcode); + + ($name::new(new_witness), new_opcodes, num_witness) + } + + /// Calculate and constrain `self` or `rhs` + pub fn or(&self, rhs: &$name, num_witness: u32) -> ($name, Vec, u32) { + let mut new_opcodes = Vec::new(); + + // a | b = (a & b) + (a ^ b) + let (a_and_b, extra_opcodes, num_witness) = self.and(rhs, num_witness); + new_opcodes.extend(extra_opcodes); + let (a_xor_b, extra_opcodes, num_witness) = self.xor(rhs, num_witness); + new_opcodes.extend(extra_opcodes); + let (or, extra_opcodes, num_witness) = a_and_b.add(&a_xor_b, num_witness); + new_opcodes.extend(extra_opcodes); + + (or, new_opcodes, num_witness) + } + + /// Calculate and constrain not `self` + pub(crate) fn not(&self, mut num_witness: u32) -> ($name, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let new_witness = variables.new_variable(); + + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), self.inner)], + q_c: FieldElement::zero(), + }), + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![], + q_c: FieldElement::from((1_u128 << self.width) - 1), + }), + ], + outputs: vec![BrilligOutputs::Simple(new_witness)], + foreign_call_results: vec![], + bytecode: vec![brillig::Opcode::BinaryIntOp { + op: brillig::BinaryIntOp::Sub, + bit_size: self.width, + lhs: RegisterIndex::from(1), + rhs: RegisterIndex::from(0), + destination: RegisterIndex::from(0), + }], + predicate: None, + }); + new_opcodes.push(brillig_opcode); + let num_witness = variables.finalize(); + + let mut not_constraint = Expression::from(new_witness); + not_constraint.push_addition_term(FieldElement::one(), self.inner); + not_constraint.q_c = -FieldElement::from((1_u128 << self.width) - 1); + new_opcodes.push(Opcode::Arithmetic(not_constraint)); + + ($name::new(new_witness), new_opcodes, num_witness) + } + + /// Calculate and constrain `self` >= `rhs` + // This should be similar to its equivalent in the Noir repo + pub(crate) fn more_than_eq_comparison( + &self, + rhs: &$name, + mut num_witness: u32, + ) -> ($name, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let new_witness = variables.new_variable(); + let q_witness = variables.new_variable(); + let r_witness = variables.new_variable(); + + // calculate 2^32 + self - rhs + let brillig_opcode = Opcode::Brillig(Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), self.inner)], + q_c: FieldElement::zero(), + }), + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![(FieldElement::one(), rhs.inner)], + q_c: FieldElement::zero(), + }), + BrilligInputs::Single(Expression { + mul_terms: vec![], + linear_combinations: vec![], + q_c: FieldElement::from(1_u128 << self.width), + }), + ], + outputs: vec![BrilligOutputs::Simple(new_witness)], + foreign_call_results: vec![], + bytecode: vec![ + brillig::Opcode::BinaryIntOp { + op: brillig::BinaryIntOp::Add, + bit_size: 127, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(2), + destination: RegisterIndex::from(0), + }, + brillig::Opcode::BinaryIntOp { + op: brillig::BinaryIntOp::Sub, + bit_size: 127, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + destination: RegisterIndex::from(0), + }, + ], + predicate: None, + }); + new_opcodes.push(brillig_opcode); + let num_witness = variables.finalize(); + + // constrain subtraction + let mut sub_constraint = Expression::from(self.inner); + sub_constraint.push_addition_term(-FieldElement::one(), new_witness); + sub_constraint.push_addition_term(-FieldElement::one(), rhs.inner); + sub_constraint.q_c = FieldElement::from(1_u128 << self.width); + new_opcodes.push(Opcode::Arithmetic(sub_constraint)); + + let (two_pow_rhs, extra_opcodes, num_witness) = self.get_max_plus_one(num_witness); + new_opcodes.extend(extra_opcodes); + + // constraint 2^{max_bits} + a - b = q * 2^{max_bits} + r + // q = 1 if a == b + // q = 1 if a > b + // q = 0 if a < b + let quotient_opcode = Opcode::Directive( + acir::circuit::directives::Directive::Quotient(QuotientDirective { + a: new_witness.into(), + b: two_pow_rhs.inner.into(), + q: q_witness, + r: r_witness, + predicate: None, + }), + ); + new_opcodes.push(quotient_opcode); + + // make sure r in 32 bit range and q is 1 bit + let r_range_opcode = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness: r_witness, num_bits: self.width }, + }); + let q_range_opcode = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness: q_witness, num_bits: 1 }, + }); + new_opcodes.push(r_range_opcode); + new_opcodes.push(q_range_opcode); + + ($name::new(q_witness), new_opcodes, num_witness) + } + + /// Calculate and constrain `self` < `rhs` + pub fn less_than_comparison( + &self, + rhs: &$name, + num_witness: u32, + ) -> ($name, Vec, u32) { + let mut new_opcodes = Vec::new(); + let (mut comparison, extra_opcodes, num_witness) = + self.more_than_eq_comparison(rhs, num_witness); + new_opcodes.extend(extra_opcodes); + comparison.width = 1; + + // `self` < `rhs` == not `self` >= `rhs` + let (less_than, extra_opcodes, num_witness) = comparison.not(num_witness); + new_opcodes.extend(extra_opcodes); + + (less_than, new_opcodes, num_witness) + } + } + }; +} diff --git a/acvm-repo/stdlib/src/blackbox_fallbacks/uint32.rs b/acvm-repo/stdlib/src/blackbox_fallbacks/uint32.rs new file mode 100644 index 00000000000..58314d6ba4c --- /dev/null +++ b/acvm-repo/stdlib/src/blackbox_fallbacks/uint32.rs @@ -0,0 +1,30 @@ +use crate::impl_uint; + +impl_uint!(UInt32, u32, 32); +impl UInt32 { + /// Load a [UInt32] from four [Witness]es each representing a [u8] + pub(crate) fn from_witnesses( + witnesses: &[Witness], + mut num_witness: u32, + ) -> (Vec, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let mut uint = Vec::new(); + + for i in 0..witnesses.len() / 4 { + let new_witness = variables.new_variable(); + uint.push(UInt32::new(new_witness)); + let mut expr = Expression::from(new_witness); + for j in 0..4 { + let scaling_factor_value = 1 << (8 * (3 - j) as u32); + let scaling_factor = FieldElement::from(scaling_factor_value as u128); + expr.push_addition_term(-scaling_factor, witnesses[i * 4 + j]); + } + + new_opcodes.push(Opcode::Arithmetic(expr)); + } + let num_witness = variables.finalize(); + + (uint, new_opcodes, num_witness) + } +} diff --git a/acvm-repo/stdlib/src/blackbox_fallbacks/uint64.rs b/acvm-repo/stdlib/src/blackbox_fallbacks/uint64.rs new file mode 100644 index 00000000000..cddb23275cb --- /dev/null +++ b/acvm-repo/stdlib/src/blackbox_fallbacks/uint64.rs @@ -0,0 +1,30 @@ +use crate::impl_uint; + +impl_uint!(UInt64, u64, 64); +impl UInt64 { + /// Load a [UInt64] from eight [Witness]es each representing a [u8] + pub(crate) fn from_witnesses( + witnesses: &[Witness], + mut num_witness: u32, + ) -> (Vec, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + let mut uint = Vec::new(); + + for i in 0..witnesses.len() / 8 { + let new_witness = variables.new_variable(); + uint.push(UInt64::new(new_witness)); + let mut expr = Expression::from(new_witness); + for j in 0..8 { + let scaling_factor_value: u128 = 1 << (8 * (7 - j) as u32); + let scaling_factor = FieldElement::from(scaling_factor_value); + expr.push_addition_term(-scaling_factor, witnesses[i * 8 + j]); + } + + new_opcodes.push(Opcode::Arithmetic(expr)); + } + let num_witness = variables.finalize(); + + (uint, new_opcodes, num_witness) + } +} diff --git a/acvm-repo/stdlib/src/blackbox_fallbacks/uint8.rs b/acvm-repo/stdlib/src/blackbox_fallbacks/uint8.rs new file mode 100644 index 00000000000..2ffc2cae1be --- /dev/null +++ b/acvm-repo/stdlib/src/blackbox_fallbacks/uint8.rs @@ -0,0 +1,2 @@ +use crate::impl_uint; +impl_uint!(UInt8, u8, 8); diff --git a/acvm-repo/stdlib/src/blackbox_fallbacks/utils.rs b/acvm-repo/stdlib/src/blackbox_fallbacks/utils.rs new file mode 100644 index 00000000000..d7daae7008c --- /dev/null +++ b/acvm-repo/stdlib/src/blackbox_fallbacks/utils.rs @@ -0,0 +1,175 @@ +use crate::helpers::VariableStore; +use acir::{ + circuit::{ + directives::Directive, + opcodes::{BlackBoxFuncCall, FunctionInput}, + Opcode, + }, + native_types::{Expression, Witness}, + FieldElement, +}; + +fn round_to_nearest_mul_8(num_bits: u32) -> u32 { + let remainder = num_bits % 8; + + if remainder == 0 { + return num_bits; + } + + num_bits + 8 - remainder +} + +pub(crate) fn round_to_nearest_byte(num_bits: u32) -> u32 { + round_to_nearest_mul_8(num_bits) / 8 +} + +pub(crate) fn boolean_expr(expr: &Expression, variables: &mut VariableStore) -> Expression { + &mul_with_witness(expr, expr, variables) - expr +} + +/// Returns an expression which represents `lhs * rhs` +/// +/// If one has multiplicative term and the other is of degree one or more, +/// the function creates [intermediate variables][`Witness`] accordingly. +/// There are two cases where we can optimize the multiplication between two expressions: +/// 1. If both expressions have at most a total degree of 1 in each term, then we can just multiply them +/// as each term in the result will be degree-2. +/// 2. If one expression is a constant, then we can just multiply the constant with the other expression +/// +/// (1) is because an [`Expression`] can hold at most a degree-2 univariate polynomial +/// which is what you get when you multiply two degree-1 univariate polynomials. +pub(crate) fn mul_with_witness( + lhs: &Expression, + rhs: &Expression, + variables: &mut VariableStore, +) -> Expression { + use std::borrow::Cow; + let lhs_is_linear = lhs.is_linear(); + let rhs_is_linear = rhs.is_linear(); + + // Case 1: Both expressions have at most a total degree of 1 in each term + if lhs_is_linear && rhs_is_linear { + return (lhs * rhs) + .expect("one of the expressions is a constant and so this should not fail"); + } + + // Case 2: One or both of the sides needs to be reduced to a degree-1 univariate polynomial + let lhs_reduced = if lhs_is_linear { + Cow::Borrowed(lhs) + } else { + Cow::Owned(variables.new_variable().into()) + }; + + // If the lhs and rhs are the same, then we do not need to reduce + // rhs, we only need to square the lhs. + if lhs == rhs { + return (&*lhs_reduced * &*lhs_reduced) + .expect("Both expressions are reduced to be degree<=1"); + }; + + let rhs_reduced = if rhs_is_linear { + Cow::Borrowed(rhs) + } else { + Cow::Owned(variables.new_variable().into()) + }; + + (&*lhs_reduced * &*rhs_reduced).expect("Both expressions are reduced to be degree<=1") +} + +// Generates opcodes and directives to bit decompose the input `opcode` +// Returns the bits and the updated witness counter +// TODO:Ideally, we return the updated witness counter, or we require the input +// TODO to be a VariableStore. We are not doing this because we want migration to +// TODO be less painful +pub(crate) fn bit_decomposition( + opcode: Expression, + bit_size: u32, + mut num_witness: u32, +) -> (Vec, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + + // First create a witness for each bit + let mut bit_vector = Vec::with_capacity(bit_size as usize); + for _ in 0..bit_size { + bit_vector.push(variables.new_variable()) + } + + // Next create a directive which computes those bits. + new_opcodes.push(Opcode::Directive(Directive::ToLeRadix { + a: opcode.clone(), + b: bit_vector.clone(), + radix: 2, + })); + + // Now apply constraints to the bits such that they are the bit decomposition + // of the input and each bit is actually a bit + let mut binary_exprs = Vec::new(); + let mut bit_decomp_constraint = opcode; + let mut two_pow: FieldElement = FieldElement::one(); + let two = FieldElement::from(2_i128); + for &bit in &bit_vector { + // Bit constraint to ensure each bit is a zero or one; bit^2 - bit = 0 + let expr = boolean_expr(&bit.into(), &mut variables); + binary_exprs.push(Opcode::Arithmetic(expr)); + + // Constraint to ensure that the bits are constrained to be a bit decomposition + // of the input + // ie \sum 2^i * x_i = input + bit_decomp_constraint.push_addition_term(-two_pow, bit); + two_pow = two * two_pow; + } + + new_opcodes.extend(binary_exprs); + bit_decomp_constraint.sort(); // TODO: we have an issue open to check if this is needed. Ideally, we remove it. + new_opcodes.push(Opcode::Arithmetic(bit_decomp_constraint)); + + (new_opcodes, bit_vector, variables.finalize()) +} + +// TODO: Maybe this can be merged with `bit_decomposition` +pub(crate) fn byte_decomposition( + opcode: Expression, + num_bytes: u32, + mut num_witness: u32, +) -> (Vec, Vec, u32) { + let mut new_opcodes = Vec::new(); + let mut variables = VariableStore::new(&mut num_witness); + + // First create a witness for each byte + let mut vector = Vec::with_capacity(num_bytes as usize); + for _ in 0..num_bytes { + vector.push(variables.new_variable()) + } + + // Next create a directive which computes those byte. + new_opcodes.push(Opcode::Directive(Directive::ToLeRadix { + a: opcode.clone(), + b: vector.clone(), + radix: 256, + })); + vector.reverse(); + + // Now apply constraints to the bytes such that they are the byte decomposition + // of the input and each byte is actually a byte + let mut byte_exprs = Vec::new(); + let mut decomp_constraint = opcode; + let byte_shift: u128 = 256; + for (i, v) in vector.iter().enumerate() { + let range = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness: *v, num_bits: 8 }, + }); + let scaling_factor_value = byte_shift.pow(num_bytes - 1 - i as u32); + let scaling_factor = FieldElement::from(scaling_factor_value); + + decomp_constraint.push_addition_term(-scaling_factor, *v); + + byte_exprs.push(range); + } + + new_opcodes.extend(byte_exprs); + decomp_constraint.sort(); + new_opcodes.push(Opcode::Arithmetic(decomp_constraint)); + + (new_opcodes, vector, variables.finalize()) +} diff --git a/acvm-repo/stdlib/src/helpers.rs b/acvm-repo/stdlib/src/helpers.rs new file mode 100644 index 00000000000..5ab258368f4 --- /dev/null +++ b/acvm-repo/stdlib/src/helpers.rs @@ -0,0 +1,23 @@ +use acir::native_types::Witness; + +// Simple helper struct to keep track of the current witness index +// and create variables +pub struct VariableStore<'a> { + witness_index: &'a mut u32, +} + +impl<'a> VariableStore<'a> { + pub fn new(witness_index: &'a mut u32) -> Self { + Self { witness_index } + } + + pub fn new_variable(&mut self) -> Witness { + let witness = Witness(*self.witness_index); + *self.witness_index += 1; + witness + } + + pub fn finalize(self) -> u32 { + *self.witness_index + } +} diff --git a/acvm-repo/stdlib/src/lib.rs b/acvm-repo/stdlib/src/lib.rs new file mode 100644 index 00000000000..39d68647c94 --- /dev/null +++ b/acvm-repo/stdlib/src/lib.rs @@ -0,0 +1,5 @@ +#![warn(unused_crate_dependencies)] +#![warn(unreachable_pub)] + +pub mod blackbox_fallbacks; +pub mod helpers; diff --git a/compiler/fm/src/file_reader.rs b/compiler/fm/src/file_reader.rs index 08df5abc349..6cf90734e18 100644 --- a/compiler/fm/src/file_reader.rs +++ b/compiler/fm/src/file_reader.rs @@ -21,6 +21,28 @@ pub(super) fn is_stdlib_asset(path: &Path) -> bool { path.starts_with("std/") } +fn get_stdlib_asset(path: &Path) -> std::io::Result { + if !is_stdlib_asset(path) { + return Err(Error::new(ErrorKind::InvalidInput, "requested a non-stdlib asset")); + } + + match StdLibAssets::get(path.to_str().unwrap()) { + Some(std_lib_asset) => { + Ok(std::str::from_utf8(std_lib_asset.data.as_ref()).unwrap().to_string()) + } + + None => Err(Error::new(ErrorKind::NotFound, "invalid stdlib path")), + } +} + +pub(crate) fn read_file_to_string(path_to_file: &Path) -> std::io::Result { + if is_stdlib_asset(path_to_file) { + get_stdlib_asset(path_to_file) + } else { + get_non_stdlib_asset(path_to_file) + } +} + cfg_if::cfg_if! { if #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] { use wasm_bindgen::{prelude::*, JsValue}; @@ -33,42 +55,17 @@ cfg_if::cfg_if! { } - pub(crate) fn read_file_to_string(path_to_file: &Path) -> Result { + fn get_non_stdlib_asset(path_to_file: &Path) -> std::io::Result { let path_str = path_to_file.to_str().unwrap(); - match StdLibAssets::get(path_str) { - - Some(std_lib_asset) => { - Ok(std::str::from_utf8(std_lib_asset.data.as_ref()).unwrap().to_string()) - }, - - None if is_stdlib_asset(path_to_file) => { - Err(Error::new(ErrorKind::NotFound, "invalid stdlib path")) - } - - None => match read_file(path_str) { - Ok(buffer) => Ok(buffer), - Err(_) => Err(Error::new(ErrorKind::Other, "could not read file using wasm")), - } - + match read_file(path_str) { + Ok(buffer) => Ok(buffer), + Err(_) => Err(Error::new(ErrorKind::Other, "could not read file using wasm")), } } } else { - pub(crate) fn read_file_to_string(path_to_file: &Path) -> Result { - - match StdLibAssets::get(path_to_file.to_str().unwrap()) { - - Some(std_lib_asset) => { - Ok(std::str::from_utf8(std_lib_asset.data.as_ref()).unwrap().to_string()) - }, - - None if is_stdlib_asset(path_to_file) => { - Err(Error::new(ErrorKind::NotFound, "invalid stdlib path")) - } - - None => std::fs::read_to_string(path_to_file) - - } + fn get_non_stdlib_asset(path_to_file: &Path) -> std::io::Result { + std::fs::read_to_string(path_to_file) } } } diff --git a/compiler/fm/src/lib.rs b/compiler/fm/src/lib.rs index f615555601c..1703db8d048 100644 --- a/compiler/fm/src/lib.rs +++ b/compiler/fm/src/lib.rs @@ -78,6 +78,7 @@ impl FileManager { // Unwrap as we ensure that all file_id's map to a corresponding file in the file map self.file_map.get_file(file_id).unwrap() } + pub fn path(&self, file_id: FileId) -> &Path { // Unwrap as we ensure that all file_ids are created by the file manager // So all file_ids will points to a corresponding path @@ -85,23 +86,38 @@ impl FileManager { } pub fn find_module(&mut self, anchor: FileId, mod_name: &str) -> Result { - let mut candidate_files = Vec::new(); - let anchor_path = self.path(anchor).to_path_buf(); let anchor_dir = anchor_path.parent().unwrap(); - // First we attempt to look at `base/anchor/mod_name.nr` (child of the anchor) - candidate_files.push(anchor_path.join(format!("{mod_name}.{FILE_EXTENSION}"))); - // If not found, we attempt to look at `base/mod_name.nr` (sibling of the anchor) - candidate_files.push(anchor_dir.join(format!("{mod_name}.{FILE_EXTENSION}"))); + // if `anchor` is a `main.nr`, `lib.nr`, `mod.nr` or `{modname}.nr`, we check siblings of + // the anchor at `base/mod_name.nr`. + let candidate = if should_check_siblings_for_module(&anchor_path, anchor_dir) { + anchor_dir.join(format!("{mod_name}.{FILE_EXTENSION}")) + } else { + // Otherwise, we check for children of the anchor at `base/anchor/mod_name.nr` + anchor_path.join(format!("{mod_name}.{FILE_EXTENSION}")) + }; - for candidate in candidate_files.iter() { - if let Some(file_id) = self.add_file(candidate) { - return Ok(file_id); - } - } + self.add_file(&candidate).ok_or_else(|| candidate.as_os_str().to_string_lossy().to_string()) + } +} - Err(candidate_files.remove(0).as_os_str().to_str().unwrap().to_owned()) +/// Returns true if a module's child module's are expected to be in the same directory. +/// Returns false if they are expected to be in a subdirectory matching the name of the module. +fn should_check_siblings_for_module(module_path: &Path, parent_path: &Path) -> bool { + if let Some(filename) = module_path.file_name() { + // This check also means a `main.nr` or `lib.nr` file outside of the crate root would + // check its same directory for child modules instead of a subdirectory. Should we prohibit + // `main.nr` and `lib.nr` files outside of the crate root? + filename == "main" + || filename == "lib" + || filename == "mod" + || Some(filename) == parent_path.file_name() + } else { + // If there's no filename, we arbitrarily return true. + // Alternatively, we could panic, but this is left to a different step where we + // ideally have some source location to issue an error. + true } } @@ -215,12 +231,13 @@ mod tests { let dep_file_name = Path::new("foo.nr"); create_dummy_file(&dir, dep_file_name); - fm.find_module(file_id, "foo").unwrap(); + fm.find_module(file_id, "foo").unwrap_err(); } + #[test] fn path_resolve_file_module_other_ext() { let dir = tempdir().unwrap(); - let file_name = Path::new("foo.noir"); + let file_name = Path::new("foo.nr"); create_dummy_file(&dir, file_name); let mut fm = FileManager::new(dir.path()); @@ -229,6 +246,7 @@ mod tests { assert!(fm.path(file_id).ends_with("foo")); } + #[test] fn path_resolve_sub_module() { let dir = tempdir().unwrap(); diff --git a/compiler/integration-tests/.gitignore b/compiler/integration-tests/.gitignore new file mode 100644 index 00000000000..3ae88ef1783 --- /dev/null +++ b/compiler/integration-tests/.gitignore @@ -0,0 +1 @@ +foundry-project diff --git a/compiler/integration-tests/package.json b/compiler/integration-tests/package.json index 4272c6d24c4..5d87dc96f12 100644 --- a/compiler/integration-tests/package.json +++ b/compiler/integration-tests/package.json @@ -12,14 +12,16 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "^0.6.7", + "@aztec/bb.js": "^0.7.3", "@noir-lang/noir_js": "workspace:*", "@noir-lang/noir_wasm": "workspace:*", "@noir-lang/source-resolver": "workspace:*", "@web/dev-server-esbuild": "^0.3.6", "@web/test-runner": "^0.15.3", "@web/test-runner-webdriver": "^0.7.0", + "ethers": "^6.7.1", "fflate": "^0.8.0", - "smol-toml": "^1.1.2" + "smol-toml": "^1.1.2", + "tslog": "^4.9.2" } } diff --git a/compiler/integration-tests/scripts/codegen-verifiers.sh b/compiler/integration-tests/scripts/codegen-verifiers.sh new file mode 100644 index 00000000000..8fd3cb7eccc --- /dev/null +++ b/compiler/integration-tests/scripts/codegen-verifiers.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +self_path=$(dirname "$(readlink -f "$0")") + +repo_root=$self_path/../../.. + +# Run codegen-verifier for 1_mul +mul_dir=$repo_root/tooling/nargo_cli/tests/execution_success/1_mul +nargo --program-dir $mul_dir codegen-verifier + +# Run codegen-verifier for main +main_dir=$repo_root/compiler/integration-tests/test/circuits/main +nargo --program-dir $main_dir codegen-verifier + +# Copy compiled contracts from the root of compiler/integration-tests +src_dir=$self_path/../foundry-project/src +cp $mul_dir/contract/1_mul/plonk_vk.sol $src_dir/1_mul.sol +cp $main_dir/contract/main/plonk_vk.sol $src_dir/main.sol diff --git a/compiler/integration-tests/scripts/deploy-verifiers.sh b/compiler/integration-tests/scripts/deploy-verifiers.sh new file mode 100644 index 00000000000..b39d26f86aa --- /dev/null +++ b/compiler/integration-tests/scripts/deploy-verifiers.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +self_path=$(dirname "$(readlink -f "$0")") +cd $self_path/../foundry-project + +forge build + +CI=${CI:-false} +if [[ $CI = true ]]; then + anvil > /dev/null 2>&1 & +else + anvil & + pid=$! +fi + +sleep 10 + +forge create --rpc-url http://127.0.0.1:8545 --mnemonic "test test test test test test test test test test test junk" src/1_mul.sol:UltraVerifier --json > mul_output.json +forge create --rpc-url http://127.0.0.1:8545 --mnemonic "test test test test test test test test test test test junk" src/main.sol:UltraVerifier --json > main_output.json + +if [[ $CI = false ]]; then + wait $pid +fi diff --git a/compiler/integration-tests/scripts/forge-init.sh b/compiler/integration-tests/scripts/forge-init.sh new file mode 100644 index 00000000000..e2b86611e92 --- /dev/null +++ b/compiler/integration-tests/scripts/forge-init.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +self_path=$(dirname "$(readlink -f "$0")") + +project_path=$self_path/../foundry-project + +# Create forge project +forge init --no-git --no-commit --no-deps --force $project_path + +# Remove unwanted files +rm -rf $project_path/script +rm -rf $project_path/src/*.sol +rm -rf $project_path/test diff --git a/compiler/integration-tests/scripts/setup-project.sh b/compiler/integration-tests/scripts/setup-project.sh new file mode 100644 index 00000000000..202c53bce1e --- /dev/null +++ b/compiler/integration-tests/scripts/setup-project.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +self_path=$(dirname "$(readlink -f "$0")") + +bash $self_path/forge-init.sh +bash $self_path/codegen-verifiers.sh +bash $self_path/deploy-verifiers.sh diff --git a/compiler/integration-tests/test/circuits/main/Nargo.toml b/compiler/integration-tests/test/circuits/main/Nargo.toml new file mode 100644 index 00000000000..664e817cc84 --- /dev/null +++ b/compiler/integration-tests/test/circuits/main/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "main" +type = "bin" +authors = [""] +compiler_version = "0.9.0" + +[dependencies] diff --git a/compiler/integration-tests/test/circuits/main/Prover.toml b/compiler/integration-tests/test/circuits/main/Prover.toml new file mode 100644 index 00000000000..2c1854573a4 --- /dev/null +++ b/compiler/integration-tests/test/circuits/main/Prover.toml @@ -0,0 +1,2 @@ +x = 1 +y = 2 diff --git a/compiler/integration-tests/test/circuits/main/src/main.nr b/compiler/integration-tests/test/circuits/main/src/main.nr new file mode 100644 index 00000000000..6e170de75fc --- /dev/null +++ b/compiler/integration-tests/test/circuits/main/src/main.nr @@ -0,0 +1,3 @@ +fn main(x : Field, y : pub Field) { + assert(x != y); +} diff --git a/compiler/integration-tests/test/circuits/recursion/Nargo.toml b/compiler/integration-tests/test/circuits/recursion/Nargo.toml new file mode 100644 index 00000000000..5469c07c7de --- /dev/null +++ b/compiler/integration-tests/test/circuits/recursion/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "recursion" +type = "bin" +authors = [""] +compiler_version = "0.9.0" + +[dependencies] diff --git a/compiler/integration-tests/test/circuits/recursion/Prover.toml b/compiler/integration-tests/test/circuits/recursion/Prover.toml new file mode 100644 index 00000000000..fd47e27df04 --- /dev/null +++ b/compiler/integration-tests/test/circuits/recursion/Prover.toml @@ -0,0 +1 @@ +input_aggregation_object = ["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"] \ No newline at end of file diff --git a/compiler/integration-tests/test/circuits/recursion/src/main.nr b/compiler/integration-tests/test/circuits/recursion/src/main.nr new file mode 100644 index 00000000000..ffa3ccef90e --- /dev/null +++ b/compiler/integration-tests/test/circuits/recursion/src/main.nr @@ -0,0 +1,20 @@ +use dep::std; + +fn main( + verification_key : [Field; 114], + proof : [Field; 94], + public_inputs : [Field; 1], + key_hash : Field, + input_aggregation_object : [Field; 16], +) -> pub [Field;16]{ + let vk : [Field] = verification_key; + let p : [Field] = proof; + let pi : [Field] = public_inputs; + std::verify_proof( + vk, + p, + pi, + key_hash, + input_aggregation_object + ) +} diff --git a/compiler/integration-tests/test/environment.js b/compiler/integration-tests/test/environment.js new file mode 100644 index 00000000000..6b5f522aff6 --- /dev/null +++ b/compiler/integration-tests/test/environment.js @@ -0,0 +1 @@ +export const TEST_LOG_LEVEL = 5; // 0: silly, 1: trace, 2: debug, 3: info, 4: warn, 5: error, 6: fatal diff --git a/compiler/integration-tests/test/integration/browser/compile_prove_verify.test.ts b/compiler/integration-tests/test/integration/browser/compile_prove_verify.test.ts index 19f6fc4a804..ea9f4db73a9 100644 --- a/compiler/integration-tests/test/integration/browser/compile_prove_verify.test.ts +++ b/compiler/integration-tests/test/integration/browser/compile_prove_verify.test.ts @@ -1,17 +1,25 @@ import { expect } from "@esm-bundle/chai"; +import { TEST_LOG_LEVEL } from "../../environment.js"; +import { Logger } from "tslog"; import { initializeResolver } from "@noir-lang/source-resolver"; import newCompiler, { compile, init_log_level as compilerLogLevel, } from "@noir-lang/noir_wasm"; +import { acvm, abi } from "@noir-lang/noir_js"; import { Barretenberg, RawBuffer, Crs } from "@aztec/bb.js"; -import { acvm, noirc } from "@noir-lang/noir_js"; import { decompressSync as gunzip } from "fflate"; - +import { ethers } from "ethers"; import * as TOML from "smol-toml"; +const mnemonic = "test test test test test test test test test test test junk"; +const provider = new ethers.JsonRpcProvider("http://localhost:8545"); +const walletMnemonic = ethers.Wallet.fromPhrase(mnemonic); +const wallet = walletMnemonic.connect(provider); +const logger = new Logger({ name: "test", minLevel: TEST_LOG_LEVEL }); + const { default: initACVM, executeCircuit, compressWitness } = acvm; -const { default: newABICoder, abiEncode } = noirc; +const { default: newABICoder, abiEncode } = abi; type WitnessMap = acvm.WitnessMap; @@ -19,7 +27,7 @@ await newCompiler(); await newABICoder(); await initACVM(); -compilerLogLevel("DEBUG"); +compilerLogLevel("INFO"); async function getFile(url: URL): Promise { const response = await fetch(url); @@ -30,13 +38,20 @@ async function getFile(url: URL): Promise { } const CIRCUIT_SIZE = 2 ** 19; +const FIELD_ELEMENT_BYTES = 32; const test_cases = [ { case: "tooling/nargo_cli/tests/execution_success/1_mul", + compiled: "foundry-project/out/1_mul.sol/UltraVerifier.json", + deployInformation: "foundry-project/mul_output.json", + numPublicInputs: 0, }, { - case: "tooling/nargo_cli/tests/execution_success/double_verify_proof", + case: "compiler/integration-tests/test/circuits/main", + compiled: "foundry-project/out/main.sol/UltraVerifier.json", + deployInformation: "foundry-project/main_output.json", + numPublicInputs: 1, }, ]; @@ -48,6 +63,9 @@ suite.timeout(60 * 20e3); //20mins test_cases.forEach((testInfo) => { const test_name = testInfo.case.split("/").pop(); + const caseLogger = logger.getSubLogger({ + prefix: [test_name], + }); const mochaTest = new Mocha.Test( `${test_name} (Compile, Execute, Prove, Verify)`, async () => { @@ -62,14 +80,29 @@ test_cases.forEach((testInfo) => { `${base_relative_path}/${test_case}/Prover.toml`, import.meta.url, ); + const compiled_contract_url = new URL( + `${base_relative_path}/${testInfo.compiled}`, + import.meta.url, + ); + const deploy_information_url = new URL( + `${base_relative_path}/${testInfo.deployInformation}`, + import.meta.url, + ); const noir_source = await getFile(noir_source_url); const prover_toml = await getFile(prover_toml_url); + const compiled_contract = await getFile(compiled_contract_url); + const deploy_information = await getFile(deploy_information_url); + + const { abi } = JSON.parse(compiled_contract); + const { deployedTo } = JSON.parse(deploy_information); + + const contract = new ethers.Contract(deployedTo, abi, wallet); expect(noir_source).to.be.a.string; initializeResolver((id: string) => { - console.log("Resolving:", id); + caseLogger.debug("source-resolver: resolving:", id); return noir_source; }); @@ -121,7 +154,7 @@ test_cases.forEach((testInfo) => { const acirUint8Array = gunzip(compressedByteCode); const witnessUint8Array = gunzip(compressedWitness); - const isRecursive = true; + const isRecursive = false; const api = await Barretenberg.new(numberOfThreads); await api.commonInitSlabAllocator(CIRCUIT_SIZE); @@ -150,7 +183,31 @@ test_cases.forEach((testInfo) => { isRecursive, ); - expect(verified).to.be.true; + expect(verified, "Proof fails verification in JS").to.be.true; + + try { + let result; + if (testInfo.numPublicInputs === 0) { + result = await contract.verify(proof, []); + } else { + const publicInputs = Array.from( + { length: testInfo.numPublicInputs }, + (_, i) => { + const offset = i * FIELD_ELEMENT_BYTES; + return proof.slice(offset, offset + FIELD_ELEMENT_BYTES); + }, + ); + const slicedProof = proof.slice( + testInfo.numPublicInputs * FIELD_ELEMENT_BYTES, + ); + result = await contract.verify(slicedProof, publicInputs); + } + + expect(result).to.be.true; + } catch (error) { + console.error("Error while submitting the proof:", error); + throw error; + } } catch (e) { expect(e, "Proving and Verifying").to.not.be.an("error"); throw e; diff --git a/compiler/integration-tests/test/integration/browser/recursion.test.ts b/compiler/integration-tests/test/integration/browser/recursion.test.ts new file mode 100644 index 00000000000..41c807f1ca0 --- /dev/null +++ b/compiler/integration-tests/test/integration/browser/recursion.test.ts @@ -0,0 +1,221 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { expect } from "@esm-bundle/chai"; +import { TEST_LOG_LEVEL } from "../../environment.js"; +import { Logger } from "tslog"; +import { initializeResolver } from "@noir-lang/source-resolver"; +import newCompiler, { + compile, + init_log_level as compilerLogLevel, +} from "@noir-lang/noir_wasm"; +import { decompressSync as gunzip } from "fflate"; +import { acvm, abi, generateWitness } from "@noir-lang/noir_js"; + +// @ts-ignore +import { Barretenberg, RawBuffer, Crs } from "@aztec/bb.js"; + +import * as TOML from "smol-toml"; + +const logger = new Logger({ name: "test", minLevel: TEST_LOG_LEVEL }); + +const { default: initACVM } = acvm; +const { default: newABICoder } = abi; + +await newCompiler(); +await newABICoder(); +await initACVM(); + +compilerLogLevel("INFO"); + +const numberOfThreads = navigator.hardwareConcurrency || 1; + +const base_relative_path = "../../../../.."; +const circuit_main = "compiler/integration-tests/test/circuits/main"; +const circuit_recursion = "compiler/integration-tests/test/circuits/recursion"; + +async function getFile(url: URL): Promise { + const response = await fetch(url); + + if (!response.ok) throw new Error("Network response was not OK"); + + return await response.text(); +} + +const CIRCUIT_SIZE = 2 ** 19; + +const api = await Barretenberg.new(numberOfThreads); +await api.commonInitSlabAllocator(CIRCUIT_SIZE); +// Plus 1 needed! +const crs = await Crs.new(CIRCUIT_SIZE + 1); +await api.srsInitSrs( + new RawBuffer(crs.getG1Data()), + crs.numPoints, + new RawBuffer(crs.getG2Data()), +); + +const acirComposer = await api.acirNewAcirComposer(CIRCUIT_SIZE); + +async function getCircuit(noirSource: string) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + initializeResolver((id: string) => { + logger.debug("source-resolver: resolving:", id); + return noirSource; + }); + + return compile({}); +} + +async function generateProof( + acirUint8Array: Uint8Array, + witnessUint8Array: Uint8Array, + optimizeForRecursion: boolean, +) { + // This took ~6.5 minutes! + return api.acirCreateProof( + acirComposer, + acirUint8Array, + witnessUint8Array, + optimizeForRecursion, + ); +} + +async function verifyProof(proof: Uint8Array, optimizeForRecursion: boolean) { + await api.acirInitVerificationKey(acirComposer); + const verified = await api.acirVerifyProof( + acirComposer, + proof, + optimizeForRecursion, + ); + return verified; +} + +describe("It compiles noir program code, receiving circuit bytes and abi object.", () => { + let circuit_main_source; + let circuit_main_toml; + let circuit_recursion_source; + + before(async () => { + const circuit_main_source_url = new URL( + `${base_relative_path}/${circuit_main}/src/main.nr`, + import.meta.url, + ); + const circuit_main_toml_url = new URL( + `${base_relative_path}/${circuit_main}/Prover.toml`, + import.meta.url, + ); + + circuit_main_source = await getFile(circuit_main_source_url); + circuit_main_toml = await getFile(circuit_main_toml_url); + + const circuit_recursion_source_url = new URL( + `${base_relative_path}/${circuit_recursion}/src/main.nr`, + import.meta.url, + ); + + circuit_recursion_source = await getFile(circuit_recursion_source_url); + }); + + it("Should generate valid inner proof for correct input, then verify proof within a proof", async () => { + const { circuit: main_circuit, abi: main_abi } = + await getCircuit(circuit_main_source); + const main_inputs = TOML.parse(circuit_main_toml); + + const main_witnessUint8Array = await generateWitness( + { + bytecode: main_circuit, + abi: main_abi, + }, + main_inputs, + ); + const main_compressedByteCode = Uint8Array.from(atob(main_circuit), (c) => + c.charCodeAt(0), + ); + const main_acirUint8Array = gunzip(main_compressedByteCode); + + const optimizeMainProofForRecursion = true; + + const main_proof = await generateProof( + main_acirUint8Array, + main_witnessUint8Array, + optimizeMainProofForRecursion, + ); + + const main_verification = await verifyProof( + main_proof, + optimizeMainProofForRecursion, + ); + + logger.debug("main_verification", main_verification); + + expect(main_verification).to.be.true; + + const numPublicInputs = 1; + const proofAsFields = ( + await api.acirSerializeProofIntoFields( + acirComposer, + main_proof, + numPublicInputs, + ) + ).map((p) => p.toString()); + + const vk = await api.acirSerializeVerificationKeyIntoFields(acirComposer); + + const vkAsFields = vk[0].map((vk) => vk.toString()); + const vkHash = vk[1].toString(); + + const recursion_inputs = { + verification_key: vkAsFields, + proof: proofAsFields, + public_inputs: [main_inputs.y], + key_hash: vkHash, + // eslint-disable-next-line prettier/prettier + input_aggregation_object: ["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"], + }; + + logger.debug("recursion_inputs", recursion_inputs); + + const { circuit: recursion_circuit, abi: recursion_abi } = await getCircuit( + circuit_recursion_source, + ); + + const recursion_witnessUint8Array = await generateWitness( + { + bytecode: recursion_circuit, + abi: recursion_abi, + }, + recursion_inputs, + ); + + const recursion_compressedByteCode = Uint8Array.from( + atob(recursion_circuit), + (c) => c.charCodeAt(0), + ); + + const recursion_acirUint8Array = gunzip(recursion_compressedByteCode); + + const optimizeRecursionProofForRecursion = false; + + const recursion_proof = await generateProof( + recursion_acirUint8Array, + recursion_witnessUint8Array, + optimizeRecursionProofForRecursion, + ); + + const recursion_numPublicInputs = 1; + + const recursion_proofAsFields = ( + await api.acirSerializeProofIntoFields( + acirComposer, + recursion_proof, + recursion_numPublicInputs, + ) + ).map((p) => p.toString()); + + logger.debug("recursion_proofAsFields", recursion_proofAsFields); + + const recursion_verification = await verifyProof(recursion_proof, false); + + logger.debug("recursion_verification", recursion_verification); + + expect(recursion_verification).to.be.true; + }).timeout(60 * 20e3); +}); diff --git a/compiler/integration-tests/web-test-runner.config.mjs b/compiler/integration-tests/web-test-runner.config.mjs index 4015da770b2..d8388dac091 100644 --- a/compiler/integration-tests/web-test-runner.config.mjs +++ b/compiler/integration-tests/web-test-runner.config.mjs @@ -4,8 +4,20 @@ import { fileURLToPath } from "url"; import { esbuildPlugin } from "@web/dev-server-esbuild"; import { webdriverLauncher } from "@web/test-runner-webdriver"; +let reporter = summaryReporter(); +const debugPlugins = []; // eslint-disable-next-line no-undef -const reporter = process.env.CI ? summaryReporter() : defaultReporter(); +if (process.env.CI !== "true" || process.env.RUNNER_DEBUG === "1") { + reporter = defaultReporter(); + debugPlugins.push({ + name: "environment", + serve(context) { + if (context.path === "/compiler/integration-tests/test/environment.js") { + return "export const TEST_LOG_LEVEL = 2;"; + } + }, + }); +} export default { browsers: [ @@ -23,6 +35,7 @@ export default { esbuildPlugin({ ts: true, }), + ...debugPlugins, ], files: ["test/integration/browser/**/*.test.ts"], nodeResolve: { browser: true }, diff --git a/compiler/noirc_driver/src/contract.rs b/compiler/noirc_driver/src/contract.rs index 69a92764318..a16da313ff6 100644 --- a/compiler/noirc_driver/src/contract.rs +++ b/compiler/noirc_driver/src/contract.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use acvm::acir::circuit::Circuit; use fm::FileId; -use noirc_abi::Abi; +use noirc_abi::{Abi, ContractEvent}; use noirc_errors::debug_info::DebugInfo; use super::debug::DebugFile; @@ -34,6 +34,11 @@ pub struct CompiledContract { /// stored in this `Vector`. pub functions: Vec, + /// All the events defined inside the contract scope. + /// An event is a struct value that can be emitted via oracles + /// by any contract function during execution. + pub events: Vec, + pub file_map: BTreeMap, } diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index 8d74825a9b6..381a29ffcda 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -6,7 +6,7 @@ use clap::Args; use debug::filter_relevant_files; use fm::FileId; -use noirc_abi::{AbiParameter, AbiType}; +use noirc_abi::{AbiParameter, AbiType, ContractEvent}; use noirc_errors::{CustomDiagnostic, FileDiagnostic}; use noirc_evaluator::{create_circuit, into_abi_params}; use noirc_frontend::graph::{CrateId, CrateName}; @@ -112,7 +112,11 @@ pub fn check_crate( deny_warnings: bool, ) -> CompilationResult<()> { let mut errors = vec![]; - CrateDefMap::collect_defs(crate_id, context, &mut errors); + let diagnostics = CrateDefMap::collect_defs(crate_id, context); + errors.extend(diagnostics.into_iter().map(|(error, file_id)| { + let diagnostic: CustomDiagnostic = error.into(); + diagnostic.in_file(file_id) + })); if has_errors(&errors, deny_warnings) { Err(errors) @@ -281,7 +285,20 @@ fn compile_contract_inner( let debug_infos: Vec<_> = functions.iter().map(|function| function.debug.clone()).collect(); let file_map = filter_relevant_files(&debug_infos, &context.file_manager); - Ok(CompiledContract { name: contract.name, functions, file_map }) + Ok(CompiledContract { + name: contract.name, + events: contract + .events + .iter() + .map(|event_id| { + let typ = context.def_interner.get_struct(*event_id); + let typ = typ.borrow(); + ContractEvent::from_struct_type(context, &typ) + }) + .collect(), + functions, + file_map, + }) } else { Err(errors) } @@ -301,9 +318,14 @@ pub fn compile_no_check( let program = monomorphize(main_function, &context.def_interner); let hash = fxhash::hash64(&program); - if let Some(cached_program) = cached_program { - if hash == cached_program.hash { - return Ok(cached_program); + + // If user has specified that they want to see intermediate steps printed then we should + // force compilation even if the program hasn't changed. + if !(options.print_acir || options.show_brillig || options.show_ssa) { + if let Some(cached_program) = cached_program { + if hash == cached_program.hash { + return Ok(cached_program); + } } } diff --git a/compiler/noirc_errors/src/reporter.rs b/compiler/noirc_errors/src/reporter.rs index d695b2007bc..ba5ac450f56 100644 --- a/compiler/noirc_errors/src/reporter.rs +++ b/compiler/noirc_errors/src/reporter.rs @@ -99,7 +99,7 @@ impl std::fmt::Display for CustomDiagnostic { #[derive(Debug, Clone, PartialEq, Eq)] pub struct CustomLabel { - message: String, + pub message: String, pub span: Span, } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index eb2eb1e5f24..9d2a7245e87 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -1034,29 +1034,12 @@ impl<'block> BrilligBlock<'block> { let binary_type = type_of_binary_operation(dfg[binary.lhs].get_type(), dfg[binary.rhs].get_type()); - let mut left = self.convert_ssa_register_value(binary.lhs, dfg); - let mut right = self.convert_ssa_register_value(binary.rhs, dfg); + let left = self.convert_ssa_register_value(binary.lhs, dfg); + let right = self.convert_ssa_register_value(binary.rhs, dfg); let brillig_binary_op = convert_ssa_binary_op_to_brillig_binary_op(binary.operator, &binary_type); - // Some binary operations with fields are issued by the compiler, such as loop comparisons, cast those to the bit size here - // TODO Remove after fixing https://github.com/noir-lang/noir/issues/1979 - if let ( - BrilligBinaryOp::Integer { bit_size, .. }, - Type::Numeric(NumericType::NativeField), - ) = (&brillig_binary_op, &binary_type) - { - let new_lhs = self.brillig_context.allocate_register(); - let new_rhs = self.brillig_context.allocate_register(); - - self.brillig_context.cast_instruction(new_lhs, left, *bit_size); - self.brillig_context.cast_instruction(new_rhs, right, *bit_size); - - left = new_lhs; - right = new_rhs; - } - self.brillig_context.binary_instruction(left, right, result_register, brillig_binary_op); } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs index ee7bbeed46e..8b5ff8e9cbd 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs @@ -39,40 +39,31 @@ pub(crate) fn directive_invert() -> GeneratedBrillig { } /// Generates brillig bytecode which computes `a / b` and returns the quotient and remainder. -/// It returns `(0,0)` if the predicate is null. -/// /// /// This is equivalent to the Noir (psuedo)code /// /// ```ignore -/// fn quotient(a: T, b: T, predicate: bool) -> (T,T) { -/// if predicate != 0 { -/// (a/b, a-a/b*b) -/// } else { -/// (0,0) -/// } +/// fn quotient(a: T, b: T) -> (T,T) { +/// (a/b, a-a/b*b) /// } /// ``` pub(crate) fn directive_quotient(bit_size: u32) -> GeneratedBrillig { // `a` is (0) (i.e register index 0) // `b` is (1) - // `predicate` is (2) GeneratedBrillig { byte_code: vec![ - // If the predicate is zero, we jump to the exit segment - BrilligOpcode::JumpIfNot { condition: RegisterIndex::from(2), location: 6 }, - //q = a/b is set into register (3) + //q = a/b is set into register (2) BrilligOpcode::BinaryIntOp { op: BinaryIntOp::UnsignedDiv, lhs: RegisterIndex::from(0), rhs: RegisterIndex::from(1), - destination: RegisterIndex::from(3), + destination: RegisterIndex::from(2), bit_size, }, //(1)= q*b BrilligOpcode::BinaryIntOp { op: BinaryIntOp::Mul, - lhs: RegisterIndex::from(3), + lhs: RegisterIndex::from(2), rhs: RegisterIndex::from(1), destination: RegisterIndex::from(1), bit_size, @@ -88,17 +79,7 @@ pub(crate) fn directive_quotient(bit_size: u32) -> GeneratedBrillig { //(0) = q BrilligOpcode::Mov { destination: RegisterIndex::from(0), - source: RegisterIndex::from(3), - }, - BrilligOpcode::Stop, - // Exit segment: we return 0,0 - BrilligOpcode::Const { - destination: RegisterIndex::from(0), - value: Value::from(0_usize), - }, - BrilligOpcode::Const { - destination: RegisterIndex::from(1), - value: Value::from(0_usize), + source: RegisterIndex::from(2), }, BrilligOpcode::Stop, ], diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index b5461269f4a..0567118b419 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -61,18 +61,9 @@ impl AcirType { AcirType::NumericType(NumericType::NativeField) } - /// Returns a boolean type - fn boolean() -> Self { - AcirType::NumericType(NumericType::Unsigned { bit_size: 1 }) - } - - /// True if type is signed - pub(crate) fn is_signed(&self) -> bool { - let numeric_type = match self { - AcirType::NumericType(numeric_type) => numeric_type, - AcirType::Array(_, _) => return false, - }; - matches!(numeric_type, NumericType::Signed { .. }) + /// Returns an unsigned type of the specified bit size + pub(crate) fn unsigned(bit_size: u32) -> Self { + AcirType::NumericType(NumericType::Unsigned { bit_size }) } } @@ -90,7 +81,7 @@ impl<'a> From<&'a SsaType> for AcirType { let elements = elements.iter().map(|e| e.into()).collect(); AcirType::Array(elements, *size) } - _ => unreachable!("The type {value} cannot be represented in ACIR"), + _ => unreachable!("The type {value} cannot be represented in ACIR"), } } } @@ -294,13 +285,12 @@ impl AcirContext { // Compute the inverse with brillig code let inverse_code = brillig_directive::directive_invert(); - let field_type = AcirType::NumericType(NumericType::NativeField); let results = self.brillig( predicate, inverse_code, - vec![AcirValue::Var(var, field_type.clone())], - vec![field_type], + vec![AcirValue::Var(var, AcirType::field())], + vec![AcirType::field()], )?; let inverted_var = Self::expect_one_var(results); @@ -312,17 +302,6 @@ impl AcirContext { Ok(inverted_var) } - // Constrains `var` to be equal to the constant value `1` - pub(crate) fn assert_eq_one( - &mut self, - var: AcirVar, - assert_message: Option, - ) -> Result<(), RuntimeError> { - let one = self.add_constant(FieldElement::one()); - self.assert_eq_var(var, one, assert_message)?; - Ok(()) - } - // Constrains `var` to be equal to predicate if the predicate is true // or to be equal to 0 if the predicate is false. // @@ -552,31 +531,6 @@ impl AcirContext { self.sub_var(max, x) } - /// Returns an `AcirVar` that is constrained to be `lhs << rhs`. - /// - /// We convert left shifts to multiplications, so this is equivalent to - /// `lhs * 2^rhs`. - /// - /// We currently require `rhs` to be a constant - /// however this can be extended, see #1478. - pub(crate) fn shift_left_var( - &mut self, - lhs: AcirVar, - rhs: AcirVar, - _typ: AcirType, - ) -> Result { - let rhs_data = &self.vars[&rhs]; - - // Compute 2^{rhs} - let two_pow_rhs = match rhs_data.as_constant() { - Some(exponent) => FieldElement::from(2_i128).pow(&exponent), - None => unimplemented!("rhs must be a constant when doing a right shift"), - }; - let two_pow_rhs_var = self.add_constant(two_pow_rhs); - - self.mul_var(lhs, two_pow_rhs_var) - } - /// Returns the quotient and remainder such that lhs = rhs * quotient + remainder fn euclidean_division_var( &mut self, @@ -630,35 +584,6 @@ impl AcirContext { Ok(remainder) } - /// Returns an `AcirVar` that is constrained to be `lhs >> rhs`. - /// - /// We convert right shifts to divisions, so this is equivalent to - /// `lhs / 2^rhs`. - /// - /// We currently require `rhs` to be a constant - /// however this can be extended, see #1478. - /// - /// This code is doing a field division instead of an integer division, - /// see #1479 about how this is expected to change. - pub(crate) fn shift_right_var( - &mut self, - lhs: AcirVar, - rhs: AcirVar, - typ: AcirType, - predicate: AcirVar, - ) -> Result { - let rhs_data = &self.vars[&rhs]; - - // Compute 2^{rhs} - let two_pow_rhs = match rhs_data.as_constant() { - Some(exponent) => FieldElement::from(2_i128).pow(&exponent), - None => unimplemented!("rhs must be a constant when doing a right shift"), - }; - let two_pow_rhs_var = self.add_constant(two_pow_rhs); - - self.div_var(lhs, two_pow_rhs_var, typ, predicate) - } - /// Converts the `AcirVar` to a `Witness` if it hasn't been already, and appends it to the /// `GeneratedAcir`'s return witnesses. pub(crate) fn return_var(&mut self, acir_var: AcirVar) -> Result<(), InternalError> { @@ -892,17 +817,6 @@ impl AcirContext { self.radix_decompose(endian, input_var, two_var, limb_count_var, result_element_type) } - /// Flatten the given Vector of AcirValues into a single vector of only variables. - /// Each AcirValue::Array in the vector is recursively flattened, so each element - /// will flattened into the resulting Vec. E.g. flatten_values([1, [2, 3]) == [1, 2, 3]. - fn flatten_values(values: Vec) -> Vec { - let mut acir_vars = Vec::with_capacity(values.len()); - for value in values { - Self::flatten_value(&mut acir_vars, value); - } - acir_vars - } - /// Recursive helper for flatten_values to flatten a single AcirValue into the result vector. pub(crate) fn flatten_value(acir_vars: &mut Vec, value: AcirValue) { match value { @@ -1002,17 +916,10 @@ impl AcirContext { AcirValue::DynamicArray(AcirDynamicArray { block_id, len }) => { for i in 0..len { // We generate witnesses corresponding to the array values - let index = AcirValue::Var( - self.add_constant(FieldElement::from(i as u128)), - AcirType::NumericType(NumericType::NativeField), - ); - let index_var = index.into_var()?; + let index_var = self.add_constant(FieldElement::from(i as u128)); let value_read_var = self.read_from_memory(block_id, &index_var)?; - let value_read = AcirValue::Var( - value_read_var, - AcirType::NumericType(NumericType::NativeField), - ); + let value_read = AcirValue::Var(value_read_var, AcirType::field()); self.brillig_array_input(var_expressions, value_read)?; } @@ -1203,35 +1110,44 @@ impl AcirContext { &mut self, block_id: BlockId, len: usize, - optional_values: Option<&[AcirValue]>, + optional_value: Option, ) -> Result<(), InternalError> { - // If the optional values are supplied, then we fill the initialized - // array with those values. If not, then we fill it with zeros. - let mut nested = false; - let initialized_values = match optional_values { + let initialized_values = match optional_value { None => { let zero = self.add_constant(FieldElement::zero()); let zero_witness = self.var_to_witness(zero)?; vec![zero_witness; len] } - Some(optional_values) => { + Some(optional_value) => { let mut values = Vec::new(); - for value in optional_values { - if let Ok(some_value) = value.clone().into_var() { - values.push(self.var_to_witness(some_value)?); - } else { - nested = true; - break; - } - } + self.initialize_array_inner(&mut values, optional_value)?; values } }; - // we do not initialize nested arrays. This means that non-const indexes are not supported for nested arrays - if !nested { - self.acir_ir.push_opcode(Opcode::MemoryInit { block_id, init: initialized_values }); - } + self.acir_ir.push_opcode(Opcode::MemoryInit { block_id, init: initialized_values }); + + Ok(()) + } + + fn initialize_array_inner( + &mut self, + witnesses: &mut Vec, + input: AcirValue, + ) -> Result<(), InternalError> { + match input { + AcirValue::Var(var, _) => { + witnesses.push(self.var_to_witness(var)?); + } + AcirValue::Array(values) => { + for value in values { + self.initialize_array_inner(witnesses, value)?; + } + } + AcirValue::DynamicArray(_) => { + unreachable!("Dynamic array should already be initialized"); + } + } Ok(()) } } diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 0adb14246b7..a2a85498f16 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -544,11 +544,7 @@ impl GeneratedAcir { let r_witness = self.next_witness_index(); let quotient_code = brillig_directive::directive_quotient(max_bit_size); - let inputs = vec![ - BrilligInputs::Single(lhs), - BrilligInputs::Single(rhs), - BrilligInputs::Single(predicate.clone()), - ]; + let inputs = vec![BrilligInputs::Single(lhs), BrilligInputs::Single(rhs)]; let outputs = vec![BrilligOutputs::Simple(q_witness), BrilligOutputs::Simple(r_witness)]; self.brillig(Some(predicate), quotient_code, inputs, outputs); @@ -581,10 +577,6 @@ impl GeneratedAcir { num_bits::() as u32 - a.leading_zeros() } - fn bit_size_u32(a: u32) -> u32 where { - num_bits::() as u32 - a.leading_zeros() - } - assert!( bits < FieldElement::max_num_bits(), "range check with bit size of the prime field is not implemented yet" @@ -604,10 +596,12 @@ impl GeneratedAcir { // we now have lhs+offset <= rhs <=> lhs_offset <= rhs_offset let bit_size = bit_size_u128(rhs_offset); - // r = 2^bit_size - rhs_offset + // r = 2^bit_size - rhs_offset -1, is of bit size 'bit_size' by construtction let r = (1_u128 << bit_size) - rhs_offset - 1; + // however, since it is a constant, we can compute it's actual bit size + let r_bit_size = bit_size_u128(r); // witness = lhs_offset + r - assert!(bits + bit_size < FieldElement::max_num_bits()); //we need to ensure lhs_offset + r does not overflow + assert!(bits + r_bit_size < FieldElement::max_num_bits()); //we need to ensure lhs_offset + r does not overflow let mut aor = lhs_offset; aor.q_c += FieldElement::from(r); let witness = self.get_or_create_witness(&aor); @@ -615,7 +609,6 @@ impl GeneratedAcir { self.range_constraint(witness, bit_size)?; return Ok(()); } - // General case: lhs_offset<=rhs <=> rhs-lhs_offset>=0 <=> rhs-lhs_offset is a 'bits' bit integer let sub_expression = rhs - &lhs_offset; //rhs-lhs_offset let w = self.create_witness_for_expression(&sub_expression); @@ -624,21 +617,6 @@ impl GeneratedAcir { Ok(()) } - /// Computes the expression x(x-1) - /// - /// If the above is constrained to zero, then it can only be - /// true, iff x equals zero or one. - fn boolean_expr(&mut self, expr: &Expression) -> Expression { - let expr_as_witness = self.create_witness_for_expression(expr); - let mut expr_squared = Expression::default(); - expr_squared.push_multiplication_term( - FieldElement::one(), - expr_as_witness, - expr_as_witness, - ); - &expr_squared - expr - } - /// Adds an inversion brillig opcode. /// /// This code will invert `expr` without applying constraints diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index d5eef00d461..d16985eb31c 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -65,6 +65,12 @@ struct Context { /// Each acir memory block corresponds to a different SSA array. memory_blocks: HashMap, BlockId>, + /// Maps SSA values to a BlockId used internally + /// A BlockId is an ACIR structure which identifies a memory block + /// Each memory blocks corresponds to a different SSA value + /// which utilizes this internal memory for ACIR generation. + internal_memory_blocks: HashMap, BlockId>, + /// Number of the next BlockId, it is used to construct /// a new BlockId max_block_id: u32, @@ -154,6 +160,7 @@ impl Context { acir_context, initialized_arrays: HashSet::new(), memory_blocks: HashMap::default(), + internal_memory_blocks: HashMap::default(), max_block_id: 0, } } @@ -244,8 +251,12 @@ impl Context { AcirValue::Var(_, _) => (), AcirValue::Array(values) => { let block_id = self.block_id(param_id); - let v = vecmap(values, |v| v.clone()); - self.initialize_array(block_id, values.len(), Some(&v))?; + let len = if matches!(typ, Type::Array(_, _)) { + typ.flattened_size() + } else { + values.len() + }; + self.initialize_array(block_id, len, Some(value.clone()))?; } AcirValue::DynamicArray(_) => unreachable!( "The dynamic array type is created in Acir gen and therefore cannot be a block parameter" @@ -298,6 +309,21 @@ impl Context { block_id } + /// Get the next BlockId for internal memory + /// used during ACIR generation. + /// This is useful for referencing information that can + /// only be computed dynamically, such as the type structure + /// of non-homogenous arrays. + fn internal_block_id(&mut self, value: &ValueId) -> BlockId { + if let Some(block_id) = self.internal_memory_blocks.get(value) { + return *block_id; + } + let block_id = BlockId(self.max_block_id); + self.max_block_id += 1; + self.internal_memory_blocks.insert(*value, block_id); + block_id + } + /// Creates an `AcirVar` corresponding to a parameter witness to appears in the abi. A range /// constraint is added if the numeric type requires it. /// @@ -374,11 +400,8 @@ impl Context { let mut read_dynamic_array_index = |block_id: BlockId, array_index: usize| -> Result { - let index = AcirValue::Var( - self.acir_context.add_constant(FieldElement::from(array_index as u128)), - AcirType::NumericType(NumericType::NativeField), - ); - let index_var = index.into_var()?; + let index_var = + self.acir_context.add_constant(FieldElement::from(array_index as u128)); self.acir_context.read_from_memory(block_id, &index_var) }; @@ -415,10 +438,11 @@ impl Context { assert_eq!(result_ids.len(), output_values.len(), "ICE: The number of Brillig output values should match the result ids in SSA"); for result in result_ids.iter().zip(output_values) { - if let AcirValue::Array(values) = &result.1 { - let block_id = self.block_id(&dfg.resolve(*result.0)); - let values: Vec = values.iter().cloned().collect(); - self.initialize_array(block_id, values.len(), Some(&values))?; + if let AcirValue::Array(_) = &result.1 { + let array_id = dfg.resolve(*result.0); + let block_id = self.block_id(&array_id); + let array_typ = dfg.type_of_value(array_id); + self.initialize_array(block_id, array_typ.flattened_size(), Some(result.1.clone()))?; } self.ssa_values.insert(*result.0, result.1); } @@ -440,8 +464,13 @@ impl Context { AcirValue::Array(values) => { let block_id = self.block_id(result); let values = vecmap(values, |v| v.clone()); - - self.initialize_array(block_id, values.len(), Some(&values))?; + let array_typ = dfg.type_of_value(*result); + let len = if matches!(array_typ, Type::Array(_, _)) { + array_typ.flattened_size() + } else { + values.len() + }; + self.initialize_array(block_id, len, Some(output.clone()))?; } AcirValue::DynamicArray(_) => { unreachable!("The output from an intrinsic call is expected to be a single value or an array but got {output:?}"); @@ -545,36 +574,36 @@ impl Context { } }; - // We compute some AcirVars: - // - index_var is the index of the array - // - predicate_index is 0, or the index if the predicate is true - // - new_value is the optional value when the operation is an array_set - // When there is a predicate, it is predicate*value + (1-predicate)*dummy, where dummy is the value of the array at the requested index. - // It is a dummy value because in the case of a false predicate, the value stored at the requested index will be itself. - let index_const = dfg.get_numeric_constant(index); - let index_var = self.convert_numeric_value(index, dfg)?; - let predicate_index = - self.acir_context.mul_var(index_var, self.current_side_effects_enabled_var)?; - let new_value = if let Some(store) = store_value { - let store_var = self.convert_value(store, dfg).into_var()?; - if self.acir_context.is_constant_one(&self.current_side_effects_enabled_var) { - Some(store_var) - } else { - let dummy = self.array_get(instruction, array, predicate_index, dfg)?; - let true_pred = - self.acir_context.mul_var(store_var, self.current_side_effects_enabled_var)?; - let one = self.acir_context.add_constant(FieldElement::one()); - let not_pred = - self.acir_context.sub_var(one, self.current_side_effects_enabled_var)?; - let false_pred = self.acir_context.mul_var(not_pred, dummy)?; - // predicate*value + (1-predicate)*dummy - Some(self.acir_context.add_var(true_pred, false_pred)?) - } + if self.handle_constant_index(instruction, dfg, index, array, store_value)? { + return Ok(()); + } + + let (new_index, new_value) = + self.convert_array_operation_inputs(array, dfg, index, store_value)?; + + let resolved_array = dfg.resolve(array); + let map_array = last_array_uses.get(&resolved_array) == Some(&instruction); + + if let Some(new_value) = new_value { + self.array_set(instruction, new_index, new_value, dfg, map_array)?; } else { - None - }; + self.array_get(instruction, array, new_index, dfg)?; + } - // Handle constant index: if there is no predicate and we have the array values, we can perform the operation directly on the array + Ok(()) + } + + /// Handle constant index: if there is no predicate and we have the array values, + /// we can perform the operation directly on the array + fn handle_constant_index( + &mut self, + instruction: InstructionId, + dfg: &DataFlowGraph, + index: ValueId, + array: ValueId, + store_value: Option, + ) -> Result { + let index_const = dfg.get_numeric_constant(index); match dfg.type_of_value(array) { Type::Array(_, _) => { match self.convert_value(array, dfg) { @@ -621,13 +650,13 @@ impl Context { }; self.define_result(dfg, instruction, value); - return Ok(()); + return Ok(true); } } // If there is a predicate and the index is not out of range, we can directly perform the read else if index < array_size && store_value.is_none() { self.define_result(dfg, instruction, array[index].clone()); - return Ok(()); + return Ok(true); } } } @@ -635,10 +664,58 @@ impl Context { } } Type::Slice(_) => { - // Do nothing we only want dynamic checks here + // Do nothing we only want dynamic checks for slices } _ => unreachable!("ICE: expected array or slice type"), } + Ok(false) + } + + /// We need to properly setup the inputs for array operations in ACIR. + /// From the original SSA values we compute the following AcirVars: + /// - index_var is the index of the array + /// - predicate_index is 0, or the index if the predicate is true + /// - new_value is the optional value when the operation is an array_set + /// When there is a predicate, it is predicate*value + (1-predicate)*dummy, where dummy is the value of the array at the requested index. + /// It is a dummy value because in the case of a false predicate, the value stored at the requested index will be itself. + fn convert_array_operation_inputs( + &mut self, + array: ValueId, + dfg: &DataFlowGraph, + index: ValueId, + store_value: Option, + ) -> Result<(AcirVar, Option), RuntimeError> { + let (array_id, array_typ, block_id) = self.check_array_is_initialized(array, dfg)?; + + let index_var = self.convert_numeric_value(index, dfg)?; + // TODO(#2752): Need to add support for dynamic indices with non-homogenous slices + let index_var = if matches!(array_typ, Type::Array(_, _)) { + let array_len = dfg.try_get_array_length(array_id).expect("ICE: expected an array"); + self.get_flattened_index(&array_typ, array_id, array_len, index_var)? + } else { + index_var + }; + + let predicate_index = + self.acir_context.mul_var(index_var, self.current_side_effects_enabled_var)?; + + let new_value = if let Some(store) = store_value { + let store_value = self.convert_value(store, dfg); + if self.acir_context.is_constant_one(&self.current_side_effects_enabled_var) { + Some(store_value) + } else { + let store_type = dfg.type_of_value(store); + + let mut dummy_predicate_index = predicate_index; + // We must setup the dummy value to match the type of the value we wish to store + let dummy = + self.array_get_value(&store_type, block_id, &mut dummy_predicate_index)?; + + Some(self.convert_array_set_store_value(&store_value, &dummy)?) + } + } else { + None + }; let new_index = if self.acir_context.is_constant_one(&self.current_side_effects_enabled_var) { @@ -647,16 +724,47 @@ impl Context { predicate_index }; - let resolved_array = dfg.resolve(array); - let map_array = last_array_uses.get(&resolved_array) == Some(&instruction); + Ok((new_index, new_value)) + } - if let Some(new_value) = new_value { - self.array_set(instruction, new_index, new_value, dfg, map_array)?; - } else { - self.array_get(instruction, array, new_index, dfg)?; - } + fn convert_array_set_store_value( + &mut self, + store_value: &AcirValue, + dummy_value: &AcirValue, + ) -> Result { + match (store_value, dummy_value) { + (AcirValue::Var(store_var, _), AcirValue::Var(dummy_var, _)) => { + let true_pred = + self.acir_context.mul_var(*store_var, self.current_side_effects_enabled_var)?; + let one = self.acir_context.add_constant(FieldElement::one()); + let not_pred = + self.acir_context.sub_var(one, self.current_side_effects_enabled_var)?; + let false_pred = self.acir_context.mul_var(not_pred, *dummy_var)?; + // predicate*value + (1-predicate)*dummy + let new_value = self.acir_context.add_var(true_pred, false_pred)?; + Ok(AcirValue::Var(new_value, AcirType::field())) + } + (AcirValue::Array(values), AcirValue::Array(dummy_values)) => { + let mut elements = im::Vector::new(); - Ok(()) + assert_eq!( + values.len(), + dummy_values.len(), + "ICE: The store value and dummy must have the same number of inner values" + ); + for (val, dummy_val) in values.iter().zip(dummy_values) { + elements.push_back(self.convert_array_set_store_value(val, dummy_val)?); + } + + Ok(AcirValue::Array(elements)) + } + (AcirValue::DynamicArray(_), AcirValue::DynamicArray(_)) => { + unimplemented!("ICE: setting a dynamic array not supported"); + } + _ => { + unreachable!("ICE: The store value and dummy value must match"); + } + } } /// Generates a read opcode for the array @@ -664,52 +772,59 @@ impl Context { &mut self, instruction: InstructionId, array: ValueId, - var_index: AcirVar, + mut var_index: AcirVar, dfg: &DataFlowGraph, - ) -> Result { - let array = dfg.resolve(array); - let block_id = self.block_id(&array); - if !self.initialized_arrays.contains(&block_id) { - match &dfg[array] { - Value::Array { array, .. } => { - let values: Vec = - array.iter().map(|i| self.convert_value(*i, dfg)).collect(); - self.initialize_array(block_id, array.len(), Some(&values))?; - } - _ => { - return Err(RuntimeError::UnInitialized { - name: "array".to_string(), - call_stack: self.acir_context.get_call_stack(), - }); - } - } - } + ) -> Result { + let (_, _, block_id) = self.check_array_is_initialized(array, dfg)?; + + let results = dfg.instruction_results(instruction); + let res_typ = dfg.type_of_value(results[0]); - let read = self.acir_context.read_from_memory(block_id, &var_index)?; - let typ = match dfg.type_of_value(array) { - Type::Array(typ, _) => { - if typ.len() != 1 { - // TODO(#2461) - unimplemented!( - "Non-const array indices is not implemented for non-homogenous array" - ); + let value = self.array_get_value(&res_typ, block_id, &mut var_index)?; + + self.define_result(dfg, instruction, value.clone()); + + Ok(value) + } + + fn array_get_value( + &mut self, + ssa_type: &Type, + block_id: BlockId, + var_index: &mut AcirVar, + ) -> Result { + let one = self.acir_context.add_constant(FieldElement::one()); + match ssa_type.clone() { + Type::Numeric(numeric_type) => { + // Read the value from the array at the specified index + let read = self.acir_context.read_from_memory(block_id, var_index)?; + + // Incremement the var_index in case of a nested array + *var_index = self.acir_context.add_var(*var_index, one)?; + + let typ = AcirType::NumericType(numeric_type); + Ok(AcirValue::Var(read, typ)) + } + Type::Array(element_types, len) => { + let mut values = Vector::new(); + for _ in 0..len { + for typ in element_types.as_ref() { + values.push_back(self.array_get_value(typ, block_id, var_index)?); + } } - typ[0].clone() - } - Type::Slice(typ) => { - if typ.len() != 1 { - // TODO(#2461) - unimplemented!( - "Non-const array indices is not implemented for non-homogenous array" - ); + Ok(AcirValue::Array(values)) + } + Type::Slice(_) => { + // TODO(#2752): need SSA values here to fetch the len like we do for a Type::Array + Err(InternalError::UnExpected { + expected: "array".to_owned(), + found: ssa_type.to_string(), + call_stack: self.acir_context.get_call_stack(), } - typ[0].clone() + .into()) } - _ => unreachable!("ICE - expected an array"), - }; - let typ = AcirType::from(typ); - self.define_result(dfg, instruction, AcirValue::Var(read, typ)); - Ok(read) + _ => unreachable!("ICE - expected an array or slice"), + } } /// Copy the array and generates a write opcode on the new array @@ -718,11 +833,11 @@ impl Context { fn array_set( &mut self, instruction: InstructionId, - var_index: AcirVar, - store_value: AcirVar, + mut var_index: AcirVar, + store_value: AcirValue, dfg: &DataFlowGraph, map_array: bool, - ) -> Result<(), InternalError> { + ) -> Result<(), RuntimeError> { // Pass the instruction between array methods rather than the internal fields themselves let (array, length) = match dfg[instruction] { Instruction::ArraySet { array, length, .. } => (array, length), @@ -731,21 +846,27 @@ impl Context { expected: "Instruction should be an ArraySet".to_owned(), found: format!("Instead got {:?}", dfg[instruction]), call_stack: self.acir_context.get_call_stack(), - }) + } + .into()) } }; - // Fetch the internal SSA ID for the array - let array = dfg.resolve(array); - - // Use the SSA ID to get or create its block ID - let block_id = self.block_id(&array); + let (_, array_typ, block_id) = self.check_array_is_initialized(array, dfg)?; // Every array has a length in its type, so we fetch that from // the SSA IR. - let len = match dfg.type_of_value(array) { - Type::Array(_, len) => len, + // + // A slice's size must be fetched from the SSA value that represents the slice. + // However, this size is simply the capacity of a slice. The capacity is dependent upon the witness + // and may contain data for which we want to restrict access. The true slice length is tracked in a + // a separate SSA value and restrictions on slice indices should be generated elsewhere in the SSA. + let array_len = match &array_typ { + Type::Array(_, _) => { + // Flatten the array length to handle arrays of complex types + array_typ.flattened_size() + } Type::Slice(_) => { + // Fetch the true length of the slice from the array_set instruction let length = length .expect("ICE: array set on slice must have a length associated with the call"); let length_acir_var = self.convert_value(length, dfg).into_var()?; @@ -757,25 +878,6 @@ impl Context { _ => unreachable!("ICE - expected an array"), }; - // Check if the array has already been initialized in ACIR gen - // if not, we initialize it using the values from SSA - let already_initialized = self.initialized_arrays.contains(&block_id); - if !already_initialized { - match &dfg[array] { - Value::Array { array, .. } => { - let values: Vec = - array.iter().map(|i| self.convert_value(*i, dfg)).collect(); - self.initialize_array(block_id, array.len(), Some(&values))?; - } - _ => { - return Err(InternalError::General { - message: format!("Array {array} should be initialized"), - call_stack: self.acir_context.get_call_stack(), - }) - } - } - } - // Since array_set creates a new array, we create a new block ID for this // array, unless map_array is true. In that case, we operate directly on block_id // and we do not create a new block ID. @@ -790,36 +892,181 @@ impl Context { } else { // Initialize the new array with the values from the old array result_block_id = self.block_id(result_id); - let init_values = try_vecmap(0..len, |i| { - let index = AcirValue::Var( - self.acir_context.add_constant(FieldElement::from(i as u128)), - AcirType::NumericType(NumericType::NativeField), - ); - let var = index.into_var()?; - let read = self.acir_context.read_from_memory(block_id, &var)?; - Ok(AcirValue::Var(read, AcirType::NumericType(NumericType::NativeField))) + let init_values = try_vecmap(0..array_len, |i| { + let index_var = self.acir_context.add_constant(FieldElement::from(i as u128)); + + let read = self.acir_context.read_from_memory(block_id, &index_var)?; + Ok::(AcirValue::Var(read, AcirType::field())) })?; - self.initialize_array(result_block_id, len, Some(&init_values))?; + self.initialize_array( + result_block_id, + array_len, + Some(AcirValue::Array(init_values.into())), + )?; } - // Write the new value into the new array at the specified index - self.acir_context.write_to_memory(result_block_id, &var_index, &store_value)?; + self.array_set_value(store_value, result_block_id, &mut var_index)?; let result_value = - AcirValue::DynamicArray(AcirDynamicArray { block_id: result_block_id, len }); + AcirValue::DynamicArray(AcirDynamicArray { block_id: result_block_id, len: array_len }); self.define_result(dfg, instruction, result_value); Ok(()) } + fn array_set_value( + &mut self, + value: AcirValue, + block_id: BlockId, + var_index: &mut AcirVar, + ) -> Result<(), RuntimeError> { + let one = self.acir_context.add_constant(FieldElement::one()); + match value { + AcirValue::Var(store_var, _) => { + // Write the new value into the new array at the specified index + self.acir_context.write_to_memory(block_id, var_index, &store_var)?; + // Incremement the var_index in case of a nested array + *var_index = self.acir_context.add_var(*var_index, one)?; + } + AcirValue::Array(values) => { + for value in values { + self.array_set_value(value, block_id, var_index)?; + } + } + AcirValue::DynamicArray(_) => { + unimplemented!("ICE: setting a dynamic array not supported"); + } + } + Ok(()) + } + + fn check_array_is_initialized( + &mut self, + array: ValueId, + dfg: &DataFlowGraph, + ) -> Result<(ValueId, Type, BlockId), RuntimeError> { + // Fetch the internal SSA ID for the array + let array_id = dfg.resolve(array); + + let array_typ = dfg.type_of_value(array_id); + + // Use the SSA ID to get or create its block ID + let block_id = self.block_id(&array_id); + + // Check if the array has already been initialized in ACIR gen + // if not, we initialize it using the values from SSA + let already_initialized = self.initialized_arrays.contains(&block_id); + if !already_initialized { + let value = &dfg[array_id]; + match value { + Value::Array { array, .. } => { + let value = self.convert_value(array_id, dfg); + let len = if matches!(array_typ, Type::Array(_, _)) { + array_typ.flattened_size() + } else { + array.len() + }; + self.initialize_array(block_id, len, Some(value))?; + } + _ => { + return Err(InternalError::General { + message: format!("Array {array} should be initialized"), + call_stack: self.acir_context.get_call_stack(), + } + .into()) + } + } + } + + Ok((array_id, array_typ, block_id)) + } + + fn init_element_types_size_array( + &mut self, + array_typ: &Type, + array_id: ValueId, + ) -> Result { + let element_type_sizes = self.internal_block_id(&array_id); + // Check whether an internal type sizes array has already been initialized + if self.initialized_arrays.contains(&element_type_sizes) { + return Ok(element_type_sizes); + } + let mut flat_elem_type_sizes = Vec::new(); + flat_elem_type_sizes.push(0); + match array_typ { + Type::Array(element_types, _) => { + for (i, typ) in element_types.as_ref().iter().enumerate() { + flat_elem_type_sizes.push(typ.flattened_size() + flat_elem_type_sizes[i]); + } + } + _ => { + return Err(InternalError::UnExpected { + expected: "array".to_owned(), + found: array_typ.to_string(), + call_stack: self.acir_context.get_call_stack(), + } + .into()) + } + } + // We do not have to initialize the last elem size value as that is the maximum array size + flat_elem_type_sizes.pop(); + let init_values = vecmap(flat_elem_type_sizes, |type_size| { + let var = self.acir_context.add_constant(FieldElement::from(type_size as u128)); + AcirValue::Var(var, AcirType::field()) + }); + self.initialize_array( + element_type_sizes, + init_values.len(), + Some(AcirValue::Array(init_values.into())), + )?; + + Ok(element_type_sizes) + } + + fn get_flattened_index( + &mut self, + array_typ: &Type, + array_id: ValueId, + array_len: usize, + var_index: AcirVar, + ) -> Result { + let element_type_sizes = self.init_element_types_size_array(array_typ, array_id)?; + + let element_size = array_typ.element_size(); + let flat_array_size = array_typ.flattened_size(); + let flat_elem_size = flat_array_size / array_len; + let flat_element_size_var = + self.acir_context.add_constant(FieldElement::from(flat_elem_size as u128)); + + let element_size_var = + self.acir_context.add_constant(FieldElement::from(element_size as u128)); + let outer_offset = self.acir_context.div_var( + var_index, + element_size_var, + AcirType::unsigned(32), + self.current_side_effects_enabled_var, + )?; + let inner_offset_index = self.acir_context.modulo_var( + var_index, + element_size_var, + 32, + self.current_side_effects_enabled_var, + )?; + let inner_offset = + self.acir_context.read_from_memory(element_type_sizes, &inner_offset_index)?; + + let var_index = self.acir_context.mul_var(outer_offset, flat_element_size_var)?; + self.acir_context.add_var(var_index, inner_offset) + } + /// Initializes an array with the given values and caches the fact that we /// have initialized this array. fn initialize_array( &mut self, array: BlockId, len: usize, - values: Option<&[AcirValue]>, + value: Option, ) -> Result<(), InternalError> { - self.acir_context.initialize_array(array, len, values)?; + self.acir_context.initialize_array(array, len, value)?; self.initialized_arrays.insert(array); Ok(()) } @@ -1353,18 +1600,11 @@ impl Context { AcirValue::DynamicArray(AcirDynamicArray { block_id, len }) => { for i in 0..len { // We generate witnesses corresponding to the array values - let index = AcirValue::Var( - self.acir_context.add_constant(FieldElement::from(i as u128)), - AcirType::NumericType(NumericType::NativeField), - ); + let index_var = self.acir_context.add_constant(FieldElement::from(i as u128)); - let index_var = index.into_var()?; let value_read_var = self.acir_context.read_from_memory(block_id, &index_var)?; - let value_read = AcirValue::Var( - value_read_var, - AcirType::NumericType(NumericType::NativeField), - ); + let value_read = AcirValue::Var(value_read_var, AcirType::field()); old_slice.push_back(value_read); } @@ -1401,15 +1641,6 @@ impl Context { acir_vars } - fn bit_count(&self, lhs: ValueId, dfg: &DataFlowGraph) -> u32 { - match dfg.type_of_value(lhs) { - Type::Numeric(NumericType::Signed { bit_size }) => bit_size, - Type::Numeric(NumericType::Unsigned { bit_size }) => bit_size, - Type::Numeric(NumericType::NativeField) => FieldElement::max_num_bits(), - _ => 0, - } - } - /// Convert a Vec into a Vec using the given result ids. /// If the type of a result id is an array, several acir vars are collected into /// a single AcirValue::Array of the same length. @@ -1450,13 +1681,6 @@ impl Context { } } } - - /// Creates a default, meaningless value meant only to be a valid value of the given type. - fn create_default_value(&mut self, param_type: &Type) -> Result { - self.create_value_from_type(param_type, &mut |this, _| { - Ok(this.acir_context.add_constant(FieldElement::zero())) - }) - } } #[cfg(test)] diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 961ff8cc33e..3489cd271ed 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -342,11 +342,6 @@ impl FunctionBuilder { pub(crate) fn import_intrinsic_id(&mut self, intrinsic: Intrinsic) -> ValueId { self.current_function.dfg.import_intrinsic(intrinsic) } - - /// Removes the given instruction from the current block or panics otherwise. - pub(crate) fn remove_instruction_from_current_block(&mut self, instruction: InstructionId) { - self.current_function.dfg[self.current_block].remove_instruction(instruction); - } } impl std::ops::Index for FunctionBuilder { diff --git a/compiler/noirc_evaluator/src/ssa/ir/basic_block.rs b/compiler/noirc_evaluator/src/ssa/ir/basic_block.rs index 998591f7210..9ca73bea762 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/basic_block.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/basic_block.rs @@ -145,13 +145,4 @@ impl BasicBlock { None => vec![].into_iter(), } } - - /// Removes the given instruction from this block if present or panics otherwise. - pub(crate) fn remove_instruction(&mut self, instruction: InstructionId) { - let index = - self.instructions.iter().position(|id| *id == instruction).unwrap_or_else(|| { - panic!("remove_instruction: No such instruction {instruction:?} in block") - }); - self.instructions.remove(index); - } } diff --git a/compiler/noirc_evaluator/src/ssa/ir/dfg.rs b/compiler/noirc_evaluator/src/ssa/ir/dfg.rs index 242278bd581..3cb6736007d 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/dfg.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/dfg.rs @@ -4,7 +4,7 @@ use crate::ssa::ir::instruction::SimplifyResult; use super::{ basic_block::{BasicBlock, BasicBlockId}, - function::{FunctionId, Signature}, + function::FunctionId, instruction::{ Instruction, InstructionId, InstructionResultType, Intrinsic, TerminatorInstruction, }, @@ -61,9 +61,6 @@ pub(crate) struct DataFlowGraph { /// represented by only 1 ValueId within this function. foreign_functions: HashMap, - /// Function signatures of external methods - signatures: DenseMap, - /// All blocks in a function blocks: DenseMap, diff --git a/compiler/noirc_evaluator/src/ssa/ir/types.rs b/compiler/noirc_evaluator/src/ssa/ir/types.rs index e8110f0901b..b576ee12d45 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/types.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/types.rs @@ -77,6 +77,21 @@ impl Type { other => panic!("element_size: Expected array or slice, found {other}"), } } + + /// Returns the flattened size of a Type + pub(crate) fn flattened_size(&self) -> usize { + let mut size = 0; + match self { + Type::Array(elements, len) => { + size = elements.iter().fold(size, |sum, elem| sum + (elem.flattened_size() * len)); + } + Type::Slice(_) => { + unimplemented!("ICE: cannot fetch flattened slice size"); + } + _ => size += 1, + } + size + } } /// Composite Types are essentially flattened struct or tuple types. diff --git a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs index e71e23d2032..a96a8d70e04 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs @@ -30,7 +30,7 @@ use crate::ssa::{ dfg::{DataFlowGraph, InsertInstructionResult}, function::Function, instruction::{Instruction, InstructionId}, - value::{Value, ValueId}, + value::ValueId, }, ssa_gen::Ssa, }; @@ -77,7 +77,6 @@ impl Context { // Cache of instructions without any side-effects along with their outputs. let mut cached_instruction_results: HashMap> = HashMap::default(); - let mut constrained_values: HashMap = HashMap::default(); for instruction_id in instructions { Self::fold_constants_into_instruction( @@ -85,7 +84,6 @@ impl Context { block, instruction_id, &mut cached_instruction_results, - &mut constrained_values, ); } self.block_queue.extend(function.dfg[block].successors()); @@ -96,9 +94,8 @@ impl Context { block: BasicBlockId, id: InstructionId, instruction_result_cache: &mut HashMap>, - constrained_values: &mut HashMap, ) { - let instruction = Self::resolve_instruction(id, dfg, constrained_values); + let instruction = Self::resolve_instruction(id, dfg); let old_results = dfg.instruction_results(id).to_vec(); // If a copy of this instruction exists earlier in the block, then reuse the previous results. @@ -112,42 +109,15 @@ impl Context { Self::replace_result_ids(dfg, &old_results, &new_results); - Self::cache_instruction( - instruction, - new_results, - dfg, - instruction_result_cache, - constrained_values, - ); + Self::cache_instruction(instruction, new_results, dfg, instruction_result_cache); } /// Fetches an [`Instruction`] by its [`InstructionId`] and fully resolves its inputs. - fn resolve_instruction( - instruction_id: InstructionId, - dfg: &DataFlowGraph, - constrained_values: &HashMap, - ) -> Instruction { + fn resolve_instruction(instruction_id: InstructionId, dfg: &DataFlowGraph) -> Instruction { let instruction = dfg[instruction_id].clone(); - // Alternate between resolving `value_id` in the `dfg` and checking to see if the resolved value - // has been constrained to be equal to some simpler value in the current block. - // - // This allows us to reach a stable final `ValueId` for each instruction input as we add more - // constraints to the cache. - fn resolve_cache( - dfg: &DataFlowGraph, - cache: &HashMap, - value_id: ValueId, - ) -> ValueId { - let resolved_id = dfg.resolve(value_id); - match cache.get(&resolved_id) { - Some(cached_value) => resolve_cache(dfg, cache, *cached_value), - None => resolved_id, - } - } - // Resolve any inputs to ensure that we're comparing like-for-like instructions. - instruction.map_values(|value_id| resolve_cache(dfg, constrained_values, value_id)) + instruction.map_values(|value_id| dfg.resolve(value_id)) } /// Pushes a new [`Instruction`] into the [`DataFlowGraph`] which applies any optimizations @@ -185,35 +155,7 @@ impl Context { instruction_results: Vec, dfg: &DataFlowGraph, instruction_result_cache: &mut HashMap>, - constraint_cache: &mut HashMap, ) { - // If the instruction was a constraint, then create a link between the two `ValueId`s - // to map from the more complex to the simpler value. - if let Instruction::Constrain(lhs, rhs, _) = instruction { - // These `ValueId`s should be fully resolved now. - match (&dfg[lhs], &dfg[rhs]) { - // Ignore trivial constraints - (Value::NumericConstant { .. }, Value::NumericConstant { .. }) => (), - - // Prefer replacing with constants where possible. - (Value::NumericConstant { .. }, _) => { - constraint_cache.insert(rhs, lhs); - } - (_, Value::NumericConstant { .. }) => { - constraint_cache.insert(lhs, rhs); - } - // Otherwise prefer block parameters over instruction results. - // This is as block parameters are more likely to be a single witness rather than a full expression. - (Value::Param { .. }, Value::Instruction { .. }) => { - constraint_cache.insert(rhs, lhs); - } - (Value::Instruction { .. }, Value::Param { .. }) => { - constraint_cache.insert(lhs, rhs); - } - (_, _) => (), - } - } - // If the instruction doesn't have side-effects, cache the results so we can reuse them if // the same instruction appears again later in the block. if instruction.is_pure(dfg) { @@ -391,55 +333,4 @@ mod test { assert_eq!(instruction, &Instruction::Cast(ValueId::test_new(0), Type::unsigned(32))); } - - #[test] - fn constrained_value_replacement() { - // fn main f0 { - // b0(v0: Field): - // constrain v0 == Field 10 - // v1 = add v0, Field 1 - // constrain v1 == Field 11 - // } - // - // After constructing this IR, we run constant folding which should replace references to `v0` - // with the constant `10`. This then allows us to optimize away the rest of the circuit. - - let main_id = Id::test_new(0); - - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id, RuntimeType::Acir); - let v0 = builder.add_parameter(Type::field()); - - let field_10 = builder.field_constant(10u128); - builder.insert_constrain(v0, field_10, None); - - let field_1 = builder.field_constant(1u128); - let v1 = builder.insert_binary(v0, BinaryOp::Add, field_1); - - let field_11 = builder.field_constant(11u128); - builder.insert_constrain(v1, field_11, None); - - let mut ssa = builder.finish(); - let main = ssa.main_mut(); - let instructions = main.dfg[main.entry_block()].instructions(); - assert_eq!(instructions.len(), 3); - - // Expected output: - // - // fn main f0 { - // b0(v0: Field): - // constrain v0 == Field 10 - // } - let ssa = ssa.fold_constants(); - let main = ssa.main(); - let instructions = main.dfg[main.entry_block()].instructions(); - - assert_eq!(instructions.len(), 1); - let instruction = &main.dfg[instructions[0]]; - - assert_eq!( - instruction, - &Instruction::Constrain(ValueId::test_new(0), ValueId::test_new(1), None) - ); - } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index d57c2cc7933..c05c4a02181 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -216,7 +216,6 @@ struct Branch { condition: ValueId, last_block: BasicBlockId, store_values: HashMap, - local_allocations: HashSet, } fn flatten_function_cfg(function: &mut Function) { @@ -461,7 +460,7 @@ impl<'f> Context<'f> { let mut get_element = |array, typevars, len| { // The smaller slice is filled with placeholder data. Codegen for slice accesses must // include checks against the dynamic slice length so that this placeholder data is not incorrectly accessed. - if (len - 1) < index_value.to_u128() as usize { + if len <= index_value.to_u128() as usize { let zero = FieldElement::zero(); self.inserter.function.dfg.make_constant(zero, Type::field()) } else { @@ -553,7 +552,8 @@ impl<'f> Context<'f> { for i in 0..len { for (element_index, element_type) in element_types.iter().enumerate() { - let index = ((i * element_types.len() + element_index) as u128).into(); + let index: FieldElement = + ((i * element_types.len() + element_index) as u128).into(); let index = self.inserter.function.dfg.make_constant(index, Type::field()); let typevars = Some(vec![element_type.clone()]); @@ -661,7 +661,6 @@ impl<'f> Context<'f> { // block arguments, it is safe to use the jmpif block here. last_block: jmpif_block, store_values: HashMap::default(), - local_allocations: HashSet::new(), } } else { self.push_condition(jmpif_block, new_condition); @@ -685,13 +684,12 @@ impl<'f> Context<'f> { self.conditions.pop(); let stores_in_branch = std::mem::replace(&mut self.store_values, old_stores); - let local_allocations = std::mem::replace(&mut self.local_allocations, old_allocations); + self.local_allocations = old_allocations; Branch { condition: new_condition, last_block: final_block, store_values: stores_in_branch, - local_allocations, } } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/inlining.rs b/compiler/noirc_evaluator/src/ssa/opt/inlining.rs index 07b524ebc96..617712c9912 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/inlining.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/inlining.rs @@ -88,9 +88,6 @@ struct PerFunctionContext<'function> { /// block. blocks: HashMap, - /// Maps InstructionIds from the function being inlined to the function being inlined into. - instructions: HashMap, - /// True if we're currently working on the entry point function. inlining_entry: bool, } @@ -191,7 +188,6 @@ impl<'function> PerFunctionContext<'function> { context, source_function, blocks: HashMap::default(), - instructions: HashMap::default(), values: HashMap::default(), inlining_entry: false, } diff --git a/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs b/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs index 71524ab9736..21c1797ca96 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -70,7 +70,6 @@ use crate::ssa::{ ir::{ basic_block::BasicBlockId, cfg::ControlFlowGraph, - dom::DominatorTree, function::Function, function_inserter::FunctionInserter, instruction::{Instruction, InstructionId, TerminatorInstruction}, @@ -100,7 +99,6 @@ impl Ssa { struct PerFunctionContext<'f> { cfg: ControlFlowGraph, post_order: PostOrder, - dom_tree: DominatorTree, blocks: BTreeMap, @@ -117,12 +115,10 @@ impl<'f> PerFunctionContext<'f> { fn new(function: &'f mut Function) -> Self { let cfg = ControlFlowGraph::with_function(function); let post_order = PostOrder::with_function(function); - let dom_tree = DominatorTree::with_cfg_and_post_order(&cfg, &post_order); PerFunctionContext { cfg, post_order, - dom_tree, inserter: FunctionInserter::new(function), blocks: BTreeMap::new(), instructions_to_remove: BTreeSet::new(), diff --git a/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs b/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs index 552d9ef6f4b..50c2f5b1524 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs @@ -75,7 +75,6 @@ struct Loops { yet_to_unroll: Vec, modified_blocks: HashSet, cfg: ControlFlowGraph, - dom_tree: DominatorTree, } /// Find a loop in the program by finding a node that dominates any predecessor node. @@ -109,7 +108,6 @@ fn find_all_loops(function: &Function) -> Loops { yet_to_unroll: loops, modified_blocks: HashSet::new(), cfg, - dom_tree, } } diff --git a/compiler/noirc_frontend/src/ast/traits.rs b/compiler/noirc_frontend/src/ast/traits.rs index 4078ea72cbe..feb627df60b 100644 --- a/compiler/noirc_frontend/src/ast/traits.rs +++ b/compiler/noirc_frontend/src/ast/traits.rs @@ -5,7 +5,7 @@ use noirc_errors::Span; use crate::{ node_interner::TraitId, BlockExpression, Expression, FunctionReturnType, Ident, NoirFunction, - UnresolvedGenerics, UnresolvedType, + Path, UnresolvedGenerics, UnresolvedType, }; /// AST node for trait definitions: @@ -57,11 +57,10 @@ pub struct TypeImpl { pub struct NoirTraitImpl { pub impl_generics: UnresolvedGenerics, - pub trait_name: Ident, + pub trait_name: Path, pub trait_generics: Vec, pub object_type: UnresolvedType, - pub object_type_span: Span, pub where_clause: Vec, @@ -83,7 +82,7 @@ pub struct UnresolvedTraitConstraint { /// Represents a single trait bound, such as `TraitX` or `TraitY` #[derive(Clone, Debug, PartialEq, Eq)] pub struct TraitBound { - pub trait_name: Ident, + pub trait_path: Path, pub trait_id: Option, // initially None, gets assigned during DC pub trait_generics: Vec, } @@ -178,9 +177,9 @@ impl Display for TraitBound { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let generics = vecmap(&self.trait_generics, |generic| generic.to_string()); if !generics.is_empty() { - write!(f, "{}<{}>", self.trait_name, generics.join(", ")) + write!(f, "{}<{}>", self.trait_path, generics.join(", ")) } else { - write!(f, "{}", self.trait_name) + write!(f, "{}", self.trait_path) } } } diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 0e4c1710ce8..86369a66758 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -1,9 +1,10 @@ use super::dc_mod::collect_defs; use super::errors::{DefCollectorErrorKind, DuplicateType}; use crate::graph::CrateId; -use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}; +use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId}; use crate::hir::resolution::errors::ResolverError; use crate::hir::resolution::import::PathResolutionError; +use crate::hir::resolution::path_resolver::PathResolver; use crate::hir::resolution::resolver::Resolver; use crate::hir::resolution::{ import::{resolve_imports, ImportDirective}, @@ -11,20 +12,22 @@ use crate::hir::resolution::{ }; use crate::hir::type_check::{type_check_func, TypeCheckError, TypeChecker}; use crate::hir::Context; -use crate::hir_def::traits::{TraitConstant, TraitFunction, TraitImpl, TraitType}; +use crate::hir_def::traits::{Trait, TraitConstant, TraitFunction, TraitImpl, TraitType}; use crate::node_interner::{ FuncId, NodeInterner, StmtId, StructId, TraitId, TraitImplKey, TypeAliasId, }; + +use crate::parser::ParserError; + use crate::{ ExpressionKind, Generics, Ident, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, - NoirTypeAlias, ParsedModule, Shared, StructType, TraitItem, Type, TypeBinding, + NoirTypeAlias, ParsedModule, Path, Shared, StructType, TraitItem, Type, TypeBinding, TypeVariableKind, UnresolvedGenerics, UnresolvedType, }; use fm::FileId; use iter_extended::vecmap; -use noirc_errors::Span; -use noirc_errors::{CustomDiagnostic, FileDiagnostic}; -use std::collections::{BTreeMap, HashMap}; +use noirc_errors::{CustomDiagnostic, Span}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::rc::Rc; use std::vec; @@ -39,6 +42,32 @@ impl UnresolvedFunctions { pub fn push_fn(&mut self, mod_id: LocalModuleId, func_id: FuncId, func: NoirFunction) { self.functions.push((mod_id, func_id, func)); } + + pub fn resolve_trait_bounds_trait_ids( + &mut self, + def_maps: &BTreeMap, + crate_id: CrateId, + ) -> Vec { + let mut errors = Vec::new(); + + for (local_id, _, func) in &mut self.functions { + let module = ModuleId { krate: crate_id, local_id: *local_id }; + + for bound in &mut func.def.where_clause { + match resolve_trait_by_path(def_maps, module, bound.trait_bound.trait_path.clone()) + { + Ok(trait_id) => { + bound.trait_bound.trait_id = Some(trait_id); + } + Err(err) => { + errors.push(err); + } + } + } + } + + errors + } } pub struct UnresolvedStruct { @@ -51,15 +80,18 @@ pub struct UnresolvedStruct { pub struct UnresolvedTrait { pub file_id: FileId, pub module_id: LocalModuleId, + pub crate_id: CrateId, pub trait_def: NoirTrait, + pub fns_with_default_impl: UnresolvedFunctions, } pub struct UnresolvedTraitImpl { pub file_id: FileId, pub module_id: LocalModuleId, - pub the_trait: UnresolvedTrait, + pub trait_id: Option, + pub trait_path: Path, + pub object_type: UnresolvedType, pub methods: UnresolvedFunctions, - pub trait_impl_ident: Ident, // for error reporting } #[derive(Clone)] @@ -87,7 +119,48 @@ pub struct DefCollector { pub(crate) collected_traits: BTreeMap, pub(crate) collected_globals: Vec, pub(crate) collected_impls: ImplMap, - pub(crate) collected_traits_impls: TraitImplMap, + pub(crate) collected_traits_impls: Vec, +} + +pub enum CompilationError { + ParseError(ParserError), + DefinitionError(DefCollectorErrorKind), + ResolveError(ResolverError), + TypeError(TypeCheckError), +} + +impl From for CustomDiagnostic { + fn from(value: CompilationError) -> Self { + match value { + CompilationError::ParseError(error) => error.into(), + CompilationError::DefinitionError(error) => error.into(), + CompilationError::ResolveError(error) => error.into(), + CompilationError::TypeError(error) => error.into(), + } + } +} + +impl From for CompilationError { + fn from(value: ParserError) -> Self { + CompilationError::ParseError(value) + } +} + +impl From for CompilationError { + fn from(value: DefCollectorErrorKind) -> Self { + CompilationError::DefinitionError(value) + } +} + +impl From for CompilationError { + fn from(value: ResolverError) -> Self { + CompilationError::ResolveError(value) + } +} +impl From for CompilationError { + fn from(value: TypeCheckError) -> Self { + CompilationError::TypeError(value) + } } /// Maps the type and the module id in which the impl is defined to the functions contained in that @@ -100,8 +173,6 @@ pub struct DefCollector { type ImplMap = HashMap<(UnresolvedType, LocalModuleId), Vec<(UnresolvedGenerics, Span, UnresolvedFunctions)>>; -type TraitImplMap = HashMap<(UnresolvedType, LocalModuleId, TraitId), UnresolvedTraitImpl>; - impl DefCollector { fn new(def_map: CrateDefMap) -> DefCollector { DefCollector { @@ -113,7 +184,7 @@ impl DefCollector { collected_traits: BTreeMap::new(), collected_impls: HashMap::new(), collected_globals: vec![], - collected_traits_impls: HashMap::new(), + collected_traits_impls: vec![], } } @@ -125,8 +196,8 @@ impl DefCollector { context: &mut Context, ast: ParsedModule, root_file_id: FileId, - errors: &mut Vec, - ) { + ) -> Vec<(CompilationError, FileId)> { + let mut errors: Vec<(CompilationError, FileId)> = vec![]; let crate_id = def_map.krate; // Recursively resolve the dependencies @@ -137,7 +208,7 @@ impl DefCollector { let crate_graph = &context.crate_graph[crate_id]; for dep in crate_graph.dependencies.clone() { - CrateDefMap::collect_defs(dep.crate_id, context, errors); + errors.extend(CrateDefMap::collect_defs(dep.crate_id, context)); let dep_def_root = context.def_map(&dep.crate_id).expect("ice: def map was just created").root; @@ -156,7 +227,14 @@ impl DefCollector { // and lowering the functions // i.e. Use a mod collector to collect the nodes at the root module // and process them - collect_defs(&mut def_collector, ast, root_file_id, crate_root, crate_id, context, errors); + errors.extend(collect_defs( + &mut def_collector, + ast, + root_file_id, + crate_root, + crate_id, + context, + )); // Add the current crate to the collection of DefMaps context.def_maps.insert(crate_id, def_collector.def_map); @@ -165,13 +243,14 @@ impl DefCollector { let (resolved, unresolved_imports) = resolve_imports(crate_id, def_collector.collected_imports, &context.def_maps); - let current_def_map = context.def_maps.get(&crate_id).unwrap(); - - errors.extend(vecmap(unresolved_imports, |(error, module_id)| { - let file_id = current_def_map.file_id(module_id); - let error = DefCollectorErrorKind::PathResolutionError(error); - error.into_file_diagnostic(file_id) - })); + { + let current_def_map = context.def_maps.get(&crate_id).unwrap(); + errors.extend(vecmap(unresolved_imports, |(error, module_id)| { + let file_id = current_def_map.file_id(module_id); + let error = DefCollectorErrorKind::PathResolutionError(error); + (error.into(), file_id) + })); + }; // Populate module namespaces according to the imports used let current_def_map = context.def_maps.get_mut(&crate_id).unwrap(); @@ -187,7 +266,7 @@ impl DefCollector { first_def, second_def, }; - errors.push(err.into_file_diagnostic(root_file_id)); + errors.push((err.into(), root_file_id)); } } } @@ -200,27 +279,35 @@ impl DefCollector { let (literal_globals, other_globals) = filter_literal_globals(def_collector.collected_globals); - let mut file_global_ids = resolve_globals(context, literal_globals, crate_id, errors); + let mut resolved_globals = resolve_globals(context, literal_globals, crate_id); - resolve_type_aliases(context, def_collector.collected_type_aliases, crate_id, errors); + errors.extend(resolve_type_aliases( + context, + def_collector.collected_type_aliases, + crate_id, + )); - resolve_traits(context, def_collector.collected_traits, crate_id, errors); + errors.extend(resolve_traits(context, def_collector.collected_traits, crate_id)); // Must resolve structs before we resolve globals. - resolve_structs(context, def_collector.collected_types, crate_id, errors); + errors.extend(resolve_structs(context, def_collector.collected_types, crate_id)); // We must wait to resolve non-integer globals until after we resolve structs since structs // globals will need to reference the struct type they're initialized to to ensure they are valid. - let mut more_global_ids = resolve_globals(context, other_globals, crate_id, errors); - - file_global_ids.append(&mut more_global_ids); + resolved_globals.extend(resolve_globals(context, other_globals, crate_id)); // Before we resolve any function symbols we must go through our impls and // re-collect the methods within into their proper module. This cannot be // done before resolution since we need to be able to resolve the type of the // impl since that determines the module we should collect into. - collect_impls(context, crate_id, &def_collector.collected_impls, errors); + errors.extend(collect_impls(context, crate_id, &def_collector.collected_impls)); - collect_trait_impls(context, crate_id, &def_collector.collected_traits_impls, errors); + // Bind trait impls to their trait. Collect trait functions, that have a + // default implementation, which hasn't been overriden. + errors.extend(collect_trait_impls( + context, + crate_id, + &mut def_collector.collected_traits_impls, + )); // Lower each function in the crate. This is now possible since imports have been resolved let file_func_ids = resolve_free_functions( @@ -229,7 +316,7 @@ impl DefCollector { &context.def_maps, def_collector.collected_functions, None, - errors, + &mut errors, ); let file_method_ids = resolve_impls( @@ -237,18 +324,24 @@ impl DefCollector { crate_id, &context.def_maps, def_collector.collected_impls, - errors, + &mut errors, + ); + // resolve_trait_impls can fill different type of errors, therefore we pass errors by mut ref + let file_trait_impls_ids = resolve_trait_impls( + context, + def_collector.collected_traits_impls, + crate_id, + &mut errors, ); - let file_trait_impls_ids = - resolve_trait_impls(context, def_collector.collected_traits_impls, crate_id, errors); - - type_check_globals(&mut context.def_interner, file_global_ids, errors); + errors.extend(resolved_globals.errors); + errors.extend(type_check_globals(&mut context.def_interner, resolved_globals.globals)); // Type check all of the functions in the crate - type_check_functions(&mut context.def_interner, file_func_ids, errors); - type_check_functions(&mut context.def_interner, file_method_ids, errors); - type_check_functions(&mut context.def_interner, file_trait_impls_ids, errors); + errors.extend(type_check_functions(&mut context.def_interner, file_func_ids)); + errors.extend(type_check_functions(&mut context.def_interner, file_method_ids)); + errors.extend(type_check_functions(&mut context.def_interner, file_trait_impls_ids)); + errors } } @@ -258,10 +351,10 @@ fn collect_impls( context: &mut Context, crate_id: CrateId, collected_impls: &ImplMap, - errors: &mut Vec, -) { +) -> Vec<(CompilationError, FileId)> { let interner = &mut context.def_interner; let def_maps = &mut context.def_maps; + let mut errors: Vec<(CompilationError, FileId)> = vec![]; for ((unresolved_type, module_id), methods) in collected_impls { let path_resolver = @@ -274,7 +367,7 @@ fn collect_impls( resolver.add_generics(generics); let typ = resolver.resolve_type(unresolved_type.clone()); - extend_errors(errors, unresolved.file_id, resolver.take_errors()); + errors.extend(take_errors(unresolved.file_id, resolver)); if let Some(struct_type) = get_struct_type(&typ) { let struct_type = struct_type.borrow(); @@ -285,7 +378,7 @@ fn collect_impls( let span = *span; let type_name = struct_type.name.to_string(); let error = DefCollectorErrorKind::ForeignImpl { span, type_name }; - errors.push(error.into_file_diagnostic(unresolved.file_id)); + errors.push((error.into(), unresolved.file_id)); continue; } @@ -298,74 +391,226 @@ fn collect_impls( let result = module.declare_function(method.name_ident().clone(), *method_id); if let Err((first_def, second_def)) = result { - let err = DefCollectorErrorKind::Duplicate { + let error = DefCollectorErrorKind::Duplicate { typ: DuplicateType::Function, first_def, second_def, }; - errors.push(err.into_file_diagnostic(unresolved.file_id)); + errors.push((error.into(), unresolved.file_id)); } } // Prohibit defining impls for primitive types if we're not in the stdlib } else if typ != Type::Error && !crate_id.is_stdlib() { let span = *span; let error = DefCollectorErrorKind::NonStructTypeInImpl { span }; - errors.push(error.into_file_diagnostic(unresolved.file_id)); + errors.push((error.into(), unresolved.file_id)); } } } + errors } -fn collect_trait_impls( +fn collect_trait_impl_methods( + interner: &mut NodeInterner, + def_maps: &mut BTreeMap, + crate_id: CrateId, + trait_id: TraitId, + trait_impl: &mut UnresolvedTraitImpl, +) -> Vec<(CompilationError, FileId)> { + // In this Vec methods[i] corresponds to trait.methods[i]. If the impl has no implementation + // for a particular method, the default implementation will be added at that slot. + let mut ordered_methods = Vec::new(); + + let the_trait = interner.get_trait(trait_id); + + // check whether the trait implementation is in the same crate as either the trait or the type + let mut errors = + check_trait_impl_crate_coherence(interner, &the_trait, trait_impl, crate_id, def_maps); + // set of function ids that have a corresponding method in the trait + let mut func_ids_in_trait = HashSet::new(); + + for method in &the_trait.methods { + let overrides: Vec<_> = trait_impl + .methods + .functions + .iter() + .filter(|(_, _, f)| f.name() == method.name.0.contents) + .collect(); + + if overrides.is_empty() { + if let Some(default_impl) = &method.default_impl { + let func_id = interner.push_empty_fn(); + let module = ModuleId { local_id: trait_impl.module_id, krate: crate_id }; + interner.push_function(func_id, &default_impl.def, module); + func_ids_in_trait.insert(func_id); + ordered_methods.push(( + method.default_impl_module_id, + func_id, + *default_impl.clone(), + )); + } else { + let error = DefCollectorErrorKind::TraitMissingMethod { + trait_name: the_trait.name.clone(), + method_name: method.name.clone(), + trait_impl_span: trait_impl.object_type.span.expect("type must have a span"), + }; + errors.push((error.into(), trait_impl.file_id)); + } + } else { + for (_, func_id, _) in &overrides { + func_ids_in_trait.insert(*func_id); + } + + if overrides.len() > 1 { + let error = DefCollectorErrorKind::Duplicate { + typ: DuplicateType::Function, + first_def: overrides[0].2.name_ident().clone(), + second_def: overrides[1].2.name_ident().clone(), + }; + errors.push((error.into(), trait_impl.file_id)); + } + + ordered_methods.push(overrides[0].clone()); + } + } + + // Emit MethodNotInTrait error for methods in the impl block that + // don't have a corresponding method signature defined in the trait + for (_, func_id, func) in &trait_impl.methods.functions { + if !func_ids_in_trait.contains(func_id) { + let error = DefCollectorErrorKind::MethodNotInTrait { + trait_name: the_trait.name.clone(), + impl_method: func.name_ident().clone(), + }; + errors.push((error.into(), trait_impl.file_id)); + } + } + trait_impl.methods.functions = ordered_methods; + errors +} + +fn add_method_to_struct_namespace( + current_def_map: &mut CrateDefMap, + struct_type: &Shared, + func_id: FuncId, + name_ident: &Ident, +) -> Result<(), DefCollectorErrorKind> { + let struct_type = struct_type.borrow(); + let type_module = struct_type.id.local_module_id(); + let module = &mut current_def_map.modules[type_module.0]; + module.declare_function(name_ident.clone(), func_id).map_err(|(first_def, second_def)| { + DefCollectorErrorKind::Duplicate { typ: DuplicateType::Function, first_def, second_def } + }) +} + +fn collect_trait_impl( context: &mut Context, crate_id: CrateId, - collected_impls: &TraitImplMap, - errors: &mut Vec, -) { + trait_impl: &mut UnresolvedTraitImpl, +) -> Vec<(CompilationError, FileId)> { let interner = &mut context.def_interner; let def_maps = &mut context.def_maps; + let mut errors: Vec<(CompilationError, FileId)> = vec![]; + let unresolved_type = trait_impl.object_type.clone(); + let module = ModuleId { local_id: trait_impl.module_id, krate: crate_id }; + trait_impl.trait_id = + match resolve_trait_by_path(def_maps, module, trait_impl.trait_path.clone()) { + Ok(trait_id) => Some(trait_id), + Err(error) => { + errors.push((error.into(), trait_impl.file_id)); + None + } + }; - // TODO: To follow the semantics of Rust, we must allow the impl if either - // 1. The type is a struct and it's defined in the current crate - // 2. The trait is defined in the current crate - for ((unresolved_type, module_id, _), trait_impl) in collected_impls { - let path_resolver = - StandardPathResolver::new(ModuleId { local_id: *module_id, krate: crate_id }); - + if let Some(trait_id) = trait_impl.trait_id { + errors + .extend(collect_trait_impl_methods(interner, def_maps, crate_id, trait_id, trait_impl)); for (_, func_id, ast) in &trait_impl.methods.functions { - let file = def_maps[&crate_id].file_id(*module_id); + let file = def_maps[&crate_id].file_id(trait_impl.module_id); + let path_resolver = StandardPathResolver::new(module); let mut resolver = Resolver::new(interner, &path_resolver, def_maps, file); resolver.add_generics(&ast.def.generics); let typ = resolver.resolve_type(unresolved_type.clone()); - // Add the method to the struct's namespace if let Some(struct_type) = get_struct_type(&typ) { - extend_errors(errors, trait_impl.file_id, resolver.take_errors()); - - let struct_type = struct_type.borrow(); - let type_module = struct_type.id.local_module_id(); - - let module = &mut def_maps.get_mut(&crate_id).unwrap().modules[type_module.0]; - - let result = module.declare_function(ast.name_ident().clone(), *func_id); - - if let Err((first_def, second_def)) = result { - let err = DefCollectorErrorKind::Duplicate { - typ: DuplicateType::Function, - first_def, - second_def, - }; - errors.push(err.into_file_diagnostic(trait_impl.file_id)); + errors.extend(take_errors(trait_impl.file_id, resolver)); + let current_def_map = def_maps.get_mut(&crate_id).unwrap(); + match add_method_to_struct_namespace( + current_def_map, + struct_type, + *func_id, + ast.name_ident(), + ) { + Ok(()) => {} + Err(err) => { + errors.push((err.into(), trait_impl.file_id)); + } } } else { - let span = trait_impl.trait_impl_ident.span(); - let trait_ident = trait_impl.the_trait.trait_def.name.clone(); - let error = DefCollectorErrorKind::NonStructTraitImpl { trait_ident, span }; - errors.push(error.into_file_diagnostic(trait_impl.file_id)); + let error = DefCollectorErrorKind::NonStructTraitImpl { + trait_path: trait_impl.trait_path.clone(), + span: trait_impl.trait_path.span(), + }; + errors.push((error.into(), trait_impl.file_id)); } } } + errors +} + +fn collect_trait_impls( + context: &mut Context, + crate_id: CrateId, + collected_impls: &mut [UnresolvedTraitImpl], +) -> Vec<(CompilationError, FileId)> { + collected_impls + .iter_mut() + .flat_map(|trait_impl| collect_trait_impl(context, crate_id, trait_impl)) + .collect() +} + +fn check_trait_impl_crate_coherence( + interner: &mut NodeInterner, + the_trait: &Trait, + trait_impl: &UnresolvedTraitImpl, + current_crate: CrateId, + def_maps: &BTreeMap, +) -> Vec<(CompilationError, FileId)> { + let mut errors: Vec<(CompilationError, FileId)> = vec![]; + + let module = ModuleId { krate: current_crate, local_id: trait_impl.module_id }; + let file = def_maps[¤t_crate].file_id(trait_impl.module_id); + let path_resolver = StandardPathResolver::new(module); + let mut resolver = Resolver::new(interner, &path_resolver, def_maps, file); + + let object_crate = match resolver.resolve_type(trait_impl.object_type.clone()) { + Type::Struct(struct_type, _) => struct_type.borrow().id.krate(), + _ => CrateId::Dummy, + }; + + if current_crate != the_trait.crate_id && current_crate != object_crate { + let error = DefCollectorErrorKind::TraitImplOrphaned { + span: trait_impl.object_type.span.expect("object type must have a span"), + }; + errors.push((error.into(), trait_impl.file_id)); + } + + errors +} + +fn resolve_trait_by_path( + def_maps: &BTreeMap, + module: ModuleId, + path: Path, +) -> Result { + let path_resolver = StandardPathResolver::new(module); + + match path_resolver.resolve(def_maps, path.clone()) { + Ok(ModuleDefId::TraitId(trait_id)) => Ok(trait_id), + Ok(_) => Err(DefCollectorErrorKind::NotATrait { not_a_trait_name: path }), + Err(_) => Err(DefCollectorErrorKind::TraitNotFound { trait_path: path }), + } } fn get_struct_type(typ: &Type) -> Option<&Shared> { @@ -375,14 +620,6 @@ fn get_struct_type(typ: &Type) -> Option<&Shared> { } } -fn extend_errors(errors: &mut Vec, file: fm::FileId, new_errors: Errs) -where - Errs: IntoIterator, - Err: Into, -{ - errors.extend(new_errors.into_iter().map(|err| err.into().in_file(file))); -} - /// Separate the globals Vec into two. The first element in the tuple will be the /// literal globals, except for arrays, and the second will be all other globals. /// We exclude array literals as they can contain complex types @@ -395,13 +632,25 @@ fn filter_literal_globals( }) } +pub struct ResolvedGlobals { + pub globals: Vec<(FileId, StmtId)>, + pub errors: Vec<(CompilationError, FileId)>, +} + +impl ResolvedGlobals { + pub fn extend(&mut self, oth: Self) { + self.globals.extend(oth.globals); + self.errors.extend(oth.errors); + } +} + fn resolve_globals( context: &mut Context, globals: Vec, crate_id: CrateId, - errors: &mut Vec, -) -> Vec<(FileId, StmtId)> { - vecmap(globals, |global| { +) -> ResolvedGlobals { + let mut errors: Vec<(CompilationError, FileId)> = vec![]; + let globals = vecmap(globals, |global| { let module_id = ModuleId { local_id: global.module_id, krate: crate_id }; let path_resolver = StandardPathResolver::new(module_id); let storage_slot = context.next_storage_slot(module_id); @@ -416,25 +665,47 @@ fn resolve_globals( let name = global.stmt_def.pattern.name_ident().clone(); let hir_stmt = resolver.resolve_global_let(global.stmt_def); - extend_errors(errors, global.file_id, resolver.take_errors()); + errors.extend(take_errors(global.file_id, resolver)); context.def_interner.update_global(global.stmt_id, hir_stmt); context.def_interner.push_global(global.stmt_id, name, global.module_id, storage_slot); (global.file_id, global.stmt_id) - }) + }); + ResolvedGlobals { globals, errors } } fn type_check_globals( interner: &mut NodeInterner, global_ids: Vec<(FileId, StmtId)>, - all_errors: &mut Vec, -) { - for (file_id, stmt_id) in global_ids { - let errors = TypeChecker::check_global(&stmt_id, interner); - extend_errors(all_errors, file_id, errors); - } +) -> Vec<(CompilationError, fm::FileId)> { + global_ids + .iter() + .flat_map(|(file_id, stmt_id)| { + TypeChecker::check_global(stmt_id, interner) + .iter() + .cloned() + .map(|e| (e.into(), *file_id)) + .collect::>() + }) + .collect() +} + +fn type_check_functions( + interner: &mut NodeInterner, + file_func_ids: Vec<(FileId, FuncId)>, +) -> Vec<(CompilationError, fm::FileId)> { + file_func_ids + .iter() + .flat_map(|(file, func)| { + type_check_func(interner, *func) + .iter() + .cloned() + .map(|e| (e.into(), *file)) + .collect::>() + }) + .collect() } /// Create the mappings from TypeId -> StructType @@ -443,36 +714,37 @@ fn resolve_structs( context: &mut Context, structs: BTreeMap, crate_id: CrateId, - errors: &mut Vec, -) { +) -> Vec<(CompilationError, FileId)> { + let mut errors: Vec<(CompilationError, FileId)> = vec![]; // Resolve each field in each struct. // Each struct should already be present in the NodeInterner after def collection. for (type_id, typ) in structs { - let (generics, fields) = resolve_struct_fields(context, crate_id, typ, errors); + let file_id = typ.file_id; + let (generics, fields, resolver_errors) = resolve_struct_fields(context, crate_id, typ); + errors.extend(vecmap(resolver_errors, |err| (err.into(), file_id))); context.def_interner.update_struct(type_id, |struct_def| { struct_def.set_fields(fields); struct_def.generics = generics; }); } + errors } fn resolve_trait_types( _context: &mut Context, _crate_id: CrateId, _unresolved_trait: &UnresolvedTrait, - _errors: &mut [FileDiagnostic], -) -> Vec { +) -> (Vec, Vec<(CompilationError, FileId)>) { // TODO - vec![] + (vec![], vec![]) } fn resolve_trait_constants( _context: &mut Context, _crate_id: CrateId, _unresolved_trait: &UnresolvedTrait, - _errors: &mut [FileDiagnostic], -) -> Vec { +) -> (Vec, Vec<(CompilationError, FileId)>) { // TODO - vec![] + (vec![], vec![]) } fn resolve_trait_methods( @@ -480,8 +752,7 @@ fn resolve_trait_methods( trait_id: TraitId, crate_id: CrateId, unresolved_trait: &UnresolvedTrait, - errors: &mut Vec, -) -> Vec { +) -> (Vec, Vec<(CompilationError, FileId)>) { let interner = &mut context.def_interner; let def_maps = &mut context.def_maps; @@ -492,7 +763,7 @@ fn resolve_trait_methods( let file = def_maps[&crate_id].file_id(unresolved_trait.module_id); let mut res = vec![]; - + let mut resolver_errors = vec![]; for item in &unresolved_trait.trait_def.items { if let TraitItem::Function { name, @@ -504,10 +775,8 @@ fn resolve_trait_methods( } = item { let the_trait = interner.get_trait(trait_id); - let self_type = Type::TypeVariable( - the_trait.borrow().self_type_typevar.clone(), - TypeVariableKind::Normal, - ); + let self_type = + Type::TypeVariable(the_trait.self_type_typevar.clone(), TypeVariableKind::Normal); let mut resolver = Resolver::new(interner, &path_resolver, def_maps, file); resolver.set_self_type(Some(self_type)); @@ -519,22 +788,43 @@ fn resolve_trait_methods( // TODO let generics: Generics = vec![]; let span: Span = name.span(); + let default_impl_list: Vec<_> = unresolved_trait + .fns_with_default_impl + .functions + .iter() + .filter(|(_, _, q)| q.name() == name.0.contents) + .collect(); + let default_impl = if !default_impl_list.is_empty() { + if default_impl_list.len() > 1 { + // TODO(nickysn): Add check for method duplicates in the trait and emit proper error messages. This is planned in a future PR. + panic!("Too many functions with the same name!"); + } + Some(Box::new(default_impl_list[0].2.clone())) + } else { + None + }; + let f = TraitFunction { name, generics, arguments, return_type: resolved_return_type, span, + default_impl, + default_impl_file_id: unresolved_trait.file_id, + default_impl_module_id: unresolved_trait.module_id, }; res.push(f); - let new_errors = take_errors_filter_self_not_resolved(resolver); - extend_errors(errors, file, new_errors); + resolver_errors.extend(take_errors_filter_self_not_resolved(file, resolver)); } } - res + (res, resolver_errors) } -fn take_errors_filter_self_not_resolved(resolver: Resolver<'_>) -> Vec { +fn take_errors_filter_self_not_resolved( + file_id: FileId, + resolver: Resolver<'_>, +) -> Vec<(CompilationError, FileId)> { resolver .take_errors() .iter() @@ -545,73 +835,75 @@ fn take_errors_filter_self_not_resolved(resolver: Resolver<'_>) -> Vec true, }) .cloned() + .map(|resolution_error| (resolution_error.into(), file_id)) .collect() } +fn take_errors(file_id: FileId, resolver: Resolver<'_>) -> Vec<(CompilationError, FileId)> { + resolver.take_errors().iter().cloned().map(|e| (e.into(), file_id)).collect() +} + /// Create the mappings from TypeId -> TraitType /// so that expressions can access the elements of traits fn resolve_traits( context: &mut Context, traits: BTreeMap, crate_id: CrateId, - errors: &mut Vec, -) { +) -> Vec<(CompilationError, FileId)> { for (trait_id, unresolved_trait) in &traits { context.def_interner.push_empty_trait(*trait_id, unresolved_trait); } + let mut res: Vec<(CompilationError, FileId)> = vec![]; for (trait_id, unresolved_trait) in traits { // Resolve order // 1. Trait Types ( Trait constants can have a trait type, therefore types before constants) - let _ = resolve_trait_types(context, crate_id, &unresolved_trait, errors); + let _ = resolve_trait_types(context, crate_id, &unresolved_trait); // 2. Trait Constants ( Trait's methods can use trait types & constants, therefore they should be after) - let _ = resolve_trait_constants(context, crate_id, &unresolved_trait, errors); + let _ = resolve_trait_constants(context, crate_id, &unresolved_trait); // 3. Trait Methods - let methods = resolve_trait_methods(context, trait_id, crate_id, &unresolved_trait, errors); - + let (methods, errors) = + resolve_trait_methods(context, trait_id, crate_id, &unresolved_trait); + res.extend(errors); context.def_interner.update_trait(trait_id, |trait_def| { trait_def.set_methods(methods); }); } + res } fn resolve_struct_fields( context: &mut Context, krate: CrateId, unresolved: UnresolvedStruct, - all_errors: &mut Vec, -) -> (Generics, Vec<(Ident, Type)>) { +) -> (Generics, Vec<(Ident, Type)>, Vec) { let path_resolver = StandardPathResolver::new(ModuleId { local_id: unresolved.module_id, krate }); - - let file = unresolved.file_id; - + let file_id = unresolved.file_id; let (generics, fields, errors) = - Resolver::new(&mut context.def_interner, &path_resolver, &context.def_maps, file) + Resolver::new(&mut context.def_interner, &path_resolver, &context.def_maps, file_id) .resolve_struct_fields(unresolved.struct_def); - - extend_errors(all_errors, unresolved.file_id, errors); - (generics, fields) + (generics, fields, errors) } fn resolve_type_aliases( context: &mut Context, type_aliases: BTreeMap, crate_id: CrateId, - all_errors: &mut Vec, -) { +) -> Vec<(CompilationError, FileId)> { + let mut errors: Vec<(CompilationError, FileId)> = vec![]; for (type_id, unresolved_typ) in type_aliases { let path_resolver = StandardPathResolver::new(ModuleId { local_id: unresolved_typ.module_id, krate: crate_id, }); let file = unresolved_typ.file_id; - let (typ, generics, errors) = + let (typ, generics, resolver_errors) = Resolver::new(&mut context.def_interner, &path_resolver, &context.def_maps, file) .resolve_type_aliases(unresolved_typ.type_alias_def); - extend_errors(all_errors, file, errors); - + errors.extend(resolver_errors.iter().cloned().map(|e| (e.into(), file))); context.def_interner.set_type_alias(type_id, typ, generics); } + errors } fn resolve_impls( @@ -619,7 +911,7 @@ fn resolve_impls( crate_id: CrateId, def_maps: &BTreeMap, collected_impls: ImplMap, - errors: &mut Vec, + errors: &mut Vec<(CompilationError, FileId)>, ) -> Vec<(FileId, FuncId)> { let mut file_method_ids = Vec::new(); @@ -656,8 +948,7 @@ fn resolve_impls( first_span: interner.function_ident(&first_fn).span(), second_span: interner.function_ident(method_id).span(), }; - - errors.push(error.into_file_diagnostic(*file_id)); + errors.push((error.into(), *file_id)); } } } @@ -670,17 +961,19 @@ fn resolve_impls( fn resolve_trait_impls( context: &mut Context, - traits: TraitImplMap, + traits: Vec, crate_id: CrateId, - errors: &mut Vec, + errors: &mut Vec<(CompilationError, FileId)>, ) -> Vec<(FileId, FuncId)> { let interner = &mut context.def_interner; let mut methods = Vec::<(FileId, FuncId)>::new(); - for ((unresolved_type, _, trait_id), trait_impl) in traits { + for trait_impl in traits { + let unresolved_type = trait_impl.object_type; let local_mod_id = trait_impl.module_id; let module_id = ModuleId { krate: crate_id, local_id: local_mod_id }; let path_resolver = StandardPathResolver::new(module_id); + let trait_definition_ident = trait_impl.trait_path.last_segment(); let self_type = { let mut resolver = @@ -688,6 +981,8 @@ fn resolve_trait_impls( resolver.resolve_type(unresolved_type.clone()) }; + let maybe_trait_id = trait_impl.trait_id; + let mut impl_methods = resolve_function_set( interner, crate_id, @@ -698,34 +993,33 @@ fn resolve_trait_impls( errors, ); - let resolved_trait_impl = Shared::new(TraitImpl { - ident: trait_impl.trait_impl_ident.clone(), - typ: self_type.clone(), - trait_id, - methods: vecmap(&impl_methods, |(_, func_id)| *func_id), - }); - let mut new_resolver = Resolver::new(interner, &path_resolver, &context.def_maps, trait_impl.file_id); new_resolver.set_self_type(Some(self_type.clone())); - check_methods_signatures(&mut new_resolver, &impl_methods, trait_id, errors); - - let trait_definition_ident = &trait_impl.trait_impl_ident; - let key = TraitImplKey { typ: self_type.clone(), trait_id }; + if let Some(trait_id) = maybe_trait_id { + check_methods_signatures(&mut new_resolver, &impl_methods, trait_id, errors); + + let key = TraitImplKey { typ: self_type.clone(), trait_id }; + if let Some(prev_trait_impl_ident) = interner.get_trait_implementation(&key) { + let err = DefCollectorErrorKind::Duplicate { + typ: DuplicateType::TraitImplementation, + first_def: prev_trait_impl_ident.borrow().ident.clone(), + second_def: trait_definition_ident.clone(), + }; + errors.push((err.into(), trait_impl.methods.file_id)); + } else { + let resolved_trait_impl = Shared::new(TraitImpl { + ident: trait_impl.trait_path.last_segment().clone(), + typ: self_type.clone(), + trait_id, + methods: vecmap(&impl_methods, |(_, func_id)| *func_id), + }); + interner.add_trait_implementation(&key, resolved_trait_impl.clone()); + } - if let Some(prev_trait_impl_ident) = interner.get_trait_implementation(&key) { - let err = DefCollectorErrorKind::Duplicate { - typ: DuplicateType::TraitImplementation, - first_def: prev_trait_impl_ident.borrow().ident.clone(), - second_def: trait_definition_ident.clone(), - }; - errors.push(err.into_file_diagnostic(trait_impl.methods.file_id)); - } else { - interner.add_trait_implementation(&key, resolved_trait_impl); + methods.append(&mut impl_methods); } - - methods.append(&mut impl_methods); } methods @@ -736,10 +1030,9 @@ fn check_methods_signatures( resolver: &mut Resolver, impl_methods: &Vec<(FileId, FuncId)>, trait_id: TraitId, - errors: &mut Vec, + errors: &mut Vec<(CompilationError, FileId)>, ) { - let the_trait_shared = resolver.interner.get_trait(trait_id); - let the_trait = the_trait_shared.borrow(); + let the_trait = resolver.interner.get_trait(trait_id); let self_type = resolver.get_self_type().expect("trait impl must have a Self type"); @@ -777,7 +1070,7 @@ fn check_methods_signatures( }); } } else { - errors.push( + errors.push(( DefCollectorErrorKind::MismatchTraitImplementationNumParameters { actual_num_parameters: meta.parameters.0.len(), expected_num_parameters: method.arguments.len(), @@ -785,8 +1078,9 @@ fn check_methods_signatures( method_name: func_name.to_string(), span: meta.location.span, } - .into_file_diagnostic(*file_id), - ); + .into(), + *file_id, + )); } } @@ -805,7 +1099,7 @@ fn check_methods_signatures( } }); - extend_errors(errors, *file_id, typecheck_errors); + errors.extend(typecheck_errors.iter().cloned().map(|e| (e.into(), *file_id))); } } @@ -818,7 +1112,7 @@ fn resolve_free_functions( def_maps: &BTreeMap, collected_functions: Vec, self_type: Option, - errors: &mut Vec, + errors: &mut Vec<(CompilationError, FileId)>, ) -> Vec<(FileId, FuncId)> { // Lower each function in the crate. This is now possible since imports have been resolved collected_functions @@ -841,13 +1135,17 @@ fn resolve_function_set( interner: &mut NodeInterner, crate_id: CrateId, def_maps: &BTreeMap, - unresolved_functions: UnresolvedFunctions, + mut unresolved_functions: UnresolvedFunctions, self_type: Option, impl_generics: Vec<(Rc, Shared, Span)>, - errors: &mut Vec, + errors: &mut Vec<(CompilationError, FileId)>, ) -> Vec<(FileId, FuncId)> { let file_id = unresolved_functions.file_id; + let where_clause_errors = + unresolved_functions.resolve_trait_bounds_trait_ids(def_maps, crate_id); + errors.extend(where_clause_errors.iter().cloned().map(|e| (e.into(), file_id))); + vecmap(unresolved_functions.functions, |(mod_id, func_id, func)| { let module_id = ModuleId { krate: crate_id, local_id: mod_id }; let path_resolver = StandardPathResolver::new(module_id); @@ -862,17 +1160,7 @@ fn resolve_function_set( let (hir_func, func_meta, errs) = resolver.resolve_function(func, func_id); interner.push_fn_meta(func_meta, func_id); interner.update_fn(func_id, hir_func); - extend_errors(errors, file_id, errs); + errors.extend(errs.iter().cloned().map(|e| (e.into(), file_id))); (file_id, func_id) }) } - -fn type_check_functions( - interner: &mut NodeInterner, - file_func_ids: Vec<(FileId, FuncId)>, - errors: &mut Vec, -) { - for (file, func) in file_func_ids { - extend_errors(errors, file, type_check_func(interner, func)); - } -} diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index 86e4eb50de3..189cfaa1569 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -1,24 +1,20 @@ -use std::collections::HashSet; +use std::vec; use fm::FileId; -use noirc_errors::{FileDiagnostic, Location}; +use noirc_errors::Location; use crate::{ graph::CrateId, - hir::{ - def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait}, - def_map::ScopeResolveError, - }, - node_interner::{FunctionModifiers, TraitId}, + hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait}, + node_interner::TraitId, parser::SubModule, - token::Attributes, FunctionDefinition, Ident, LetStatement, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, ParsedModule, TraitImplItem, TraitItem, TypeImpl, }; use super::{ dc_crate::{ - DefCollector, UnresolvedFunctions, UnresolvedGlobal, UnresolvedTraitImpl, + CompilationError, DefCollector, UnresolvedFunctions, UnresolvedGlobal, UnresolvedTraitImpl, UnresolvedTypeAlias, }, errors::{DefCollectorErrorKind, DuplicateType}, @@ -44,16 +40,15 @@ pub fn collect_defs( module_id: LocalModuleId, crate_id: CrateId, context: &mut Context, - errors: &mut Vec, -) { +) -> Vec<(CompilationError, FileId)> { let mut collector = ModCollector { def_collector, file_id, module_id }; - + let mut errors: Vec<(CompilationError, FileId)> = vec![]; // First resolve the module declarations for decl in ast.module_decls { - collector.parse_module_declaration(context, &decl, crate_id, errors); + errors.extend(collector.parse_module_declaration(context, &decl, crate_id)); } - collector.collect_submodules(context, crate_id, ast.submodules, file_id, errors); + errors.extend(collector.collect_submodules(context, crate_id, ast.submodules, file_id)); // Then add the imports to defCollector to resolve once all modules in the hierarchy have been resolved for import in ast.imports { @@ -64,19 +59,21 @@ pub fn collect_defs( }); } - collector.collect_globals(context, ast.globals, errors); + errors.extend(collector.collect_globals(context, ast.globals)); - collector.collect_traits(ast.traits, crate_id, errors); + errors.extend(collector.collect_traits(context, ast.traits, crate_id)); - collector.collect_structs(context, ast.types, crate_id, errors); + errors.extend(collector.collect_structs(context, ast.types, crate_id)); - collector.collect_type_aliases(context, ast.type_aliases, errors); + errors.extend(collector.collect_type_aliases(context, ast.type_aliases)); - collector.collect_functions(context, ast.functions, crate_id, errors); + errors.extend(collector.collect_functions(context, ast.functions, crate_id)); - collector.collect_trait_impls(context, ast.trait_impls, crate_id, errors); + errors.extend(collector.collect_trait_impls(context, ast.trait_impls, crate_id)); collector.collect_impls(context, ast.impls, crate_id); + + errors } impl<'a> ModCollector<'a> { @@ -84,8 +81,8 @@ impl<'a> ModCollector<'a> { &mut self, context: &mut Context, globals: Vec, - errors: &mut Vec, - ) { + ) -> Vec<(CompilationError, fm::FileId)> { + let mut errors = vec![]; for global in globals { let name = global.pattern.name_ident().clone(); @@ -103,7 +100,7 @@ impl<'a> ModCollector<'a> { first_def, second_def, }; - errors.push(err.into_file_diagnostic(self.file_id)); + errors.push((err.into(), self.file_id)); } self.def_collector.collected_globals.push(UnresolvedGlobal { @@ -113,6 +110,7 @@ impl<'a> ModCollector<'a> { stmt_def: global, }); } + errors } fn collect_impls(&mut self, context: &mut Context, impls: Vec, krate: CrateId) { @@ -139,75 +137,38 @@ impl<'a> ModCollector<'a> { context: &mut Context, impls: Vec, krate: CrateId, - errors: &mut Vec, - ) { - let module_id = ModuleId { krate, local_id: self.module_id }; - + ) -> Vec<(CompilationError, fm::FileId)> { for trait_impl in impls { - let trait_name = &trait_impl.trait_name; - let module = &self.def_collector.def_map.modules[self.module_id.0]; - - if let Some(trait_id) = self.find_trait_or_emit_error(module, trait_name, errors) { - let collected_trait = - self.def_collector.collected_traits.get(&trait_id).cloned().unwrap(); + let trait_name = trait_impl.trait_name.clone(); - let unresolved_functions = self.collect_trait_implementations( - context, - &trait_impl, - &collected_trait.trait_def, - krate, - errors, - ); - - for (_, func_id, noir_function) in &unresolved_functions.functions { - let function = &noir_function.def; - context.def_interner.push_function(*func_id, function, module_id); - } + let unresolved_functions = + self.collect_trait_impl_function_overrides(context, &trait_impl, krate); - let unresolved_trait_impl = UnresolvedTraitImpl { - file_id: self.file_id, - module_id: self.module_id, - the_trait: collected_trait, - methods: unresolved_functions, - trait_impl_ident: trait_impl.trait_name.clone(), - }; + let module = ModuleId { krate, local_id: self.module_id }; - let key = (trait_impl.object_type, self.module_id, trait_id); - self.def_collector.collected_traits_impls.insert(key, unresolved_trait_impl); + for (_, func_id, noir_function) in &unresolved_functions.functions { + context.def_interner.push_function(*func_id, &noir_function.def, module); } - } - } - fn find_trait_or_emit_error( - &self, - module: &ModuleData, - trait_name: &Ident, - errors: &mut Vec, - ) -> Option { - match module.find_trait_with_name(trait_name) { - Ok(trait_id) => Some(trait_id), - Err(ScopeResolveError::WrongKind) => { - let error = - DefCollectorErrorKind::NotATrait { not_a_trait_name: trait_name.clone() }; - errors.push(error.into_file_diagnostic(self.file_id)); - None - } - Err(ScopeResolveError::NotFound) => { - let error = - DefCollectorErrorKind::TraitNotFound { trait_ident: trait_name.clone() }; - errors.push(error.into_file_diagnostic(self.file_id)); - None - } + let unresolved_trait_impl = UnresolvedTraitImpl { + file_id: self.file_id, + module_id: self.module_id, + trait_path: trait_name, + methods: unresolved_functions, + object_type: trait_impl.object_type, + trait_id: None, // will be filled later + }; + + self.def_collector.collected_traits_impls.push(unresolved_trait_impl); } + vec![] } - fn collect_trait_implementations( + fn collect_trait_impl_function_overrides( &mut self, context: &mut Context, trait_impl: &NoirTraitImpl, - trait_def: &NoirTrait, krate: CrateId, - errors: &mut Vec, ) -> UnresolvedFunctions { let mut unresolved_functions = UnresolvedFunctions { file_id: self.file_id, functions: Vec::new() }; @@ -222,90 +183,6 @@ impl<'a> ModCollector<'a> { } } - // set of function ids that have a corresponding method in the trait - let mut func_ids_in_trait = HashSet::new(); - - for item in &trait_def.items { - // TODO(Maddiaa): Investigate trait implementations with attributes see: https://github.com/noir-lang/noir/issues/2629 - if let TraitItem::Function { - name, - generics, - parameters, - return_type, - where_clause, - body, - } = item - { - // List of functions in the impl block with the same name as the method - // `matching_fns.len() == 0` => missing method impl - // `matching_fns.len() > 1` => duplicate definition (collect_functions will throw a Duplicate error) - let matching_fns: Vec<_> = unresolved_functions - .functions - .iter() - .filter(|(_, _, func_impl)| func_impl.name() == name.0.contents) - .collect(); - - if matching_fns.is_empty() { - match body { - Some(body) => { - // if there's a default implementation for the method, use it - let method_name = name.0.contents.clone(); - let func_id = context.def_interner.push_empty_fn(); - let modifiers = FunctionModifiers { - // trait functions are always public - visibility: crate::Visibility::Public, - attributes: Attributes::empty(), - is_unconstrained: false, - contract_function_type: None, - is_internal: None, - }; - - context.def_interner.push_function_definition( - method_name, - func_id, - modifiers, - module, - ); - let impl_method = NoirFunction::normal(FunctionDefinition::normal( - name, - generics, - parameters, - body, - where_clause, - return_type, - )); - func_ids_in_trait.insert(func_id); - unresolved_functions.push_fn(self.module_id, func_id, impl_method); - } - None => { - let error = DefCollectorErrorKind::TraitMissingMethod { - trait_name: trait_def.name.clone(), - method_name: name.clone(), - trait_impl_span: trait_impl.object_type_span, - }; - errors.push(error.into_file_diagnostic(self.file_id)); - } - } - } else { - for (_, func_id, _) in &matching_fns { - func_ids_in_trait.insert(*func_id); - } - } - } - } - - // Emit MethodNotInTrait error for methods in the impl block that - // don't have a corresponding method signature defined in the trait - for (_, func_id, func) in &unresolved_functions.functions { - if !func_ids_in_trait.contains(func_id) { - let error = DefCollectorErrorKind::MethodNotInTrait { - trait_name: trait_def.name.clone(), - impl_method: func.name_ident().clone(), - }; - errors.push(error.into_file_diagnostic(self.file_id)); - } - } - unresolved_functions } @@ -314,14 +191,14 @@ impl<'a> ModCollector<'a> { context: &mut Context, functions: Vec, krate: CrateId, - errors: &mut Vec, - ) { + ) -> Vec<(CompilationError, FileId)> { let mut unresolved_functions = UnresolvedFunctions { file_id: self.file_id, functions: Vec::new() }; + let mut errors = vec![]; let module = ModuleId { krate, local_id: self.module_id }; - for mut function in functions { + for function in functions { let name = function.name_ident().clone(); let func_id = context.def_interner.push_empty_fn(); @@ -329,19 +206,6 @@ impl<'a> ModCollector<'a> { // So that we can get a FuncId context.def_interner.push_function(func_id, &function.def, module); - // Then go over the where clause and assign trait_ids to the constraints - for constraint in &mut function.def.where_clause { - let module = &self.def_collector.def_map.modules[self.module_id.0]; - - if let Some(trait_id) = self.find_trait_or_emit_error( - module, - &constraint.trait_bound.trait_name, - errors, - ) { - constraint.trait_bound.trait_id = Some(trait_id); - } - } - // Now link this func_id to a crate level map with the noir function and the module id // Encountering a NoirFunction, we retrieve it's module_data to get the namespace // Once we have lowered it to a HirFunction, we retrieve it's Id from the DefInterner @@ -360,11 +224,12 @@ impl<'a> ModCollector<'a> { first_def, second_def, }; - errors.push(error.into_file_diagnostic(self.file_id)); + errors.push((error.into(), self.file_id)); } } self.def_collector.collected_functions.push(unresolved_functions); + errors } /// Collect any struct definitions declared within the ast. @@ -374,8 +239,8 @@ impl<'a> ModCollector<'a> { context: &mut Context, types: Vec, krate: CrateId, - errors: &mut Vec, - ) { + ) -> Vec<(CompilationError, FileId)> { + let mut definiton_errors = vec![]; for struct_definition in types { let name = struct_definition.name.clone(); @@ -386,9 +251,12 @@ impl<'a> ModCollector<'a> { }; // Create the corresponding module for the struct namespace - let id = match self.push_child_module(&name, self.file_id, false, false, errors) { - Some(local_id) => context.def_interner.new_struct(&unresolved, krate, local_id), - None => continue, + let id = match self.push_child_module(&name, self.file_id, false, false) { + Ok(local_id) => context.def_interner.new_struct(&unresolved, krate, local_id), + Err(error) => { + definiton_errors.push((error.into(), self.file_id)); + continue; + } }; // Add the struct to scope so its path can be looked up later @@ -396,17 +264,18 @@ impl<'a> ModCollector<'a> { self.def_collector.def_map.modules[self.module_id.0].declare_struct(name, id); if let Err((first_def, second_def)) = result { - let err = DefCollectorErrorKind::Duplicate { + let error = DefCollectorErrorKind::Duplicate { typ: DuplicateType::TypeDefinition, first_def, second_def, }; - errors.push(err.into_file_diagnostic(self.file_id)); + definiton_errors.push((error.into(), self.file_id)); } // And store the TypeId -> StructType mapping somewhere it is reachable self.def_collector.collected_types.insert(id, unresolved); } + definiton_errors } /// Collect any type aliases definitions declared within the ast. @@ -415,8 +284,8 @@ impl<'a> ModCollector<'a> { &mut self, context: &mut Context, type_aliases: Vec, - errors: &mut Vec, - ) { + ) -> Vec<(CompilationError, FileId)> { + let mut errors: Vec<(CompilationError, FileId)> = vec![]; for type_alias in type_aliases { let name = type_alias.name.clone(); @@ -439,28 +308,33 @@ impl<'a> ModCollector<'a> { first_def, second_def, }; - errors.push(err.into_file_diagnostic(self.file_id)); + errors.push((err.into(), self.file_id)); } self.def_collector.collected_type_aliases.insert(type_alias_id, unresolved); } + errors } /// Collect any traits definitions declared within the ast. /// Returns a vector of errors if any traits were already defined. fn collect_traits( &mut self, + context: &mut Context, traits: Vec, krate: CrateId, - errors: &mut Vec, - ) { + ) -> Vec<(CompilationError, FileId)> { + let mut errors: Vec<(CompilationError, FileId)> = vec![]; for trait_definition in traits { let name = trait_definition.name.clone(); // Create the corresponding module for the trait namespace - let id = match self.push_child_module(&name, self.file_id, false, false, errors) { - Some(local_id) => TraitId(ModuleId { krate, local_id }), - None => continue, + let id = match self.push_child_module(&name, self.file_id, false, false) { + Ok(local_id) => TraitId(ModuleId { krate, local_id }), + Err(error) => { + errors.push((error.into(), self.file_id)); + continue; + } }; // Add the trait to scope so its path can be looked up later @@ -468,22 +342,53 @@ impl<'a> ModCollector<'a> { self.def_collector.def_map.modules[self.module_id.0].declare_trait(name, id); if let Err((first_def, second_def)) = result { - let err = DefCollectorErrorKind::Duplicate { + let error = DefCollectorErrorKind::Duplicate { typ: DuplicateType::Trait, first_def, second_def, }; - errors.push(err.into_file_diagnostic(self.file_id)); + errors.push((error.into(), self.file_id)); + } + + // Add all functions that have a default implementation in the trait + let mut unresolved_functions = + UnresolvedFunctions { file_id: self.file_id, functions: Vec::new() }; + for trait_item in &trait_definition.items { + // TODO(Maddiaa): Investigate trait implementations with attributes see: https://github.com/noir-lang/noir/issues/2629 + if let TraitItem::Function { + name, + generics, + parameters, + return_type, + where_clause, + body: Some(body), + } = trait_item + { + let func_id = context.def_interner.push_empty_fn(); + + let impl_method = NoirFunction::normal(FunctionDefinition::normal( + name, + generics, + parameters, + body, + where_clause, + return_type, + )); + unresolved_functions.push_fn(self.module_id, func_id, impl_method); + } } // And store the TraitId -> TraitType mapping somewhere it is reachable let unresolved = UnresolvedTrait { file_id: self.file_id, module_id: self.module_id, + crate_id: krate, trait_def: trait_definition, + fns_with_default_impl: unresolved_functions, }; self.def_collector.collected_traits.insert(id, unresolved); } + errors } fn collect_submodules( @@ -492,27 +397,26 @@ impl<'a> ModCollector<'a> { crate_id: CrateId, submodules: Vec, file_id: FileId, - errors: &mut Vec, - ) { + ) -> Vec<(CompilationError, FileId)> { + let mut errors: Vec<(CompilationError, FileId)> = vec![]; for submodule in submodules { - if let Some(child) = self.push_child_module( - &submodule.name, - file_id, - true, - submodule.is_contract, - errors, - ) { - collect_defs( - self.def_collector, - submodule.contents, - file_id, - child, - crate_id, - context, - errors, - ); - } + match self.push_child_module(&submodule.name, file_id, true, submodule.is_contract) { + Ok(child) => { + errors.extend(collect_defs( + self.def_collector, + submodule.contents, + file_id, + child, + crate_id, + context, + )); + } + Err(error) => { + errors.push((error.into(), file_id)); + } + }; } + errors } /// Search for a module named `mod_name` @@ -523,36 +427,64 @@ impl<'a> ModCollector<'a> { context: &mut Context, mod_name: &Ident, crate_id: CrateId, - errors: &mut Vec, - ) { + ) -> Vec<(CompilationError, FileId)> { + let mut errors: Vec<(CompilationError, FileId)> = vec![]; let child_file_id = match context.file_manager.find_module(self.file_id, &mod_name.0.contents) { Ok(child_file_id) => child_file_id, - Err(_) => { + Err(expected_path) => { + let mod_name = mod_name.clone(); let err = - DefCollectorErrorKind::UnresolvedModuleDecl { mod_name: mod_name.clone() }; - errors.push(err.into_file_diagnostic(self.file_id)); - return; + DefCollectorErrorKind::UnresolvedModuleDecl { mod_name, expected_path }; + errors.push((err.into(), self.file_id)); + return errors; } }; + let location = Location { file: self.file_id, span: mod_name.span() }; + + if let Some(old_location) = context.visited_files.get(&child_file_id) { + let error = DefCollectorErrorKind::ModuleAlreadyPartOfCrate { + mod_name: mod_name.clone(), + span: location.span, + }; + errors.push((error.into(), location.file)); + + let error2 = DefCollectorErrorKind::ModuleOrignallyDefined { + mod_name: mod_name.clone(), + span: old_location.span, + }; + errors.push((error2.into(), old_location.file)); + return errors; + } + + context.visited_files.insert(child_file_id, location); + // Parse the AST for the module we just found and then recursively look for it's defs - let ast = parse_file(&context.file_manager, child_file_id, errors); + //let ast = parse_file(&context.file_manager, child_file_id, errors); + let (ast, parsing_errors) = parse_file(&context.file_manager, child_file_id); + + errors.extend( + parsing_errors.iter().map(|e| (e.clone().into(), child_file_id)).collect::>(), + ); // Add module into def collector and get a ModuleId - if let Some(child_mod_id) = - self.push_child_module(mod_name, child_file_id, true, false, errors) - { - collect_defs( - self.def_collector, - ast, - child_file_id, - child_mod_id, - crate_id, - context, - errors, - ); + match self.push_child_module(mod_name, child_file_id, true, false) { + Ok(child_mod_id) => { + errors.extend(collect_defs( + self.def_collector, + ast, + child_file_id, + child_mod_id, + crate_id, + context, + )); + } + Err(error) => { + errors.push((error.into(), child_file_id)); + } } + errors } /// Add a child module to the current def_map. @@ -563,8 +495,7 @@ impl<'a> ModCollector<'a> { file_id: FileId, add_to_parent_scope: bool, is_contract: bool, - errors: &mut Vec, - ) -> Option { + ) -> Result { let parent = Some(self.module_id); let location = Location::new(mod_name.span(), file_id); let new_module = ModuleData::new(parent, location, is_contract); @@ -596,11 +527,10 @@ impl<'a> ModCollector<'a> { first_def, second_def, }; - errors.push(err.into_file_diagnostic(self.file_id)); - return None; + return Err(err); } } - Some(LocalModuleId(module_id)) + Ok(LocalModuleId(module_id)) } } diff --git a/compiler/noirc_frontend/src/hir/def_collector/errors.rs b/compiler/noirc_frontend/src/hir/def_collector/errors.rs index afd0599ef4e..f959cdec598 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/errors.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/errors.rs @@ -1,5 +1,6 @@ use crate::hir::resolution::import::PathResolutionError; use crate::Ident; +use crate::Path; use noirc_errors::CustomDiagnostic as Diagnostic; use noirc_errors::FileDiagnostic; @@ -8,7 +9,7 @@ use thiserror::Error; use std::fmt; -#[derive(Debug)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum DuplicateType { Function, Module, @@ -19,18 +20,18 @@ pub enum DuplicateType { TraitImplementation, } -#[derive(Error, Debug)] +#[derive(Error, Debug, Clone)] pub enum DefCollectorErrorKind { #[error("duplicate {typ} found in namespace")] Duplicate { typ: DuplicateType, first_def: Ident, second_def: Ident }, #[error("unresolved import")] - UnresolvedModuleDecl { mod_name: Ident }, + UnresolvedModuleDecl { mod_name: Ident, expected_path: String }, #[error("path resolution error")] PathResolutionError(PathResolutionError), #[error("Non-struct type used in impl")] NonStructTypeInImpl { span: Span }, #[error("Non-struct type used in trait impl")] - NonStructTraitImpl { trait_ident: Ident, span: Span }, + NonStructTraitImpl { trait_path: Path, span: Span }, #[error("Cannot `impl` a type defined outside the current crate")] ForeignImpl { span: Span, type_name: String }, #[error("Mismatch number of parameters in of trait implementation")] @@ -44,11 +45,22 @@ pub enum DefCollectorErrorKind { #[error("Method is not defined in trait")] MethodNotInTrait { trait_name: Ident, impl_method: Ident }, #[error("Only traits can be implemented")] - NotATrait { not_a_trait_name: Ident }, + NotATrait { not_a_trait_name: Path }, #[error("Trait not found")] - TraitNotFound { trait_ident: Ident }, + TraitNotFound { trait_path: Path }, #[error("Missing Trait method implementation")] TraitMissingMethod { trait_name: Ident, method_name: Ident, trait_impl_span: Span }, + #[error("Module is already part of the crate")] + ModuleAlreadyPartOfCrate { mod_name: Ident, span: Span }, + #[error("Module was originally declared here")] + ModuleOrignallyDefined { mod_name: Ident, span: Span }, + #[cfg(feature = "aztec")] + #[error("Aztec dependency not found. Please add aztec as a dependency in your Cargo.toml")] + AztecNotFound {}, + #[error( + "Either the type or the trait must be from the same crate as the trait implementation" + )] + TraitImplOrphaned { span: Span }, } impl DefCollectorErrorKind { @@ -76,7 +88,7 @@ impl From for Diagnostic { match error { DefCollectorErrorKind::Duplicate { typ, first_def, second_def } => { let primary_message = format!( - "duplicate definitions of {} with name {} found", + "Duplicate definitions of {} with name {} found", &typ, &first_def.0.contents ); { @@ -84,19 +96,19 @@ impl From for Diagnostic { let second_span = second_def.0.span(); let mut diag = Diagnostic::simple_error( primary_message, - format!("first {} found here", &typ), + format!("First {} found here", &typ), first_span, ); - diag.add_secondary(format!("second {} found here", &typ), second_span); + diag.add_secondary(format!("Second {} found here", &typ), second_span); diag } } - DefCollectorErrorKind::UnresolvedModuleDecl { mod_name } => { + DefCollectorErrorKind::UnresolvedModuleDecl { mod_name, expected_path } => { let span = mod_name.0.span(); let mod_name = &mod_name.0.contents; Diagnostic::simple_error( - format!("could not resolve module `{mod_name}` "), + format!("No module `{mod_name}` at path `{expected_path}`"), String::new(), span, ) @@ -107,9 +119,9 @@ impl From for Diagnostic { "Only struct types may have implementation methods".into(), span, ), - DefCollectorErrorKind::NonStructTraitImpl { trait_ident, span } => { + DefCollectorErrorKind::NonStructTraitImpl { trait_path, span } => { Diagnostic::simple_error( - format!("Only struct types may implement trait `{trait_ident}`"), + format!("Only struct types may implement trait `{trait_path}`"), "Only struct types may implement traits".into(), span, ) @@ -119,10 +131,10 @@ impl From for Diagnostic { format!("{type_name} was defined outside the current crate"), span, ), - DefCollectorErrorKind::TraitNotFound { trait_ident } => Diagnostic::simple_error( - format!("Trait {trait_ident} not found"), + DefCollectorErrorKind::TraitNotFound { trait_path } => Diagnostic::simple_error( + format!("Trait {trait_path} not found"), "".to_string(), - trait_ident.span(), + trait_path.span(), ), DefCollectorErrorKind::MismatchTraitImplementationNumParameters { expected_num_parameters, @@ -159,14 +171,32 @@ impl From for Diagnostic { ) } DefCollectorErrorKind::NotATrait { not_a_trait_name } => { - let span = not_a_trait_name.0.span(); - let name = ¬_a_trait_name.0.contents; + let span = not_a_trait_name.span(); Diagnostic::simple_error( - format!("{name} is not a trait, therefore it can't be implemented"), + format!("{not_a_trait_name} is not a trait, therefore it can't be implemented"), String::new(), span, ) } + DefCollectorErrorKind::ModuleAlreadyPartOfCrate { mod_name, span } => { + let message = format!("Module '{mod_name}' is already part of the crate"); + let secondary = String::new(); + Diagnostic::simple_error(message, secondary, span) + } + DefCollectorErrorKind::ModuleOrignallyDefined { mod_name, span } => { + let message = format!("Note: {mod_name} was originally declared here"); + let secondary = String::new(); + Diagnostic::simple_error(message, secondary, span) + } + #[cfg(feature = "aztec")] + DefCollectorErrorKind::AztecNotFound {} => Diagnostic::from_message( + "Aztec dependency not found. Please add aztec as a dependency in your Cargo.toml", + ), + DefCollectorErrorKind::TraitImplOrphaned { span } => Diagnostic::simple_error( + "Orphaned trait implementation".into(), + "Either the type or the trait must be from the same crate as the trait implementation".into(), + span, + ), } } } diff --git a/compiler/noirc_frontend/src/hir/def_collector/mod.rs b/compiler/noirc_frontend/src/hir/def_collector/mod.rs index 26f360642ef..f7d11c343fd 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/mod.rs @@ -19,4 +19,4 @@ //! These passes are performed sequentially (along with type checking afterward) in dc_crate. pub mod dc_crate; pub mod dc_mod; -mod errors; +pub mod errors; diff --git a/compiler/noirc_frontend/src/hir/def_map/aztec_library.rs b/compiler/noirc_frontend/src/hir/def_map/aztec_library.rs index e3a9735f936..69adb864317 100644 --- a/compiler/noirc_frontend/src/hir/def_map/aztec_library.rs +++ b/compiler/noirc_frontend/src/hir/def_map/aztec_library.rs @@ -1,7 +1,8 @@ use acvm::FieldElement; -use noirc_errors::{CustomDiagnostic, Span}; +use noirc_errors::Span; use crate::graph::CrateId; +use crate::hir::def_collector::errors::DefCollectorErrorKind; use crate::token::SecondaryAttribute; use crate::{ hir::Context, BlockExpression, CallExpression, CastExpression, Distinctness, Expression, @@ -11,7 +12,7 @@ use crate::{ Visibility, }; use crate::{PrefixExpression, UnaryOp}; -use noirc_errors::FileDiagnostic; +use fm::FileId; // // Helper macros for creating noir ast nodes @@ -155,8 +156,7 @@ pub(crate) fn transform( mut ast: ParsedModule, crate_id: &CrateId, context: &Context, - errors: &mut Vec, -) -> ParsedModule { +) -> Result { // Usage -> mut ast -> aztec_library::transform(&mut ast) // Covers all functions in the ast @@ -164,11 +164,15 @@ pub(crate) fn transform( let storage_defined = check_for_storage_definition(&submodule.contents); if transform_module(&mut submodule.contents.functions, storage_defined) { - check_for_aztec_dependency(crate_id, context, errors); - include_relevant_imports(&mut submodule.contents); + match check_for_aztec_dependency(crate_id, context) { + Ok(()) => include_relevant_imports(&mut submodule.contents), + Err(file_id) => { + return Err((DefCollectorErrorKind::AztecNotFound {}, file_id)); + } + } } } - ast + Ok(ast) } /// Includes an import to the aztec library if it has not been included yet @@ -187,21 +191,13 @@ fn include_relevant_imports(ast: &mut ParsedModule) { } /// Creates an error alerting the user that they have not downloaded the Aztec-noir library -fn check_for_aztec_dependency( - crate_id: &CrateId, - context: &Context, - errors: &mut Vec, -) { +fn check_for_aztec_dependency(crate_id: &CrateId, context: &Context) -> Result<(), FileId> { let crate_graph = &context.crate_graph[crate_id]; let has_aztec_dependency = crate_graph.dependencies.iter().any(|dep| dep.as_name() == "aztec"); - - if !has_aztec_dependency { - errors.push(FileDiagnostic::new( - crate_graph.root_file_id, - CustomDiagnostic::from_message( - "Aztec dependency not found. Please add aztec as a dependency in your Cargo.toml", - ), - )); + if has_aztec_dependency { + Ok(()) + } else { + Err(crate_graph.root_file_id) } } diff --git a/compiler/noirc_frontend/src/hir/def_map/item_scope.rs b/compiler/noirc_frontend/src/hir/def_map/item_scope.rs index 490e046a9d9..7dcc5051a0c 100644 --- a/compiler/noirc_frontend/src/hir/def_map/item_scope.rs +++ b/compiler/noirc_frontend/src/hir/def_map/item_scope.rs @@ -1,8 +1,5 @@ use super::{namespace::PerNs, ModuleDefId, ModuleId}; -use crate::{ - node_interner::{FuncId, TraitId}, - Ident, -}; +use crate::{node_interner::FuncId, Ident}; use std::collections::{hash_map::Entry, HashMap}; #[derive(Debug, PartialEq, Eq, Copy, Clone)] @@ -18,14 +15,6 @@ pub struct ItemScope { defs: Vec, } -pub enum ScopeResolveError { - /// the ident we attempted to resolve isn't declared - NotFound, - - /// The ident we attempted to resolve is declared, but is the wrong kind - e.g. we want a trait, but it's a function - WrongKind, -} - impl ItemScope { pub fn add_definition( &mut self, @@ -80,14 +69,6 @@ impl ItemScope { _ => None, } } - pub fn find_trait_with_name(&self, trait_name: &Ident) -> Result { - let (module_def, _) = self.types.get(trait_name).ok_or(ScopeResolveError::NotFound)?; - - match module_def { - ModuleDefId::TraitId(id) => Ok(*id), - _ => Err(ScopeResolveError::WrongKind), - } - } pub fn find_name(&self, name: &Ident) -> PerNs { PerNs { types: self.types.get(name).cloned(), values: self.values.get(name).cloned() } diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index 017027a1da2..27f757074f6 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -1,14 +1,13 @@ use crate::graph::CrateId; -use crate::hir::def_collector::dc_crate::DefCollector; +use crate::hir::def_collector::dc_crate::{CompilationError, DefCollector}; use crate::hir::Context; -use crate::node_interner::{FuncId, NodeInterner}; -use crate::parser::{parse_program, ParsedModule}; -use crate::token::{FunctionAttribute, TestScope}; +use crate::node_interner::{FuncId, NodeInterner, StructId}; +use crate::parser::{parse_program, ParsedModule, ParserError}; +use crate::token::{FunctionAttribute, SecondaryAttribute, TestScope}; use arena::{Arena, Index}; use fm::{FileId, FileManager}; -use noirc_errors::{FileDiagnostic, Location}; +use noirc_errors::Location; use std::collections::BTreeMap; - mod module_def; pub use module_def::*; mod item_scope; @@ -73,23 +72,29 @@ impl CrateDefMap { pub fn collect_defs( crate_id: CrateId, context: &mut Context, - errors: &mut Vec, - ) { + ) -> Vec<(CompilationError, FileId)> { // Check if this Crate has already been compiled // XXX: There is probably a better alternative for this. // Without this check, the compiler will panic as it does not // expect the same crate to be processed twice. It would not // make the implementation wrong, if the same crate was processed twice, it just makes it slow. + let mut errors: Vec<(CompilationError, FileId)> = vec![]; if context.def_map(&crate_id).is_some() { - return; + return errors; } // First parse the root file. let root_file_id = context.crate_graph[crate_id].root_file_id; - let ast = parse_file(&context.file_manager, root_file_id, errors); + let (ast, parsing_errors) = parse_file(&context.file_manager, root_file_id); #[cfg(feature = "aztec")] - let ast = aztec_library::transform(ast, &crate_id, context, errors); + let ast = match aztec_library::transform(ast, &crate_id, context) { + Ok(ast) => ast, + Err((error, file_id)) => { + errors.push((error.into(), file_id)); + return errors; + } + }; // Allocate a default Module for the root, giving it a ModuleId let mut modules: Arena = Arena::default(); @@ -104,7 +109,11 @@ impl CrateDefMap { }; // Now we want to populate the CrateDefMap using the DefCollector - DefCollector::collect(def_map, context, ast, root_file_id, errors); + errors.extend(DefCollector::collect(def_map, context, ast, root_file_id)); + errors.extend( + parsing_errors.iter().map(|e| (e.clone().into(), root_file_id)).collect::>(), + ); + errors } pub fn root(&self) -> LocalModuleId { @@ -173,8 +182,20 @@ impl CrateDefMap { }) .collect(); + let events = module + .type_definitions() + .filter_map(|id| { + id.as_type().filter(|struct_id| { + interner + .struct_attributes(struct_id) + .iter() + .any(|attr| attr == &SecondaryAttribute::Event) + }) + }) + .collect(); + let name = self.get_module_path(id, module.parent); - Some(Contract { name, location: module.location, functions }) + Some(Contract { name, location: module.location, functions, events }) } else { None } @@ -227,25 +248,22 @@ pub struct ContractFunctionMeta { pub is_entry_point: bool, } -/// A 'contract' in Noir source code with the given name and functions. +/// A 'contract' in Noir source code with a given name, functions and events. /// This is not an AST node, it is just a convenient form to return for CrateDefMap::get_all_contracts. pub struct Contract { /// To keep `name` semi-unique, it is prefixed with the names of parent modules via CrateDefMap::get_module_path pub name: String, pub location: Location, pub functions: Vec, + pub events: Vec, } /// Given a FileId, fetch the File, from the FileManager and parse it's content -pub fn parse_file( - fm: &FileManager, - file_id: FileId, - all_errors: &mut Vec, -) -> ParsedModule { +pub fn parse_file(fm: &FileManager, file_id: FileId) -> (ParsedModule, Vec) { let file = fm.fetch_file(file_id); let (program, errors) = parse_program(file.source()); - all_errors.extend(errors.into_iter().map(|error| error.in_file(file_id))); - program + + (program, errors) } impl std::ops::Index for CrateDefMap { diff --git a/compiler/noirc_frontend/src/hir/def_map/module_data.rs b/compiler/noirc_frontend/src/hir/def_map/module_data.rs index 1f1fa44108d..a1ed5587306 100644 --- a/compiler/noirc_frontend/src/hir/def_map/module_data.rs +++ b/compiler/noirc_frontend/src/hir/def_map/module_data.rs @@ -7,7 +7,7 @@ use crate::{ Ident, }; -use super::{ItemScope, LocalModuleId, ModuleDefId, ModuleId, PerNs, ScopeResolveError}; +use super::{ItemScope, LocalModuleId, ModuleDefId, ModuleId, PerNs}; /// Contains the actual contents of a module: its parent (if one exists), /// children, and scope with all definitions defined within the scope. @@ -85,10 +85,6 @@ impl ModuleData { self.scope.find_func_with_name(name) } - pub fn find_trait_with_name(&self, name: &Ident) -> Result { - self.scope.find_trait_with_name(name) - } - pub fn import(&mut self, name: Ident, id: ModuleDefId) -> Result<(), (Ident, Ident)> { self.scope.add_item_to_namespace(name, id) } @@ -97,6 +93,10 @@ impl ModuleData { self.scope.find_name(name) } + pub fn type_definitions(&self) -> impl Iterator + '_ { + self.definitions.types().values().map(|(id, _)| *id) + } + /// Return an iterator over all definitions defined within this module, /// excluding any type definitions. pub fn value_definitions(&self) -> impl Iterator + '_ { diff --git a/compiler/noirc_frontend/src/hir/def_map/module_def.rs b/compiler/noirc_frontend/src/hir/def_map/module_def.rs index ade0fcaf7aa..659d7712ab4 100644 --- a/compiler/noirc_frontend/src/hir/def_map/module_def.rs +++ b/compiler/noirc_frontend/src/hir/def_map/module_def.rs @@ -87,6 +87,12 @@ impl From for ModuleDefId { } } +impl From for ModuleDefId { + fn from(trait_id: TraitId) -> Self { + ModuleDefId::TraitId(trait_id) + } +} + pub trait TryFromModuleDefId: Sized { fn try_from(id: ModuleDefId) -> Option; fn dummy_id() -> Self; diff --git a/compiler/noirc_frontend/src/hir/mod.rs b/compiler/noirc_frontend/src/hir/mod.rs index 63f057a63d3..9c747bfead1 100644 --- a/compiler/noirc_frontend/src/hir/mod.rs +++ b/compiler/noirc_frontend/src/hir/mod.rs @@ -9,6 +9,7 @@ use crate::hir_def::function::FuncMeta; use crate::node_interner::{FuncId, NodeInterner, StructId}; use def_map::{Contract, CrateDefMap}; use fm::FileManager; +use noirc_errors::Location; use std::collections::BTreeMap; use self::def_map::TestFunction; @@ -22,6 +23,10 @@ pub struct Context { pub(crate) def_maps: BTreeMap, pub file_manager: FileManager, + /// A map of each file that already has been visited from a prior `mod foo;` declaration. + /// This is used to issue an error if a second `mod foo;` is declared to the same file. + pub visited_files: BTreeMap, + /// Maps a given (contract) module id to the next available storage slot /// for that contract. pub storage_slots: BTreeMap, @@ -41,6 +46,7 @@ impl Context { Context { def_interner: NodeInterner::default(), def_maps: BTreeMap::new(), + visited_files: BTreeMap::new(), crate_graph, file_manager, storage_slots: BTreeMap::new(), diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 78388eacb94..3ddb3b9efa9 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -105,6 +105,11 @@ struct ResolverMeta { warn_if_unused: bool, } +pub enum ResolvePathError { + WrongKind, + NotFound, +} + impl<'a> Resolver<'a> { pub fn new( interner: &'a mut NodeInterner, @@ -672,7 +677,7 @@ impl<'a> Resolver<'a> { ) -> Vec { vecmap(where_clause, |constraint| TraitConstraint { typ: self.resolve_type(constraint.typ.clone()), - trait_id: constraint.trait_bound.trait_id, + trait_id: constraint.trait_bound.trait_id.unwrap_or_else(TraitId::dummy_id), }) } @@ -1370,8 +1375,8 @@ impl<'a> Resolver<'a> { self.interner.get_struct(type_id) } - pub fn get_trait(&self, type_id: TraitId) -> Shared { - self.interner.get_trait(type_id) + pub fn get_trait(&self, trait_id: TraitId) -> Trait { + self.interner.get_trait(trait_id) } fn lookup(&mut self, path: Path) -> Result { @@ -1570,6 +1575,7 @@ mod test { use crate::hir::resolution::import::PathResolutionError; use crate::hir::resolution::resolver::StmtId; + use super::{PathResolver, Resolver}; use crate::graph::CrateId; use crate::hir_def::expr::HirExpression; use crate::hir_def::stmt::HirStatement; @@ -1579,8 +1585,7 @@ mod test { hir::def_map::{CrateDefMap, LocalModuleId, ModuleDefId}, parse_program, Path, }; - - use super::{PathResolver, Resolver}; + use noirc_errors::CustomDiagnostic; // func_namespace is used to emulate the fact that functions can be imported // and functions can be forward declared @@ -1589,7 +1594,10 @@ mod test { ) -> (ParsedModule, NodeInterner, BTreeMap, FileId, TestPathResolver) { let (program, errors) = parse_program(src); - if errors.iter().any(|e| e.is_error()) { + if errors.iter().any(|e| { + let diagnostic: CustomDiagnostic = e.clone().into(); + diagnostic.is_error() + }) { panic!("Unexpected parse errors in test code: {:?}", errors); } diff --git a/compiler/noirc_frontend/src/hir/type_check/expr.rs b/compiler/noirc_frontend/src/hir/type_check/expr.rs index fdec07ae62d..16e8f2d4200 100644 --- a/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ b/compiler/noirc_frontend/src/hir/type_check/expr.rs @@ -6,11 +6,11 @@ use crate::{ hir_def::{ expr::{ self, HirArrayLiteral, HirBinaryOp, HirExpression, HirLiteral, HirMethodCallExpression, - HirPrefixExpression, + HirMethodReference, HirPrefixExpression, }, types::Type, }, - node_interner::{DefinitionKind, ExprId, FuncId}, + node_interner::{DefinitionKind, ExprId, FuncId, TraitMethodId}, Shared, Signedness, TypeBinding, TypeVariableKind, UnaryOp, }; @@ -144,7 +144,7 @@ impl<'interner> TypeChecker<'interner> { let object_type = self.check_expression(&method_call.object).follow_bindings(); let method_name = method_call.method.0.contents.as_str(); match self.lookup_method(&object_type, method_name, expr_id) { - Some(method_id) => { + Some(method_ref) => { let mut args = vec![( object_type, method_call.object, @@ -160,22 +160,27 @@ impl<'interner> TypeChecker<'interner> { // so that the backend doesn't need to worry about methods let location = method_call.location; - // Automatically add `&mut` if the method expects a mutable reference and - // the object is not already one. - if method_id != FuncId::dummy_id() { - let func_meta = self.interner.function_meta(&method_id); - self.try_add_mutable_reference_to_object( - &mut method_call, - &func_meta.typ, - &mut args, - ); + if let HirMethodReference::FuncId(func_id) = method_ref { + // Automatically add `&mut` if the method expects a mutable reference and + // the object is not already one. + if func_id != FuncId::dummy_id() { + let func_meta = self.interner.function_meta(&func_id); + self.try_add_mutable_reference_to_object( + &mut method_call, + &func_meta.typ, + &mut args, + ); + } } - let (function_id, function_call) = - method_call.into_function_call(method_id, location, self.interner); + let (function_id, function_call) = method_call.into_function_call( + method_ref, + location, + self.interner, + ); let span = self.interner.expr_span(expr_id); - let ret = self.check_method_call(&function_id, &method_id, args, span); + let ret = self.check_method_call(&function_id, method_ref, args, span); self.interner.replace_expr(expr_id, function_call); ret @@ -286,6 +291,7 @@ impl<'interner> TypeChecker<'interner> { Type::Function(params, Box::new(lambda.return_type), Box::new(env_type)) } + HirExpression::TraitMethodReference(_) => unreachable!("unexpected TraitMethodReference - they should be added after initial type checking"), }; self.interner.push_expr_type(expr_id, typ.clone()); @@ -477,34 +483,45 @@ impl<'interner> TypeChecker<'interner> { fn check_method_call( &mut self, function_ident_id: &ExprId, - func_id: &FuncId, + method_ref: HirMethodReference, arguments: Vec<(Type, ExprId, Span)>, span: Span, ) -> Type { - if func_id == &FuncId::dummy_id() { - Type::Error - } else { - let func_meta = self.interner.function_meta(func_id); + let (fntyp, param_len) = match method_ref { + HirMethodReference::FuncId(func_id) => { + if func_id == FuncId::dummy_id() { + return Type::Error; + } - // Check function call arity is correct - let param_len = func_meta.parameters.len(); - let arg_len = arguments.len(); + let func_meta = self.interner.function_meta(&func_id); + let param_len = func_meta.parameters.len(); - if param_len != arg_len { - self.errors.push(TypeCheckError::ArityMisMatch { - expected: param_len as u16, - found: arg_len as u16, - span, - }); + (func_meta.typ, param_len) } + HirMethodReference::TraitMethodId(method) => { + let the_trait = self.interner.get_trait(method.trait_id); + let method = &the_trait.methods[method.method_index]; - let (function_type, instantiation_bindings) = func_meta.typ.instantiate(self.interner); + (method.get_type(), method.arguments.len()) + } + }; - self.interner.store_instantiation_bindings(*function_ident_id, instantiation_bindings); - self.interner.push_expr_type(function_ident_id, function_type.clone()); + let arg_len = arguments.len(); - self.bind_function_type(function_type, arguments, span) + if param_len != arg_len { + self.errors.push(TypeCheckError::ArityMisMatch { + expected: param_len as u16, + found: arg_len as u16, + span, + }); } + + let (function_type, instantiation_bindings) = fntyp.instantiate(self.interner); + + self.interner.store_instantiation_bindings(*function_ident_id, instantiation_bindings); + self.interner.push_expr_type(function_ident_id, function_type.clone()); + + self.bind_function_type(function_type, arguments, span) } fn check_if_expr(&mut self, if_expr: &expr::HirIfExpression, expr_id: &ExprId) -> Type { @@ -818,11 +835,11 @@ impl<'interner> TypeChecker<'interner> { object_type: &Type, method_name: &str, expr_id: &ExprId, - ) -> Option { + ) -> Option { match object_type { Type::Struct(typ, _args) => { match self.interner.lookup_method(typ.borrow().id, method_name) { - Some(method_id) => Some(method_id), + Some(method_id) => Some(HirMethodReference::FuncId(method_id)), None => { self.errors.push(TypeCheckError::UnresolvedMethodCall { method_name: method_name.to_string(), @@ -833,6 +850,32 @@ impl<'interner> TypeChecker<'interner> { } } } + Type::NamedGeneric(_, _) => { + let func_meta = self.interner.function_meta( + &self.current_function.expect("unexpected method outside a function"), + ); + + for constraint in func_meta.trait_constraints { + if *object_type == constraint.typ { + let the_trait = self.interner.get_trait(constraint.trait_id); + + for (method_index, method) in the_trait.methods.iter().enumerate() { + if method.name.0.contents == method_name { + let trait_method = + TraitMethodId { trait_id: constraint.trait_id, method_index }; + return Some(HirMethodReference::TraitMethodId(trait_method)); + } + } + } + } + + self.errors.push(TypeCheckError::UnresolvedMethodCall { + method_name: method_name.to_string(), + object_type: object_type.clone(), + span: self.interner.expr_span(expr_id), + }); + None + } // Mutable references to another type should resolve to methods of their element type. // This may be a struct or a primitive type. Type::MutableReference(element) => self.lookup_method(element, method_name, expr_id), @@ -843,7 +886,7 @@ impl<'interner> TypeChecker<'interner> { // In the future we could support methods for non-struct types if we have a context // (in the interner?) essentially resembling HashMap other => match self.interner.lookup_primitive_method(other, method_name) { - Some(method_id) => Some(method_id), + Some(method_id) => Some(HirMethodReference::FuncId(method_id)), None => { self.errors.push(TypeCheckError::UnresolvedMethodCall { method_name: method_name.to_string(), diff --git a/compiler/noirc_frontend/src/hir/type_check/mod.rs b/compiler/noirc_frontend/src/hir/type_check/mod.rs index 11af0869fd5..c2afa44c495 100644 --- a/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -27,6 +27,7 @@ pub struct TypeChecker<'interner> { delayed_type_checks: Vec, interner: &'interner mut NodeInterner, errors: Vec, + current_function: Option, } /// Type checks a function and assigns the @@ -40,6 +41,7 @@ pub fn type_check_func(interner: &mut NodeInterner, func_id: FuncId) -> Vec (noirc_e impl<'interner> TypeChecker<'interner> { fn new(interner: &'interner mut NodeInterner) -> Self { - Self { delayed_type_checks: Vec::new(), interner, errors: vec![] } + Self { delayed_type_checks: Vec::new(), interner, errors: vec![], current_function: None } } pub fn push_delayed_type_check(&mut self, f: TypeCheckFn) { @@ -127,7 +129,12 @@ impl<'interner> TypeChecker<'interner> { } pub fn check_global(id: &StmtId, interner: &'interner mut NodeInterner) -> Vec { - let mut this = Self { delayed_type_checks: Vec::new(), interner, errors: vec![] }; + let mut this = Self { + delayed_type_checks: Vec::new(), + interner, + errors: vec![], + current_function: None, + }; this.check_statement(id); this.errors } @@ -327,9 +334,7 @@ mod test { "#; - // expect a deprecation warning since we are changing for-loop default type. - // There is a deprecation warning per for-loop. - let expected_num_errors = 2; + let expected_num_errors = 0; type_check_src_code_errors_expected( src, expected_num_errors, diff --git a/compiler/noirc_frontend/src/hir/type_check/stmt.rs b/compiler/noirc_frontend/src/hir/type_check/stmt.rs index 5db23c3f4f2..11f106dab10 100644 --- a/compiler/noirc_frontend/src/hir/type_check/stmt.rs +++ b/compiler/noirc_frontend/src/hir/type_check/stmt.rs @@ -115,8 +115,8 @@ impl<'interner> TypeChecker<'interner> { let span = self.interner.expr_span(&assign_stmt.expression); self.unify_with_coercions(&expr_type, &lvalue_type, assign_stmt.expression, || { TypeCheckError::TypeMismatchWithSource { - actual: lvalue_type.clone(), - expected: expr_type.clone(), + actual: expr_type.clone(), + expected: lvalue_type.clone(), span, source: Source::Assignment, } diff --git a/compiler/noirc_frontend/src/hir_def/expr.rs b/compiler/noirc_frontend/src/hir_def/expr.rs index 3c49d3e4afc..4989dd12bd6 100644 --- a/compiler/noirc_frontend/src/hir_def/expr.rs +++ b/compiler/noirc_frontend/src/hir_def/expr.rs @@ -2,7 +2,7 @@ use acvm::FieldElement; use fm::FileId; use noirc_errors::Location; -use crate::node_interner::{DefinitionId, ExprId, FuncId, NodeInterner, StmtId}; +use crate::node_interner::{DefinitionId, ExprId, FuncId, NodeInterner, StmtId, TraitMethodId}; use crate::{BinaryOp, BinaryOpKind, Ident, Shared, UnaryOp}; use super::stmt::HirPattern; @@ -30,6 +30,7 @@ pub enum HirExpression { If(HirIfExpression), Tuple(Vec), Lambda(HirLambda), + TraitMethodReference(TraitMethodId), Error, } @@ -150,20 +151,39 @@ pub struct HirMethodCallExpression { pub location: Location, } +#[derive(Debug, Copy, Clone)] +pub enum HirMethodReference { + /// A method can be defined in a regular `impl` block, in which case + /// it's syntax sugar for a normal function call, and can be + /// translated to one during type checking + FuncId(FuncId), + + /// Or a method can come from a Trait impl block, in which case + /// the actual function called will depend on the instantiated type, + /// which can be only known during monomorphizaiton. + TraitMethodId(TraitMethodId), +} + impl HirMethodCallExpression { pub fn into_function_call( mut self, - func: FuncId, + method: HirMethodReference, location: Location, interner: &mut NodeInterner, ) -> (ExprId, HirExpression) { let mut arguments = vec![self.object]; arguments.append(&mut self.arguments); - let id = interner.function_definition_id(func); - let ident = HirExpression::Ident(HirIdent { location, id }); - let func = interner.push_expr(ident); - + let expr = match method { + HirMethodReference::FuncId(func_id) => { + let id = interner.function_definition_id(func_id); + HirExpression::Ident(HirIdent { location, id }) + } + HirMethodReference::TraitMethodId(method_id) => { + HirExpression::TraitMethodReference(method_id) + } + }; + let func = interner.push_expr(expr); (func, HirExpression::Call(HirCallExpression { func, arguments, location })) } } diff --git a/compiler/noirc_frontend/src/hir_def/traits.rs b/compiler/noirc_frontend/src/hir_def/traits.rs index 4176e4fc89b..b51405122cd 100644 --- a/compiler/noirc_frontend/src/hir_def/traits.rs +++ b/compiler/noirc_frontend/src/hir_def/traits.rs @@ -1,26 +1,30 @@ use crate::{ + graph::CrateId, node_interner::{FuncId, TraitId}, - Generics, Ident, Type, TypeVariable, TypeVariableId, + Generics, Ident, NoirFunction, Type, TypeVariable, TypeVariableId, }; use noirc_errors::Span; -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct TraitFunction { pub name: Ident, pub generics: Generics, pub arguments: Vec, pub return_type: Type, pub span: Span, + pub default_impl: Option>, + pub default_impl_file_id: fm::FileId, + pub default_impl_module_id: crate::hir::def_map::LocalModuleId, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct TraitConstant { pub name: Ident, pub ty: Type, pub span: Span, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct TraitType { pub name: Ident, pub ty: Type, @@ -30,12 +34,14 @@ pub struct TraitType { /// Represents a trait in the type system. Each instance of this struct /// will be shared across all Type::Trait variants that represent /// the same trait. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Trait { /// A unique id representing this trait type. Used to check if two /// struct traits are equal. pub id: TraitId, + pub crate_id: CrateId, + pub methods: Vec, pub constants: Vec, pub types: Vec, @@ -62,7 +68,7 @@ pub struct TraitImpl { #[derive(Debug, Clone)] pub struct TraitConstraint { pub typ: Type, - pub trait_id: Option, + pub trait_id: TraitId, // pub trait_generics: Generics, TODO } @@ -82,6 +88,7 @@ impl Trait { pub fn new( id: TraitId, name: Ident, + crate_id: CrateId, span: Span, generics: Generics, self_type_typevar_id: TypeVariableId, @@ -90,6 +97,7 @@ impl Trait { Trait { id, name, + crate_id, span, methods: Vec::new(), constants: Vec::new(), diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index eb837ec5f55..e6c9d7bee9a 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -410,6 +410,10 @@ impl Type { Type::FieldElement } + pub fn default_range_loop_type() -> Type { + Type::Integer(Signedness::Unsigned, 64) + } + pub fn type_variable(id: TypeVariableId) -> Type { Type::TypeVariable(Shared::new(TypeBinding::Unbound(id)), TypeVariableKind::Normal) } @@ -840,8 +844,8 @@ impl Type { // No recursive try_unify call for struct fields. Don't want // to mutate shared type variables within struct definitions. // This isn't possible currently but will be once noir gets generic types - (Struct(fields_a, args_a), Struct(fields_b, args_b)) => { - if fields_a == fields_b { + (Struct(id_a, args_a), Struct(id_b, args_b)) => { + if id_a == id_b && args_a.len() == args_b.len() { for (a, b) in args_a.iter().zip(args_b) { a.try_unify(b)?; } diff --git a/compiler/noirc_frontend/src/lexer/errors.rs b/compiler/noirc_frontend/src/lexer/errors.rs index 6b382d76f40..8b5aac8c787 100644 --- a/compiler/noirc_frontend/src/lexer/errors.rs +++ b/compiler/noirc_frontend/src/lexer/errors.rs @@ -21,6 +21,12 @@ pub enum LexerErrorKind { LogicalAnd { span: Span }, #[error("Unterminated block comment")] UnterminatedBlockComment { span: Span }, + #[error("Unterminated string literal")] + UnterminatedStringLiteral { span: Span }, + #[error( + "'\\{escaped}' is not a valid escape sequence. Use '\\' for a literal backslash character." + )] + InvalidEscape { escaped: char, span: Span }, } impl LexerErrorKind { @@ -33,6 +39,8 @@ impl LexerErrorKind { LexerErrorKind::TooManyBits { span, .. } => *span, LexerErrorKind::LogicalAnd { span } => *span, LexerErrorKind::UnterminatedBlockComment { span } => *span, + LexerErrorKind::UnterminatedStringLiteral { span } => *span, + LexerErrorKind::InvalidEscape { span, .. } => *span, } } @@ -46,30 +54,30 @@ impl LexerErrorKind { let found: String = found.map(Into::into).unwrap_or_else(|| "".into()); ( - "an unexpected character was found".to_string(), - format!(" expected {expected} , but got {found}"), + "An unexpected character was found".to_string(), + format!("Expected {expected}, but found {found}"), *span, ) }, LexerErrorKind::NotADoubleChar { span, found } => ( - format!("tried to parse {found} as double char"), + format!("Tried to parse {found} as double char"), format!( " {found:?} is not a double char, this is an internal error" ), *span, ), LexerErrorKind::InvalidIntegerLiteral { span, found } => ( - "invalid integer literal".to_string(), + "Invalid integer literal".to_string(), format!(" {found} is not an integer"), *span, ), LexerErrorKind::MalformedFuncAttribute { span, found } => ( - "malformed function attribute".to_string(), + "Malformed function attribute".to_string(), format!(" {found} is not a valid attribute"), *span, ), LexerErrorKind::TooManyBits { span, max, got } => ( - "integer literal too large".to_string(), + "Integer literal too large".to_string(), format!( "The maximum number of bits needed to represent a field is {max}, This integer type needs {got} bits" ), @@ -80,7 +88,11 @@ impl LexerErrorKind { "Try `&` instead, or use `if` only if you require short-circuiting".to_string(), *span, ), - LexerErrorKind::UnterminatedBlockComment { span } => ("unterminated block comment".to_string(), "Unterminated block comment".to_string(), *span), + LexerErrorKind::UnterminatedBlockComment { span } => ("Unterminated block comment".to_string(), "Unterminated block comment".to_string(), *span), + LexerErrorKind::UnterminatedStringLiteral { span } => + ("Unterminated string literal".to_string(), "Unterminated string literal".to_string(), *span), + LexerErrorKind::InvalidEscape { escaped, span } => + (format!("'\\{escaped}' is not a valid escape sequence. Use '\\' for a literal backslash character."), "Invalid escape sequence".to_string(), *span), } } } diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index c32b956b716..0b8922efce6 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -320,12 +320,34 @@ impl<'a> Lexer<'a> { fn eat_string_literal(&mut self) -> SpannedTokenResult { let start = self.position; + let mut string = String::new(); + + while let Some(next) = self.next_char() { + let char = match next { + '"' => break, + '\\' => match self.next_char() { + Some('r') => '\r', + Some('n') => '\n', + Some('t') => '\t', + Some('0') => '\0', + Some('"') => '"', + Some('\\') => '\\', + Some(escaped) => { + let span = Span::inclusive(start, self.position); + return Err(LexerErrorKind::InvalidEscape { escaped, span }); + } + None => { + let span = Span::inclusive(start, self.position); + return Err(LexerErrorKind::UnterminatedStringLiteral { span }); + } + }, + other => other, + }; - let str_literal = self.eat_while(None, |ch| ch != '"'); - - let str_literal_token = Token::Str(str_literal); + string.push(char); + } - self.next_char(); // Advance past the closing quote + let str_literal_token = Token::Str(string); let end = self.position; Ok(str_literal_token.into_span(start, end)) diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index fb5ab220b2c..6aabacd8940 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -302,7 +302,7 @@ impl IntType { Err(_) => return Ok(None), }; - let max_bits = FieldElement::max_num_bits(); + let max_bits = FieldElement::max_num_bits() / 2; if str_as_u32 > max_bits { return Err(LexerErrorKind::TooManyBits { span, max: max_bits, got: str_as_u32 }); @@ -467,6 +467,7 @@ impl Attribute { ["contract_library_method"] => { Attribute::Secondary(SecondaryAttribute::ContractLibraryMethod) } + ["event"] => Attribute::Secondary(SecondaryAttribute::Event), ["deprecated", name] => { if !name.starts_with('"') && !name.ends_with('"') { return Err(LexerErrorKind::MalformedFuncAttribute { @@ -544,6 +545,7 @@ pub enum SecondaryAttribute { // is a helper method for a contract and should not be seen as // the entry point. ContractLibraryMethod, + Event, Custom(String), } @@ -556,6 +558,7 @@ impl fmt::Display for SecondaryAttribute { } SecondaryAttribute::Custom(ref k) => write!(f, "#[{k}]"), SecondaryAttribute::ContractLibraryMethod => write!(f, "#[contract_library_method]"), + SecondaryAttribute::Event => write!(f, "#[event]"), } } } @@ -578,6 +581,7 @@ impl AsRef for SecondaryAttribute { SecondaryAttribute::Deprecated(None) => "", SecondaryAttribute::Custom(string) => string, SecondaryAttribute::ContractLibraryMethod => "", + SecondaryAttribute::Event => "", } } } diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index c912464695e..0a62b71f105 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -11,7 +11,10 @@ use acvm::FieldElement; use iter_extended::{btree_map, vecmap}; use noirc_printable_type::PrintableType; -use std::collections::{BTreeMap, HashMap, VecDeque}; +use std::{ + collections::{BTreeMap, HashMap, VecDeque}, + unreachable, +}; use crate::{ hir_def::{ @@ -20,7 +23,7 @@ use crate::{ stmt::{HirAssignStatement, HirLValue, HirLetStatement, HirPattern, HirStatement}, types, }, - node_interner::{self, DefinitionKind, NodeInterner, StmtId}, + node_interner::{self, DefinitionKind, NodeInterner, StmtId, TraitImplKey, TraitMethodId}, token::FunctionAttribute, ContractFunctionType, FunctionKind, Type, TypeBinding, TypeBindings, TypeVariableKind, Visibility, @@ -68,6 +71,8 @@ struct Monomorphizer<'interner> { next_local_id: u32, next_function_id: u32, + + is_range_loop: bool, } type HirType = crate::Type; @@ -112,6 +117,7 @@ impl<'interner> Monomorphizer<'interner> { next_function_id: 0, interner, lambda_envs_stack: Vec::new(), + is_range_loop: false, } } @@ -201,7 +207,7 @@ impl<'interner> Monomorphizer<'interner> { let modifiers = self.interner.function_modifiers(&f); let name = self.interner.function_name(&f).to_owned(); - let return_type = Self::convert_type(meta.return_type()); + let return_type = self.convert_type(meta.return_type()); let parameters = self.parameters(meta.parameters); let body = self.expr(*self.interner.function(&f).as_expr()); let unconstrained = modifiers.is_unconstrained @@ -237,7 +243,7 @@ impl<'interner> Monomorphizer<'interner> { let new_id = self.next_local_id(); let definition = self.interner.definition(ident.id); let name = definition.name.clone(); - new_params.push((new_id, definition.mutable, name, Self::convert_type(typ))); + new_params.push((new_id, definition.mutable, name, self.convert_type(typ))); self.define_local(ident.id, new_id); } HirPattern::Mutable(pattern, _) => self.parameter(*pattern, typ, new_params), @@ -284,7 +290,7 @@ impl<'interner> Monomorphizer<'interner> { } HirExpression::Literal(HirLiteral::Bool(value)) => Literal(Bool(value)), HirExpression::Literal(HirLiteral::Integer(value)) => { - let typ = Self::convert_type(&self.interner.id_type(expr)); + let typ = self.convert_type(&self.interner.id_type(expr)); Literal(Integer(value, typ)) } HirExpression::Literal(HirLiteral::Array(array)) => match array { @@ -301,7 +307,7 @@ impl<'interner> Monomorphizer<'interner> { ast::Expression::Unary(ast::Unary { operator: prefix.operator, rhs: Box::new(self.expr(prefix.rhs)), - result_type: Self::convert_type(&self.interner.id_type(expr)), + result_type: self.convert_type(&self.interner.id_type(expr)), location, }) } @@ -326,13 +332,15 @@ impl<'interner> Monomorphizer<'interner> { HirExpression::Cast(cast) => ast::Expression::Cast(ast::Cast { lhs: Box::new(self.expr(cast.lhs)), - r#type: Self::convert_type(&cast.r#type), + r#type: self.convert_type(&cast.r#type), location: self.interner.expr_location(&expr), }), HirExpression::For(for_expr) => { + self.is_range_loop = true; let start = self.expr(for_expr.start_range); let end = self.expr(for_expr.end_range); + self.is_range_loop = false; let index_variable = self.next_local_id(); self.define_local(for_expr.identifier.id, index_variable); @@ -341,7 +349,7 @@ impl<'interner> Monomorphizer<'interner> { ast::Expression::For(ast::For { index_variable, index_name: self.interner.definition_name(for_expr.identifier.id).to_owned(), - index_type: Self::convert_type(&self.interner.id_type(for_expr.start_range)), + index_type: self.convert_type(&self.interner.id_type(for_expr.start_range)), start_range: Box::new(start), end_range: Box::new(end), start_range_location: self.interner.expr_location(&for_expr.start_range), @@ -358,7 +366,7 @@ impl<'interner> Monomorphizer<'interner> { condition: Box::new(cond), consequence: Box::new(then), alternative: else_, - typ: Self::convert_type(&self.interner.id_type(expr)), + typ: self.convert_type(&self.interner.id_type(expr)), }) } @@ -370,6 +378,17 @@ impl<'interner> Monomorphizer<'interner> { HirExpression::Lambda(lambda) => self.lambda(lambda, expr), + HirExpression::TraitMethodReference(method) => { + if let Type::Function(args, _, _) = self.interner.id_type(expr) { + let self_type = args[0].clone(); + self.resolve_trait_method_reference(self_type, expr, method) + } else { + unreachable!( + "Calling a non-function, this should've been caught in typechecking" + ); + } + } + HirExpression::MethodCall(_) => { unreachable!("Encountered HirExpression::MethodCall during monomorphization") } @@ -382,7 +401,7 @@ impl<'interner> Monomorphizer<'interner> { array: node_interner::ExprId, array_elements: Vec, ) -> ast::Expression { - let typ = Self::convert_type(&self.interner.id_type(array)); + let typ = self.convert_type(&self.interner.id_type(array)); let contents = vecmap(array_elements, |id| self.expr(id)); ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { contents, typ })) } @@ -393,7 +412,7 @@ impl<'interner> Monomorphizer<'interner> { repeated_element: node_interner::ExprId, length: HirType, ) -> ast::Expression { - let typ = Self::convert_type(&self.interner.id_type(array)); + let typ = self.convert_type(&self.interner.id_type(array)); let contents = self.expr(repeated_element); let length = length @@ -405,7 +424,7 @@ impl<'interner> Monomorphizer<'interner> { } fn index(&mut self, id: node_interner::ExprId, index: HirIndexExpression) -> ast::Expression { - let element_type = Self::convert_type(&self.interner.id_type(id)); + let element_type = self.convert_type(&self.interner.id_type(id)); let collection = Box::new(self.expr(index.collection)); let index = Box::new(self.expr(index.index)); @@ -452,7 +471,7 @@ impl<'interner> Monomorphizer<'interner> { for (field_name, expr_id) in constructor.fields { let new_id = self.next_local_id(); let field_type = field_type_map.get(&field_name.0.contents).unwrap(); - let typ = Self::convert_type(field_type); + let typ = self.convert_type(field_type); field_vars.insert(field_name.0.contents.clone(), (new_id, typ)); let expression = Box::new(self.expr(expr_id)); @@ -548,7 +567,7 @@ impl<'interner> Monomorphizer<'interner> { let mutable = false; let definition = Definition::Local(fresh_id); let name = i.to_string(); - let typ = Self::convert_type(&field_type); + let typ = self.convert_type(&field_type); let new_rhs = ast::Expression::Ident(ast::Ident { location, mutable, definition, name, typ }); @@ -590,7 +609,7 @@ impl<'interner> Monomorphizer<'interner> { let mutable = definition.mutable; let definition = self.lookup_local(ident.id)?; - let typ = Self::convert_type(&self.interner.id_type(ident.id)); + let typ = self.convert_type(&self.interner.id_type(ident.id)); Some(ast::Ident { location: Some(ident.location), mutable, definition, name, typ }) } @@ -605,7 +624,7 @@ impl<'interner> Monomorphizer<'interner> { let typ = self.interner.id_type(expr_id); let definition = self.lookup_function(*func_id, expr_id, &typ); - let typ = Self::convert_type(&typ); + let typ = self.convert_type(&typ); let ident = ast::Ident { location, mutable, definition, name, typ: typ.clone() }; let ident_expression = ast::Expression::Ident(ident); if self.is_function_closure_type(&typ) { @@ -642,7 +661,7 @@ impl<'interner> Monomorphizer<'interner> { } /// Convert a non-tuple/struct type to a monomorphized type - fn convert_type(typ: &HirType) -> ast::Type { + fn convert_type(&self, typ: &HirType) -> ast::Type { match typ { HirType::FieldElement => ast::Type::Field, HirType::Integer(sign, bits) => ast::Type::Integer(*sign, *bits), @@ -650,13 +669,13 @@ impl<'interner> Monomorphizer<'interner> { HirType::String(size) => ast::Type::String(size.evaluate_to_u64().unwrap_or(0)), HirType::FmtString(size, fields) => { let size = size.evaluate_to_u64().unwrap_or(0); - let fields = Box::new(Self::convert_type(fields.as_ref())); + let fields = Box::new(self.convert_type(fields.as_ref())); ast::Type::FmtString(size, fields) } HirType::Unit => ast::Type::Unit, HirType::Array(length, element) => { - let element = Box::new(Self::convert_type(element.as_ref())); + let element = Box::new(self.convert_type(element.as_ref())); if let Some(length) = length.evaluate_to_u64() { ast::Type::Array(length, element) @@ -667,7 +686,7 @@ impl<'interner> Monomorphizer<'interner> { HirType::NamedGeneric(binding, _) => { if let TypeBinding::Bound(binding) = &*binding.borrow() { - return Self::convert_type(binding); + return self.convert_type(binding); } // Default any remaining unbound type variables. @@ -683,7 +702,7 @@ impl<'interner> Monomorphizer<'interner> { HirType::TypeVariable(binding, kind) => { if let TypeBinding::Bound(binding) = &*binding.borrow() { - return Self::convert_type(binding); + return self.convert_type(binding); } // Default any remaining unbound type variables. @@ -693,27 +712,33 @@ impl<'interner> Monomorphizer<'interner> { // like automatic solving of traits. It should be fine since it is strictly // after type checking, but care should be taken that it doesn't change which // impls are chosen. - let default = kind.default_type(); - let monomorphized_default = Self::convert_type(&default); + let default = + if self.is_range_loop && matches!(kind, TypeVariableKind::IntegerOrField) { + Type::default_range_loop_type() + } else { + kind.default_type() + }; + + let monomorphized_default = self.convert_type(&default); *binding.borrow_mut() = TypeBinding::Bound(default); monomorphized_default } HirType::Struct(def, args) => { let fields = def.borrow().get_fields(args); - let fields = vecmap(fields, |(_, field)| Self::convert_type(&field)); + let fields = vecmap(fields, |(_, field)| self.convert_type(&field)); ast::Type::Tuple(fields) } HirType::Tuple(fields) => { - let fields = vecmap(fields, Self::convert_type); + let fields = vecmap(fields, |x| self.convert_type(x)); ast::Type::Tuple(fields) } HirType::Function(args, ret, env) => { - let args = vecmap(args, Self::convert_type); - let ret = Box::new(Self::convert_type(ret)); - let env = Self::convert_type(env); + let args = vecmap(args, |x| self.convert_type(x)); + let ret = Box::new(self.convert_type(ret)); + let env = self.convert_type(env); match &env { ast::Type::Unit => ast::Type::Function(args, ret, Box::new(env)), ast::Type::Tuple(_elements) => ast::Type::Tuple(vec![ @@ -729,7 +754,7 @@ impl<'interner> Monomorphizer<'interner> { } HirType::MutableReference(element) => { - let element = Self::convert_type(element); + let element = self.convert_type(element); ast::Type::MutableReference(Box::new(element)) } @@ -743,7 +768,7 @@ impl<'interner> Monomorphizer<'interner> { } fn is_function_closure(&self, raw_func_id: node_interner::ExprId) -> bool { - let t = Self::convert_type(&self.interner.id_type(raw_func_id)); + let t = self.convert_type(&self.interner.id_type(raw_func_id)); if self.is_function_closure_type(&t) { true } else if let ast::Type::Tuple(elements) = t { @@ -766,6 +791,44 @@ impl<'interner> Monomorphizer<'interner> { } } + fn resolve_trait_method_reference( + &mut self, + self_type: HirType, + expr_id: node_interner::ExprId, + method: TraitMethodId, + ) -> ast::Expression { + let function_type = self.interner.id_type(expr_id); + + // the substitute() here is to replace all internal occurences of the 'Self' typevar + // with whatever 'Self' is currently bound to, so we don't lose type information + // if we need to rebind the trait. + let trait_impl = self + .interner + .get_trait_implementation(&TraitImplKey { + typ: self_type.follow_bindings(), + trait_id: method.trait_id, + }) + .expect("ICE: missing trait impl - should be caught during type checking"); + + let hir_func_id = trait_impl.borrow().methods[method.method_index]; + + let func_def = self.lookup_function(hir_func_id, expr_id, &function_type); + let func_id = match func_def { + Definition::Function(func_id) => func_id, + _ => unreachable!(), + }; + + let the_trait = self.interner.get_trait(method.trait_id); + + ast::Expression::Ident(ast::Ident { + definition: Definition::Function(func_id), + mutable: false, + location: None, + name: the_trait.methods[method.method_index].name.0.contents.clone(), + typ: self.convert_type(&function_type), + }) + } + fn function_call( &mut self, call: HirCallExpression, @@ -776,7 +839,7 @@ impl<'interner> Monomorphizer<'interner> { let hir_arguments = vecmap(&call.arguments, |id| self.interner.expression(id)); let func: Box; let return_type = self.interner.id_type(id); - let return_type = Self::convert_type(&return_type); + let return_type = self.convert_type(&return_type); let location = call.location; if let ast::Expression::Ident(ident) = original_func.as_ref() { @@ -811,7 +874,7 @@ impl<'interner> Monomorphizer<'interner> { definition: Definition::Local(local_id), mutable: false, name: "tmp".to_string(), - typ: Self::convert_type(&self.interner.id_type(call.func)), + typ: self.convert_type(&self.interner.id_type(call.func)), }); func = Box::new(ast::Expression::ExtractTupleField( @@ -1010,12 +1073,12 @@ impl<'interner> Monomorphizer<'interner> { let location = self.interner.expr_location(&index); let array = Box::new(self.lvalue(*array)); let index = Box::new(self.expr(index)); - let element_type = Self::convert_type(&typ); + let element_type = self.convert_type(&typ); ast::LValue::Index { array, index, element_type, location } } HirLValue::Dereference { lvalue, element_type } => { let reference = Box::new(self.lvalue(*lvalue)); - let element_type = Self::convert_type(&element_type); + let element_type = self.convert_type(&element_type); ast::LValue::Dereference { reference, element_type } } } @@ -1031,9 +1094,9 @@ impl<'interner> Monomorphizer<'interner> { } fn lambda_no_capture(&mut self, lambda: HirLambda) -> ast::Expression { - let ret_type = Self::convert_type(&lambda.return_type); + let ret_type = self.convert_type(&lambda.return_type); let lambda_name = "lambda"; - let parameter_types = vecmap(&lambda.parameters, |(_, typ)| Self::convert_type(typ)); + let parameter_types = vecmap(&lambda.parameters, |(_, typ)| self.convert_type(typ)); // Manually convert to Parameters type so we can reuse the self.parameters method let parameters = @@ -1082,9 +1145,9 @@ impl<'interner> Monomorphizer<'interner> { // patterns in the resulting tree, // which seems more fragile, we directly reuse the return parameters // of this function in those cases - let ret_type = Self::convert_type(&lambda.return_type); + let ret_type = self.convert_type(&lambda.return_type); let lambda_name = "lambda"; - let parameter_types = vecmap(&lambda.parameters, |(_, typ)| Self::convert_type(typ)); + let parameter_types = vecmap(&lambda.parameters, |(_, typ)| self.convert_type(typ)); // Manually convert to Parameters type so we can reuse the self.parameters method let parameters = @@ -1117,7 +1180,7 @@ impl<'interner> Monomorphizer<'interner> { })); let expr_type = self.interner.id_type(expr); let env_typ = if let types::Type::Function(_, _, function_env_type) = expr_type { - Self::convert_type(&function_env_type) + self.convert_type(&function_env_type) } else { unreachable!("expected a Function type for a Lambda node") }; @@ -1333,88 +1396,14 @@ fn undo_instantiation_bindings(bindings: TypeBindings) { mod tests { use std::collections::{BTreeMap, HashMap}; - use fm::FileId; - use iter_extended::vecmap; - use noirc_errors::Location; - use crate::{ graph::CrateId, hir::{ - def_map::{CrateDefMap, LocalModuleId, ModuleData, ModuleDefId, ModuleId}, - resolution::{ - import::PathResolutionError, path_resolver::PathResolver, resolver::Resolver, - }, + def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId}, + resolution::{import::PathResolutionError, path_resolver::PathResolver}, }, - node_interner::{FuncId, NodeInterner}, - parse_program, }; - use super::monomorphize; - - // TODO: refactor into a more general test utility? - // mostly copied from hir / type_check / mod.rs and adapted a bit - fn type_check_src_code(src: &str, func_namespace: Vec) -> (FuncId, NodeInterner) { - let (program, errors) = parse_program(src); - let mut interner = NodeInterner::default(); - - // Using assert_eq here instead of assert(errors.is_empty()) displays - // the whole vec if the assert fails rather than just two booleans - assert_eq!(errors, vec![]); - - let main_id = interner.push_test_function_definition("main".into()); - - let func_ids = - vecmap(&func_namespace, |name| interner.push_test_function_definition(name.into())); - - let mut path_resolver = TestPathResolver(HashMap::new()); - for (name, id) in func_namespace.into_iter().zip(func_ids.clone()) { - path_resolver.insert_func(name.to_owned(), id); - } - - let mut def_maps = BTreeMap::new(); - let file = FileId::default(); - - let mut modules = arena::Arena::new(); - let location = Location::new(Default::default(), file); - modules.insert(ModuleData::new(None, location, false)); - - def_maps.insert( - CrateId::dummy_id(), - CrateDefMap { - root: path_resolver.local_module_id(), - modules, - krate: CrateId::dummy_id(), - extern_prelude: BTreeMap::new(), - }, - ); - - let func_meta = vecmap(program.functions, |nf| { - let resolver = Resolver::new(&mut interner, &path_resolver, &def_maps, file); - let (hir_func, func_meta, _resolver_errors) = resolver.resolve_function(nf, main_id); - // TODO: not sure why, we do get an error here, - // but otherwise seem to get an ok monomorphization result - // assert_eq!(resolver_errors, vec![]); - (hir_func, func_meta) - }); - - println!("Before update_fn"); - - for ((hir_func, meta), func_id) in func_meta.into_iter().zip(func_ids.clone()) { - interner.update_fn(func_id, hir_func); - interner.push_fn_meta(meta, func_id); - } - - println!("Before type_check_func"); - - // Type check section - let errors = crate::hir::type_check::type_check_func( - &mut interner, - func_ids.first().cloned().unwrap(), - ); - assert_eq!(errors, vec![]); - (func_ids.first().cloned().unwrap(), interner) - } - // TODO: refactor into a more general test utility? // TestPathResolver struct and impls copied from hir / type_check / mod.rs struct TestPathResolver(HashMap); @@ -1441,51 +1430,4 @@ mod tests { ModuleId { krate: CrateId::dummy_id(), local_id: self.local_module_id() } } } - - impl TestPathResolver { - fn insert_func(&mut self, name: String, func_id: FuncId) { - self.0.insert(name, func_id.into()); - } - } - - // a helper test method - // TODO: maybe just compare trimmed src/expected - // for easier formatting? - fn check_rewrite(src: &str, expected: &str) { - let (func, interner) = type_check_src_code(src, vec!["main".to_string()]); - let program = monomorphize(func, &interner); - // println!("[{}]", program); - assert!(format!("{}", program) == expected); - } - - #[test] - fn simple_closure_with_no_captured_variables() { - let src = r#" - fn main() -> pub Field { - let x = 1; - let closure = || x; - closure() - } - "#; - - let expected_rewrite = r#"fn main$f0() -> Field { - let x$0 = 1; - let closure$3 = { - let closure_variable$2 = { - let env$1 = (x$l0); - (env$l1, lambda$f1) - }; - closure_variable$l2 - }; - { - let tmp$4 = closure$l3; - tmp$l4.1(tmp$l4.0) - } -} -fn lambda$f1(mut env$l1: (Field)) -> Field { - env$l1.0 -} -"#; - check_rewrite(src, expected_rewrite); - } } diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 26c98de7feb..5c893682143 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -19,7 +19,7 @@ use crate::hir_def::{ function::{FuncMeta, HirFunction}, stmt::HirStatement, }; -use crate::token::Attributes; +use crate::token::{Attributes, SecondaryAttribute}; use crate::{ ContractFunctionType, FunctionDefinition, Generics, Shared, TypeAliasType, TypeBinding, TypeBindings, TypeVariable, TypeVariableId, TypeVariableKind, Visibility, @@ -32,6 +32,8 @@ pub struct TraitImplKey { // pub generics: Generics - TODO } +type StructAttributes = Vec; + /// The node interner is the central storage location of all nodes in Noir's Hir (the /// various node types can be found in hir_def). The interner is also used to collect /// extra information about the Hir, such as the type of each node, information about @@ -73,6 +75,7 @@ pub struct NodeInterner { // methods from impls to the type. structs: HashMap>, + struct_attributes: HashMap, // Type Aliases map. // // Map type aliases to the actual type. @@ -87,7 +90,7 @@ pub struct NodeInterner { // // TODO: We may be able to remove the Shared wrapper once traits are no longer types. // We'd just lookup their methods as needed through the NodeInterner. - traits: HashMap>, + traits: HashMap, // Trait implementation map // For each type that implements a given Trait ( corresponding TraitId), there should be an entry here @@ -114,7 +117,12 @@ pub struct NodeInterner { primitive_methods: HashMap<(TypeMethodKey, String), FuncId>, } +/// All the information from a function that is filled out during definition collection rather than +/// name resolution. Resultingly, if information about a function is needed during name resolution, +/// this is the only place where it is safe to retrieve it (where all fields are guaranteed to be initialized). pub struct FunctionModifiers { + pub name: String, + /// Whether the function is `pub` or not. pub visibility: Visibility, @@ -135,8 +143,10 @@ pub struct FunctionModifiers { impl FunctionModifiers { /// A semi-reasonable set of default FunctionModifiers used for testing. #[cfg(test)] + #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { + name: String::new(), visibility: Visibility::Public, attributes: Attributes::empty(), is_unconstrained: false, @@ -358,6 +368,7 @@ impl Default for NodeInterner { definitions: vec![], id_to_type: HashMap::new(), structs: HashMap::new(), + struct_attributes: HashMap::new(), type_aliases: Vec::new(), traits: HashMap::new(), trait_implementations: HashMap::new(), @@ -409,9 +420,10 @@ impl NodeInterner { self.traits.insert( type_id, - Shared::new(Trait::new( + Trait::new( type_id, typ.trait_def.name.clone(), + typ.crate_id, typ.trait_def.span, vecmap(&typ.trait_def.generics, |_| { // Temporary type variable ids before the trait is resolved to its actual ids. @@ -423,7 +435,7 @@ impl NodeInterner { }), self_type_typevar_id, self_type_typevar, - )), + ), ); } @@ -449,6 +461,7 @@ impl NodeInterner { let new_struct = StructType::new(struct_id, name, typ.struct_def.span, no_fields, generics); self.structs.insert(struct_id, Shared::new(new_struct)); + self.struct_attributes.insert(struct_id, typ.struct_def.attributes.clone()); struct_id } @@ -475,8 +488,8 @@ impl NodeInterner { } pub fn update_trait(&mut self, trait_id: TraitId, f: impl FnOnce(&mut Trait)) { - let mut value = self.traits.get_mut(&trait_id).unwrap().borrow_mut(); - f(&mut value); + let value = self.traits.get_mut(&trait_id).unwrap(); + f(value); } pub fn set_type_alias(&mut self, type_id: TypeAliasId, typ: Type, generics: Generics) { @@ -593,6 +606,7 @@ impl NodeInterner { // We're filling in contract_function_type and is_internal now, but these will be verified // later during name resolution. let modifiers = FunctionModifiers { + name: function.name.0.contents.clone(), visibility: if function.is_public { Visibility::Public } else { Visibility::Private }, attributes: function.attributes.clone(), is_unconstrained: function.is_unconstrained, @@ -655,8 +669,7 @@ impl NodeInterner { } pub fn function_name(&self, func_id: &FuncId) -> &str { - let name_id = self.function_meta(func_id).name.id; - self.definition_name(name_id) + &self.function_modifiers[func_id].name } pub fn function_modifiers(&self, func_id: &FuncId) -> &FunctionModifiers { @@ -671,6 +684,10 @@ impl NodeInterner { &self.function_modifiers[func_id].attributes } + pub fn struct_attributes(&self, struct_id: &StructId) -> &StructAttributes { + &self.struct_attributes[struct_id] + } + /// Returns the interned statement corresponding to `stmt_id` pub fn statement(&self, stmt_id: &StmtId) -> HirStatement { let def = @@ -744,7 +761,7 @@ impl NodeInterner { self.structs[&id].clone() } - pub fn get_trait(&self, id: TraitId) -> Shared { + pub fn get_trait(&self, id: TraitId) -> Trait { self.traits[&id].clone() } diff --git a/compiler/noirc_frontend/src/parser/errors.rs b/compiler/noirc_frontend/src/parser/errors.rs index 3c5bc777186..c9740f5c119 100644 --- a/compiler/noirc_frontend/src/parser/errors.rs +++ b/compiler/noirc_frontend/src/parser/errors.rs @@ -27,8 +27,6 @@ pub enum ParserErrorReason { PatternInTraitFunctionParameter, #[error("comptime keyword is deprecated")] ComptimeDeprecated, - #[error("the default type of a for-loop range will be changing from a Field to a u64")] - ForLoopDefaultTypeChanging, #[error("{0} are experimental and aren't fully supported yet")] ExperimentalFeature(&'static str), #[error("Where clauses are allowed only on functions with generic parameters")] @@ -136,11 +134,6 @@ impl From for Diagnostic { "The 'comptime' keyword has been deprecated. It can be removed without affecting your program".into(), error.span, ), - ParserErrorReason::ForLoopDefaultTypeChanging => Diagnostic::simple_warning( - "The default type for the incrementor in a for-loop will be changed.".into(), - "The default type in a for-loop will be changing from Field to u64".into(), - error.span, - ), ParserErrorReason::ExperimentalFeature(_) => Diagnostic::simple_warning( reason.to_string(), "".into(), diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 13385108603..2d84bcdd4b4 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -46,7 +46,7 @@ use crate::{ use chumsky::prelude::*; use iter_extended::vecmap; -use noirc_errors::{CustomDiagnostic, Span, Spanned}; +use noirc_errors::{Span, Spanned}; /// Entry function for the parser - also handles lexing internally. /// @@ -54,14 +54,10 @@ use noirc_errors::{CustomDiagnostic, Span, Spanned}; /// of the program along with any parsing errors encountered. If the parsing errors /// Vec is non-empty, there may be Error nodes in the Ast to fill in the gaps that /// failed to parse. Otherwise the Ast is guaranteed to have 0 Error nodes. -pub fn parse_program(source_program: &str) -> (ParsedModule, Vec) { - let (tokens, lexing_errors) = Lexer::lex(source_program); - let mut errors = vecmap(lexing_errors, Into::into); - +pub fn parse_program(source_program: &str) -> (ParsedModule, Vec) { + let (tokens, _lexing_errors) = Lexer::lex(source_program); let (module, parsing_errors) = program().parse_recovery_verbose(tokens); - errors.extend(parsing_errors.into_iter().map(Into::into)); - - (module.unwrap(), errors) + (module.unwrap(), parsing_errors) } /// program: module EOF @@ -556,18 +552,17 @@ fn implementation() -> impl NoirParser { fn trait_implementation() -> impl NoirParser { keyword(Keyword::Impl) .ignore_then(generics()) - .then(ident()) + .then(path()) .then(generic_type_args(parse_type())) .then_ignore(keyword(Keyword::For)) - .then(parse_type().map_with_span(|typ, span| (typ, span))) + .then(parse_type()) .then(where_clause()) .then_ignore(just(Token::LeftBrace)) .then(trait_implementation_body()) .then_ignore(just(Token::RightBrace)) .validate(|args, span, emit| { let ((other_args, where_clause), items) = args; - let (((impl_generics, trait_name), trait_generics), (object_type, object_type_span)) = - other_args; + let (((impl_generics, trait_name), trait_generics), object_type) = other_args; emit(ParserError::with_reason(ParserErrorReason::ExperimentalFeature("Traits"), span)); TopLevelStatement::TraitImpl(NoirTraitImpl { @@ -575,7 +570,6 @@ fn trait_implementation() -> impl NoirParser { trait_name, trait_generics, object_type, - object_type_span, items, where_clause, }) @@ -631,8 +625,8 @@ fn trait_bounds() -> impl NoirParser> { } fn trait_bound() -> impl NoirParser { - ident().then(generic_type_args(parse_type())).map(|(trait_name, trait_generics)| TraitBound { - trait_name, + path().then(generic_type_args(parse_type())).map(|(trait_path, trait_generics)| TraitBound { + trait_path, trait_generics, trait_id: None, }) @@ -1418,10 +1412,6 @@ where .then(expr_no_constructors.clone()) .map(|(start, end)| ForRange::Range(start, end)) .or(expr_no_constructors.map(ForRange::Array)) - .validate(|expr, span, emit| { - emit(ParserError::with_reason(ParserErrorReason::ForLoopDefaultTypeChanging, span)); - expr - }) } fn array_expr

(expr_parser: P) -> impl NoirParser diff --git a/compiler/source-resolver/package.json b/compiler/source-resolver/package.json index 7295343b163..51180e82d37 100644 --- a/compiler/source-resolver/package.json +++ b/compiler/source-resolver/package.json @@ -1,6 +1,6 @@ { "name": "@noir-lang/source-resolver", - "version": "0.12.0", + "version": "0.15.0", "license": "MIT", "main": "./lib-node/index_node.js", "types": "./types/index_node.d.ts", @@ -23,6 +23,7 @@ "build": "npm run clean-modules && npm run build:node && npm run build:web && npm run generate-types", "test": "ava", "generate-types": "tsc src/*.ts --declaration --emitDeclarationOnly --outDir types", + "clean": "rm -rf ./lib ./lib-node", "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "devDependencies": { diff --git a/compiler/wasm/build.sh b/compiler/wasm/build.sh index 8157e42c6de..37f2fd0a5a9 100755 --- a/compiler/wasm/build.sh +++ b/compiler/wasm/build.sh @@ -22,14 +22,13 @@ function run_or_fail { fi } -require_command toml2json require_command jq require_command cargo require_command wasm-bindgen check_installed wasm-opt self_path=$(dirname "$(readlink -f "$0")") -export pname=$(toml2json < $self_path/Cargo.toml | jq -r .package.name) +export pname=$(cargo read-manifest | jq -r '.name') export CARGO_TARGET_DIR=$self_path/target rm -rf $self_path/outputs >/dev/null 2>&1 @@ -45,4 +44,4 @@ fi run_or_fail $self_path/buildPhaseCargoCommand.sh run_or_fail $self_path/installPhase.sh -ln -s $out $self_path/result \ No newline at end of file +ln -s $out $self_path/result diff --git a/compiler/wasm/package.json b/compiler/wasm/package.json index 4c2b2181d2a..ade2be689a7 100644 --- a/compiler/wasm/package.json +++ b/compiler/wasm/package.json @@ -3,7 +3,7 @@ "collaborators": [ "The Noir Team " ], - "version": "0.12.0", + "version": "0.15.0", "license": "(MIT OR Apache-2.0)", "main": "./nodejs/noir_wasm.js", "types": "./web/noir_wasm.d.ts", @@ -23,6 +23,7 @@ "test": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha", "test:node": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha", "test:browser": "web-test-runner", + "clean": "chmod u+w web nodejs && rm -rf ./nodejs ./web ./target ./result", "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "peerDependencies": { diff --git a/flake.nix b/flake.nix index c65e985ffae..28f0a485578 100644 --- a/flake.nix +++ b/flake.nix @@ -73,7 +73,7 @@ # Configuration shared between builds config = { # x-release-please-start-version - version = "0.12.0"; + version = "0.15.0"; # x-release-please-end src = pkgs.lib.cleanSourceWith { @@ -109,7 +109,6 @@ rustToolchain wasm-bindgen-cli binaryen - toml2json ]; buildInputs = [ ] ++ extraBuildInputs; @@ -128,6 +127,9 @@ noir-lsp-wasm-cargo-artifacts = craneLib.buildDepsOnly (wasmConfig // { pname = "noir_lsp_wasm"; }); + acvm-js-cargo-artifacts = craneLib.buildDepsOnly (wasmConfig // { + pname = "acvm_js"; + }); nargo = craneLib.buildPackage (nativeConfig // { pname = "nargo"; @@ -195,6 +197,27 @@ doCheck = false; }); + acvm_js = craneLib.buildPackage (wasmConfig // rec { + pname = "acvm_js"; + + inherit GIT_COMMIT GIT_DIRTY; + + cargoArtifacts = acvm-js-cargo-artifacts; + + cargoExtraArgs = "--package ${pname} --target wasm32-unknown-unknown"; + + buildPhaseCargoCommand = '' + bash acvm-repo/acvm_js/buildPhaseCargoCommand.sh release + ''; + + installPhase = '' + bash acvm-repo/acvm_js/installPhase.sh + ''; + + # We don't want to run tests because they don't work in the Nix sandbox + doCheck = false; + }); + wasm-bindgen-cli = pkgs.callPackage ./wasm-bindgen-cli.nix { rustPlatform = pkgs.makeRustPlatform { rustc = rustToolchain; @@ -227,19 +250,22 @@ # Nix flakes cannot build more than one derivation in one command (see https://github.com/NixOS/nix/issues/5591) # so we use `symlinkJoin` to build everything as the "all" package. - all = pkgs.symlinkJoin { name = "all"; paths = [ nargo noir_wasm noirc_abi_wasm noir_lsp_wasm ]; }; + all = pkgs.symlinkJoin { name = "all"; paths = [ nargo noir_wasm noirc_abi_wasm noir_lsp_wasm acvm_js ]; }; + all_wasm = pkgs.symlinkJoin { name = "all_wasm"; paths = [ noir_wasm noirc_abi_wasm acvm_js ]; }; # We also export individual packages to enable `nix build .#nargo -L`, etc. inherit nargo; inherit noir_wasm; inherit noirc_abi_wasm; inherit noir_lsp_wasm; + inherit acvm_js; # We expose the `*-cargo-artifacts` derivations so we can cache our cargo dependencies in CI inherit native-cargo-artifacts; inherit noir-wasm-cargo-artifacts; inherit noirc-abi-wasm-cargo-artifacts; inherit noir-lsp-wasm-cargo-artifacts; + inherit acvm-js-cargo-artifacts; }; # Setup the environment to match the environment settings, the inputs from our checks derivations, @@ -249,6 +275,7 @@ nargo noir_wasm noirc_abi_wasm + acvm_js ]; # Additional tools that weren't included as `nativeBuildInputs` of any of the derivations in `inputsFrom` diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index db2f0fc59b6..428eb77aa47 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -40,10 +40,10 @@ pub fn assert_constant(_x: T) {} // `as` should be the default for users to cast between primitive types, and in the future // traits can be used to work with generic types. #[builtin(from_field)] -fn from_field(x : Field) -> T {} +fn from_field(_x : Field) -> T {} #[builtin(as_field)] -fn as_field(x : T) -> Field {} +fn as_field(_x : T) -> Field {} pub fn wrapping_add(x : T, y: T) -> T { diff --git a/package.json b/package.json index 718bd757042..07ca6b4689a 100644 --- a/package.json +++ b/package.json @@ -5,16 +5,26 @@ "workspaces": [ "compiler/wasm", "compiler/source-resolver", - "tooling/noirc_abi_wasm", "compiler/integration-tests", + "tooling/noir_js_types", + "tooling/noirc_abi_wasm", "tooling/noir_js", + "acvm-repo/acvm_js", "release-tests" ], "scripts": { - "build": "yarn workspaces foreach run build", + "build": "yarn workspaces foreach --parallel --topological-dev --verbose run build", "test": "yarn workspaces foreach run test", "test:integration": "yarn workspace integration-tests test", - "lint": "yarn workspaces foreach run lint" + "clean:workspaces": "yarn workspaces foreach --exclude @noir-lang/root run clean", + "clean:root": "rm -rf ./result ./target", + "clean": "yarn clean:workspaces && yarn clean:root", + "lint": "yarn workspaces foreach run lint", + "build:with:nix": "nix build -L .#all_wasm", + "install:from:nix:noirc_abi_wasm": "cp -r ./result/noirc_abi_wasm/nodejs ./tooling/noirc_abi_wasm && cp -r ./result/noirc_abi_wasm/web ./tooling/noirc_abi_wasm", + "install:from:nix:noir_wasm": "cp -r ./result/noir_wasm/nodejs ./compiler/wasm && cp -r ./result/noir_wasm/web ./compiler/wasm", + "install:from:nix:acvm_js": "cp -rf ./result/acvm_js/nodejs ./acvm-repo/acvm_js && cp -rf ./result/acvm_js/web ./acvm-repo/acvm_js", + "install:from:nix": "yarn build:with:nix && yarn install:from:nix:noirc_abi_wasm && yarn install:from:nix:noir_wasm && yarn install:from:nix:acvm_js" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.59.5", @@ -27,5 +37,8 @@ "ts-node": "^10.9.1", "typescript": "^5.0.4" }, - "packageManager": "yarn@3.6.3" + "packageManager": "yarn@3.6.3", + "dependencies": { + "tslog": "^4.9.2" + } } diff --git a/release-please-config.json b/release-please-config.json index ef222bef77f..23522286222 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -3,12 +3,13 @@ "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": true, "prerelease": true, - "pull-request-title-pattern": "chore(noir): Release ${version}", - "group-pull-request-title-pattern": "chore(noir): Release ${version}", + "pull-request-title-pattern": "chore(noir): Release ${scope}", + "group-pull-request-title-pattern": "chore(noir): Release ${scope}", "packages": { ".": { "release-type": "simple", "component": "noir", + "package-name": "noir", "include-component-in-tag": false, "extra-files": [ "Cargo.toml", @@ -34,7 +35,32 @@ "jsonpath": "$.version" } ] + }, + "acvm-repo" : { + "release-type": "simple", + "package-name": "acvm", + "component": "acvm", + "include-component-in-tag": false, + "extra-files": [ + "acir/Cargo.toml", + "acir_field/Cargo.toml", + "acvm/Cargo.toml", + "acvm_js/Cargo.toml", + "barretenberg_blackbox_solver/Cargo.toml", + "blackbox_solver/Cargo.toml", + "brillig/Cargo.toml", + "brillig_vm/Cargo.toml", + "stdlib/Cargo.toml", + "flake.nix", + { + "type": "json", + "path": "acvm_js/package.json", + "jsonpath": "$.version" + } + ] } }, - "plugins": ["sentence-case"] + "plugins": [ + "sentence-case" + ] } diff --git a/tooling/backend_interface/src/cli/contract.rs b/tooling/backend_interface/src/cli/contract.rs index 6301431d0f3..47ad1e4cc5b 100644 --- a/tooling/backend_interface/src/cli/contract.rs +++ b/tooling/backend_interface/src/cli/contract.rs @@ -2,6 +2,8 @@ use std::path::{Path, PathBuf}; use crate::BackendError; +use super::string_from_stderr; + /// VerifyCommand will call the barretenberg binary /// to return a solidity library with the verification key /// that can be used to verify proofs on-chain. @@ -31,9 +33,9 @@ impl ContractCommand { if output.status.success() { String::from_utf8(output.stdout) - .map_err(|error| BackendError::MalformedResponse(error.into_bytes())) + .map_err(|error| BackendError::InvalidUTF8Vector(error.into_bytes())) } else { - Err(BackendError::CommandFailed(output.stderr)) + Err(BackendError::CommandFailed(string_from_stderr(&output.stderr))) } } } diff --git a/tooling/backend_interface/src/cli/gates.rs b/tooling/backend_interface/src/cli/gates.rs index 446c6eee817..aca05f0232a 100644 --- a/tooling/backend_interface/src/cli/gates.rs +++ b/tooling/backend_interface/src/cli/gates.rs @@ -2,6 +2,8 @@ use std::path::{Path, PathBuf}; use crate::BackendError; +use super::string_from_stderr; + /// GatesCommand will call the barretenberg binary /// to return the number of gates needed to create a proof /// for the given bytecode. @@ -21,13 +23,16 @@ impl GatesCommand { .output()?; if !output.status.success() { - return Err(BackendError::CommandFailed(output.stderr)); + return Err(BackendError::CommandFailed(string_from_stderr(&output.stderr))); } // Note: barretenberg includes the newline, so that subsequent prints to stdout // are not on the same line as the gates output. - let gates_bytes: [u8; 8] = - output.stdout.try_into().map_err(BackendError::MalformedResponse)?; + const EXPECTED_BYTES: usize = 8; + let gates_bytes: [u8; EXPECTED_BYTES] = + output.stdout.as_slice().try_into().map_err(|_| { + BackendError::UnexpectedNumberOfBytes(EXPECTED_BYTES, output.stdout.clone()) + })?; // Convert bytes to u64 in little-endian format let value = u64::from_le_bytes(gates_bytes); diff --git a/tooling/backend_interface/src/cli/info.rs b/tooling/backend_interface/src/cli/info.rs index d015284e704..d3fd89bd2bc 100644 --- a/tooling/backend_interface/src/cli/info.rs +++ b/tooling/backend_interface/src/cli/info.rs @@ -5,6 +5,8 @@ use std::path::{Path, PathBuf}; use crate::{BackendError, BackendOpcodeSupport}; +use super::string_from_stderr; + pub(crate) struct InfoCommand { pub(crate) crs_path: PathBuf, } @@ -43,7 +45,7 @@ impl InfoCommand { let output = command.output()?; if !output.status.success() { - return Err(BackendError::CommandFailed(output.stderr)); + return Err(BackendError::CommandFailed(string_from_stderr(&output.stderr))); } let backend_info: InfoResponse = diff --git a/tooling/backend_interface/src/cli/mod.rs b/tooling/backend_interface/src/cli/mod.rs index 5018def4160..848c5e11e81 100644 --- a/tooling/backend_interface/src/cli/mod.rs +++ b/tooling/backend_interface/src/cli/mod.rs @@ -22,9 +22,14 @@ fn no_command_provided_works() -> Result<(), crate::BackendError> { let output = std::process::Command::new(backend.binary_path()).output()?; - let stderr = String::from_utf8_lossy(&output.stderr); + let stderr = string_from_stderr(&output.stderr); // Assert help message is printed due to no command being provided. assert!(stderr.contains("Usage: mock_backend ")); Ok(()) } + +// Converts a stderr byte array to a string (including invalid characters) +fn string_from_stderr(stderr: &[u8]) -> String { + String::from_utf8_lossy(stderr).to_string() +} diff --git a/tooling/backend_interface/src/cli/prove.rs b/tooling/backend_interface/src/cli/prove.rs index a229506a3dc..c12e516db57 100644 --- a/tooling/backend_interface/src/cli/prove.rs +++ b/tooling/backend_interface/src/cli/prove.rs @@ -2,6 +2,8 @@ use std::path::{Path, PathBuf}; use crate::BackendError; +use super::string_from_stderr; + /// ProveCommand will call the barretenberg binary /// to create a proof, given the witness and the bytecode. /// @@ -39,7 +41,7 @@ impl ProveCommand { if output.status.success() { Ok(output.stdout) } else { - Err(BackendError::CommandFailed(output.stderr)) + Err(BackendError::CommandFailed(string_from_stderr(&output.stderr))) } } } diff --git a/tooling/backend_interface/src/cli/write_vk.rs b/tooling/backend_interface/src/cli/write_vk.rs index 05db7e47388..987dac7cf8d 100644 --- a/tooling/backend_interface/src/cli/write_vk.rs +++ b/tooling/backend_interface/src/cli/write_vk.rs @@ -1,5 +1,6 @@ use std::path::{Path, PathBuf}; +use super::string_from_stderr; use crate::BackendError; /// WriteCommand will call the barretenberg binary @@ -32,7 +33,7 @@ impl WriteVkCommand { if output.status.success() { Ok(()) } else { - Err(BackendError::CommandFailed(output.stderr)) + Err(BackendError::CommandFailed(string_from_stderr(&output.stderr))) } } } diff --git a/tooling/backend_interface/src/lib.rs b/tooling/backend_interface/src/lib.rs index 6d18b6b6ead..d6a9cd0d293 100644 --- a/tooling/backend_interface/src/lib.rs +++ b/tooling/backend_interface/src/lib.rs @@ -40,11 +40,16 @@ pub enum BackendError { #[error("Backend binary does not exist")] MissingBinary, - #[error("The backend responded with malformed data: {0:?}")] - MalformedResponse(Vec), + #[error("The backend responded with a malformed UTF8 byte vector: {0:?}")] + InvalidUTF8Vector(Vec), - #[error("The backend encountered an error")] - CommandFailed(Vec), + #[error( + "The backend responded with a unexpected number of bytes. Expected: {0} but got {} ({1:?})", .1.len() + )] + UnexpectedNumberOfBytes(usize, Vec), + + #[error("The backend encountered an error: {0:?}")] + CommandFailed(String), } #[derive(Debug)] diff --git a/tooling/bb_abstraction_leaks/build.rs b/tooling/bb_abstraction_leaks/build.rs index e4d213cfa38..6f92d1bc7c3 100644 --- a/tooling/bb_abstraction_leaks/build.rs +++ b/tooling/bb_abstraction_leaks/build.rs @@ -9,9 +9,9 @@ use const_format::formatcp; // } const USERNAME: &str = "AztecProtocol"; -const REPO: &str = "barretenberg"; -const VERSION: &str = "0.5.1"; -const TAG: &str = formatcp!("barretenberg-v{}", VERSION); +const REPO: &str = "aztec-packages"; +const VERSION: &str = "0.7.3"; +const TAG: &str = formatcp!("aztec-packages-v{}", VERSION); const API_URL: &str = formatcp!("https://github.com/{}/{}/releases/download/{}", USERNAME, REPO, TAG); diff --git a/tooling/lsp/src/lib.rs b/tooling/lsp/src/lib.rs index 2f84a969d81..3b219631ecc 100644 --- a/tooling/lsp/src/lib.rs +++ b/tooling/lsp/src/lib.rs @@ -205,10 +205,7 @@ fn on_test_run_request( Ok(toml_path) => toml_path, Err(err) => { // If we cannot find a manifest, we can't run the test - return future::ready(Err(ResponseError::new( - ErrorCode::REQUEST_FAILED, - format!("{}", err), - ))); + return future::ready(Err(ResponseError::new(ErrorCode::REQUEST_FAILED, err))); } }; @@ -222,10 +219,7 @@ fn on_test_run_request( Ok(workspace) => workspace, Err(err) => { // If we found a manifest, but the workspace is invalid, we raise an error about it - return future::ready(Err(ResponseError::new( - ErrorCode::REQUEST_FAILED, - format!("{}", err), - ))); + return future::ready(Err(ResponseError::new(ErrorCode::REQUEST_FAILED, err))); } }; @@ -309,7 +303,7 @@ fn on_tests_request( // We can reconsider this when we can build a file without the need for a Nargo.toml file to resolve deps let _ = state.client.log_message(LogMessageParams { typ: MessageType::WARNING, - message: format!("{}", err), + message: err.to_string(), }); return future::ready(Ok(None)); } @@ -320,7 +314,7 @@ fn on_tests_request( // If we found a manifest, but the workspace is invalid, we raise an error about it return future::ready(Err(ResponseError::new( ErrorCode::REQUEST_FAILED, - format!("{}", err), + err.to_string(), ))); } }; diff --git a/tooling/nargo/src/artifacts/contract.rs b/tooling/nargo/src/artifacts/contract.rs index 4db7d95731e..fa161b63a5b 100644 --- a/tooling/nargo/src/artifacts/contract.rs +++ b/tooling/nargo/src/artifacts/contract.rs @@ -1,5 +1,5 @@ use acvm::acir::circuit::Circuit; -use noirc_abi::Abi; +use noirc_abi::{Abi, ContractEvent}; use noirc_driver::ContractFunctionType; use serde::{Deserialize, Serialize}; @@ -16,6 +16,8 @@ pub struct PreprocessedContract { pub backend: String, /// Each of the contract's functions are compiled into a separate program stored in this `Vec`. pub functions: Vec, + /// All the events defined inside the contract scope. + pub events: Vec, } /// Each function in the contract will be compiled as a separate noir program. diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index d7bcf475a40..7ac7fe58500 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -267,6 +267,7 @@ fn save_contract( name: contract.name, backend: String::from(BACKEND_IDENTIFIER), functions: preprocessed_functions, + events: contract.events, }; save_contract_to_file( diff --git a/tooling/nargo_cli/src/cli/fs/program.rs b/tooling/nargo_cli/src/cli/fs/program.rs index ac5e6c5c32f..377786627be 100644 --- a/tooling/nargo_cli/src/cli/fs/program.rs +++ b/tooling/nargo_cli/src/cli/fs/program.rs @@ -55,8 +55,8 @@ pub(crate) fn read_program_from_file>( let input_string = std::fs::read(&file_path).map_err(|_| FilesystemError::PathNotValid(file_path))?; - - let program = serde_json::from_slice(&input_string).expect("could not deserialize program"); + let program = serde_json::from_slice(&input_string) + .map_err(|err| FilesystemError::ProgramSerializationError(err.to_string()))?; Ok(program) } diff --git a/tooling/nargo_cli/src/errors.rs b/tooling/nargo_cli/src/errors.rs index 49fa02d281d..b73a7888f32 100644 --- a/tooling/nargo_cli/src/errors.rs +++ b/tooling/nargo_cli/src/errors.rs @@ -26,6 +26,9 @@ pub(crate) enum FilesystemError { /// WitnessMap serialization error #[error(transparent)] WitnessMapSerialization(#[from] WitnessMapError), + + #[error("Error: could not deserialize build program: {0}")] + ProgramSerializationError(String), } #[derive(Debug, Error)] diff --git a/tooling/nargo_cli/tests/acir_artifacts/1327_concrete_in_generic/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/1327_concrete_in_generic/target/acir.gz index 5d296af254b..16cefef0bc3 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/1327_concrete_in_generic/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/1327_concrete_in_generic/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/1_mul/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/1_mul/target/acir.gz index 46db025c3c7..081be55f265 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/1_mul/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/1_mul/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/2_div/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/2_div/target/acir.gz index a45dbe7e40d..6a915b0bdbd 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/2_div/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/2_div/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/3_add/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/3_add/target/acir.gz index 73b60810519..2236c2321eb 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/3_add/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/3_add/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/4_sub/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/4_sub/target/acir.gz index d28c95e99f8..392663f2e3a 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/4_sub/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/4_sub/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/4_sub/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/4_sub/target/witness.gz index d968b23674c..d5e6db671b6 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/4_sub/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/4_sub/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/5_over/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/5_over/target/acir.gz index 5e448f8afe9..ba75fe8181c 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/5_over/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/5_over/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/5_over/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/5_over/target/witness.gz index c063eb5651b..d7091d4f733 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/5_over/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/5_over/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/6/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/6/target/acir.gz index 42bfbbaed0e..5cc3cdb8357 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/6/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/6/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/6_array/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/6_array/target/acir.gz index 05d33bd7ce3..3be65e545dd 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/6_array/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/6_array/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/6_array/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/6_array/target/witness.gz index fbb8e0115f3..38414a85ee2 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/6_array/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/6_array/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/7/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/7/target/acir.gz index 3238e1bc26b..5fd2c337747 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/7/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/7/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/7_function/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/7_function/target/acir.gz index eb0132dc063..3c6bba5e6b1 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/7_function/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/7_function/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/7_function/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/7_function/target/witness.gz index 02ea81fbc1f..db9838b64db 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/7_function/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/7_function/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/arithmetic_binary_operations/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/arithmetic_binary_operations/target/acir.gz index 4f874bb01db..ebd7d79c733 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/arithmetic_binary_operations/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/arithmetic_binary_operations/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/arithmetic_binary_operations/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/arithmetic_binary_operations/target/witness.gz index 77ee10b4d4f..413f9deeaf4 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/arithmetic_binary_operations/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/arithmetic_binary_operations/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/array_dynamic/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/array_dynamic/target/acir.gz index 78e27fb7119..de9a5ad86a0 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/array_dynamic/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/array_dynamic/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/array_dynamic/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/array_dynamic/target/witness.gz index 2622c55c676..44be6af0042 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/array_dynamic/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/array_dynamic/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/array_eq/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/array_eq/target/acir.gz index 3209865ae28..98b89ffc5a5 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/array_eq/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/array_eq/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/array_len/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/array_len/target/acir.gz index 691299e7baf..cd46a654155 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/array_len/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/array_len/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/array_neq/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/array_neq/target/acir.gz index e55fac6b6f3..f627efe6de0 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/array_neq/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/array_neq/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/array_sort/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/array_sort/target/acir.gz index 6001b7e4bb8..9f8e44e39a9 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/array_sort/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/array_sort/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/assert/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/assert/target/acir.gz index 21b9f6fdb15..e59ce0d64f8 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/assert/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/assert/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/assert_statement/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/assert_statement/target/acir.gz index 6d208fe704b..1fb979d26f7 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/assert_statement/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/assert_statement/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/assign_ex/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/assign_ex/target/acir.gz index 851e429e7f0..0acd67b06c1 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/assign_ex/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/assign_ex/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/bit_and/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/bit_and/target/acir.gz index a3db36d28ca..52e2c3386b1 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/bit_and/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/bit_and/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/bit_and/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/bit_and/target/witness.gz index ba27f611f48..47c6ffac9b9 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/bit_and/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/bit_and/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_comptime/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_comptime/target/acir.gz index ceb1a1a613a..05aa3a08469 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_comptime/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_comptime/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_comptime/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_comptime/target/witness.gz index b9283fbd0be..bc2149d74f7 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_comptime/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_comptime/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_runtime/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_runtime/target/acir.gz index 9314658a5ae..b1aed01e9b0 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_runtime/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_runtime/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_runtime/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_runtime/target/witness.gz index 7e88d1f9071..e477f7286c9 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_runtime/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/bit_shifts_runtime/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/bool_not/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/bool_not/target/acir.gz index 1ac995acfe9..144584c4bc5 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/bool_not/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/bool_not/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/bool_or/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/bool_or/target/acir.gz index c3193e9b4e4..e3b6a07b49d 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/bool_or/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/bool_or/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_acir_as_brillig/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_acir_as_brillig/target/acir.gz index 297ad64a9ec..ed2a11590ad 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_acir_as_brillig/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_acir_as_brillig/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_arrays/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_arrays/target/acir.gz index 1077aca8e7b..3faa8867e15 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_arrays/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_arrays/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_assert/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_assert/target/acir.gz index c9316354d45..fc4672e4e02 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_assert/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_assert/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_assert/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_assert/target/witness.gz index f173f3c5625..176f694ce1a 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_assert/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_assert/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_blake2s/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_blake2s/target/acir.gz index f3ffc6c9059..396cadb440e 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_blake2s/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_blake2s/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_calls/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_calls/target/acir.gz index d431b2284d1..ee814b626e3 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_calls/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_calls/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_calls_array/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_calls_array/target/acir.gz index 12986ecc1c6..9d00c45efd6 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_calls_array/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_calls_array/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_calls_conditionals/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_calls_conditionals/target/acir.gz index 669cb15f683..5219c75c09c 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_calls_conditionals/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_calls_conditionals/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_conditional/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_conditional/target/acir.gz index c75b63980e5..28a34a435b2 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_conditional/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_conditional/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_conditional/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_conditional/target/witness.gz index b998e51d692..162d33a5fd3 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_conditional/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_conditional/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_ecdsa/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_ecdsa/target/acir.gz index ef22f99cc20..c8989b4770f 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_ecdsa/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_ecdsa/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_fns_as_values/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_fns_as_values/target/acir.gz index c6da0c97dd5..39b54c10316 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_fns_as_values/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_fns_as_values/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_hash_to_field/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_hash_to_field/target/acir.gz index bf59013d233..b92855cf9de 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_hash_to_field/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_hash_to_field/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_identity_function/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_identity_function/target/acir.gz index 622f321136e..b076a65c4dd 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_identity_function/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_identity_function/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_keccak/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_keccak/target/acir.gz index 59aa76b5fdb..882339544b4 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_keccak/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_keccak/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_keccak/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_keccak/target/witness.gz index 0f7d857c3b7..f45b2ddb62e 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_keccak/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_keccak/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_loop/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_loop/target/acir.gz index 1e04c51b0f7..c93d385a2a7 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_loop/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_loop/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_nested_arrays/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_nested_arrays/target/acir.gz index 33e37658fa1..81c60622294 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_nested_arrays/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_nested_arrays/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_nested_slices/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_nested_slices/target/acir.gz index 321a0ddc970..caf0623b807 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_nested_slices/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_nested_slices/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_not/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_not/target/acir.gz index 69f6d860aef..ceec57a975e 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_not/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_not/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_not/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_not/target/witness.gz index 457a006e849..c31ab58fbcf 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_not/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_not/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_oracle/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_oracle/target/acir.gz index f1199536501..d5db6f49bff 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_oracle/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_oracle/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_pedersen/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_pedersen/target/acir.gz index 8fa443e37cc..5bfc7385a68 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_pedersen/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_pedersen/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_recursion/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_recursion/target/acir.gz index bd7acdd501a..e2a840a5724 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_recursion/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_recursion/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_references/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_references/target/acir.gz index 46c4767aa3a..10d5cbc9968 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_references/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_references/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_scalar_mul/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_scalar_mul/target/acir.gz index 244ba764db2..5c2e2a5c89b 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_scalar_mul/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_scalar_mul/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_schnorr/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_schnorr/target/acir.gz index d6c31f996d2..d4651b4d655 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_schnorr/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_schnorr/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_sha256/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_sha256/target/acir.gz index 1bff7454a7c..c9c725e191e 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_sha256/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_sha256/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_slices/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_slices/target/acir.gz index 59dc0bcadf6..f5aa1fa1d41 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_slices/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_slices/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_to_be_bytes/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_to_be_bytes/target/acir.gz index 6633577c695..4e8defaf0cc 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_to_be_bytes/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_to_be_bytes/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_to_bytes_integration/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_to_bytes_integration/target/acir.gz index 48d01aed773..b38fafb7577 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_to_bytes_integration/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_to_bytes_integration/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_to_le_bytes/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_to_le_bytes/target/acir.gz index 52c21fa08a0..b0a73fbdbf3 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_to_le_bytes/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_to_le_bytes/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_top_level/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_top_level/target/acir.gz index c61f3cc89a4..147717fe624 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/brillig_top_level/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/brillig_top_level/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_unitialised_arrays/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_unitialised_arrays/target/acir.gz new file mode 100644 index 00000000000..89cfc520bc9 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/brillig_unitialised_arrays/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/brillig_unitialised_arrays/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/brillig_unitialised_arrays/target/witness.gz new file mode 100644 index 00000000000..4523699482a Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/brillig_unitialised_arrays/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/cast_bool/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/cast_bool/target/acir.gz index 5b8a657e5bc..5dbc69913b4 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/cast_bool/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/cast_bool/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/closures_mut_ref/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/closures_mut_ref/target/acir.gz index e2e04f5fb42..9a0635f2226 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/closures_mut_ref/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/closures_mut_ref/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_1/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_1/target/acir.gz new file mode 100644 index 00000000000..74e57b7d988 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_1/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_1/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_1/target/witness.gz new file mode 100644 index 00000000000..3edb25a161a Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_1/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_2/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_2/target/acir.gz new file mode 100644 index 00000000000..80aaa3873a5 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_2/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_2/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_2/target/witness.gz new file mode 100644 index 00000000000..177f179c9cf Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_2/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_3_regression/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_3_regression/target/acir.gz new file mode 100644 index 00000000000..6ee1f3d1bf8 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_3_regression/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_3_regression/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_3_regression/target/witness.gz new file mode 100644 index 00000000000..00e39a68b37 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_3_regression/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_421/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_421/target/acir.gz new file mode 100644 index 00000000000..9c576e46a33 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_421/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_421/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_421/target/witness.gz new file mode 100644 index 00000000000..979094dc9b3 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_421/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_547/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_547/target/acir.gz new file mode 100644 index 00000000000..720e8a3bd00 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_547/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_547/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_547/target/witness.gz new file mode 100644 index 00000000000..37c6d67fada Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_547/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_579/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_579/target/acir.gz new file mode 100644 index 00000000000..3628a8d6b1e Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_579/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_579/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_579/target/witness.gz new file mode 100644 index 00000000000..4e90289d5e1 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_579/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_661/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_661/target/acir.gz new file mode 100644 index 00000000000..6224ce71207 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_661/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_661/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_661/target/witness.gz new file mode 100644 index 00000000000..9fe13d5277c Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_661/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_short_circuit/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_short_circuit/target/acir.gz new file mode 100644 index 00000000000..e01f75195f1 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_short_circuit/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_short_circuit/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_short_circuit/target/witness.gz new file mode 100644 index 00000000000..200d182c516 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_short_circuit/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_to_bits/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_to_bits/target/acir.gz new file mode 100644 index 00000000000..3628a8d6b1e Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_to_bits/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_to_bits/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_to_bits/target/witness.gz new file mode 100644 index 00000000000..4e90289d5e1 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/conditional_regression_to_bits/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/constant_return/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/constant_return/target/acir.gz index ea36782c233..f12dad0a254 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/constant_return/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/constant_return/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/custom_entry/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/custom_entry/target/acir.gz index 21b9f6fdb15..e59ce0d64f8 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/custom_entry/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/custom_entry/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/debug_logs/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/debug_logs/target/acir.gz index f0990c560cb..af5b3f7021a 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/debug_logs/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/debug_logs/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/diamond_deps_0/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/diamond_deps_0/target/acir.gz index e9a4783cb82..802695728d2 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/diamond_deps_0/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/diamond_deps_0/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/distinct_keyword/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/distinct_keyword/target/acir.gz index bca04661d30..42e909de7d9 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/distinct_keyword/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/distinct_keyword/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/double_verify_proof/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/double_verify_proof/target/acir.gz index b955dca361a..c5e29b7d428 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/double_verify_proof/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/double_verify_proof/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/ecdsa_secp256k1/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/ecdsa_secp256k1/target/acir.gz index a5aeebe7fcf..5907766b3fa 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/ecdsa_secp256k1/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/ecdsa_secp256k1/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/ecdsa_secp256r1/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/ecdsa_secp256r1/target/acir.gz index 39aabcbf301..dd7dd3a3f84 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/ecdsa_secp256r1/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/ecdsa_secp256r1/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/eddsa/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/eddsa/target/acir.gz index 39b67fe46b9..fc80e5ba1d0 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/eddsa/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/eddsa/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/eddsa/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/eddsa/target/witness.gz index 102f119ea30..a3562d5cde3 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/eddsa/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/eddsa/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/generics/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/generics/target/acir.gz index 468587714e3..2100e796d5d 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/generics/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/generics/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/global_consts/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/global_consts/target/acir.gz index eb9875c8fef..4d164bf6a55 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/global_consts/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/global_consts/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/hash_to_field/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/hash_to_field/target/acir.gz index 6ec14e066fa..499c53b9ace 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/hash_to_field/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/hash_to_field/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/higher_order_functions/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/higher_order_functions/target/acir.gz index 50036c797d9..628098f6607 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/higher_order_functions/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/higher_order_functions/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/if_else_chain/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/if_else_chain/target/acir.gz index 377d3a94464..f7cbe4184ef 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/if_else_chain/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/if_else_chain/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/import/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/import/target/acir.gz index c3f579f7cc0..bcbbf8d5d07 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/import/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/import/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/integer_array_indexing/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/integer_array_indexing/target/acir.gz index bcef795065a..09fd507b81b 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/integer_array_indexing/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/integer_array_indexing/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/keccak256/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/keccak256/target/acir.gz index 103eb91fe85..a3ba99691ac 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/keccak256/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/keccak256/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/keccak256/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/keccak256/target/witness.gz index 51a5b73c88b..56c3f23c260 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/keccak256/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/keccak256/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/main_bool_arg/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/main_bool_arg/target/acir.gz index dce2019c75b..4d703e01a09 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/main_bool_arg/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/main_bool_arg/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/main_return/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/main_return/target/acir.gz index d5d8b6dba85..98a51b6a16d 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/main_return/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/main_return/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/merkle_insert/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/merkle_insert/target/acir.gz index b4177c73e7c..fc63998b349 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/merkle_insert/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/merkle_insert/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/merkle_insert/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/merkle_insert/target/witness.gz index 78e92c59029..6bca1746e27 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/merkle_insert/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/merkle_insert/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/modules/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/modules/target/acir.gz index b0523be6e92..f3938401d43 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/modules/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/modules/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/modules_more/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/modules_more/target/acir.gz index c3f579f7cc0..bcbbf8d5d07 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/modules_more/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/modules_more/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/modulus/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/modulus/target/acir.gz index 2135a5c0eb6..23a2d4d7dda 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/modulus/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/modulus/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/nested_array_dynamic/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/nested_array_dynamic/target/acir.gz new file mode 100644 index 00000000000..b03ec1646bb Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/nested_array_dynamic/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/nested_array_dynamic/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/nested_array_dynamic/target/witness.gz new file mode 100644 index 00000000000..3b726d19d3e Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/nested_array_dynamic/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/nested_arrays_from_brillig/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/nested_arrays_from_brillig/target/acir.gz index 2578e60c8ae..74bb0d0d1f2 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/nested_arrays_from_brillig/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/nested_arrays_from_brillig/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/pedersen_check/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/pedersen_check/target/acir.gz index 5846defb902..02d8f9daac0 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/pedersen_check/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/pedersen_check/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/poseidon_bn254_hash/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/poseidon_bn254_hash/target/acir.gz index ef1911d1095..86a04cbb1d9 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/poseidon_bn254_hash/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/poseidon_bn254_hash/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/poseidonsponge_x5_254/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/poseidonsponge_x5_254/target/acir.gz index 2737018de8e..aae71250365 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/poseidonsponge_x5_254/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/poseidonsponge_x5_254/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/pred_eq/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/pred_eq/target/acir.gz index 5b8a657e5bc..5dbc69913b4 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/pred_eq/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/pred_eq/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/references/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/references/target/acir.gz index 937b81e9ef6..7c60b6b92b2 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/references/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/references/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/references_aliasing/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/references_aliasing/target/acir.gz index f27a429a80f..3628a8d6b1e 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/references_aliasing/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/references_aliasing/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/regression/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/regression/target/acir.gz index cd99055837a..fb2e69338af 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/regression/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/regression/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/regression/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/regression/target/witness.gz index cbb756470b3..f6ff335a189 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/regression/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/regression/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/regression_mem_op_predicate/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/regression_mem_op_predicate/target/acir.gz new file mode 100644 index 00000000000..311ce4fbadf Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/regression_mem_op_predicate/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/regression_mem_op_predicate/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/regression_mem_op_predicate/target/witness.gz new file mode 100644 index 00000000000..1e5eb38c5fb Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/regression_mem_op_predicate/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/regression_method_cannot_be_found/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/regression_method_cannot_be_found/target/acir.gz index f029de3a84f..b69375dabde 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/regression_method_cannot_be_found/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/regression_method_cannot_be_found/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/scalar_mul/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/scalar_mul/target/acir.gz index ec786bac1f9..8438a74fc68 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/scalar_mul/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/scalar_mul/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/schnorr/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/schnorr/target/acir.gz index b0099027030..a2293c5417e 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/schnorr/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/schnorr/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/schnorr/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/schnorr/target/witness.gz index d7aa71c0446..de9fd3017f7 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/schnorr/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/schnorr/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/sha256/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/sha256/target/acir.gz index 279e4edf44d..d8d80fcbfc0 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/sha256/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/sha256/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/sha256/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/sha256/target/witness.gz index 1e88e682b72..4b9fd27e967 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/sha256/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/sha256/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/sha2_blocks/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/sha2_blocks/target/acir.gz index 62479a7035f..8f94ffddf81 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/sha2_blocks/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/sha2_blocks/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/sha2_blocks/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/sha2_blocks/target/witness.gz index 44cdea653b1..73d262b0399 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/sha2_blocks/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/sha2_blocks/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/sha2_byte/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/sha2_byte/target/acir.gz index 5db49355157..ff15af8a918 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/sha2_byte/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/sha2_byte/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/sha2_byte/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/sha2_byte/target/witness.gz index bae2217f391..d1dded60be6 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/sha2_byte/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/sha2_byte/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/signed_arithmetic/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/signed_arithmetic/target/acir.gz new file mode 100644 index 00000000000..273b36b51a4 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/signed_arithmetic/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/signed_arithmetic/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/signed_arithmetic/target/witness.gz new file mode 100644 index 00000000000..e37b743f9c5 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/signed_arithmetic/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/signed_division/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/signed_division/target/acir.gz index b26cef3993e..20ef585f32e 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/signed_division/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/signed_division/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/signed_division/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/signed_division/target/witness.gz index 7aeb670e9ed..3d44a82dd7a 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/signed_division/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/signed_division/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_2d_array/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_2d_array/target/acir.gz new file mode 100644 index 00000000000..efe3e815d46 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/simple_2d_array/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_2d_array/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_2d_array/target/witness.gz new file mode 100644 index 00000000000..321a76492da Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/simple_2d_array/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_add_and_ret_arr/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_add_and_ret_arr/target/acir.gz index 6a2322de7db..48d9d1aba75 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/simple_add_and_ret_arr/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/simple_add_and_ret_arr/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_array_param/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_array_param/target/acir.gz index 1aa1f33cd77..56fada91df5 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/simple_array_param/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/simple_array_param/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_bitwise/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_bitwise/target/acir.gz index 2b1f825fd5a..eb85a4b5b05 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/simple_bitwise/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/simple_bitwise/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_comparison/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_comparison/target/acir.gz index 55c23c46dd8..d06b5715aa3 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/simple_comparison/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/simple_comparison/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_comparison/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_comparison/target/witness.gz index 63bf586cfa9..e3f0c25154d 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/simple_comparison/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/simple_comparison/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_mut/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_mut/target/acir.gz index 78d246c6861..75566241c92 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/simple_mut/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/simple_mut/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_not/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_not/target/acir.gz index ce3e632afe6..5d1b5e460c6 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/simple_not/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/simple_not/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_print/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_print/target/acir.gz index 8ddcf94c704..0182c469132 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/simple_print/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/simple_print/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_program_addition/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_program_addition/target/acir.gz index 6a2322de7db..48d9d1aba75 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/simple_program_addition/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/simple_program_addition/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_radix/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_radix/target/acir.gz index 656b636c637..3f3264b7482 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/simple_radix/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/simple_radix/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_radix/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_radix/target/witness.gz index e3ba9bd1428..92ad6a66e62 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/simple_radix/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/simple_radix/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_shield/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_shield/target/acir.gz index a34e79fc3a6..d349459da26 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/simple_shield/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/simple_shield/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_shield/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_shield/target/witness.gz index d07e36549ea..a4d4e521011 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/simple_shield/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/simple_shield/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/simple_shift_left_right/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/simple_shift_left_right/target/acir.gz index 5164f54d87a..0232f12a872 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/simple_shift_left_right/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/simple_shift_left_right/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/slice_dynamic_index/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/slice_dynamic_index/target/acir.gz index b7e1fb4beae..7691dc273c4 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/slice_dynamic_index/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/slice_dynamic_index/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/slice_dynamic_index/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/slice_dynamic_index/target/witness.gz index e4611fdd14f..49efec73dff 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/slice_dynamic_index/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/slice_dynamic_index/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/slices/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/slices/target/acir.gz index 3d899c46572..130696a5389 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/slices/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/slices/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/slices/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/slices/target/witness.gz index d1e63b18d37..a8ce08925d7 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/slices/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/slices/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/strings/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/strings/target/acir.gz index 0976e9f2b2e..24d6dc2337b 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/strings/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/strings/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/struct/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/struct/target/acir.gz index eb913661993..371f434fc5a 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/struct/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/struct/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/struct_array_inputs/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/struct_array_inputs/target/acir.gz index 19621ede7d6..be6d1a0961d 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/struct_array_inputs/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/struct_array_inputs/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/struct_fields_ordering/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/struct_fields_ordering/target/acir.gz index d9a07903860..f802fec624f 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/struct_fields_ordering/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/struct_fields_ordering/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/struct_inputs/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/struct_inputs/target/acir.gz index 06aa93a1e03..346dee10280 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/struct_inputs/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/struct_inputs/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/submodules/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/submodules/target/acir.gz index c3193e9b4e4..e3b6a07b49d 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/submodules/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/submodules/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/to_be_bytes/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/to_be_bytes/target/acir.gz index 0a6b73ede0a..56753c8968d 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/to_be_bytes/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/to_be_bytes/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/to_be_bytes/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/to_be_bytes/target/witness.gz index 27fe7ec8d02..45f8ee35317 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/to_be_bytes/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/to_be_bytes/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/to_bytes_consistent/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/to_bytes_consistent/target/acir.gz index 75dde3237b3..e04c1fd7482 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/to_bytes_consistent/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/to_bytes_consistent/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/to_bytes_consistent/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/to_bytes_consistent/target/witness.gz index 66202f79cd0..c08fd54ed90 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/to_bytes_consistent/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/to_bytes_consistent/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/to_bytes_integration/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/to_bytes_integration/target/acir.gz index b0238934a54..88767c2c6e6 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/to_bytes_integration/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/to_bytes_integration/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/to_bytes_integration/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/to_bytes_integration/target/witness.gz index 0d9d7d619bb..0ae6a7787ac 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/to_bytes_integration/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/to_bytes_integration/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/to_le_bytes/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/to_le_bytes/target/acir.gz index 94d1e673ec4..f00d56961a5 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/to_le_bytes/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/to_le_bytes/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/to_le_bytes/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/to_le_bytes/target/witness.gz index f7824210634..dcfa97a7a02 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/to_le_bytes/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/to_le_bytes/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/trait_default_implementation/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/trait_default_implementation/target/acir.gz new file mode 100644 index 00000000000..4b691b72625 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/trait_default_implementation/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/trait_default_implementation/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/trait_default_implementation/target/witness.gz new file mode 100644 index 00000000000..c3b8e758662 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/trait_default_implementation/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/trait_override_implementation/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/trait_override_implementation/target/acir.gz new file mode 100644 index 00000000000..4b691b72625 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/trait_override_implementation/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/trait_override_implementation/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/trait_override_implementation/target/witness.gz new file mode 100644 index 00000000000..c3b8e758662 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/trait_override_implementation/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/trait_self/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/trait_self/target/acir.gz new file mode 100644 index 00000000000..3628a8d6b1e Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/trait_self/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/trait_self/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/trait_self/target/witness.gz new file mode 100644 index 00000000000..4e90289d5e1 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/trait_self/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/trait_where_clause/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/trait_where_clause/target/acir.gz new file mode 100644 index 00000000000..3628a8d6b1e Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/trait_where_clause/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/trait_where_clause/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/trait_where_clause/target/witness.gz new file mode 100644 index 00000000000..4e90289d5e1 Binary files /dev/null and b/tooling/nargo_cli/tests/acir_artifacts/trait_where_clause/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/tuples/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/tuples/target/acir.gz index b680ed268d1..2dd6b2d5e33 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/tuples/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/tuples/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/tuples/target/witness.gz b/tooling/nargo_cli/tests/acir_artifacts/tuples/target/witness.gz index 10cffba7141..85e47d0262e 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/tuples/target/witness.gz and b/tooling/nargo_cli/tests/acir_artifacts/tuples/target/witness.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/type_aliases/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/type_aliases/target/acir.gz index d3b2c7e67ad..14af3d072e9 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/type_aliases/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/type_aliases/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/acir_artifacts/xor/target/acir.gz b/tooling/nargo_cli/tests/acir_artifacts/xor/target/acir.gz index 5298ffbcdef..972b688bf9d 100644 Binary files a/tooling/nargo_cli/tests/acir_artifacts/xor/target/acir.gz and b/tooling/nargo_cli/tests/acir_artifacts/xor/target/acir.gz differ diff --git a/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/Nargo.toml b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/Nargo.toml new file mode 100644 index 00000000000..7c2c50884fe --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "dup_trait_implementation_4" +type = "bin" +authors = [""] +compiler_version = "0.11.1" + +[dependencies] \ No newline at end of file diff --git a/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/Prover.toml b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/src/main.nr b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/src/main.nr new file mode 100644 index 00000000000..b9f712ceff0 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/src/main.nr @@ -0,0 +1,6 @@ +mod module1; +mod module2; +mod module3; + +fn main() { +} diff --git a/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/src/module1.nr b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/src/module1.nr new file mode 100644 index 00000000000..4d41ff2909a --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/src/module1.nr @@ -0,0 +1,2 @@ +trait MyTrait { +} diff --git a/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/src/module2.nr b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/src/module2.nr new file mode 100644 index 00000000000..3cadb6d78cb --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/src/module2.nr @@ -0,0 +1,2 @@ +struct MyStruct { +} diff --git a/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/src/module3.nr b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/src/module3.nr new file mode 100644 index 00000000000..a7612345cf1 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_4/src/module3.nr @@ -0,0 +1,7 @@ +use crate::module1::MyTrait; +use crate::module2::MyStruct; + +// those are not the same 'Path', but they refer to the same trait & impl +// so a Duplicate error should be thrown +impl MyTrait for MyStruct {} +impl crate::module1::MyTrait for crate::module2::MyStruct { } diff --git a/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/Nargo.toml b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/Nargo.toml new file mode 100644 index 00000000000..b5bacc433f3 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "dup_trait_implementation_5" +type = "bin" +authors = [""] +compiler_version = "0.11.1" + +[dependencies] \ No newline at end of file diff --git a/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/Prover.toml b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/main.nr b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/main.nr new file mode 100644 index 00000000000..78e3867e4a1 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/main.nr @@ -0,0 +1,7 @@ +mod module1; +mod module2; +mod module3; +mod module4; + +fn main() { +} diff --git a/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/module1.nr b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/module1.nr new file mode 100644 index 00000000000..4d41ff2909a --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/module1.nr @@ -0,0 +1,2 @@ +trait MyTrait { +} diff --git a/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/module2.nr b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/module2.nr new file mode 100644 index 00000000000..3cadb6d78cb --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/module2.nr @@ -0,0 +1,2 @@ +struct MyStruct { +} diff --git a/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/module3.nr b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/module3.nr new file mode 100644 index 00000000000..ac886d441cd --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/module3.nr @@ -0,0 +1,4 @@ +use crate::module1::MyTrait; +use crate::module2::MyStruct; + +impl MyTrait for MyStruct {} diff --git a/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/module4.nr b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/module4.nr new file mode 100644 index 00000000000..dfa660f80b1 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/dup_trait_implementation_5/src/module4.nr @@ -0,0 +1,3 @@ +// another module in the crate implements the same trait + struct +// a Duplicate error should be thrown +impl crate::module1::MyTrait for crate::module2::MyStruct { } diff --git a/tooling/nargo_cli/tests/compile_failure/dyn_index_fail_nested_array/Nargo.toml b/tooling/nargo_cli/tests/compile_failure/dyn_index_fail_nested_array/Nargo.toml new file mode 100644 index 00000000000..52a547e8d7b --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/dyn_index_fail_nested_array/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "dyn_index_fail_nested_array" +type = "bin" +authors = [""] +compiler_version = "0.11.1" + +[dependencies] \ No newline at end of file diff --git a/tooling/nargo_cli/tests/compile_failure/dyn_index_fail_nested_array/Prover.toml b/tooling/nargo_cli/tests/compile_failure/dyn_index_fail_nested_array/Prover.toml new file mode 100644 index 00000000000..00ffa6e4620 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/dyn_index_fail_nested_array/Prover.toml @@ -0,0 +1,13 @@ +y = "2" + +[[x]] +a = "1" +b = "2" + +[[x]] +a = "3" +b = "4" + +[[x]] +a = "5" +b = "6" diff --git a/tooling/nargo_cli/tests/compile_failure/dyn_index_fail_nested_array/src/main.nr b/tooling/nargo_cli/tests/compile_failure/dyn_index_fail_nested_array/src/main.nr new file mode 100644 index 00000000000..e26625457d9 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/dyn_index_fail_nested_array/src/main.nr @@ -0,0 +1,8 @@ +struct Foo { + a: Field, + b: Field, +} + +fn main(mut x : [Foo; 3], y : pub Field) { + assert(x[y + 2].a == 5); +} \ No newline at end of file diff --git a/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/Nargo.toml b/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/Nargo.toml new file mode 100644 index 00000000000..47df960cc33 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "orphaned_trait_impl" +type = "bin" +authors = [""] +compiler_version = "0.12.0" + +[dependencies] +crate1 = { path = "crate1" } +crate2 = { path = "crate2" } diff --git a/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/Prover.toml b/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/Prover.toml new file mode 100644 index 00000000000..2c1854573a4 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/Prover.toml @@ -0,0 +1,2 @@ +x = 1 +y = 2 diff --git a/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/crate1/Nargo.toml b/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/crate1/Nargo.toml new file mode 100644 index 00000000000..b28e0e840c4 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/crate1/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "crate1" +type = "lib" +authors = [""] +compiler_version = "0.12.0" + +[dependencies] \ No newline at end of file diff --git a/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/crate1/src/lib.nr b/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/crate1/src/lib.nr new file mode 100644 index 00000000000..4d41ff2909a --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/crate1/src/lib.nr @@ -0,0 +1,2 @@ +trait MyTrait { +} diff --git a/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/crate2/Nargo.toml b/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/crate2/Nargo.toml new file mode 100644 index 00000000000..a90a5dcceea --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/crate2/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "crate2" +type = "lib" +authors = [""] +compiler_version = "0.12.0" + +[dependencies] \ No newline at end of file diff --git a/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/crate2/src/lib.nr b/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/crate2/src/lib.nr new file mode 100644 index 00000000000..3cadb6d78cb --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/crate2/src/lib.nr @@ -0,0 +1,2 @@ +struct MyStruct { +} diff --git a/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/src/main.nr b/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/src/main.nr new file mode 100644 index 00000000000..d245bd68ea1 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/orphaned_trait_impl/src/main.nr @@ -0,0 +1,6 @@ +impl dep::crate1::MyTrait for dep::crate2::MyStruct { +} + +fn main(x : Field, y : pub Field) { + assert(x != y); +} diff --git a/tooling/nargo_cli/tests/execution_success/conditional_3_regression/src/main.nr b/tooling/nargo_cli/tests/execution_success/conditional_3_regression/src/main.nr deleted file mode 100644 index cc3e2b2c5ba..00000000000 --- a/tooling/nargo_cli/tests/execution_success/conditional_3_regression/src/main.nr +++ /dev/null @@ -1,155 +0,0 @@ -use dep::std; - - - -fn call_intrinsic(x: [u8; 5], result: [u8; 32]) { - let mut digest = std::hash::sha256(x); - digest[0] = 5 as u8; - digest = std::hash::sha256(x); - assert(digest == result); -} - -fn test4() -> [u32; 4] { - let b: [u32; 4] = [1,2,3,4]; - b -} - -fn main(a: u32, mut c: [u32; 4], x: [u8; 5], result: pub [u8; 32]){ - // Regression test for issue #547 - // Warning: it must be kept at the start of main - let arr: [u8; 2] = [1, 2]; - if arr[0] != arr[1] { - for i in 0..1 { - assert(i != 2); - } - } - - //Issue reported in #421 - if a == c[0] { - assert(c[0] == 0); - } else { - if a == c[1] { - assert(c[1] == 0); - } else { - if a == c[2] { - assert(c[2] == 0); - } - } - } - - //Regression for to_le_bits() constant evaluation - // binary array representation of u8 1 - let as_bits_hardcode_1 = [1, 0]; - let mut c1 = 0; - for i in 0..2 { - let mut as_bits = (arr[i] as Field).to_le_bits(2); - c1 = c1 + as_bits[0] as Field; - - if i == 0 { - assert(arr[i] == 1);// 1 - for k in 0..2 { - assert(as_bits_hardcode_1[k] == as_bits[k]); - } - } - if i == 1 { - assert(arr[i] == 2);//2 - for k in 0..2 { - assert(as_bits_hardcode_1[k] != as_bits[k]); - } - } - } - assert(c1 == 1); - - //Regression for Issue #579 - let result1_true = test(true); - assert(result1_true.array_param[0] == 1); - let result1_false = test(false); - assert(result1_false.array_param[0] == 0); - - //regression for short-circuit2 - if 35 == a { - assert(false); - } - bar(a as Field); - - if a == 3 { - c = test4(); - } - assert(c[1] != 2); - call_intrinsic(x, result); - - - // Regression for issue #661: - let mut c_661 :[u32;1]=[0]; - if a > 5 { - c_661 = issue_661_foo(issue_661_bar(c), a); - } else { - c_661 = issue_661_foo(issue_661_bar(c), a + 2); - } - assert(c_661[0] < 20000); - - // Regression for predicate simplification - safe_inverse(0); -} - -fn test5(a : u32) { - if a > 1 { - let q = a / 2; - assert(q == 2); - } -} - - - -fn foo() { - let mut x = 1; - x /= 0; -} - -fn bar(x:Field) { - if x == 15 { - foo(); - } -} - - -struct MyStruct579 { - array_param: [u32; 2] -} - -impl MyStruct579 { - fn new(array_param: [u32; 2]) -> MyStruct579 { - MyStruct579 { - array_param: array_param - } - } -} - -fn test(flag: bool) -> MyStruct579 { - let mut my_struct = MyStruct579::new([0; 2]); - - if flag == true { - my_struct= MyStruct579::new([1; 2]); - } - my_struct -} - -fn issue_661_foo(array: [u32;4], b:u32) ->[u32;1] { - [array[0]+b] -} - -fn issue_661_bar(a : [u32;4]) ->[u32;4] { - let mut b:[u32;4] = [0;4]; - b[0]=a[0]+1; - b -} - -fn safe_inverse(n: Field) -> Field -{ - if n == 0 { - 0 - } - else { - 1 / n - } -} diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_421/Nargo.toml b/tooling/nargo_cli/tests/execution_success/conditional_regression_421/Nargo.toml new file mode 100644 index 00000000000..9fddef0e86f --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/conditional_regression_421/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "conditional_regression_421" +type = "bin" +authors = [""] +compiler_version = "0.1" + +[dependencies] diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_421/Prover.toml b/tooling/nargo_cli/tests/execution_success/conditional_regression_421/Prover.toml new file mode 100644 index 00000000000..73fa4a5e31a --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/conditional_regression_421/Prover.toml @@ -0,0 +1,2 @@ +c=[2, 4, 3, 0, ] +a=0 diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_421/src/main.nr b/tooling/nargo_cli/tests/execution_success/conditional_regression_421/src/main.nr new file mode 100644 index 00000000000..b31cf761be8 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/conditional_regression_421/src/main.nr @@ -0,0 +1,14 @@ +fn main(a: u32, mut c: [u32; 4]){ + //Issue reported in #421 + if a == c[0] { + assert(c[0] == 0); + } else { + if a == c[1] { + assert(c[1] == 0); + } else { + if a == c[2] { + assert(c[2] == 0); + } + } + } +} diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_547/Nargo.toml b/tooling/nargo_cli/tests/execution_success/conditional_regression_547/Nargo.toml new file mode 100644 index 00000000000..2729aca911c --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/conditional_regression_547/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "conditional_regression_547" +type = "bin" +authors = [""] +compiler_version = "0.1" + +[dependencies] diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_547/Prover.toml b/tooling/nargo_cli/tests/execution_success/conditional_regression_547/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_547/src/main.nr b/tooling/nargo_cli/tests/execution_success/conditional_regression_547/src/main.nr new file mode 100644 index 00000000000..e5df4cbf8da --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/conditional_regression_547/src/main.nr @@ -0,0 +1,23 @@ +fn main() -> pub Field { + // Regression test for issue #547 + // Warning: it must be kept at the start of main + let arr: [u8; 2] = [1, 2]; + if arr[0] != arr[1] { + for i in 0..1 { + assert(i != 2); + } + } + + // Regression for predicate simplification + safe_inverse(0) +} + +fn safe_inverse(n: Field) -> Field +{ + if n == 0 { + 0 + } + else { + 1 / n + } +} diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_579/Nargo.toml b/tooling/nargo_cli/tests/execution_success/conditional_regression_579/Nargo.toml new file mode 100644 index 00000000000..fab92005fba --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/conditional_regression_579/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "conditional_regression_579" +type = "bin" +authors = [""] +compiler_version = "0.1" + +[dependencies] diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_579/Prover.toml b/tooling/nargo_cli/tests/execution_success/conditional_regression_579/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_579/src/main.nr b/tooling/nargo_cli/tests/execution_success/conditional_regression_579/src/main.nr new file mode 100644 index 00000000000..ae1d11eef77 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/conditional_regression_579/src/main.nr @@ -0,0 +1,28 @@ +fn main(){ + //Regression for Issue #579 + let result1_true = test(true); + assert(result1_true.array_param[0] == 1); + let result1_false = test(false); + assert(result1_false.array_param[0] == 0); +} + +struct MyStruct579 { + array_param: [u32; 2] +} + +impl MyStruct579 { + fn new(array_param: [u32; 2]) -> MyStruct579 { + MyStruct579 { + array_param: array_param + } + } +} + +fn test(flag: bool) -> MyStruct579 { + let mut my_struct = MyStruct579::new([0; 2]); + + if flag == true { + my_struct= MyStruct579::new([1; 2]); + } + my_struct +} diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_661/Nargo.toml b/tooling/nargo_cli/tests/execution_success/conditional_regression_661/Nargo.toml new file mode 100644 index 00000000000..902408f5d56 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/conditional_regression_661/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "conditional_regression_661" +type = "bin" +authors = [""] +compiler_version = "0.1" + +[dependencies] diff --git a/tooling/nargo_cli/tests/execution_success/conditional_3_regression/Prover.toml b/tooling/nargo_cli/tests/execution_success/conditional_regression_661/Prover.toml similarity index 100% rename from tooling/nargo_cli/tests/execution_success/conditional_3_regression/Prover.toml rename to tooling/nargo_cli/tests/execution_success/conditional_regression_661/Prover.toml diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_661/src/main.nr b/tooling/nargo_cli/tests/execution_success/conditional_regression_661/src/main.nr new file mode 100644 index 00000000000..a4765ecfc00 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/conditional_regression_661/src/main.nr @@ -0,0 +1,29 @@ +fn main(a: u32, mut c: [u32; 4]){ + // Regression for issue #661: + let mut c_661 :[u32;1]=[0]; + if a > 5 { + c_661 = issue_661_foo(issue_661_bar(c), a); + } else { + c_661 = issue_661_foo(issue_661_bar(c), a + 2); + } + assert(c_661[0] < 20000); +} + +fn test5(a : u32) { + if a > 1 { + let q = a / 2; + assert(q == 2); + } +} + + +fn issue_661_foo(array: [u32;4], b:u32) ->[u32;1] { + [array[0]+b] +} + +fn issue_661_bar(a : [u32;4]) ->[u32;4] { + let mut b:[u32;4] = [0;4]; + b[0]=a[0]+1; + b +} + diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_short_circuit/Nargo.toml b/tooling/nargo_cli/tests/execution_success/conditional_regression_short_circuit/Nargo.toml new file mode 100644 index 00000000000..24c16982cc4 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/conditional_regression_short_circuit/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "conditional_regression_short_circuit" +type = "bin" +authors = [""] +compiler_version = "0.1" + +[dependencies] diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_short_circuit/Prover.toml b/tooling/nargo_cli/tests/execution_success/conditional_regression_short_circuit/Prover.toml new file mode 100644 index 00000000000..baad8be126a --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/conditional_regression_short_circuit/Prover.toml @@ -0,0 +1,38 @@ +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, +] diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_short_circuit/src/main.nr b/tooling/nargo_cli/tests/execution_success/conditional_regression_short_circuit/src/main.nr new file mode 100644 index 00000000000..4fb4c37a1ab --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/conditional_regression_short_circuit/src/main.nr @@ -0,0 +1,41 @@ +use dep::std; + +fn main(a: u32, mut c: [u32; 4], x: [u8; 5], result: pub [u8; 32]){ + //regression for short-circuit2 + if 35 == a { + assert(false); + } + bar(a as Field); + + if a == 3 { + c = test4(); + } + assert(c[1] != 2); + call_intrinsic(x, result); +} + + + +fn foo() { + let mut x = 1; + x /= 0; +} + +fn bar(x:Field) { + if x == 15 { + foo(); + } +} + + +fn call_intrinsic(x: [u8; 5], result: [u8; 32]) { + let mut digest = std::hash::sha256(x); + digest[0] = 5 as u8; + digest = std::hash::sha256(x); + assert(digest == result); +} + +fn test4() -> [u32; 4] { + let b: [u32; 4] = [1,2,3,4]; + b +} \ No newline at end of file diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_to_bits/Nargo.toml b/tooling/nargo_cli/tests/execution_success/conditional_regression_to_bits/Nargo.toml new file mode 100644 index 00000000000..e886f083a6c --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/conditional_regression_to_bits/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "conditional_regression_to_bits" +type = "bin" +authors = [""] +compiler_version = "0.1" + +[dependencies] diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_to_bits/Prover.toml b/tooling/nargo_cli/tests/execution_success/conditional_regression_to_bits/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tooling/nargo_cli/tests/execution_success/conditional_regression_to_bits/src/main.nr b/tooling/nargo_cli/tests/execution_success/conditional_regression_to_bits/src/main.nr new file mode 100644 index 00000000000..8c612ca0eb2 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/conditional_regression_to_bits/src/main.nr @@ -0,0 +1,32 @@ +use dep::std; + + +fn main() { + + //Regression for to_le_bits() constant evaluation + // binary array representation of u8 1 + let arr: [u8; 2] = [1, 2]; + let as_bits_hardcode_1 = [1, 0]; + let mut c1 = 0; + for i in 0..2 { + let mut as_bits = (arr[i] as Field).to_le_bits(2); + c1 = c1 + as_bits[0] as Field; + + if i == 0 { + assert(arr[i] == 1);// 1 + for k in 0..2 { + assert(as_bits_hardcode_1[k] == as_bits[k]); + } + } + if i == 1 { + assert(arr[i] == 2);//2 + for k in 0..2 { + assert(as_bits_hardcode_1[k] != as_bits[k]); + } + } + } + assert(c1 == 1); + + +} + diff --git a/tooling/nargo_cli/tests/execution_success/nested_array_dynamic/Nargo.toml b/tooling/nargo_cli/tests/execution_success/nested_array_dynamic/Nargo.toml new file mode 100644 index 00000000000..5be06d0f8af --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/nested_array_dynamic/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "nested_array_dynamic" +type = "bin" +authors = [""] +compiler_version = "0.11.1" + +[dependencies] \ No newline at end of file diff --git a/tooling/nargo_cli/tests/execution_success/nested_array_dynamic/Prover.toml b/tooling/nargo_cli/tests/execution_success/nested_array_dynamic/Prover.toml new file mode 100644 index 00000000000..6c7e77b581d --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/nested_array_dynamic/Prover.toml @@ -0,0 +1,29 @@ +y = "3" + +[[x]] +a = "1" +b = ["2", "3", "20"] + +[x.bar] +inner = ["100", "101", "102"] + +[[x]] +a = "4" # idx = 3, flattened start idx = 7 +b = ["5", "6", "21"] # idx = 4, flattened start idx = 8 + +[x.bar] +inner = ["103", "104", "105"] # idx = 5, flattened start idx = 11 + +[[x]] +a = "7" +b = ["8", "9", "22"] + +[x.bar] +inner = ["106", "107", "108"] + +[[x]] +a = "10" # idx = 9, flattened start idx = 21 +b = ["11", "12", "23"] # idx = 10, flattened start idx = 22 + +[x.bar] +inner = ["109", "110", "111"] # idx = 11, flattened start idx = 25 diff --git a/tooling/nargo_cli/tests/execution_success/nested_array_dynamic/src/main.nr b/tooling/nargo_cli/tests/execution_success/nested_array_dynamic/src/main.nr new file mode 100644 index 00000000000..076c2b68f11 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/nested_array_dynamic/src/main.nr @@ -0,0 +1,42 @@ +struct Bar { + inner: [Field; 3], +} + +struct Foo { + a: Field, + b: [Field; 3], + bar: Bar, +} + +fn main(mut x : [Foo; 4], y : pub Field) { + assert(x[y - 3].a == 1); + assert(x[y - 3].b == [2, 3, 20]); + assert(x[y - 2].a == 4); + assert(x[y - 2].b == [5, 6, 21]); + assert(x[y - 1].a == 7); + assert(x[y - 1].b == [8, 9, 22]); + assert(x[y].a == 10); + assert(x[y].b == [11, 12, 23]); + assert(x[y].bar.inner == [109, 110, 111]); + + // Check dynamic array set + if y != 2 { + x[y].a = 50; + } else { + x[y].a = 100; + } + assert(x[y].a == 50); + + if y == 2 { + x[y - 1].b = [50, 51, 52]; + } else { + x[y - 1].b = [100, 101, 102]; + } + assert(x[2].b == [100, 101, 102]); + + assert(x[y - 3].bar.inner == [100, 101, 102]); + assert(x[y - 2].bar.inner == [103, 104, 105]); + assert(x[y - 1].bar.inner == [106, 107, 108]); + assert(x[y].bar.inner == [109, 110, 111]); +} + diff --git a/tooling/nargo_cli/tests/execution_success/conditional_3_regression/Nargo.toml b/tooling/nargo_cli/tests/execution_success/regression_2854/Nargo.toml similarity index 69% rename from tooling/nargo_cli/tests/execution_success/conditional_3_regression/Nargo.toml rename to tooling/nargo_cli/tests/execution_success/regression_2854/Nargo.toml index 868208a4fe2..33bd007d898 100644 --- a/tooling/nargo_cli/tests/execution_success/conditional_3_regression/Nargo.toml +++ b/tooling/nargo_cli/tests/execution_success/regression_2854/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "conditional_3_regression" +name = "regression_2854" type = "bin" authors = [""] compiler_version = "0.1" diff --git a/tooling/nargo_cli/tests/execution_success/regression_2854/Prover.toml b/tooling/nargo_cli/tests/execution_success/regression_2854/Prover.toml new file mode 100644 index 00000000000..07890234a19 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/regression_2854/Prover.toml @@ -0,0 +1 @@ +x = "3" diff --git a/tooling/nargo_cli/tests/execution_success/regression_2854/src/main.nr b/tooling/nargo_cli/tests/execution_success/regression_2854/src/main.nr new file mode 100644 index 00000000000..10ada5faeec --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/regression_2854/src/main.nr @@ -0,0 +1,3 @@ +fn main(x: Field) -> pub i127 { + x as i127 +} \ No newline at end of file diff --git a/tooling/nargo_cli/tests/execution_success/simple_2d_array/Nargo.toml b/tooling/nargo_cli/tests/execution_success/simple_2d_array/Nargo.toml new file mode 100644 index 00000000000..033c9cf1c25 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/simple_2d_array/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "simple_2d_array" +type = "bin" +authors = [""] +compiler_version = "0.1" + +[dependencies] diff --git a/tooling/nargo_cli/tests/execution_success/simple_2d_array/Prover.toml b/tooling/nargo_cli/tests/execution_success/simple_2d_array/Prover.toml new file mode 100644 index 00000000000..a4616f907bb --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/simple_2d_array/Prover.toml @@ -0,0 +1,3 @@ +x = "1" +y = "2" +array_input = [[1, 2], [3, 3]] diff --git a/tooling/nargo_cli/tests/execution_success/simple_2d_array/src/main.nr b/tooling/nargo_cli/tests/execution_success/simple_2d_array/src/main.nr new file mode 100644 index 00000000000..c098a679a3b --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/simple_2d_array/src/main.nr @@ -0,0 +1,8 @@ +// Test accessing a multidimensional array +fn main(x: Field, y: Field, array_input: [[Field; 2]; 2]) { + assert(array_input[0][0] == x); + assert(array_input[0][1] == y); + + let arr: [[Field; 2]; 1] = [[3,3]]; + assert_eq(arr[0], array_input[1]); +} diff --git a/tooling/nargo_cli/tests/execution_success/slices/src/main.nr b/tooling/nargo_cli/tests/execution_success/slices/src/main.nr index 8fbe14bfea3..1e5a7a605aa 100644 --- a/tooling/nargo_cli/tests/execution_success/slices/src/main.nr +++ b/tooling/nargo_cli/tests/execution_success/slices/src/main.nr @@ -92,16 +92,16 @@ fn regression_merge_slices(x: Field, y: Field) { fn merge_slices_if(x: Field, y: Field) { let slice = merge_slices_return(x, y); - assert(slice[2] == 10); assert(slice.len() == 3); + assert(slice[2] == 10); let slice = merge_slices_mutate(x, y); - assert(slice[3] == 5); assert(slice.len() == 4); + assert(slice[3] == 5); let slice = merge_slices_mutate_in_loop(x, y); - assert(slice[6] == 4); assert(slice.len() == 7); + assert(slice[6] == 4); let slice = merge_slices_mutate_two_ifs(x, y); assert(slice.len() == 6); diff --git a/tooling/nargo_cli/tests/execution_success/strings/src/main.nr b/tooling/nargo_cli/tests/execution_success/strings/src/main.nr index 9f122c3a137..1d401260179 100644 --- a/tooling/nargo_cli/tests/execution_success/strings/src/main.nr +++ b/tooling/nargo_cli/tests/execution_success/strings/src/main.nr @@ -19,7 +19,7 @@ fn main(message : pub str<11>, y : Field, hex_as_string : str<4>, hex_as_field : assert(y == 5); // Change to y != 5 to see how the later print statements are not called std::println(array); - bad_message = "helld world"; + bad_message = "hell\0\"world"; std::println(bad_message); assert(message != bad_message); diff --git a/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/Nargo.toml b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/Nargo.toml new file mode 100644 index 00000000000..89d83e18f66 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "trait_multi_module_test" +type = "bin" +authors = [""] +compiler_version = "0.11.1" + +[dependencies] \ No newline at end of file diff --git a/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/Prover.toml b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/main.nr b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/main.nr new file mode 100644 index 00000000000..63b6f08ed52 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/main.nr @@ -0,0 +1,9 @@ +mod module1; +mod module2; +mod module3; +mod module4; +mod module5; +mod module6; + +fn main() { +} diff --git a/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module1.nr b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module1.nr new file mode 100644 index 00000000000..4d41ff2909a --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module1.nr @@ -0,0 +1,2 @@ +trait MyTrait { +} diff --git a/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module2.nr b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module2.nr new file mode 100644 index 00000000000..3cadb6d78cb --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module2.nr @@ -0,0 +1,2 @@ +struct MyStruct { +} diff --git a/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module3.nr b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module3.nr new file mode 100644 index 00000000000..4b2fbc9bfcc --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module3.nr @@ -0,0 +1,5 @@ +use crate::module1::MyTrait; +use crate::module2::MyStruct; + +// ensure we can implement traits that are imported with the `use` syntax +impl MyTrait for MyStruct {} diff --git a/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module4.nr b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module4.nr new file mode 100644 index 00000000000..f9458e83c4a --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module4.nr @@ -0,0 +1,2 @@ +trait MyTrait4 { +} diff --git a/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module5.nr b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module5.nr new file mode 100644 index 00000000000..cd9b7f0bf39 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module5.nr @@ -0,0 +1,2 @@ +struct MyStruct5 { +} diff --git a/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module6.nr b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module6.nr new file mode 100644 index 00000000000..35f5ce3a183 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/trait_multi_module_test/src/module6.nr @@ -0,0 +1,2 @@ +// ensure we can implement traits using the Path syntax +impl crate::module4::MyTrait4 for crate::module5::MyStruct5 { } diff --git a/tooling/nargo_cli/tests/execution_success/trait_override_implementation/src/main.nr b/tooling/nargo_cli/tests/execution_success/trait_override_implementation/src/main.nr index 92e19f97d50..0ca6171ad9e 100644 --- a/tooling/nargo_cli/tests/execution_success/trait_override_implementation/src/main.nr +++ b/tooling/nargo_cli/tests/execution_success/trait_override_implementation/src/main.nr @@ -24,7 +24,30 @@ impl Default for Foo { } } +trait F { + fn f1(self) -> Field; + fn f2(self) -> Field { 2 }; + fn f3(self) -> Field { 3 }; + fn f4(self) -> Field { 4 }; + fn f5(self) -> Field { 5 }; +} + +struct Bar {} + +impl F for Bar { + fn f5(self) -> Field { 50 } + fn f1(self) -> Field { 10 } + fn f3(self) -> Field { 30 } +} + fn main(x: Field) { let first = Foo::method2(x); assert(first == 3 * x); -} + + let bar = Bar{}; + assert(bar.f1() == 10); + assert(bar.f2() == 2); + assert(bar.f3() == 30); + assert(bar.f4() == 4); + assert(bar.f5() == 50); +} \ No newline at end of file diff --git a/tooling/nargo_cli/tests/execution_success/trait_where_clause/Nargo.toml b/tooling/nargo_cli/tests/execution_success/trait_where_clause/Nargo.toml new file mode 100644 index 00000000000..9f17579976b --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/trait_where_clause/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "trait_where_clause" +type = "bin" +authors = [""] +compiler_version = "0.11.1" + +[dependencies] \ No newline at end of file diff --git a/tooling/nargo_cli/tests/execution_success/trait_where_clause/src/main.nr b/tooling/nargo_cli/tests/execution_success/trait_where_clause/src/main.nr new file mode 100644 index 00000000000..aac2362c1d9 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/trait_where_clause/src/main.nr @@ -0,0 +1,41 @@ +// TODO(#2568): Currently we only support trait constraints on free functions. +// There's a bunch of other places where they can pop up: +// - trait methods (trait Foo where T: ... { ) +// - free impl blocks (impl Foo where T...) +// - trait impl blocks (impl Foo for Bar where T...) +// - structs (struct Foo where T: ...) + +// import the trait from another module to ensure the where clauses are ok with that +mod the_trait; +use crate::the_trait::Asd; + +struct Add10 { x: Field, } +struct Add20 { x: Field, } +struct Add30 { x: Field, } +struct AddXY { x: Field, y: Field, } + +impl Asd for Add10 { fn asd(self) -> Field { self.x + 10 } } +impl Asd for Add20 { fn asd(self) -> Field { self.x + 20 } } +impl Asd for Add30 { fn asd(self) -> Field { self.x + 30 } } + +impl Asd for AddXY { + fn asd(self) -> Field { + self.x + self.y + } +} + +fn assert_asd_eq_100(t: T) where T: crate::the_trait::Asd { + assert(t.asd() == 100); +} + +fn main() { + let x = Add10{ x: 90 }; + let z = Add20{ x: 80 }; + let a = Add30{ x: 70 }; + let xy = AddXY{ x: 30, y: 70 }; + + assert_asd_eq_100(x); + assert_asd_eq_100(z); + assert_asd_eq_100(a); + assert_asd_eq_100(xy); +} diff --git a/tooling/nargo_cli/tests/execution_success/trait_where_clause/src/the_trait.nr b/tooling/nargo_cli/tests/execution_success/trait_where_clause/src/the_trait.nr new file mode 100644 index 00000000000..1b8803fddfd --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/trait_where_clause/src/the_trait.nr @@ -0,0 +1,3 @@ +trait Asd { + fn asd(self) -> Field; +} \ No newline at end of file diff --git a/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/Nargo.toml b/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/Nargo.toml new file mode 100644 index 00000000000..a13ed98b632 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "traits_in_crates_1" +type = "bin" +authors = [""] +compiler_version = "0.12.0" + +[dependencies] +crate1 = { path = "crate1" } +crate2 = { path = "crate2" } diff --git a/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/Prover.toml b/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/Prover.toml new file mode 100644 index 00000000000..c2005d59807 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/Prover.toml @@ -0,0 +1,2 @@ +x = 1 +y = 11 diff --git a/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/crate1/Nargo.toml b/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/crate1/Nargo.toml new file mode 100644 index 00000000000..0a94742b76a --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/crate1/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "crate1" +type = "lib" +authors = [""] +compiler_version = "0.12.0" + +[dependencies] +crate2 = { path = "../crate2" } diff --git a/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/crate1/src/lib.nr b/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/crate1/src/lib.nr new file mode 100644 index 00000000000..62dd5a2c111 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/crate1/src/lib.nr @@ -0,0 +1,9 @@ +trait MyTrait { + fn Add10(&mut self); +} + +impl MyTrait for dep::crate2::MyStruct { + fn Add10(&mut self) { + self.Q += 10; + } +} diff --git a/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/crate2/Nargo.toml b/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/crate2/Nargo.toml new file mode 100644 index 00000000000..a90a5dcceea --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/crate2/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "crate2" +type = "lib" +authors = [""] +compiler_version = "0.12.0" + +[dependencies] \ No newline at end of file diff --git a/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/crate2/src/lib.nr b/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/crate2/src/lib.nr new file mode 100644 index 00000000000..c59bf0387c1 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/crate2/src/lib.nr @@ -0,0 +1,3 @@ +struct MyStruct { + Q: Field, +} diff --git a/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/src/main.nr b/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/src/main.nr new file mode 100644 index 00000000000..cb6416f7732 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/traits_in_crates_1/src/main.nr @@ -0,0 +1,5 @@ +fn main(x : Field, y : pub Field) { + let mut V = dep::crate2::MyStruct { Q: x }; + V.Add10(); + assert(V.Q == y); +} diff --git a/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/Nargo.toml b/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/Nargo.toml new file mode 100644 index 00000000000..8b5bc49e0bb --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "traits_in_crates_2" +type = "bin" +authors = [""] +compiler_version = "0.12.0" + +[dependencies] +crate1 = { path = "crate1" } +crate2 = { path = "crate2" } diff --git a/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/Prover.toml b/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/Prover.toml new file mode 100644 index 00000000000..c2005d59807 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/Prover.toml @@ -0,0 +1,2 @@ +x = 1 +y = 11 diff --git a/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/crate1/Nargo.toml b/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/crate1/Nargo.toml new file mode 100644 index 00000000000..b28e0e840c4 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/crate1/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "crate1" +type = "lib" +authors = [""] +compiler_version = "0.12.0" + +[dependencies] \ No newline at end of file diff --git a/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/crate1/src/lib.nr b/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/crate1/src/lib.nr new file mode 100644 index 00000000000..59a28a50c79 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/crate1/src/lib.nr @@ -0,0 +1,3 @@ +trait MyTrait { + fn Add10(&mut self); +} diff --git a/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/crate2/Nargo.toml b/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/crate2/Nargo.toml new file mode 100644 index 00000000000..51c201372ee --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/crate2/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "crate2" +type = "lib" +authors = [""] +compiler_version = "0.12.0" + +[dependencies] +crate1 = { path = "../crate1" } diff --git a/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/crate2/src/lib.nr b/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/crate2/src/lib.nr new file mode 100644 index 00000000000..38870489131 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/crate2/src/lib.nr @@ -0,0 +1,9 @@ +struct MyStruct { + Q: Field, +} + +impl dep::crate1::MyTrait for MyStruct { + fn Add10(&mut self) { + self.Q += 10; + } +} diff --git a/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/src/main.nr b/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/src/main.nr new file mode 100644 index 00000000000..cb6416f7732 --- /dev/null +++ b/tooling/nargo_cli/tests/execution_success/traits_in_crates_2/src/main.nr @@ -0,0 +1,5 @@ +fn main(x : Field, y : pub Field) { + let mut V = dep::crate2::MyStruct { Q: x }; + V.Add10(); + assert(V.Q == y); +} diff --git a/tooling/noir_js/.gitignore b/tooling/noir_js/.gitignore index 9d26861cc3c..5b57ba1708d 100644 --- a/tooling/noir_js/.gitignore +++ b/tooling/noir_js/.gitignore @@ -1 +1,3 @@ -!test/noir_compiled_examples/*/target \ No newline at end of file +crs + +!test/noir_compiled_examples/*/target diff --git a/tooling/noir_js/.mocharc.cjs.json b/tooling/noir_js/.mocharc.cjs.json new file mode 100644 index 00000000000..c1c37373f18 --- /dev/null +++ b/tooling/noir_js/.mocharc.cjs.json @@ -0,0 +1,6 @@ +{ + "extensions": ["cjs"], + "spec": [ + "test/node/**/*.test.cjs" + ] +} \ No newline at end of file diff --git a/tooling/noir_js/.mocharc.json b/tooling/noir_js/.mocharc.json index 9e1a6acc542..c2e70b73d0f 100644 --- a/tooling/noir_js/.mocharc.json +++ b/tooling/noir_js/.mocharc.json @@ -1,7 +1,7 @@ { "require": "ts-node/register", "loader": "ts-node/esm", - "extensions": ["ts"], + "extensions": ["ts", "cjs"], "spec": [ "test/node/**/*.test.ts*" ] diff --git a/tooling/noir_js/package.json b/tooling/noir_js/package.json index 5fd66672f0f..de9e2831f01 100644 --- a/tooling/noir_js/package.json +++ b/tooling/noir_js/package.json @@ -3,29 +3,39 @@ "collaborators": [ "The Noir Team " ], - "version": "0.12.0", + "version": "0.15.0", "packageManager": "yarn@3.5.1", "license": "(MIT OR Apache-2.0)", "type": "module", "dependencies": { - "@noir-lang/acvm_js": "0.27.0", + "@noir-lang/acvm_js": "workspace:*", "@noir-lang/noirc_abi": "workspace:*", + "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, "files": [ "lib", "package.json" ], - "main": "lib/index.js", + "source": "src/index.ts", + "main": "lib/index.cjs", + "module": "lib/index.mjs", + "exports": { + "require": "./lib/index.cjs", + "types": "./lib/index.d.ts", + "default": "./lib/index.mjs" + }, "types": "lib/index.d.ts", "scripts": { - "dev": "tsc --watch", - "build": "tsc", - "test": "yarn test:node", - "test:node": "mocha --timeout 25000", + "dev": "tsc-multi --watch", + "build": "tsc-multi", + "test": "yarn test:node:esm && yarn test:node:cjs", + "test:node:esm": "mocha --timeout 25000 --exit --config ./.mocharc.json", + "test:node:cjs": "mocha --timeout 25000 --exit --config ./.mocharc.cjs.json", "prettier": "prettier 'src/**/*.ts'", "prettier:fix": "prettier --write 'src/**/*.ts' 'test/**/*.ts'", - "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" + "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0", + "clean": "rm -rf ./lib" }, "devDependencies": { "@aztec/bb.js": "0.7.2", @@ -39,6 +49,7 @@ "mocha": "^10.2.0", "prettier": "3.0.3", "ts-node": "^10.9.1", + "tsc-multi": "^1.1.0", "typescript": "^5.2.2" } } diff --git a/tooling/noir_js/src/base64_decode.ts b/tooling/noir_js/src/base64_decode.ts index d53aed187c7..fddbc2640cb 100644 --- a/tooling/noir_js/src/base64_decode.ts +++ b/tooling/noir_js/src/base64_decode.ts @@ -1,13 +1,13 @@ // Since this is a simple function, we can use feature detection to // see if we are in the nodeJs environment or the browser environment. export function base64Decode(input: string): Uint8Array { - if (typeof Buffer !== 'undefined') { + if (typeof Buffer !== "undefined") { // Node.js environment - return Buffer.from(input, 'base64'); - } else if (typeof atob === 'function') { + return Buffer.from(input, "base64"); + } else if (typeof atob === "function") { // Browser environment return Uint8Array.from(atob(input), (c) => c.charCodeAt(0)); } else { - throw new Error('No implementation found for base64 decoding.'); + throw new Error("No implementation found for base64 decoding."); } } diff --git a/tooling/noir_js/src/index.ts b/tooling/noir_js/src/index.ts index 4125affae6d..2d3801bf2c1 100644 --- a/tooling/noir_js/src/index.ts +++ b/tooling/noir_js/src/index.ts @@ -1,8 +1,9 @@ -import * as acvm from '@noir-lang/acvm_js'; -import * as abi from '@noir-lang/noirc_abi'; +import * as acvm from "@noir-lang/acvm_js"; +import * as abi from "@noir-lang/noirc_abi"; export { acvm, abi }; -import { generateWitness } from './witness_generation.js'; -import { acirToUint8Array, witnessMapToUint8Array } from './serialize.js'; -export { acirToUint8Array, witnessMapToUint8Array, generateWitness }; +export { generateWitness } from "./witness_generation.js"; +export { acirToUint8Array, witnessMapToUint8Array } from "./serialize.js"; + +export { Noir } from "./program.js"; diff --git a/tooling/noir_js/src/input_validation.ts b/tooling/noir_js/src/input_validation.ts deleted file mode 100644 index 9a19528a0cf..00000000000 --- a/tooling/noir_js/src/input_validation.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Check if all of the input values are correct according to the ABI -export function validateInputs(inputs, abi) { - for (const param of abi.parameters) { - const inputValue = inputs[param.name]; - if (inputValue === undefined) { - // This is checked by noirc_abi, so we could likely remove this check - return { isValid: false, error: `Input for ${param.name} is missing` }; - } - if (!checkType(inputValue, param.type)) { - return { - isValid: false, - error: `Input for ${param.name} is the wrong type, expected ${type_to_string(param.type)}, got "${inputValue}"`, - }; - } - } - return { isValid: true, error: null }; -} - -// Checks that value is of type "type" -// Where type is taken from the abi -function checkType(value, type) { - switch (type.kind) { - case 'integer': - if (type.sign === 'unsigned') { - return isUnsignedInteger(value, type.width); - } - // Other integer sign checks can be added here - break; - // Other type.kind checks can be added here - } - return false; -} - -function type_to_string(type): string { - switch (type.kind) { - case 'integer': - if (type.sign === 'unsigned') { - return `uint${type.width}`; - } - break; - case 'array': - return `${type_to_string(type.element)}[${type.length}]`; - } - return 'unknown type'; -} - -// Returns true if `value` is an unsigned integer that is less than 2^{width} -function isUnsignedInteger(value: bigint, width: bigint) { - try { - const bigIntValue = BigInt(value); - return bigIntValue >= 0 && bigIntValue <= BigInt(2) ** BigInt(width) - 1n; - } catch (e) { - return false; // Not a valid integer - } -} diff --git a/tooling/noir_js/src/program.ts b/tooling/noir_js/src/program.ts new file mode 100644 index 00000000000..1aea14644c9 --- /dev/null +++ b/tooling/noir_js/src/program.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Backend, CompiledCircuit } from "@noir-lang/types"; +import { generateWitness } from "./witness_generation.js"; + +export class Noir { + constructor( + private circuit: CompiledCircuit, + private backend: Backend, + ) {} + + // Initial inputs to your program + async generateFinalProof(inputs: any): Promise { + const serializedWitness = await generateWitness(this.circuit, inputs); + return this.backend.generateFinalProof(serializedWitness); + } + + async verifyFinalProof(proof: Uint8Array): Promise { + return this.backend.verifyFinalProof(proof); + } +} diff --git a/tooling/noir_js/src/serialize.ts b/tooling/noir_js/src/serialize.ts index df01769c06e..0193f465091 100644 --- a/tooling/noir_js/src/serialize.ts +++ b/tooling/noir_js/src/serialize.ts @@ -1,6 +1,6 @@ -import { WitnessMap, compressWitness } from '@noir-lang/acvm_js'; -import { decompressSync as gunzip } from 'fflate'; -import { base64Decode } from './base64_decode.js'; +import { WitnessMap, compressWitness } from "@noir-lang/acvm_js"; +import { decompressSync as gunzip } from "fflate"; +import { base64Decode } from "./base64_decode.js"; // After solving the witness, to pass it a backend, we need to serialize it to a Uint8Array export function witnessMapToUint8Array(solvedWitness: WitnessMap): Uint8Array { diff --git a/tooling/noir_js/src/witness_generation.ts b/tooling/noir_js/src/witness_generation.ts index d72033b275a..f3b31e0eb77 100644 --- a/tooling/noir_js/src/witness_generation.ts +++ b/tooling/noir_js/src/witness_generation.ts @@ -1,23 +1,28 @@ -import { abiEncode } from '@noir-lang/noirc_abi'; -import { validateInputs } from './input_validation.js'; -import { base64Decode } from './base64_decode.js'; -import { WitnessMap, executeCircuit } from '@noir-lang/acvm_js'; +import { abiEncode } from "@noir-lang/noirc_abi"; +import { base64Decode } from "./base64_decode.js"; +import { executeCircuit } from "@noir-lang/acvm_js"; +import { witnessMapToUint8Array } from "./serialize.js"; +import { CompiledCircuit } from "@noir-lang/types"; // Generates the witnesses needed to feed into the chosen proving system -export async function generateWitness(compiledProgram, inputs): Promise { - // Validate inputs - const { isValid, error } = validateInputs(inputs, compiledProgram.abi); - if (!isValid) { - throw new Error(error?.toString()); - } +export async function generateWitness( + compiledProgram: CompiledCircuit, + inputs: unknown, +): Promise { + // Throws on ABI encoding error const witnessMap = abiEncode(compiledProgram.abi, inputs, null); - // Execute the circuit to generate the rest of the witnesses + // Execute the circuit to generate the rest of the witnesses and serialize + // them into a Uint8Array. try { - const solvedWitness = await executeCircuit(base64Decode(compiledProgram.bytecode), witnessMap, () => { - throw Error('unexpected oracle during execution'); - }); - return solvedWitness; + const solvedWitness = await executeCircuit( + base64Decode(compiledProgram.bytecode), + witnessMap, + () => { + throw Error("unexpected oracle during execution"); + }, + ); + return witnessMapToUint8Array(solvedWitness); } catch (err) { throw new Error(`Circuit execution failed: ${err}`); } diff --git a/tooling/noir_js/test/backend/barretenberg.ts b/tooling/noir_js/test/backend/barretenberg.ts index 6a770cde2f2..557f2b3a74a 100644 --- a/tooling/noir_js/test/backend/barretenberg.ts +++ b/tooling/noir_js/test/backend/barretenberg.ts @@ -1,22 +1,31 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { Barretenberg, Crs, RawBuffer } from '@aztec/bb.js'; -// TODO: This should be re-exported from @aztec/bb-js -import { Ptr } from '@aztec/bb.js/dest/browser/types'; import { acirToUint8Array } from '../../src/index.js'; +import { Backend } from '@noir-lang/types'; -export class Backend { +export class BarretenbergBackend implements Backend { // These type assertions are used so that we don't // have to initialize `api` and `acirComposer` in the constructor. // These are initialized asynchronously in the `init` function, // constructors cannot be asynchronous which is why we do this. api = {} as Barretenberg; - acirComposer = {} as Ptr; + acirComposer = {} as any; acirUncompressedBytecode: Uint8Array; - constructor(acirBytecodeBase64: string) { + private constructor(acirCircuit: { bytecode: string }) { + const acirBytecodeBase64 = acirCircuit.bytecode; this.acirUncompressedBytecode = acirToUint8Array(acirBytecodeBase64); } - async init() { + + static async initialize(acirCircuit: { bytecode: string }): Promise { + const backend = new BarretenbergBackend(acirCircuit); + await backend.init(); + return backend; + } + + private async init(): Promise { const numThreads = 4; const { api, composer } = await this.initBarretenberg(numThreads, this.acirUncompressedBytecode); @@ -25,7 +34,7 @@ export class Backend { this.acirComposer = composer; } - async initBarretenberg(numThreads: number, acirUncompressedBytecode: Uint8Array) { + private async initBarretenberg(numThreads: number, acirUncompressedBytecode: Uint8Array) { const api = await Barretenberg.new(numThreads); const [_exact, _total, subgroupSize] = await api.acirGetCircuitSizes(acirUncompressedBytecode); @@ -42,7 +51,7 @@ export class Backend { // // The settings for this proof are the same as the settings for a "normal" proof // ie one that is not in the recursive setting. - async generateOuterProof(decompressedWitness: Uint8Array) { + async generateFinalProof(decompressedWitness: Uint8Array): Promise { const makeEasyToVerifyInCircuit = false; return this.generateProof(decompressedWitness, makeEasyToVerifyInCircuit); } @@ -58,12 +67,12 @@ export class Backend { // We set `makeEasyToVerifyInCircuit` to true, which will tell the backend to // generate the proof using components that will make the proof // easier to verify in a circuit. - async generateInnerProof(witness: Uint8Array) { + async generateIntermediateProof(witness: Uint8Array): Promise { const makeEasyToVerifyInCircuit = true; return this.generateProof(witness, makeEasyToVerifyInCircuit); } - async generateProof(decompressedWitness: Uint8Array, makeEasyToVerifyInCircuit: boolean) { + async generateProof(decompressedWitness: Uint8Array, makeEasyToVerifyInCircuit: boolean): Promise { const proof = await this.api.acirCreateProof( this.acirComposer, this.acirUncompressedBytecode, @@ -83,7 +92,14 @@ export class Backend { // method. // // The number of public inputs denotes how many public inputs are in the inner proof. - async generateInnerProofArtifacts(proof: Uint8Array, numOfPublicInputs = 0) { + async generateIntermediateProofArtifacts( + proof: Uint8Array, + numOfPublicInputs = 0, + ): Promise<{ + proofAsFields: string[]; + vkAsFields: string[]; + vkHash: string; + }> { const proofAsFields = await this.api.acirSerializeProofIntoFields(this.acirComposer, proof, numOfPublicInputs); // TODO: perhaps we should put this in the init function. Need to benchmark @@ -100,23 +116,23 @@ export class Backend { }; } - async verifyOuterProof(proof: Uint8Array) { + async verifyFinalProof(proof: Uint8Array): Promise { const makeEasyToVerifyInCircuit = false; const verified = await this.verifyProof(proof, makeEasyToVerifyInCircuit); return verified; } - async verifyInnerProof(proof: Uint8Array) { + async verifyIntermediateProof(proof: Uint8Array): Promise { const makeEasyToVerifyInCircuit = true; return this.verifyProof(proof, makeEasyToVerifyInCircuit); } - async verifyProof(proof: Uint8Array, makeEasyToVerifyInCircuit: boolean) { + async verifyProof(proof: Uint8Array, makeEasyToVerifyInCircuit: boolean): Promise { await this.api.acirInitVerificationKey(this.acirComposer); return await this.api.acirVerifyProof(this.acirComposer, proof, makeEasyToVerifyInCircuit); } - async destroy() { + async destroy(): Promise { await this.api.destroy(); } } diff --git a/tooling/noir_js/test/node/cjs.test.cjs b/tooling/noir_js/test/node/cjs.test.cjs new file mode 100644 index 00000000000..b7b30d7dcdb --- /dev/null +++ b/tooling/noir_js/test/node/cjs.test.cjs @@ -0,0 +1,80 @@ +/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-var-requires */ +const chai = require('chai'); +const assert_lt_json = require('../noir_compiled_examples/assert_lt/target/assert_lt.json'); +const noirjs = require('@noir-lang/noir_js'); + +it('generates witnesses successfully', async () => { + const inputs = { + x: '2', + y: '3', + }; + const _solvedWitness = await noirjs.generateWitness(assert_lt_json, inputs); +}); + +it('string input and number input are the same', async () => { + const inputsString = { + x: '2', + y: '3', + }; + const inputsNumber = { + x: 2, + y: 3, + }; + const solvedWitnessString = await noirjs.generateWitness(assert_lt_json, inputsString); + const solvedWitnessNumber = await noirjs.generateWitness(assert_lt_json, inputsNumber); + chai.expect(solvedWitnessString).to.deep.equal(solvedWitnessNumber); +}); + +it('string input and number input are the same', async () => { + const inputsString = { + x: '2', + y: '3', + }; + const inputsNumber = { + x: 2, + y: 3, + }; + + const solvedWitnessString = await noirjs.generateWitness(assert_lt_json, inputsString); + const solvedWitnessNumber = await noirjs.generateWitness(assert_lt_json, inputsNumber); + chai.expect(solvedWitnessString).to.deep.equal(solvedWitnessNumber); +}); + +it('0x prefixed string input for inputs will throw', async () => { + const inputsHexPrefix = { + x: '0x2', + y: '0x3', + }; + + try { + await noirjs.generateWitness(assert_lt_json, inputsHexPrefix); + chai.expect.fail( + 'Expected generatedWitness to throw, due to inputs being prefixed with 0x. Currently not supported', + ); + } catch (error) { + // Successfully errored due to 0x not being supported. Update this test once/if we choose + // to support 0x prefixed inputs. + } +}); + +describe('input validation', () => { + it('x should be a uint64 not a string', async () => { + const inputs = { + x: 'foo', + y: '3', + }; + + try { + await noirjs.generateWitness(assert_lt_json, inputs); + chai.expect.fail('Expected generatedWitness to throw, due to x not being convertible to a uint64'); + } catch (error) { + const knownError = error; + chai + .expect(knownError.message) + .to.equal( + 'Expected witness values to be integers, provided value causes `invalid digit found in string` error', + ); + } + }); +}); diff --git a/tooling/noir_js/test/node/e2e.test.ts b/tooling/noir_js/test/node/e2e.test.ts index ddd91cca834..3d13119e422 100644 --- a/tooling/noir_js/test/node/e2e.test.ts +++ b/tooling/noir_js/test/node/e2e.test.ts @@ -1,47 +1,63 @@ -import { expect } from 'chai'; -import assert_lt_json from '../noir_compiled_examples/assert_lt/target/assert_lt.json' assert { type: 'json' }; -import { generateWitness, witnessMapToUint8Array } from '../../src/index.js'; -import { Backend } from '../backend/barretenberg.js'; +import { expect } from "chai"; +import assert_lt_json from "../noir_compiled_examples/assert_lt/target/assert_lt.json" assert { type: "json" }; +import { generateWitness } from "../../src/index.js"; +import { Noir } from "../../src/program.js"; +import { BarretenbergBackend as Backend } from "../backend/barretenberg.js"; -it('end-to-end proof creation and verification (outer)', async () => { +it("end-to-end proof creation and verification (outer)", async () => { // Noir.Js part const inputs = { - x: '2', - y: '3', + x: "2", + y: "3", }; - const solvedWitness = await generateWitness(assert_lt_json, inputs); + const serializedWitness = await generateWitness(assert_lt_json, inputs); // bb.js part // // Proof creation - const prover = new Backend(assert_lt_json.bytecode); - await prover.init(); - const serializedWitness = witnessMapToUint8Array(solvedWitness); - const proof = await prover.generateOuterProof(serializedWitness); + const prover = await Backend.initialize(assert_lt_json); + const proof = await prover.generateFinalProof(serializedWitness); // Proof verification - const isValid = await prover.verifyOuterProof(proof); + const isValid = await prover.verifyFinalProof(proof); expect(isValid).to.be.true; }); -it('end-to-end proof creation and verification (inner)', async () => { +it("end-to-end proof creation and verification (outer) -- Program API", async () => { // Noir.Js part const inputs = { - x: '2', - y: '3', + x: "2", + y: "3", }; - const solvedWitness = await generateWitness(assert_lt_json, inputs); + + // Initialize backend + const backend = await Backend.initialize(assert_lt_json); + // Initialize program + const program = new Noir(assert_lt_json, backend); + // Generate proof + const proof = await program.generateFinalProof(inputs); + + // Proof verification + const isValid = await program.verifyFinalProof(proof); + expect(isValid).to.be.true; +}); + +it("end-to-end proof creation and verification (inner)", async () => { + // Noir.Js part + const inputs = { + x: "2", + y: "3", + }; + const serializedWitness = await generateWitness(assert_lt_json, inputs); // bb.js part // // Proof creation - const prover = new Backend(assert_lt_json.bytecode); - await prover.init(); - const serializedWitness = witnessMapToUint8Array(solvedWitness); - const proof = await prover.generateInnerProof(serializedWitness); + const prover = await Backend.initialize(assert_lt_json); + const proof = await prover.generateIntermediateProof(serializedWitness); // Proof verification - const isValid = await prover.verifyInnerProof(proof); + const isValid = await prover.verifyIntermediateProof(proof); expect(isValid).to.be.true; }); @@ -57,31 +73,30 @@ it('end-to-end proof creation and verification (inner)', async () => { // a prover and verifier class to more accurately reflect what happens in production. // // If its not fixable, we can leave it in as documentation of this behavior. -it('[BUG] -- bb.js null function or function signature mismatch (different instance) ', async () => { +it("[BUG] -- bb.js null function or function signature mismatch (different instance) ", async () => { // Noir.Js part const inputs = { - x: '2', - y: '3', + x: "2", + y: "3", }; - const solvedWitness = await generateWitness(assert_lt_json, inputs); + const serializedWitness = await generateWitness(assert_lt_json, inputs); // bb.js part - const prover = new Backend(assert_lt_json.bytecode); - await prover.init(); + const prover = await Backend.initialize(assert_lt_json); - const serializedWitness = witnessMapToUint8Array(solvedWitness); - const proof = await prover.generateOuterProof(serializedWitness); + const proof = await prover.generateFinalProof(serializedWitness); try { - const verifier = new Backend(assert_lt_json.bytecode); - await verifier.init(); - await verifier.verifyOuterProof(proof); + const verifier = await Backend.initialize(assert_lt_json); + await verifier.verifyFinalProof(proof); expect.fail( - 'bb.js currently returns a bug when we try to verify a proof with a different Barretenberg instance that created it.', + "bb.js currently returns a bug when we try to verify a proof with a different Barretenberg instance that created it.", ); } catch (error) { const knownError = error as Error; - expect(knownError.message).to.contain('null function or function signature mismatch'); + expect(knownError.message).to.contain( + "null function or function signature mismatch", + ); } }); @@ -92,37 +107,39 @@ it('[BUG] -- bb.js null function or function signature mismatch (different insta // If we only create one type of proof, then this works as expected. // // If we do not create an inner proof, then this will work as expected. -it('[BUG] -- bb.js null function or function signature mismatch (outer-inner) ', async () => { +it("[BUG] -- bb.js null function or function signature mismatch (outer-inner) ", async () => { // Noir.Js part const inputs = { - x: '2', - y: '3', + x: "2", + y: "3", }; - const solvedWitness = await generateWitness(assert_lt_json, inputs); + const serializedWitness = await generateWitness(assert_lt_json, inputs); // bb.js part // // Proof creation // - const prover = new Backend(assert_lt_json.bytecode); - await prover.init(); - const serializedWitness = witnessMapToUint8Array(solvedWitness); + const prover = await Backend.initialize(assert_lt_json); // Create a proof using both proving systems, the majority of the time // one would only use outer proofs. - const proofOuter = await prover.generateOuterProof(serializedWitness); - const _proofInner = await prover.generateInnerProof(serializedWitness); + const proofOuter = await prover.generateFinalProof(serializedWitness); + const _proofInner = await prover.generateIntermediateProof(serializedWitness); // Proof verification // try { - const isValidOuter = await prover.verifyOuterProof(proofOuter); + const isValidOuter = await prover.verifyFinalProof(proofOuter); expect(isValidOuter).to.be.true; // We can also try verifying an inner proof and it will fail. // const isValidInner = await prover.verifyInnerProof(_proofInner); // expect(isValidInner).to.be.true; - expect.fail('bb.js currently returns a bug when we try to verify an inner and outer proof with the same backend'); + expect.fail( + "bb.js currently returns a bug when we try to verify an inner and outer proof with the same backend", + ); } catch (error) { const knownError = error as Error; - expect(knownError.message).to.contain('null function or function signature mismatch'); + expect(knownError.message).to.contain( + "null function or function signature mismatch", + ); } }); diff --git a/tooling/noir_js/test/node/smoke.test.ts b/tooling/noir_js/test/node/smoke.test.ts index 179246745fe..c78b297f4b2 100644 --- a/tooling/noir_js/test/node/smoke.test.ts +++ b/tooling/noir_js/test/node/smoke.test.ts @@ -1,72 +1,90 @@ -import { expect } from 'chai'; -import assert_lt_json from '../noir_compiled_examples/assert_lt/target/assert_lt.json' assert { type: 'json' }; -import { generateWitness } from '../../src/index.js'; +import { expect } from "chai"; +import assert_lt_json from "../noir_compiled_examples/assert_lt/target/assert_lt.json" assert { type: "json" }; +import { generateWitness } from "../../src/index.js"; -it('generates witnesses successfully', async () => { +it("generates witnesses successfully", async () => { const inputs = { - x: '2', - y: '3', + x: "2", + y: "3", }; - const _solvedWitness = await generateWitness(assert_lt_json, inputs); + expect(() => generateWitness(assert_lt_json, inputs)).to.not.throw; }); -it('string input and number input are the same', async () => { +it("string input and number input are the same", async () => { const inputsString = { - x: '2', - y: '3', + x: "2", + y: "3", }; const inputsNumber = { x: 2, y: 3, }; - const solvedWitnessString = await generateWitness(assert_lt_json, inputsString); - const solvedWitnessNumber = await generateWitness(assert_lt_json, inputsNumber); + const solvedWitnessString = await generateWitness( + assert_lt_json, + inputsString, + ); + const solvedWitnessNumber = await generateWitness( + assert_lt_json, + inputsNumber, + ); expect(solvedWitnessString).to.deep.equal(solvedWitnessNumber); }); -it('string input and number input are the same', async () => { +it("string input and number input are the same", async () => { const inputsString = { - x: '2', - y: '3', + x: "2", + y: "3", }; const inputsNumber = { x: 2, y: 3, }; - const solvedWitnessString = await generateWitness(assert_lt_json, inputsString); - const solvedWitnessNumber = await generateWitness(assert_lt_json, inputsNumber); + const solvedWitnessString = await generateWitness( + assert_lt_json, + inputsString, + ); + const solvedWitnessNumber = await generateWitness( + assert_lt_json, + inputsNumber, + ); expect(solvedWitnessString).to.deep.equal(solvedWitnessNumber); }); -it('0x prefixed string input for inputs will throw', async () => { +it("0x prefixed string input for inputs will throw", async () => { const inputsHexPrefix = { - x: '0x2', - y: '0x3', + x: "0x2", + y: "0x3", }; try { await generateWitness(assert_lt_json, inputsHexPrefix); - expect.fail('Expected generatedWitness to throw, due to inputs being prefixed with 0x. Currently not supported'); + expect.fail( + "Expected generatedWitness to throw, due to inputs being prefixed with 0x. Currently not supported", + ); } catch (error) { // Successfully errored due to 0x not being supported. Update this test once/if we choose // to support 0x prefixed inputs. } }); -describe('input validation', () => { - it('x should be a uint64 not a string', async () => { +describe("input validation", () => { + it("x should be a uint64 not a string", async () => { const inputs = { - x: 'foo', - y: '3', + x: "foo", + y: "3", }; try { await generateWitness(assert_lt_json, inputs); - expect.fail('Expected generatedWitness to throw, due to x not being convertible to a uint64'); + expect.fail( + "Expected generatedWitness to throw, due to x not being convertible to a uint64", + ); } catch (error) { const knownError = error as Error; - expect(knownError.message).to.equal('Input for x is the wrong type, expected uint64, got "foo"'); + expect(knownError.message).to.equal( + "Expected witness values to be integers, provided value causes `invalid digit found in string` error", + ); } }); }); diff --git a/tooling/noir_js/tsc-multi.json b/tooling/noir_js/tsc-multi.json new file mode 100644 index 00000000000..af26d1e4aa6 --- /dev/null +++ b/tooling/noir_js/tsc-multi.json @@ -0,0 +1,7 @@ +{ + "targets": [ + { "extname": ".cjs", "module": "commonjs" }, + { "extname": ".mjs", "module": "esnext" } + ], + "projects": ["packages/*/tsconfig.json"] +} \ No newline at end of file diff --git a/tooling/noir_js/tsconfig.json b/tooling/noir_js/tsconfig.json index 0fd439990a6..1e0fdea09c7 100644 --- a/tooling/noir_js/tsconfig.json +++ b/tooling/noir_js/tsconfig.json @@ -13,4 +13,4 @@ }, "include": ["src/**/*.ts"], "exclude": ["node_modules"] -} +} \ No newline at end of file diff --git a/tooling/noir_js_types/.eslintignore b/tooling/noir_js_types/.eslintignore new file mode 100644 index 00000000000..3c3629e647f --- /dev/null +++ b/tooling/noir_js_types/.eslintignore @@ -0,0 +1 @@ +node_modules diff --git a/tooling/noir_js_types/.eslintrc.cjs b/tooling/noir_js_types/.eslintrc.cjs new file mode 100644 index 00000000000..5a2cc7f1ec0 --- /dev/null +++ b/tooling/noir_js_types/.eslintrc.cjs @@ -0,0 +1,3 @@ +module.exports = { + extends: ['../../.eslintrc.js'], +}; diff --git a/tooling/noir_js_types/.gitignore b/tooling/noir_js_types/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tooling/noir_js/.prettierrc b/tooling/noir_js_types/.prettierrc similarity index 100% rename from tooling/noir_js/.prettierrc rename to tooling/noir_js_types/.prettierrc diff --git a/tooling/noir_js_types/lib/types.ts b/tooling/noir_js_types/lib/types.ts new file mode 100644 index 00000000000..2b6ea8ad4b0 --- /dev/null +++ b/tooling/noir_js_types/lib/types.ts @@ -0,0 +1,18 @@ +export interface Backend { + // Generate an outer proof. This is the proof for the circuit which will verify + // inner proofs and or can be seen as the proof created for regular circuits. + generateFinalProof(decompressedWitness: Uint8Array): Promise; + + // Generates an inner proof. This is the proof that will be verified + // in another circuit. + generateIntermediateProof(decompressedWitness: Uint8Array): Promise; + + verifyFinalProof(proof: Uint8Array): Promise; + + verifyIntermediateProof(proof: Uint8Array): Promise; +} + +export type CompiledCircuit = { + bytecode: string; + abi: object; +}; diff --git a/tooling/noir_js_types/package.json b/tooling/noir_js_types/package.json new file mode 100644 index 00000000000..188b052273a --- /dev/null +++ b/tooling/noir_js_types/package.json @@ -0,0 +1,14 @@ +{ + "name": "@noir-lang/types", + "collaborators": [ + "The Noir Team " + ], + "version": "0.14.1", + "packageManager": "yarn@3.5.1", + "license": "(MIT OR Apache-2.0)", + "files": [ + "lib", + "package.json" + ], + "types": "lib/types.ts" +} diff --git a/tooling/noirc_abi/src/lib.rs b/tooling/noirc_abi/src/lib.rs index d0c7e4e58c1..eb654e25f8c 100644 --- a/tooling/noirc_abi/src/lib.rs +++ b/tooling/noirc_abi/src/lib.rs @@ -12,7 +12,9 @@ use acvm::{ use errors::AbiError; use input_parser::InputValue; use iter_extended::{try_btree_map, try_vecmap, vecmap}; -use noirc_frontend::{hir::Context, Signedness, Type, TypeBinding, TypeVariableKind, Visibility}; +use noirc_frontend::{ + hir::Context, Signedness, StructType, Type, TypeBinding, TypeVariableKind, Visibility, +}; use serde::{Deserialize, Serialize}; // This is the ABI used to bridge the different TOML formats for the initial // witness, the partial witness generator and the interpreter. @@ -477,6 +479,33 @@ fn decode_string_value(field_elements: &[FieldElement]) -> String { final_string.to_owned() } +#[derive(Debug, Serialize, Deserialize)] +pub struct ContractEvent { + /// Event name + name: String, + /// The fully qualified path to the event definition + path: String, + + /// Fields of the event + #[serde( + serialize_with = "serialization::serialize_struct_fields", + deserialize_with = "serialization::deserialize_struct_fields" + )] + fields: Vec<(String, AbiType)>, +} + +impl ContractEvent { + pub fn from_struct_type(context: &Context, struct_type: &StructType) -> Self { + let fields = vecmap(struct_type.get_fields(&[]), |(name, typ)| { + (name, AbiType::from_type(context, &typ)) + }); + // For the ABI, we always want to resolve the struct paths from the root crate + let path = context.fully_qualified_struct_path(context.root_crate_id(), struct_type.id); + + Self { name: struct_type.name.0.contents.clone(), path, fields } + } +} + #[cfg(test)] mod test { use std::collections::BTreeMap; diff --git a/tooling/noirc_abi_wasm/build.sh b/tooling/noirc_abi_wasm/build.sh index 8157e42c6de..37f2fd0a5a9 100755 --- a/tooling/noirc_abi_wasm/build.sh +++ b/tooling/noirc_abi_wasm/build.sh @@ -22,14 +22,13 @@ function run_or_fail { fi } -require_command toml2json require_command jq require_command cargo require_command wasm-bindgen check_installed wasm-opt self_path=$(dirname "$(readlink -f "$0")") -export pname=$(toml2json < $self_path/Cargo.toml | jq -r .package.name) +export pname=$(cargo read-manifest | jq -r '.name') export CARGO_TARGET_DIR=$self_path/target rm -rf $self_path/outputs >/dev/null 2>&1 @@ -45,4 +44,4 @@ fi run_or_fail $self_path/buildPhaseCargoCommand.sh run_or_fail $self_path/installPhase.sh -ln -s $out $self_path/result \ No newline at end of file +ln -s $out $self_path/result diff --git a/tooling/noirc_abi_wasm/package.json b/tooling/noirc_abi_wasm/package.json index c56eb2178bb..5c1f39ee396 100644 --- a/tooling/noirc_abi_wasm/package.json +++ b/tooling/noirc_abi_wasm/package.json @@ -3,7 +3,7 @@ "collaborators": [ "The Noir Team " ], - "version": "0.12.0", + "version": "0.15.0", "license": "(MIT OR Apache-2.0)", "files": [ "nodejs", @@ -25,6 +25,7 @@ "build": "bash ./build.sh", "test": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha", "test:browser": "web-test-runner", + "clean": "chmod u+w web nodejs && rm -rf ./nodejs ./web ./target ./result", "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "devDependencies": { diff --git a/tooling/noirc_abi_wasm/src/errors.rs b/tooling/noirc_abi_wasm/src/errors.rs new file mode 100644 index 00000000000..14ee2d5fd8e --- /dev/null +++ b/tooling/noirc_abi_wasm/src/errors.rs @@ -0,0 +1,47 @@ +use js_sys::{Error, JsString}; +use noirc_abi::errors::{AbiError, InputParserError}; +use wasm_bindgen::prelude::wasm_bindgen; + +#[wasm_bindgen(typescript_custom_section)] +const ABI_ERROR: &'static str = r#" +export type ABIError = Error; +"#; + +/// JsAbiError is a raw js error. +/// It'd be ideal that ABI error was a subclass of Error, but for that we'd need to use JS snippets or a js module. +/// Currently JS snippets don't work with a nodejs target. And a module would be too much for just a custom error type. +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Error, js_name = "AbiError", typescript_type = "AbiError")] + #[derive(Clone, Debug, PartialEq, Eq)] + pub type JsAbiError; + + #[wasm_bindgen(constructor, js_class = "Error")] + fn constructor(message: JsString) -> JsAbiError; +} + +impl JsAbiError { + /// Creates a new execution error with the given call stack. + /// Call stacks won't be optional in the future, after removing ErrorLocation in ACVM. + pub fn new(message: String) -> Self { + JsAbiError::constructor(JsString::from(message)) + } +} + +impl From for JsAbiError { + fn from(value: String) -> Self { + JsAbiError::new(value) + } +} + +impl From for JsAbiError { + fn from(value: AbiError) -> Self { + JsAbiError::new(value.to_string()) + } +} + +impl From for JsAbiError { + fn from(value: InputParserError) -> Self { + JsAbiError::new(value.to_string()) + } +} diff --git a/tooling/noirc_abi_wasm/src/js_witness_map.rs b/tooling/noirc_abi_wasm/src/js_witness_map.rs index fcc6e75f18c..c21e3cd3f01 100644 --- a/tooling/noirc_abi_wasm/src/js_witness_map.rs +++ b/tooling/noirc_abi_wasm/src/js_witness_map.rs @@ -60,7 +60,7 @@ pub(crate) fn js_value_to_field_element(js_value: JsValue) -> Result JsString { diff --git a/tooling/noirc_abi_wasm/src/lib.rs b/tooling/noirc_abi_wasm/src/lib.rs index 91ed724a101..2b1fc672fc4 100644 --- a/tooling/noirc_abi_wasm/src/lib.rs +++ b/tooling/noirc_abi_wasm/src/lib.rs @@ -14,9 +14,11 @@ use std::collections::BTreeMap; use gloo_utils::format::JsValueSerdeExt; use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; +mod errors; mod js_witness_map; mod temp; +use errors::JsAbiError; use js_witness_map::JsWitnessMap; use temp::{input_value_from_json_type, JsonTypes}; @@ -25,7 +27,7 @@ pub fn abi_encode( abi: JsValue, inputs: JsValue, return_value: JsValue, -) -> Result { +) -> Result { console_error_panic_hook::set_once(); let abi: Abi = JsValueSerdeExt::into_serde(&abi).map_err(|err| err.to_string())?; let inputs: BTreeMap = @@ -36,14 +38,11 @@ pub fn abi_encode( } else { let toml_return_value = JsValueSerdeExt::into_serde(&return_value).expect("could not decode return value"); - Some( - input_value_from_json_type( - toml_return_value, - abi.return_type.as_ref().unwrap(), - MAIN_RETURN_NAME, - ) - .map_err(|err| err.to_string())?, - ) + Some(input_value_from_json_type( + toml_return_value, + abi.return_type.as_ref().unwrap(), + MAIN_RETURN_NAME, + )?) }; let abi_map = abi.to_btree_map(); @@ -55,32 +54,30 @@ pub fn abi_encode( .ok_or_else(|| InputParserError::MissingArgument(arg_name.clone()))?; input_value_from_json_type(value.clone(), &abi_type, &arg_name) .map(|input_value| (arg_name, input_value)) - }) - .map_err(|err| err.to_string())?; + })?; - let witness_map = abi.encode(&parsed_inputs, return_value).map_err(|err| err.to_string())?; + let witness_map = abi.encode(&parsed_inputs, return_value)?; Ok(witness_map.into()) } #[wasm_bindgen(js_name = abiDecode)] -pub fn abi_decode(abi: JsValue, witness_map: JsWitnessMap) -> Result { +pub fn abi_decode(abi: JsValue, witness_map: JsWitnessMap) -> Result { console_error_panic_hook::set_once(); let abi: Abi = JsValueSerdeExt::into_serde(&abi).map_err(|err| err.to_string())?; let witness_map = WitnessMap::from(witness_map); - let (inputs, return_value) = abi.decode(&witness_map).map_err(|err| err.to_string())?; + let (inputs, return_value) = abi.decode(&witness_map)?; let abi_types = abi.to_btree_map(); let inputs_map: BTreeMap = try_btree_map(inputs, |(key, value)| { JsonTypes::try_from_input_value(&value, &abi_types[&key]).map(|value| (key, value)) - }) - .map_err(|err| err.to_string())?; - let return_value = return_value.map(|value| { - JsonTypes::try_from_input_value(&value, &abi.return_type.unwrap()) - .expect("could not decode return value") - }); + })?; + + let return_value = return_value + .map(|value| JsonTypes::try_from_input_value(&value, &abi.return_type.unwrap())) + .transpose()?; #[derive(Serialize)] struct InputsAndReturn { diff --git a/tooling/noirc_abi_wasm/test/browser/errors.test.ts b/tooling/noirc_abi_wasm/test/browser/errors.test.ts new file mode 100644 index 00000000000..6cfb3d6b192 --- /dev/null +++ b/tooling/noirc_abi_wasm/test/browser/errors.test.ts @@ -0,0 +1,30 @@ +import { expect } from "@esm-bundle/chai"; +import initNoirAbi, { abiEncode } from "@noir-lang/noirc_abi"; + +beforeEach(async () => { + await initNoirAbi(); +}); + +it("errors when an integer input overflows", async () => { + const { abi, inputs } = await import("../shared/uint_overflow"); + + expect(() => abiEncode(abi, inputs, null)).to.throw( + "The parameter foo is expected to be a Integer { sign: Unsigned, width: 32 } but found incompatible value Field(2³⁸)", + ); +}); + +it("errors when passing a field in place of an array", async () => { + const { abi, inputs } = await import("../shared/field_as_array"); + + expect(() => abiEncode(abi, inputs, null)).to.throw( + "cannot parse value into Array { length: 2, typ: Field }", + ); +}); + +it("errors when passing an array in place of a field", async () => { + const { abi, inputs } = await import("../shared/array_as_field"); + + expect(() => abiEncode(abi, inputs, null)).to.throw( + "cannot parse value into Field", + ); +}); diff --git a/tooling/noirc_abi_wasm/test/node/errors.test.ts b/tooling/noirc_abi_wasm/test/node/errors.test.ts new file mode 100644 index 00000000000..a1bda73763f --- /dev/null +++ b/tooling/noirc_abi_wasm/test/node/errors.test.ts @@ -0,0 +1,26 @@ +import { expect } from "chai"; +import { abiEncode } from "@noir-lang/noirc_abi"; + +it("errors when an integer input overflows", async () => { + const { abi, inputs } = await import("../shared/uint_overflow"); + + expect(() => abiEncode(abi, inputs, null)).to.throw( + "The parameter foo is expected to be a Integer { sign: Unsigned, width: 32 } but found incompatible value Field(2³⁸)", + ); +}); + +it("errors when passing a field in place of an array", async () => { + const { abi, inputs } = await import("../shared/field_as_array"); + + expect(() => abiEncode(abi, inputs, null)).to.throw( + "cannot parse value into Array { length: 2, typ: Field }", + ); +}); + +it("errors when passing an array in place of a field", async () => { + const { abi, inputs } = await import("../shared/array_as_field"); + + expect(() => abiEncode(abi, inputs, null)).to.throw( + "cannot parse value into Field", + ); +}); diff --git a/tooling/noirc_abi_wasm/test/shared/array_as_field.ts b/tooling/noirc_abi_wasm/test/shared/array_as_field.ts new file mode 100644 index 00000000000..ff62ec75259 --- /dev/null +++ b/tooling/noirc_abi_wasm/test/shared/array_as_field.ts @@ -0,0 +1,16 @@ +export const abi = { + parameters: [ + { + name: "foo", + type: { kind: "field" }, + visibility: "private", + }, + ], + param_witnesses: { foo: [1, 2] }, + return_type: null, + return_witnesses: [], +}; + +export const inputs = { + foo: ["1", "2"], +}; diff --git a/tooling/noirc_abi_wasm/test/shared/field_as_array.ts b/tooling/noirc_abi_wasm/test/shared/field_as_array.ts new file mode 100644 index 00000000000..c669154962a --- /dev/null +++ b/tooling/noirc_abi_wasm/test/shared/field_as_array.ts @@ -0,0 +1,16 @@ +export const abi = { + parameters: [ + { + name: "foo", + type: { kind: "array", length: 2, type: { kind: "field" } }, + visibility: "private", + }, + ], + param_witnesses: { foo: [1, 2] }, + return_type: null, + return_witnesses: [], +}; + +export const inputs = { + foo: "1", +}; diff --git a/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts b/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts new file mode 100644 index 00000000000..97bfe79e926 --- /dev/null +++ b/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts @@ -0,0 +1,16 @@ +export const abi = { + parameters: [ + { + name: "foo", + type: { kind: "integer", sign: "unsigned", width: 32 }, + visibility: "private", + }, + ], + param_witnesses: { foo: [1] }, + return_type: null, + return_witnesses: [], +}; + +export const inputs = { + foo: `0x${(1n << 38n).toString(16)}`, +}; diff --git a/yarn.lock b/yarn.lock index 6ce7d6ae1bf..5998147de80 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,6 +22,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:1.9.2": + version: 1.9.2 + resolution: "@adraffy/ens-normalize@npm:1.9.2" + checksum: a9fdeb9e080774c19e4b7f9f60aa5b37cf23fe0e8fe80284bf8221f7396e9f78642bfd39a09a94a4dc3fb8e70f2ac81545204bdcaf222d93f4c4c2ae1f0dca0b + languageName: node + linkType: hard + "@aztec/bb.js@npm:0.7.2": version: 0.7.2 resolution: "@aztec/bb.js@npm:0.7.2" @@ -36,9 +43,9 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@npm:^0.6.7": - version: 0.6.7 - resolution: "@aztec/bb.js@npm:0.6.7" +"@aztec/bb.js@npm:^0.7.3": + version: 0.7.3 + resolution: "@aztec/bb.js@npm:0.7.3" dependencies: comlink: ^4.4.1 commander: ^10.0.1 @@ -46,7 +53,7 @@ __metadata: tslib: ^2.4.0 bin: bb.js: dest/node/main.js - checksum: 9067e9c4c5e51de173261bb5feebe6c4f6fc2be0381e2b30301fd5ed6794c5a20f5242427b7701384bc6285cd65e04e2fa914010923d1671cf59c8674b6545eb + checksum: 4da507d3de83b56c24f074cf61dfa6812b9c37f2047af25f79feac9973a900fd517bedb707ce00de9bf8acffec871f1894385e2bc3b74fd81f2c6b4c41a02293 languageName: node linkType: hard @@ -372,6 +379,20 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.1.2": + version: 1.1.2 + resolution: "@noble/hashes@npm:1.1.2" + checksum: 3c2a8cb7c2e053811032f242155d870c5eb98844d924d69702244d48804cb03b42d4a666c49c2b71164420d8229cb9a6f242b972d50d5bb2f1d673b98b041de2 + languageName: node + linkType: hard + +"@noble/secp256k1@npm:1.7.1": + version: 1.7.1 + resolution: "@noble/secp256k1@npm:1.7.1" + checksum: d2301f1f7690368d8409a3152450458f27e54df47e3f917292de3de82c298770890c2de7c967d237eff9c95b70af485389a9695f73eb05a43e2bd562d18b18cb + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -399,20 +420,34 @@ __metadata: languageName: node linkType: hard -"@noir-lang/acvm_js@npm:0.27.0": - version: 0.27.0 - resolution: "@noir-lang/acvm_js@npm:0.27.0" - checksum: ec122e347f92ff2ec08c6d462e09e81a85c3b0243f70f9af14569b47c566fa08908f8bab558740a09aa3c57f9b25230332ea37168528553a877f0bd2d541cc1e - languageName: node - linkType: hard +"@noir-lang/acvm_js@workspace:*, @noir-lang/acvm_js@workspace:acvm-repo/acvm_js": + version: 0.0.0-use.local + resolution: "@noir-lang/acvm_js@workspace:acvm-repo/acvm_js" + dependencies: + "@esm-bundle/chai": ^4.3.4-fix.0 + "@typescript-eslint/eslint-plugin": ^5.59.5 + "@typescript-eslint/parser": ^5.59.5 + "@web/dev-server-esbuild": ^0.3.6 + "@web/test-runner": ^0.15.3 + "@web/test-runner-playwright": ^0.10.0 + chai: ^4.3.7 + eslint: ^8.40.0 + eslint-plugin-prettier: ^5.0.0 + mocha: ^10.2.0 + prettier: 3.0.3 + ts-node: ^10.9.1 + typescript: ^5.0.4 + languageName: unknown + linkType: soft "@noir-lang/noir_js@workspace:*, @noir-lang/noir_js@workspace:tooling/noir_js": version: 0.0.0-use.local resolution: "@noir-lang/noir_js@workspace:tooling/noir_js" dependencies: "@aztec/bb.js": 0.7.2 - "@noir-lang/acvm_js": 0.27.0 + "@noir-lang/acvm_js": "workspace:*" "@noir-lang/noirc_abi": "workspace:*" + "@noir-lang/types": "workspace:*" "@types/chai": ^4 "@types/mocha": ^10.0.1 "@types/node": ^20.6.2 @@ -424,6 +459,7 @@ __metadata: mocha: ^10.2.0 prettier: 3.0.3 ts-node: ^10.9.1 + tsc-multi: ^1.1.0 typescript: ^5.2.2 languageName: unknown linkType: soft @@ -469,6 +505,7 @@ __metadata: mocha: ^10.2.0 prettier: 3.0.3 ts-node: ^10.9.1 + tslog: ^4.9.2 typescript: ^5.0.4 languageName: unknown linkType: soft @@ -483,6 +520,12 @@ __metadata: languageName: unknown linkType: soft +"@noir-lang/types@workspace:*, @noir-lang/types@workspace:tooling/noir_js_types": + version: 0.0.0-use.local + resolution: "@noir-lang/types@workspace:tooling/noir_js_types" + languageName: unknown + linkType: soft + "@npmcli/fs@npm:^3.1.0": version: 3.1.0 resolution: "@npmcli/fs@npm:3.1.0" @@ -921,6 +964,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:18.15.13": + version: 18.15.13 + resolution: "@types/node@npm:18.15.13" + checksum: 79cc5a2b5f98e8973061a4260a781425efd39161a0e117a69cd089603964816c1a14025e1387b4590c8e82d05133b7b4154fa53a7dffb3877890a66145e76515 + languageName: node + linkType: hard + "@types/node@npm:^18.7.20": version: 18.17.18 resolution: "@types/node@npm:18.17.18" @@ -1605,6 +1655,13 @@ __metadata: languageName: node linkType: hard +"aes-js@npm:4.0.0-beta.5": + version: 4.0.0-beta.5 + resolution: "aes-js@npm:4.0.0-beta.5" + checksum: cc2ea969d77df939c32057f7e361b6530aa6cb93cb10617a17a45cd164e6d761002f031ff6330af3e67e58b1f0a3a8fd0b63a720afd591a653b02f649470e15b + languageName: node + linkType: hard + "agent-base@npm:6, agent-base@npm:^6.0.2": version: 6.0.2 resolution: "agent-base@npm:6.0.2" @@ -3456,6 +3513,21 @@ __metadata: languageName: node linkType: hard +"ethers@npm:^6.7.1": + version: 6.7.1 + resolution: "ethers@npm:6.7.1" + dependencies: + "@adraffy/ens-normalize": 1.9.2 + "@noble/hashes": 1.1.2 + "@noble/secp256k1": 1.7.1 + "@types/node": 18.15.13 + aes-js: 4.0.0-beta.5 + tslib: 2.4.0 + ws: 8.5.0 + checksum: 07833692e3f53b18e28c4cba9f53f3d5ebff8360de02ad57b2584c00c52b88f5b790373f9b9f6b4f6b52ffa2074530a6101192b30c3260f7cdeff929d34bb88b + languageName: node + linkType: hard + "event-stream@npm:=3.3.4": version: 3.3.4 resolution: "event-stream@npm:3.3.4" @@ -3557,7 +3629,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0": +"fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0": version: 3.3.1 resolution: "fast-glob@npm:3.3.1" dependencies: @@ -3921,6 +3993,13 @@ __metadata: languageName: node linkType: hard +"get-stdin@npm:^8.0.0": + version: 8.0.0 + resolution: "get-stdin@npm:8.0.0" + checksum: 40128b6cd25781ddbd233344f1a1e4006d4284906191ed0a7d55ec2c1a3e44d650f280b2c9eeab79c03ac3037da80257476c0e4e5af38ddfb902d6ff06282d77 + languageName: node + linkType: hard + "get-stream@npm:^5.1.0": version: 5.2.0 resolution: "get-stream@npm:5.2.0" @@ -4437,15 +4516,17 @@ __metadata: version: 0.0.0-use.local resolution: "integration-tests@workspace:compiler/integration-tests" dependencies: - "@aztec/bb.js": ^0.6.7 + "@aztec/bb.js": ^0.7.3 "@noir-lang/noir_js": "workspace:*" "@noir-lang/noir_wasm": "workspace:*" "@noir-lang/source-resolver": "workspace:*" "@web/dev-server-esbuild": ^0.3.6 "@web/test-runner": ^0.15.3 "@web/test-runner-webdriver": ^0.7.0 + ethers: ^6.7.1 fflate: ^0.8.0 smol-toml: ^1.1.2 + tslog: ^4.9.2 languageName: unknown linkType: soft @@ -5829,6 +5910,15 @@ __metadata: languageName: node linkType: hard +"p-all@npm:^3.0.0": + version: 3.0.0 + resolution: "p-all@npm:3.0.0" + dependencies: + p-map: ^4.0.0 + checksum: 267a620c2330b14246b92008f4be8758debe74e1454c8fb5808544f51fd038ac4597dbeeaa1542f237794e613cd42e4f1a58c01e5a0a6a6b21340fef616257df + languageName: node + linkType: hard + "p-cancelable@npm:^3.0.0": version: 3.0.0 resolution: "p-cancelable@npm:3.0.0" @@ -6965,6 +7055,15 @@ __metadata: languageName: node linkType: hard +"string-to-stream@npm:^3.0.1": + version: 3.0.1 + resolution: "string-to-stream@npm:3.0.1" + dependencies: + readable-stream: ^3.4.0 + checksum: e8ac7f7497f8f101196e39dd529e98bb97165c532cc4cae5003083a420db62f46ffd67ddff7112b45f9f8d0f9ff1cc6cda9b06362236d43fa6b1685e8b0d446e + languageName: node + linkType: hard + "string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" @@ -7044,6 +7143,13 @@ __metadata: languageName: node linkType: hard +"superstruct@npm:^1.0.3": + version: 1.0.3 + resolution: "superstruct@npm:1.0.3" + checksum: 761790bb111e6e21ddd608299c252f3be35df543263a7ebbc004e840d01fcf8046794c274bcb351bdf3eae4600f79d317d085cdbb19ca05803a4361840cc9bb1 + languageName: node + linkType: hard + "supertap@npm:^3.0.1": version: 3.0.1 resolution: "supertap@npm:3.0.1" @@ -7290,6 +7396,35 @@ __metadata: languageName: node linkType: hard +"tsc-multi@npm:^1.1.0": + version: 1.1.0 + resolution: "tsc-multi@npm:1.1.0" + dependencies: + debug: ^4.3.4 + fast-glob: ^3.2.12 + get-stdin: ^8.0.0 + p-all: ^3.0.0 + picocolors: ^1.0.0 + signal-exit: ^3.0.7 + string-to-stream: ^3.0.1 + superstruct: ^1.0.3 + tslib: ^2.5.0 + yargs: ^17.7.1 + peerDependencies: + typescript: ">=4.3.0" + bin: + tsc-multi: bin/tsc-multi.js + checksum: a82c0358611ac15667aa148ade33b6ad64cc0a94299fb9afc01e3e6224a994dff8812960a43643f25e4c0dac8419707027c3096d0e60bff3522591c06d5f4eeb + languageName: node + linkType: hard + +"tslib@npm:2.4.0": + version: 2.4.0 + resolution: "tslib@npm:2.4.0" + checksum: 8c4aa6a3c5a754bf76aefc38026134180c053b7bd2f81338cb5e5ebf96fefa0f417bff221592bf801077f5bf990562f6264fecbc42cd3309b33872cb6fc3b113 + languageName: node + linkType: hard + "tslib@npm:^1.8.1": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -7304,6 +7439,13 @@ __metadata: languageName: node linkType: hard +"tslog@npm:^4.9.2": + version: 4.9.2 + resolution: "tslog@npm:4.9.2" + checksum: 702e45647a68b127d63c5eb63a0f322af8d01f17b689127d32238d6ca2ef76889648a00b88c040430e3126acedef070022b20ebd81823879ba3766cf5188c868 + languageName: node + linkType: hard + "tsscmp@npm:1.0.6": version: 1.0.6 resolution: "tsscmp@npm:1.0.6" @@ -7831,6 +7973,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:8.5.0": + version: 8.5.0 + resolution: "ws@npm:8.5.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 76f2f90e40344bf18fd544194e7067812fb1372b2a37865678d8f12afe4b478ff2ebc0c7c0aff82cd5e6b66fc43d889eec0f1865c2365d8f7a66d92da7744a77 + languageName: node + linkType: hard + "ws@npm:^7.4.2": version: 7.5.9 resolution: "ws@npm:7.5.9" @@ -7945,7 +8102,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.7.2": +"yargs@npm:^17.7.1, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: