diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index b5ded344eb9..00000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,4 +0,0 @@ -contact_links: - - name: Ideas - url: https://github.com/orgs/noir-lang/discussions/new?category=ideas - about: Share ideas for new features diff --git a/.github/ISSUE_TEMPLATE/idea_action_plan.yml b/.github/ISSUE_TEMPLATE/feature_request.yml similarity index 90% rename from .github/ISSUE_TEMPLATE/idea_action_plan.yml rename to .github/ISSUE_TEMPLATE/feature_request.yml index 02fed1fc48b..979ac75811e 100644 --- a/.github/ISSUE_TEMPLATE/idea_action_plan.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,5 +1,5 @@ -name: Idea Action Plan -description: Outline the scope and steps for implementing an enhancement. Start with "Ideas" instead to request and discuss new features. +name: Feature Request +description: Suggest an idea for this project. labels: ["enhancement"] body: - type: markdown diff --git a/.github/scripts/noir-js-test.sh b/.github/scripts/noir-js-test.sh index b5fe34038fe..72458d8de6a 100755 --- a/.github/scripts/noir-js-test.sh +++ b/.github/scripts/noir-js-test.sh @@ -1,6 +1,4 @@ #!/bin/bash set -eu -./scripts/nargo_compile_noir_js_assert_lt.sh -rm -rf /usr/src/noir/tooling/noir_js/test/noir_compiled_examples/assert_lt/target/debug_assert_lt.json -yarn workspace @noir-lang/noir_js test \ No newline at end of file +yarn workspace @noir-lang/noir_js test diff --git a/.github/scripts/wasm-bindgen-install.sh b/.github/scripts/wasm-bindgen-install.sh index b8c41393ab0..a147a46cde8 100755 --- a/.github/scripts/wasm-bindgen-install.sh +++ b/.github/scripts/wasm-bindgen-install.sh @@ -1,5 +1,5 @@ #!/bin/bash set -eu -curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash -cargo-binstall wasm-bindgen-cli --version 0.2.86 -y +# TODO call this script directly +./scripts/install_wasm-bindgen.sh diff --git a/.github/workflows/docs-pr.yml b/.github/workflows/docs-pr.yml index cf40b9357cd..87bec37c438 100644 --- a/.github/workflows/docs-pr.yml +++ b/.github/workflows/docs-pr.yml @@ -40,7 +40,7 @@ jobs: script: | const labels = context.payload.pull_request.labels.map(label => label.name); if (!labels.includes('documentation')) { - github.issues.addLabels({ + github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, @@ -54,6 +54,15 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Setup toolchain + uses: dtolnay/rust-toolchain@1.71.1 + + - uses: Swatinem/rust-cache@v2 + with: + key: x86_64-unknown-linux-gnu + cache-on-failure: false + save-if: false + - name: Install Yarn dependencies uses: ./.github/actions/setup diff --git a/.github/workflows/publish-es-packages.yml b/.github/workflows/publish-es-packages.yml index e360654b46a..2c825ffd45f 100644 --- a/.github/workflows/publish-es-packages.yml +++ b/.github/workflows/publish-es-packages.yml @@ -15,7 +15,7 @@ on: run-name: Publish ES Packages from ${{ inputs.noir-ref }} under @${{ inputs.npm-tag }} tag. jobs: - build-noir_wasm: + build-noirc_abi_wasm: runs-on: ubuntu-latest steps: - name: Checkout sources @@ -32,16 +32,17 @@ jobs: - name: Build wasm package run: | - nix build -L .#noir_wasm + nix build -L .#noirc_abi_wasm - uses: actions/upload-artifact@v3 with: - name: noir_wasm + name: noirc_abi_wasm path: | - result/noir_wasm/nodejs - result/noir_wasm/web + result/noirc_abi_wasm/nodejs + result/noirc_abi_wasm/web - build-noirc_abi_wasm: + build-noir_wasm: + needs: [build-noirc_abi_wasm] runs-on: ubuntu-latest steps: - name: Checkout sources @@ -49,23 +50,34 @@ jobs: with: ref: ${{ inputs.noir-ref }} - - name: Setup Nix - uses: ./.github/actions/nix + - name: Setup toolchain + uses: dtolnay/rust-toolchain@1.71.1 + + - uses: Swatinem/rust-cache@v2 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - nix-cache-name: "noir" - cachix-auth-token: ${{ secrets.CACHIXAUTHTOKEN }} - - - name: Build wasm package - run: | - nix build -L .#noirc_abi_wasm + key: noir-wasm + save-if: false - - uses: actions/upload-artifact@v3 + - name: Download noirc_abi_wasm package artifact + uses: actions/download-artifact@v3 with: name: noirc_abi_wasm + path: ./tooling/noirc_abi_wasm + + - name: Install Yarn dependencies + uses: ./.github/actions/setup + + - name: Build noir_wasm + run: yarn workspace @noir-lang/noir_wasm build + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: noir_wasm path: | - result/noirc_abi_wasm/nodejs - result/noirc_abi_wasm/web + ./compiler/wasm/dist + ./compiler/wasm/build + retention-days: 3 build-acvm_js: runs-on: ubuntu-latest @@ -97,7 +109,6 @@ jobs: runs-on: ubuntu-latest needs: [build-acvm_js, build-noirc_abi_wasm, build-noir_wasm] steps: - - name: Checkout sources uses: actions/checkout@v4 with: @@ -107,10 +118,12 @@ jobs: with: name: acvm_js path: acvm-repo/acvm_js + - uses: actions/download-artifact@v3 with: name: noir_wasm path: compiler/wasm + - uses: actions/download-artifact@v3 with: name: noirc_abi_wasm diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 22a733b38c5..71a0ab6d894 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,6 @@ jobs: outputs: release-pr: ${{ steps.release.outputs.pr }} tag-name: ${{ steps.release.outputs.tag_name }} - pending-release-semver: v${{ steps.release.outputs.major }}.${{steps.release.outputs.minor}}.${{steps.release.outputs.patch}} runs-on: ubuntu-latest steps: - name: Run release-please @@ -80,9 +79,15 @@ jobs: - name: Install Yarn dependencies uses: ./.github/actions/setup + - name: Query new noir version + id: noir-version + run: | + NOIR_VERSION=$(grep '^version =' ./Cargo.toml | sed -E 's/version = "([^"]+)"/v\1/') + echo "semver=$NOIR_VERSION" >> $GITHUB_OUTPUT + - name: Cut a new version working-directory: ./docs - run: yarn docusaurus docs:version ${{ needs.release-please.outputs.pending-release-semver }} + run: yarn version ${{ steps.noir-version.outputs.semver }} - name: Configure git run: | @@ -92,7 +97,7 @@ jobs: - name: Commit new documentation version run: | git add . - git commit -m "chore(docs): cut new docs version for tag ${{ needs.release-please.outputs.pending-release-semver }}" + git commit -m "chore(docs): cut new docs version for tag ${{ steps.noir-version.outputs.semver }}" git push build-binaries: diff --git a/.github/workflows/test-js-packages.yml b/.github/workflows/test-js-packages.yml index addc9ce3d83..036c582eaa4 100644 --- a/.github/workflows/test-js-packages.yml +++ b/.github/workflows/test-js-packages.yml @@ -247,7 +247,7 @@ jobs: yarn workspace @noir-lang/backend_barretenberg test test-noir-js: - needs: [build-acvm-js, build-noirc-abi] + needs: [build-nargo, build-acvm-js, build-noirc-abi] name: Noir JS runs-on: ubuntu-latest timeout-minutes: 30 @@ -256,6 +256,12 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Download nargo binary + uses: actions/download-artifact@v3 + with: + name: nargo + path: ./nargo + - name: Download artifact uses: actions/download-artifact@v3 with: @@ -268,6 +274,14 @@ jobs: 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 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 01f6fb140b1..f440a7a2c51 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,4 +1,4 @@ { - ".": "0.22.0", - "acvm-repo": "0.38.0" + ".": "0.23.0", + "acvm-repo": "0.39.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fc044076a0..af7eb5b2f19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,97 @@ # Changelog +## [0.23.0](https://github.com/noir-lang/noir/compare/v0.22.0...v0.23.0) (2024-01-22) + + +### ⚠ BREAKING CHANGES + +* Ban nested slices ([#4018](https://github.com/noir-lang/noir/issues/4018)) +* Breaking changes from aztec-packages ([#3955](https://github.com/noir-lang/noir/issues/3955)) +* Rename Arithmetic opcode to AssertZero ([#3840](https://github.com/noir-lang/noir/issues/3840)) +* remove circuit methods from noir_wasm ([#3869](https://github.com/noir-lang/noir/issues/3869)) + +### Features + +* Add `assert_max_bit_size` method to `Field` ([#4016](https://github.com/noir-lang/noir/issues/4016)) ([bc9a44f](https://github.com/noir-lang/noir/commit/bc9a44f285e0569825a307b06ee8acd93461c87e)) +* Add `noir-compiler` checks to `aztec_macros` ([#4031](https://github.com/noir-lang/noir/issues/4031)) ([420a5c7](https://github.com/noir-lang/noir/commit/420a5c74a14dcfeede04337a42282093a7b5e63e)) +* Add a `--force` flag to force a full recompile ([#4054](https://github.com/noir-lang/noir/issues/4054)) ([27a8e68](https://github.com/noir-lang/noir/commit/27a8e6864643d81d96e84990e2e26cd16596a695)) +* Add dependency resolver for `noir_wasm` and implement `FileManager` for consistency with native interface ([#3891](https://github.com/noir-lang/noir/issues/3891)) ([c29c7d7](https://github.com/noir-lang/noir/commit/c29c7d7c9615b9f45c696b1bdc1c497d55469dfa)) +* Add foreign call support to `noir_codegen` functions ([#3933](https://github.com/noir-lang/noir/issues/3933)) ([e5e52a8](https://github.com/noir-lang/noir/commit/e5e52a81b31d7735b680e97a9bef89a010a99763)) +* Add MVP `nargo export` command ([#3870](https://github.com/noir-lang/noir/issues/3870)) ([fbb51ed](https://github.com/noir-lang/noir/commit/fbb51ed33e9e4d9105d8946cdfc4ea387c85258e)) +* Add support for codegenning multiple functions which use the same structs in their interface ([#3868](https://github.com/noir-lang/noir/issues/3868)) ([1dcfcc5](https://github.com/noir-lang/noir/commit/1dcfcc5265f618685a783504b1d4be213e4cda2d)) +* Added efficient field comparisons for bn254 ([#4042](https://github.com/noir-lang/noir/issues/4042)) ([1f9cad0](https://github.com/noir-lang/noir/commit/1f9cad00c57ea257f57419d2446a46938beb19f9)) +* Assert maximum bit size when creating a U128 from an integer ([#4024](https://github.com/noir-lang/noir/issues/4024)) ([8f9c7e4](https://github.com/noir-lang/noir/commit/8f9c7e4de9f2ae5b39714d8e0d26b2befcd11c4a)) +* Avoid unnecessary range checks by inspecting instructions for casts ([#4039](https://github.com/noir-lang/noir/issues/4039)) ([378c18e](https://github.com/noir-lang/noir/commit/378c18eb42d75852b97f849d05c9e3f650601339)) +* Breaking changes from aztec-packages ([#3955](https://github.com/noir-lang/noir/issues/3955)) ([5be049e](https://github.com/noir-lang/noir/commit/5be049eee6c342649462282ee04f6411e6ea392c)) +* Bubble up `Instruction::Constrain`s to be applied as early as possible. ([#4065](https://github.com/noir-lang/noir/issues/4065)) ([66f5cdd](https://github.com/noir-lang/noir/commit/66f5cddc133ba0311028eba96c0ff6ec2ecaee59)) +* Cached LSP parsing ([#4083](https://github.com/noir-lang/noir/issues/4083)) ([b4f724e](https://github.com/noir-lang/noir/commit/b4f724e848b291a733e417c394ac3fc7649c08c5)) +* Comparison for signed integers ([#3873](https://github.com/noir-lang/noir/issues/3873)) ([bcbd49b](https://github.com/noir-lang/noir/commit/bcbd49b8b44749e149f83c1240094fa2f0a19087)) +* Decompose `Instruction::Cast` to have an explicit truncation instruction ([#3946](https://github.com/noir-lang/noir/issues/3946)) ([35f18ef](https://github.com/noir-lang/noir/commit/35f18ef4d7c8041e3cf622a5643748d0793c2aa6)) +* Decompose `Instruction::Constrain` into multiple more basic constraints ([#3892](https://github.com/noir-lang/noir/issues/3892)) ([51cf9d3](https://github.com/noir-lang/noir/commit/51cf9d37c8b9fbb14bb54b178d93129a7563e131)) +* Docker testing flow ([#3895](https://github.com/noir-lang/noir/issues/3895)) ([179c90d](https://github.com/noir-lang/noir/commit/179c90dc3263c85de105c57925d9c5894427e8e1)) +* Extract parsing to its own pass and do it in parallel ([#4063](https://github.com/noir-lang/noir/issues/4063)) ([569cbbc](https://github.com/noir-lang/noir/commit/569cbbc231a242c32821cba56f3649f3228a1cc7)) +* Implement `Eq` trait on curve points ([#3944](https://github.com/noir-lang/noir/issues/3944)) ([abf751a](https://github.com/noir-lang/noir/commit/abf751ab7f57f87520be16b2bc6168efdf95a430)) +* Implement DAP protocol in Nargo ([#3627](https://github.com/noir-lang/noir/issues/3627)) ([13834d4](https://github.com/noir-lang/noir/commit/13834d43bd876909cb50494a41b42297f7e6375b)) +* Implement generic traits ([#4000](https://github.com/noir-lang/noir/issues/4000)) ([916fd15](https://github.com/noir-lang/noir/commit/916fd158aa361ac80d32767f575ad896c3462b15)) +* Implement Operator Overloading ([#3931](https://github.com/noir-lang/noir/issues/3931)) ([4b16090](https://github.com/noir-lang/noir/commit/4b16090beecd0fcdd41c9e7b8f615c4625c26a5b)) +* **lsp:** Cache definitions for goto requests ([#3930](https://github.com/noir-lang/noir/issues/3930)) ([4a2140f](https://github.com/noir-lang/noir/commit/4a2140f1f36bbe3afbc006f8db74820308ae27d5)) +* **lsp:** Goto global ([#4043](https://github.com/noir-lang/noir/issues/4043)) ([15237b3](https://github.com/noir-lang/noir/commit/15237b34dbce5ea54973a178449e67cca8ac4f9d)) +* **lsp:** Goto struct member inside Impl method ([#3918](https://github.com/noir-lang/noir/issues/3918)) ([99c2c5a](https://github.com/noir-lang/noir/commit/99c2c5a2c2c0da6bad783b60d9e3de8d9a1f4ee4)) +* **lsp:** Goto trait from trait impl ([#3956](https://github.com/noir-lang/noir/issues/3956)) ([eb566e2](https://github.com/noir-lang/noir/commit/eb566e2125e847a3e3efbd2bc15a88a1c454a7df)) +* **lsp:** Goto trait method declaration ([#3991](https://github.com/noir-lang/noir/issues/3991)) ([eb79166](https://github.com/noir-lang/noir/commit/eb79166f7d2b7aa45c9c6c0aa37db1c0a5dfa00f)) +* **lsp:** Goto type alias ([#4061](https://github.com/noir-lang/noir/issues/4061)) ([dc83385](https://github.com/noir-lang/noir/commit/dc83385e9fe5766cd8218265be38c54243cae76e)) +* **lsp:** Goto type definition ([#4029](https://github.com/noir-lang/noir/issues/4029)) ([8bb4ddf](https://github.com/noir-lang/noir/commit/8bb4ddfdd81d491ff713a056a7eae522f329d173)) +* **lsp:** Re-add code lens feature with improved performance ([#3829](https://github.com/noir-lang/noir/issues/3829)) ([8f5cd6c](https://github.com/noir-lang/noir/commit/8f5cd6c0b641b3970bf626e8910b2a4c7cc8c310)) +* Optimize array ops for arrays of structs ([#4027](https://github.com/noir-lang/noir/issues/4027)) ([c9ec0d8](https://github.com/noir-lang/noir/commit/c9ec0d811ddc8653201ed765b51585a7c1b946fb)) +* Optimize logic gate ACIR-gen ([#3897](https://github.com/noir-lang/noir/issues/3897)) ([926460a](https://github.com/noir-lang/noir/commit/926460a0c70e21e2f4720148cf424e44ab9b0678)) +* Prefer `AcirContext`-native methods for performing logic operations ([#3898](https://github.com/noir-lang/noir/issues/3898)) ([0ec39b8](https://github.com/noir-lang/noir/commit/0ec39b8396084ed1e7f20609c8ad8a5844a86674)) +* Remove range constraints from witnesses which are constrained to be constants ([#3928](https://github.com/noir-lang/noir/issues/3928)) ([afe9c7a](https://github.com/noir-lang/noir/commit/afe9c7a38bb9d4245205d3aa46d4ce23d70a5671)) +* Remove truncation from brillig casts ([#3997](https://github.com/noir-lang/noir/issues/3997)) ([857ff97](https://github.com/noir-lang/noir/commit/857ff97b196174a0999f0fe7e387bfca5c3b7cd3)) +* Remove truncations which can be seen to be noops using type information ([#3953](https://github.com/noir-lang/noir/issues/3953)) ([cc3c2c2](https://github.com/noir-lang/noir/commit/cc3c2c22644f0b5d8369bad2362ea6e9112a0713)) +* Remove unnecessary predicate from `Lt` instruction ([#3922](https://github.com/noir-lang/noir/issues/3922)) ([a63433f](https://github.com/noir-lang/noir/commit/a63433fb8747722ec3cf2c6eb85d34e5b04bc15c)) +* Simplify chains of casts to be all in terms of the original `ValueId` ([#3984](https://github.com/noir-lang/noir/issues/3984)) ([2384d3e](https://github.com/noir-lang/noir/commit/2384d3e97af24a8718fbf57f6b276a5ce1de06fe)) +* Simplify multiplications by `0` or `1` in ACIR gen ([#3924](https://github.com/noir-lang/noir/issues/3924)) ([e58844d](https://github.com/noir-lang/noir/commit/e58844daf9f040626a3a7595f8c4f831e48a4037)) +* Support for u128 ([#3913](https://github.com/noir-lang/noir/issues/3913)) ([b4911dc](https://github.com/noir-lang/noir/commit/b4911dcf676f0925ac631ba6f60fc9c4945b2fee)) +* Support printing more types ([#4071](https://github.com/noir-lang/noir/issues/4071)) ([f5c4632](https://github.com/noir-lang/noir/commit/f5c4632e174beba508e1e31d0e2ae3f6d028ae2c)) +* Sync `aztec-packages` ([#4011](https://github.com/noir-lang/noir/issues/4011)) ([fee2452](https://github.com/noir-lang/noir/commit/fee24523c427c27f0bdaf98ea09a852a2da3e94c)) +* Sync commits from `aztec-packages` ([#4068](https://github.com/noir-lang/noir/issues/4068)) ([7a8f3a3](https://github.com/noir-lang/noir/commit/7a8f3a33b57875e681e3d81e667e3570a1cdbdcc)) +* Use singleton `WasmBlackBoxFunctionSolver` in `noir_js` ([#3966](https://github.com/noir-lang/noir/issues/3966)) ([10b28de](https://github.com/noir-lang/noir/commit/10b28def4d74822b7af2c19a1cc693788272b00b)) + + +### Bug Fixes + +* Acir gen doesn't panic on unsupported BB function ([#3866](https://github.com/noir-lang/noir/issues/3866)) ([34fd978](https://github.com/noir-lang/noir/commit/34fd978d206789a9e9f5167bfd690a34386834d0)) +* Allow abi encoding arrays of structs from JS ([#3867](https://github.com/noir-lang/noir/issues/3867)) ([9b713f8](https://github.com/noir-lang/noir/commit/9b713f8cf599df262a12ec1098136c50b2b46766)) +* Allow abi encoding tuples from JS ([#3894](https://github.com/noir-lang/noir/issues/3894)) ([f7fa181](https://github.com/noir-lang/noir/commit/f7fa1811ad2591020c914976f26e2f11a91cd177)) +* Allow ast when macro errors ([#4005](https://github.com/noir-lang/noir/issues/4005)) ([efccec3](https://github.com/noir-lang/noir/commit/efccec3c24eb093fba99b1c29f01a78aae5776d0)) +* Allow lsp to run inside of a docker container ([#3876](https://github.com/noir-lang/noir/issues/3876)) ([2529977](https://github.com/noir-lang/noir/commit/2529977acd684219f57ef086415557cc07af043b)) +* Bit-shifts for signed integers ([#3890](https://github.com/noir-lang/noir/issues/3890)) ([6ddd98a](https://github.com/noir-lang/noir/commit/6ddd98ab7d3fefde491cf12b785f76bf0585609e)) +* Checks for cyclic dependencies ([#3699](https://github.com/noir-lang/noir/issues/3699)) ([642011a](https://github.com/noir-lang/noir/commit/642011ab6ebbe8f012eda1da1abbf8660500723d)) +* **debugger:** Crash when stepping through locations spanning multiple lines ([#3920](https://github.com/noir-lang/noir/issues/3920)) ([223e860](https://github.com/noir-lang/noir/commit/223e860975c2698bd5043340b937de74552ec15b)) +* Don't fail if no tests and the user didn't provide a pattern ([#3864](https://github.com/noir-lang/noir/issues/3864)) ([decbd0f](https://github.com/noir-lang/noir/commit/decbd0f0c019844cd2b235e7804d2f6ba7b23897)) +* Fix advisory issue in cargo-deny ([#4077](https://github.com/noir-lang/noir/issues/4077)) ([19baea0](https://github.com/noir-lang/noir/commit/19baea0d18e2d26bd04b649f79dd8e681488d1dc)) +* Fixing dark mode background on the CTA button ([#3882](https://github.com/noir-lang/noir/issues/3882)) ([57eae42](https://github.com/noir-lang/noir/commit/57eae42080d6a928e8010c6bc77489964a5777ef)) +* Fixup exports from `noir_wasm` ([#4022](https://github.com/noir-lang/noir/issues/4022)) ([358cdd2](https://github.com/noir-lang/noir/commit/358cdd2725444091b3322c47754e3cbd9b1d3614)) +* Handle multiple imports in the same file ([#3903](https://github.com/noir-lang/noir/issues/3903)) ([219423e](https://github.com/noir-lang/noir/commit/219423eb87fa12bd8cca2a6fd2ce4c06e308783c)) +* Hoist constraints on inputs to top of program ([#4076](https://github.com/noir-lang/noir/issues/4076)) ([447aa34](https://github.com/noir-lang/noir/commit/447aa343555cbd5a7cd735876e08f43271ecdd40)) +* Implement missing codegen for `BlackBoxFunc::EcdsaSecp256r1` in brillig ([#3943](https://github.com/noir-lang/noir/issues/3943)) ([2c5eceb](https://github.com/noir-lang/noir/commit/2c5eceb04ab6bc38e954492642121c7fe3da866f)) +* Improve `nargo test` output ([#3973](https://github.com/noir-lang/noir/issues/3973)) ([3ab5ff4](https://github.com/noir-lang/noir/commit/3ab5ff431145a1f747b698caed15caebaa145f04)) +* Make `constant_to_radix` emit a slice instead of an array ([#4049](https://github.com/noir-lang/noir/issues/4049)) ([5cdb1d0](https://github.com/noir-lang/noir/commit/5cdb1d0dabe2e38a1610f718747cc2fb4263339d)) +* Operator overloading & static trait method references resolving to generic impls ([#3967](https://github.com/noir-lang/noir/issues/3967)) ([f1de8fa](https://github.com/noir-lang/noir/commit/f1de8fa3247bcee624bcd7a0f89fe7c7cd8430f1)) +* Preserve brillig entrypoint functions without arguments ([#3951](https://github.com/noir-lang/noir/issues/3951)) ([1111465](https://github.com/noir-lang/noir/commit/1111465551557ed9e97e4b43d6eccc4b5896a39f)) +* Prevent `Instruction::Constrain`s for non-primitive types ([#3916](https://github.com/noir-lang/noir/issues/3916)) ([467948f](https://github.com/noir-lang/noir/commit/467948f9ee9ae65b4e2badaa1d15835fced3e835)) +* Remove panic for adding an invalid crate name in wasm compiler ([#3977](https://github.com/noir-lang/noir/issues/3977)) ([7a1baa5](https://github.com/noir-lang/noir/commit/7a1baa56faa2deb385ef1b6c9da9073dafd5a376)) +* Return error rather instead of panicking on invalid circuit ([#3976](https://github.com/noir-lang/noir/issues/3976)) ([67201bf](https://github.com/noir-lang/noir/commit/67201bfc21a9c8858aa86be9cd47d463fb78d925)) +* Search all levels of struct nesting before codegenning primitive types ([#3970](https://github.com/noir-lang/noir/issues/3970)) ([13ae014](https://github.com/noir-lang/noir/commit/13ae014ddcbd9eddb401c563b95053f7a1a89f1c)) +* Update generics docs to mention we have traits now ([#3980](https://github.com/noir-lang/noir/issues/3980)) ([c2acdf1](https://github.com/noir-lang/noir/commit/c2acdf1793a67abc9a074457e057a44da3b82c39)) + + +### Miscellaneous Chores + +* Ban nested slices ([#4018](https://github.com/noir-lang/noir/issues/4018)) ([f8a1fb7](https://github.com/noir-lang/noir/commit/f8a1fb7eed1ae4a9779eb16b142a64094aa603c6)) +* Remove circuit methods from noir_wasm ([#3869](https://github.com/noir-lang/noir/issues/3869)) ([12d884e](https://github.com/noir-lang/noir/commit/12d884e2b74efab7257626d8878ea1a7455ecf85)) +* Rename Arithmetic opcode to AssertZero ([#3840](https://github.com/noir-lang/noir/issues/3840)) ([836f171](https://github.com/noir-lang/noir/commit/836f17145c2901060706294461c2d282dd121b3e)) + ## [0.22.0](https://github.com/noir-lang/noir/compare/v0.21.0...v0.22.0) (2023-12-18) diff --git a/Cargo.lock b/Cargo.lock index 95be87306ce..1585d81d6e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "acir" -version = "0.38.0" +version = "0.39.0" dependencies = [ "acir_field", "base64 0.21.2", @@ -23,7 +23,7 @@ dependencies = [ [[package]] name = "acir_field" -version = "0.38.0" +version = "0.39.0" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -37,7 +37,7 @@ dependencies = [ [[package]] name = "acvm" -version = "0.38.0" +version = "0.39.0" dependencies = [ "acir", "acvm_blackbox_solver", @@ -53,7 +53,7 @@ dependencies = [ [[package]] name = "acvm_blackbox_solver" -version = "0.38.0" +version = "0.39.0" dependencies = [ "acir", "blake2", @@ -68,7 +68,7 @@ dependencies = [ [[package]] name = "acvm_js" -version = "0.38.0" +version = "0.39.0" dependencies = [ "acvm", "bn254_blackbox_solver", @@ -212,7 +212,7 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arena" -version = "0.22.0" +version = "0.23.0" dependencies = [ "generational-arena", ] @@ -416,7 +416,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "aztec_macros" -version = "0.22.0" +version = "0.23.0" dependencies = [ "iter-extended", "noirc_frontend", @@ -580,7 +580,7 @@ dependencies = [ [[package]] name = "bn254_blackbox_solver" -version = "0.38.0" +version = "0.39.0" dependencies = [ "acir", "acvm_blackbox_solver", @@ -602,7 +602,7 @@ dependencies = [ [[package]] name = "brillig" -version = "0.38.0" +version = "0.39.0" dependencies = [ "acir_field", "serde", @@ -610,7 +610,7 @@ dependencies = [ [[package]] name = "brillig_vm" -version = "0.38.0" +version = "0.39.0" dependencies = [ "acir", "acvm_blackbox_solver", @@ -1683,7 +1683,7 @@ dependencies = [ [[package]] name = "fm" -version = "0.22.0" +version = "0.23.0" dependencies = [ "codespan-reporting", "iter-extended", @@ -2295,7 +2295,7 @@ dependencies = [ [[package]] name = "iter-extended" -version = "0.22.0" +version = "0.23.0" [[package]] name = "itertools" @@ -2648,7 +2648,7 @@ checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" [[package]] name = "nargo" -version = "0.22.0" +version = "0.23.0" dependencies = [ "acvm", "codespan-reporting", @@ -2676,7 +2676,7 @@ dependencies = [ [[package]] name = "nargo_cli" -version = "0.22.0" +version = "0.23.0" dependencies = [ "acvm", "assert_cmd", @@ -2727,7 +2727,7 @@ dependencies = [ [[package]] name = "nargo_fmt" -version = "0.22.0" +version = "0.23.0" dependencies = [ "bytecount", "noirc_frontend", @@ -2739,7 +2739,7 @@ dependencies = [ [[package]] name = "nargo_toml" -version = "0.22.0" +version = "0.23.0" dependencies = [ "dirs", "fm", @@ -2812,7 +2812,7 @@ dependencies = [ [[package]] name = "noir_debugger" -version = "0.22.0" +version = "0.23.0" dependencies = [ "acvm", "assert_cmd", @@ -2835,7 +2835,7 @@ dependencies = [ [[package]] name = "noir_lsp" -version = "0.22.0" +version = "0.23.0" dependencies = [ "acvm", "async-lsp", @@ -2861,7 +2861,7 @@ dependencies = [ [[package]] name = "noir_wasm" -version = "0.22.0" +version = "0.23.0" dependencies = [ "acvm", "build-data", @@ -2884,7 +2884,7 @@ dependencies = [ [[package]] name = "noirc_abi" -version = "0.22.0" +version = "0.23.0" dependencies = [ "acvm", "iter-extended", @@ -2901,7 +2901,7 @@ dependencies = [ [[package]] name = "noirc_abi_wasm" -version = "0.22.0" +version = "0.23.0" dependencies = [ "acvm", "build-data", @@ -2918,7 +2918,7 @@ dependencies = [ [[package]] name = "noirc_driver" -version = "0.22.0" +version = "0.23.0" dependencies = [ "acvm", "aztec_macros", @@ -2931,6 +2931,7 @@ dependencies = [ "noirc_errors", "noirc_evaluator", "noirc_frontend", + "noirc_macros", "rust-embed", "serde", "tracing", @@ -2938,7 +2939,7 @@ dependencies = [ [[package]] name = "noirc_errors" -version = "0.22.0" +version = "0.23.0" dependencies = [ "acvm", "base64 0.21.2", @@ -2955,7 +2956,7 @@ dependencies = [ [[package]] name = "noirc_evaluator" -version = "0.22.0" +version = "0.23.0" dependencies = [ "acvm", "fxhash", @@ -2971,7 +2972,7 @@ dependencies = [ [[package]] name = "noirc_frontend" -version = "0.22.0" +version = "0.23.0" dependencies = [ "acvm", "arena", @@ -2993,9 +2994,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "noirc_macros" +version = "0.23.0" +dependencies = [ + "iter-extended", + "noirc_frontend", +] + [[package]] name = "noirc_printable_type" -version = "0.22.0" +version = "0.23.0" dependencies = [ "acvm", "iter-extended", diff --git a/Cargo.toml b/Cargo.toml index f0fc7249efc..2ba97e906b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [workspace] members = [ + # Macros crates for metaprogramming "aztec_macros", + "noirc_macros", + # Compiler crates "compiler/noirc_evaluator", "compiler/noirc_frontend", "compiler/noirc_errors", @@ -38,7 +41,7 @@ resolver = "2" [workspace.package] # x-release-please-start-version -version = "0.22.0" +version = "0.23.0" # x-release-please-end authors = ["The Noir Team "] edition = "2021" @@ -49,14 +52,13 @@ repository = "https://github.com/noir-lang/noir/" [workspace.dependencies] # ACVM workspace dependencies -acir_field = { version = "0.38.0", path = "acvm-repo/acir_field", default-features = false } -acir = { version = "0.38.0", path = "acvm-repo/acir", default-features = false } -acvm = { version = "0.38.0", path = "acvm-repo/acvm" } -stdlib = { version = "0.37.1", package = "acvm_stdlib", path = "acvm-repo/stdlib", default-features = false } -brillig = { version = "0.38.0", path = "acvm-repo/brillig", default-features = false } -brillig_vm = { version = "0.38.0", path = "acvm-repo/brillig_vm", default-features = false } -acvm_blackbox_solver = { version = "0.38.0", path = "acvm-repo/blackbox_solver", default-features = false } -bn254_blackbox_solver = { version = "0.38.0", path = "acvm-repo/bn254_blackbox_solver", default-features = false } +acir_field = { version = "0.39.0", path = "acvm-repo/acir_field", default-features = false } +acir = { version = "0.39.0", path = "acvm-repo/acir", default-features = false } +acvm = { version = "0.39.0", path = "acvm-repo/acvm" } +brillig = { version = "0.39.0", path = "acvm-repo/brillig", default-features = false } +brillig_vm = { version = "0.39.0", path = "acvm-repo/brillig_vm", default-features = false } +acvm_blackbox_solver = { version = "0.39.0", path = "acvm-repo/blackbox_solver", default-features = false } +bn254_blackbox_solver = { version = "0.39.0", path = "acvm-repo/bn254_blackbox_solver", default-features = false } # Noir compiler workspace dependencies arena = { path = "compiler/utils/arena" } diff --git a/acvm-repo/CHANGELOG.md b/acvm-repo/CHANGELOG.md index d413bd390c4..7f68244a7eb 100644 --- a/acvm-repo/CHANGELOG.md +++ b/acvm-repo/CHANGELOG.md @@ -5,6 +5,38 @@ 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.39.0](https://github.com/noir-lang/noir/compare/v0.38.0...v0.39.0) (2024-01-22) + + +### ⚠ BREAKING CHANGES + +* Breaking changes from aztec-packages ([#3955](https://github.com/noir-lang/noir/issues/3955)) +* Rename Arithmetic opcode to AssertZero ([#3840](https://github.com/noir-lang/noir/issues/3840)) +* Remove unused methods on ACIR opcodes ([#3841](https://github.com/noir-lang/noir/issues/3841)) +* Remove partial backend feature ([#3805](https://github.com/noir-lang/noir/issues/3805)) + +### Features + +* Aztec-packages ([#3754](https://github.com/noir-lang/noir/issues/3754)) ([c043265](https://github.com/noir-lang/noir/commit/c043265e550b59bd4296504826fe15d3ce3e9ad2)) +* Breaking changes from aztec-packages ([#3955](https://github.com/noir-lang/noir/issues/3955)) ([5be049e](https://github.com/noir-lang/noir/commit/5be049eee6c342649462282ee04f6411e6ea392c)) +* Remove range constraints from witnesses which are constrained to be constants ([#3928](https://github.com/noir-lang/noir/issues/3928)) ([afe9c7a](https://github.com/noir-lang/noir/commit/afe9c7a38bb9d4245205d3aa46d4ce23d70a5671)) +* Speed up transformation of debug messages ([#3815](https://github.com/noir-lang/noir/issues/3815)) ([2a8af1e](https://github.com/noir-lang/noir/commit/2a8af1e4141ffff61547ee1c2837a6392bd5db48)) +* Sync `aztec-packages` ([#4011](https://github.com/noir-lang/noir/issues/4011)) ([fee2452](https://github.com/noir-lang/noir/commit/fee24523c427c27f0bdaf98ea09a852a2da3e94c)) +* Sync commits from `aztec-packages` ([#4068](https://github.com/noir-lang/noir/issues/4068)) ([7a8f3a3](https://github.com/noir-lang/noir/commit/7a8f3a33b57875e681e3d81e667e3570a1cdbdcc)) + + +### Bug Fixes + +* Deserialize odd length hex literals ([#3747](https://github.com/noir-lang/noir/issues/3747)) ([4000fb2](https://github.com/noir-lang/noir/commit/4000fb279221eb07187d657bfaa7f1c7b311abf2)) +* Return error rather instead of panicking on invalid circuit ([#3976](https://github.com/noir-lang/noir/issues/3976)) ([67201bf](https://github.com/noir-lang/noir/commit/67201bfc21a9c8858aa86be9cd47d463fb78d925)) + + +### Miscellaneous Chores + +* Remove partial backend feature ([#3805](https://github.com/noir-lang/noir/issues/3805)) ([0383100](https://github.com/noir-lang/noir/commit/0383100853a80a5b28b797cdfeae0d271f1b7805)) +* Remove unused methods on ACIR opcodes ([#3841](https://github.com/noir-lang/noir/issues/3841)) ([9e5d0e8](https://github.com/noir-lang/noir/commit/9e5d0e813d61a0bfb5ee68174ed287c5a20f1579)) +* Rename Arithmetic opcode to AssertZero ([#3840](https://github.com/noir-lang/noir/issues/3840)) ([836f171](https://github.com/noir-lang/noir/commit/836f17145c2901060706294461c2d282dd121b3e)) + ## [0.38.0](https://github.com/noir-lang/noir/compare/v0.37.1...v0.38.0) (2023-12-18) diff --git a/acvm-repo/acir/Cargo.toml b/acvm-repo/acir/Cargo.toml index b44c64dd838..49b10c57cc8 100644 --- a/acvm-repo/acir/Cargo.toml +++ b/acvm-repo/acir/Cargo.toml @@ -2,7 +2,7 @@ name = "acir" description = "ACIR is the IR that the VM processes, it is analogous to LLVM IR" # x-release-please-start-version -version = "0.38.0" +version = "0.39.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/acvm-repo/acir/acir_docs.md b/acvm-repo/acir/README.md similarity index 100% rename from acvm-repo/acir/acir_docs.md rename to acvm-repo/acir/README.md diff --git a/acvm-repo/acir/codegen/acir.cpp b/acvm-repo/acir/codegen/acir.cpp index 9b74a8ea631..0f94e91ab10 100644 --- a/acvm-repo/acir/codegen/acir.cpp +++ b/acvm-repo/acir/codegen/acir.cpp @@ -157,16 +157,6 @@ namespace Circuit { static EmbeddedCurveAdd bincodeDeserialize(std::vector); }; - struct EmbeddedCurveDouble { - Circuit::FunctionInput input_x; - Circuit::FunctionInput input_y; - std::array outputs; - - friend bool operator==(const EmbeddedCurveDouble&, const EmbeddedCurveDouble&); - std::vector bincodeSerialize() const; - static EmbeddedCurveDouble bincodeDeserialize(std::vector); - }; - struct Keccak256 { std::vector inputs; std::vector outputs; @@ -206,7 +196,86 @@ namespace Circuit { static RecursiveAggregation bincodeDeserialize(std::vector); }; - std::variant value; + struct BigIntAdd { + uint32_t lhs; + uint32_t rhs; + uint32_t output; + + friend bool operator==(const BigIntAdd&, const BigIntAdd&); + std::vector bincodeSerialize() const; + static BigIntAdd bincodeDeserialize(std::vector); + }; + + struct BigIntNeg { + uint32_t lhs; + uint32_t rhs; + uint32_t output; + + friend bool operator==(const BigIntNeg&, const BigIntNeg&); + std::vector bincodeSerialize() const; + static BigIntNeg bincodeDeserialize(std::vector); + }; + + struct BigIntMul { + uint32_t lhs; + uint32_t rhs; + uint32_t output; + + friend bool operator==(const BigIntMul&, const BigIntMul&); + std::vector bincodeSerialize() const; + static BigIntMul bincodeDeserialize(std::vector); + }; + + struct BigIntDiv { + uint32_t lhs; + uint32_t rhs; + uint32_t output; + + friend bool operator==(const BigIntDiv&, const BigIntDiv&); + std::vector bincodeSerialize() const; + static BigIntDiv bincodeDeserialize(std::vector); + }; + + struct BigIntFromLeBytes { + std::vector inputs; + std::vector modulus; + uint32_t output; + + friend bool operator==(const BigIntFromLeBytes&, const BigIntFromLeBytes&); + std::vector bincodeSerialize() const; + static BigIntFromLeBytes bincodeDeserialize(std::vector); + }; + + struct BigIntToLeBytes { + uint32_t input; + std::vector outputs; + + friend bool operator==(const BigIntToLeBytes&, const BigIntToLeBytes&); + std::vector bincodeSerialize() const; + static BigIntToLeBytes bincodeDeserialize(std::vector); + }; + + struct Poseidon2Permutation { + std::vector inputs; + std::vector outputs; + uint32_t len; + + friend bool operator==(const Poseidon2Permutation&, const Poseidon2Permutation&); + std::vector bincodeSerialize() const; + static Poseidon2Permutation bincodeDeserialize(std::vector); + }; + + struct Sha256Compression { + std::vector inputs; + std::vector hash_values; + std::vector outputs; + + friend bool operator==(const Sha256Compression&, const Sha256Compression&); + std::vector bincodeSerialize() const; + static Sha256Compression bincodeDeserialize(std::vector); + }; + + std::variant value; friend bool operator==(const BlackBoxFuncCall&, const BlackBoxFuncCall&); std::vector bincodeSerialize() const; @@ -533,17 +602,86 @@ namespace Circuit { static EmbeddedCurveAdd bincodeDeserialize(std::vector); }; - struct EmbeddedCurveDouble { - Circuit::RegisterIndex input1_x; - Circuit::RegisterIndex input1_y; - Circuit::HeapArray result; + struct BigIntAdd { + Circuit::RegisterIndex lhs; + Circuit::RegisterIndex rhs; + Circuit::RegisterIndex output; + + friend bool operator==(const BigIntAdd&, const BigIntAdd&); + std::vector bincodeSerialize() const; + static BigIntAdd bincodeDeserialize(std::vector); + }; + + struct BigIntNeg { + Circuit::RegisterIndex lhs; + Circuit::RegisterIndex rhs; + Circuit::RegisterIndex output; + + friend bool operator==(const BigIntNeg&, const BigIntNeg&); + std::vector bincodeSerialize() const; + static BigIntNeg bincodeDeserialize(std::vector); + }; + + struct BigIntMul { + Circuit::RegisterIndex lhs; + Circuit::RegisterIndex rhs; + Circuit::RegisterIndex output; + + friend bool operator==(const BigIntMul&, const BigIntMul&); + std::vector bincodeSerialize() const; + static BigIntMul bincodeDeserialize(std::vector); + }; + + struct BigIntDiv { + Circuit::RegisterIndex lhs; + Circuit::RegisterIndex rhs; + Circuit::RegisterIndex output; + + friend bool operator==(const BigIntDiv&, const BigIntDiv&); + std::vector bincodeSerialize() const; + static BigIntDiv bincodeDeserialize(std::vector); + }; + + struct BigIntFromLeBytes { + Circuit::HeapVector inputs; + Circuit::HeapVector modulus; + Circuit::RegisterIndex output; + + friend bool operator==(const BigIntFromLeBytes&, const BigIntFromLeBytes&); + std::vector bincodeSerialize() const; + static BigIntFromLeBytes bincodeDeserialize(std::vector); + }; + + struct BigIntToLeBytes { + Circuit::RegisterIndex input; + Circuit::HeapVector output; - friend bool operator==(const EmbeddedCurveDouble&, const EmbeddedCurveDouble&); + friend bool operator==(const BigIntToLeBytes&, const BigIntToLeBytes&); std::vector bincodeSerialize() const; - static EmbeddedCurveDouble bincodeDeserialize(std::vector); + static BigIntToLeBytes bincodeDeserialize(std::vector); }; - std::variant value; + struct Poseidon2Permutation { + Circuit::HeapVector message; + Circuit::HeapArray output; + Circuit::RegisterIndex len; + + friend bool operator==(const Poseidon2Permutation&, const Poseidon2Permutation&); + std::vector bincodeSerialize() const; + static Poseidon2Permutation bincodeDeserialize(std::vector); + }; + + struct Sha256Compression { + Circuit::HeapVector input; + Circuit::HeapVector hash_values; + Circuit::HeapArray output; + + friend bool operator==(const Sha256Compression&, const Sha256Compression&); + std::vector bincodeSerialize() const; + static Sha256Compression bincodeDeserialize(std::vector); + }; + + std::variant value; friend bool operator==(const BlackBoxOp&, const BlackBoxOp&); std::vector bincodeSerialize() const; @@ -2252,50 +2390,6 @@ Circuit::BlackBoxFuncCall::EmbeddedCurveAdd serde::Deserializable BlackBoxFuncCall::EmbeddedCurveDouble::bincodeSerialize() const { - auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); - return std::move(serializer).bytes(); - } - - inline BlackBoxFuncCall::EmbeddedCurveDouble BlackBoxFuncCall::EmbeddedCurveDouble::bincodeDeserialize(std::vector input) { - auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); - if (deserializer.get_buffer_offset() < input.size()) { - throw serde::deserialization_error("Some input bytes were not read"); - } - return value; - } - -} // end of namespace Circuit - -template <> -template -void serde::Serializable::serialize(const Circuit::BlackBoxFuncCall::EmbeddedCurveDouble &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.input_x, serializer); - serde::Serializable::serialize(obj.input_y, serializer); - serde::Serializable::serialize(obj.outputs, serializer); -} - -template <> -template -Circuit::BlackBoxFuncCall::EmbeddedCurveDouble serde::Deserializable::deserialize(Deserializer &deserializer) { - Circuit::BlackBoxFuncCall::EmbeddedCurveDouble obj; - obj.input_x = serde::Deserializable::deserialize(deserializer); - obj.input_y = serde::Deserializable::deserialize(deserializer); - obj.outputs = serde::Deserializable::deserialize(deserializer); - return obj; -} - namespace Circuit { inline bool operator==(const BlackBoxFuncCall::Keccak256 &lhs, const BlackBoxFuncCall::Keccak256 &rhs) { @@ -2471,20 +2565,22 @@ Circuit::BlackBoxFuncCall::RecursiveAggregation serde::Deserializable BlackBoxOp::bincodeSerialize() const { + inline std::vector BlackBoxFuncCall::BigIntAdd::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline BlackBoxOp BlackBoxOp::bincodeDeserialize(std::vector input) { + inline BlackBoxFuncCall::BigIntAdd BlackBoxFuncCall::BigIntAdd::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -2495,39 +2591,40 @@ namespace Circuit { template <> template -void serde::Serializable::serialize(const Circuit::BlackBoxOp &obj, Serializer &serializer) { - serializer.increase_container_depth(); - serde::Serializable::serialize(obj.value, serializer); - serializer.decrease_container_depth(); +void serde::Serializable::serialize(const Circuit::BlackBoxFuncCall::BigIntAdd &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.lhs, serializer); + serde::Serializable::serialize(obj.rhs, serializer); + serde::Serializable::serialize(obj.output, serializer); } template <> template -Circuit::BlackBoxOp serde::Deserializable::deserialize(Deserializer &deserializer) { - deserializer.increase_container_depth(); - Circuit::BlackBoxOp obj; - obj.value = serde::Deserializable::deserialize(deserializer); - deserializer.decrease_container_depth(); +Circuit::BlackBoxFuncCall::BigIntAdd serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxFuncCall::BigIntAdd obj; + obj.lhs = serde::Deserializable::deserialize(deserializer); + obj.rhs = serde::Deserializable::deserialize(deserializer); + obj.output = serde::Deserializable::deserialize(deserializer); return obj; } namespace Circuit { - inline bool operator==(const BlackBoxOp::Sha256 &lhs, const BlackBoxOp::Sha256 &rhs) { - if (!(lhs.message == rhs.message)) { return false; } + inline bool operator==(const BlackBoxFuncCall::BigIntNeg &lhs, const BlackBoxFuncCall::BigIntNeg &rhs) { + if (!(lhs.lhs == rhs.lhs)) { return false; } + if (!(lhs.rhs == rhs.rhs)) { return false; } if (!(lhs.output == rhs.output)) { return false; } return true; } - inline std::vector BlackBoxOp::Sha256::bincodeSerialize() const { + inline std::vector BlackBoxFuncCall::BigIntNeg::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline BlackBoxOp::Sha256 BlackBoxOp::Sha256::bincodeDeserialize(std::vector input) { + inline BlackBoxFuncCall::BigIntNeg BlackBoxFuncCall::BigIntNeg::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -2538,37 +2635,40 @@ namespace Circuit { template <> template -void serde::Serializable::serialize(const Circuit::BlackBoxOp::Sha256 &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.message, serializer); +void serde::Serializable::serialize(const Circuit::BlackBoxFuncCall::BigIntNeg &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.lhs, serializer); + serde::Serializable::serialize(obj.rhs, serializer); serde::Serializable::serialize(obj.output, serializer); } template <> template -Circuit::BlackBoxOp::Sha256 serde::Deserializable::deserialize(Deserializer &deserializer) { - Circuit::BlackBoxOp::Sha256 obj; - obj.message = serde::Deserializable::deserialize(deserializer); +Circuit::BlackBoxFuncCall::BigIntNeg serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxFuncCall::BigIntNeg obj; + obj.lhs = serde::Deserializable::deserialize(deserializer); + obj.rhs = serde::Deserializable::deserialize(deserializer); obj.output = serde::Deserializable::deserialize(deserializer); return obj; } namespace Circuit { - inline bool operator==(const BlackBoxOp::Blake2s &lhs, const BlackBoxOp::Blake2s &rhs) { - if (!(lhs.message == rhs.message)) { return false; } + inline bool operator==(const BlackBoxFuncCall::BigIntMul &lhs, const BlackBoxFuncCall::BigIntMul &rhs) { + if (!(lhs.lhs == rhs.lhs)) { return false; } + if (!(lhs.rhs == rhs.rhs)) { return false; } if (!(lhs.output == rhs.output)) { return false; } return true; } - inline std::vector BlackBoxOp::Blake2s::bincodeSerialize() const { + inline std::vector BlackBoxFuncCall::BigIntMul::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline BlackBoxOp::Blake2s BlackBoxOp::Blake2s::bincodeDeserialize(std::vector input) { + inline BlackBoxFuncCall::BigIntMul BlackBoxFuncCall::BigIntMul::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -2579,37 +2679,40 @@ namespace Circuit { template <> template -void serde::Serializable::serialize(const Circuit::BlackBoxOp::Blake2s &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.message, serializer); +void serde::Serializable::serialize(const Circuit::BlackBoxFuncCall::BigIntMul &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.lhs, serializer); + serde::Serializable::serialize(obj.rhs, serializer); serde::Serializable::serialize(obj.output, serializer); } template <> template -Circuit::BlackBoxOp::Blake2s serde::Deserializable::deserialize(Deserializer &deserializer) { - Circuit::BlackBoxOp::Blake2s obj; - obj.message = serde::Deserializable::deserialize(deserializer); +Circuit::BlackBoxFuncCall::BigIntMul serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxFuncCall::BigIntMul obj; + obj.lhs = serde::Deserializable::deserialize(deserializer); + obj.rhs = serde::Deserializable::deserialize(deserializer); obj.output = serde::Deserializable::deserialize(deserializer); return obj; } namespace Circuit { - inline bool operator==(const BlackBoxOp::Blake3 &lhs, const BlackBoxOp::Blake3 &rhs) { - if (!(lhs.message == rhs.message)) { return false; } + inline bool operator==(const BlackBoxFuncCall::BigIntDiv &lhs, const BlackBoxFuncCall::BigIntDiv &rhs) { + if (!(lhs.lhs == rhs.lhs)) { return false; } + if (!(lhs.rhs == rhs.rhs)) { return false; } if (!(lhs.output == rhs.output)) { return false; } return true; } - inline std::vector BlackBoxOp::Blake3::bincodeSerialize() const { + inline std::vector BlackBoxFuncCall::BigIntDiv::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline BlackBoxOp::Blake3 BlackBoxOp::Blake3::bincodeDeserialize(std::vector input) { + inline BlackBoxFuncCall::BigIntDiv BlackBoxFuncCall::BigIntDiv::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -2620,37 +2723,40 @@ namespace Circuit { template <> template -void serde::Serializable::serialize(const Circuit::BlackBoxOp::Blake3 &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.message, serializer); +void serde::Serializable::serialize(const Circuit::BlackBoxFuncCall::BigIntDiv &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.lhs, serializer); + serde::Serializable::serialize(obj.rhs, serializer); serde::Serializable::serialize(obj.output, serializer); } template <> template -Circuit::BlackBoxOp::Blake3 serde::Deserializable::deserialize(Deserializer &deserializer) { - Circuit::BlackBoxOp::Blake3 obj; - obj.message = serde::Deserializable::deserialize(deserializer); +Circuit::BlackBoxFuncCall::BigIntDiv serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxFuncCall::BigIntDiv obj; + obj.lhs = serde::Deserializable::deserialize(deserializer); + obj.rhs = serde::Deserializable::deserialize(deserializer); obj.output = serde::Deserializable::deserialize(deserializer); return obj; } namespace Circuit { - inline bool operator==(const BlackBoxOp::Keccak256 &lhs, const BlackBoxOp::Keccak256 &rhs) { - if (!(lhs.message == rhs.message)) { return false; } + inline bool operator==(const BlackBoxFuncCall::BigIntFromLeBytes &lhs, const BlackBoxFuncCall::BigIntFromLeBytes &rhs) { + if (!(lhs.inputs == rhs.inputs)) { return false; } + if (!(lhs.modulus == rhs.modulus)) { return false; } if (!(lhs.output == rhs.output)) { return false; } return true; } - inline std::vector BlackBoxOp::Keccak256::bincodeSerialize() const { + inline std::vector BlackBoxFuncCall::BigIntFromLeBytes::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline BlackBoxOp::Keccak256 BlackBoxOp::Keccak256::bincodeDeserialize(std::vector input) { + inline BlackBoxFuncCall::BigIntFromLeBytes BlackBoxFuncCall::BigIntFromLeBytes::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -2661,37 +2767,39 @@ namespace Circuit { template <> template -void serde::Serializable::serialize(const Circuit::BlackBoxOp::Keccak256 &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.message, serializer); +void serde::Serializable::serialize(const Circuit::BlackBoxFuncCall::BigIntFromLeBytes &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.inputs, serializer); + serde::Serializable::serialize(obj.modulus, serializer); serde::Serializable::serialize(obj.output, serializer); } template <> template -Circuit::BlackBoxOp::Keccak256 serde::Deserializable::deserialize(Deserializer &deserializer) { - Circuit::BlackBoxOp::Keccak256 obj; - obj.message = serde::Deserializable::deserialize(deserializer); +Circuit::BlackBoxFuncCall::BigIntFromLeBytes serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxFuncCall::BigIntFromLeBytes obj; + obj.inputs = serde::Deserializable::deserialize(deserializer); + obj.modulus = serde::Deserializable::deserialize(deserializer); obj.output = serde::Deserializable::deserialize(deserializer); return obj; } namespace Circuit { - inline bool operator==(const BlackBoxOp::Keccakf1600 &lhs, const BlackBoxOp::Keccakf1600 &rhs) { - if (!(lhs.message == rhs.message)) { return false; } - if (!(lhs.output == rhs.output)) { return false; } + inline bool operator==(const BlackBoxFuncCall::BigIntToLeBytes &lhs, const BlackBoxFuncCall::BigIntToLeBytes &rhs) { + if (!(lhs.input == rhs.input)) { return false; } + if (!(lhs.outputs == rhs.outputs)) { return false; } return true; } - inline std::vector BlackBoxOp::Keccakf1600::bincodeSerialize() const { + inline std::vector BlackBoxFuncCall::BigIntToLeBytes::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline BlackBoxOp::Keccakf1600 BlackBoxOp::Keccakf1600::bincodeDeserialize(std::vector input) { + inline BlackBoxFuncCall::BigIntToLeBytes BlackBoxFuncCall::BigIntToLeBytes::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -2702,40 +2810,38 @@ namespace Circuit { template <> template -void serde::Serializable::serialize(const Circuit::BlackBoxOp::Keccakf1600 &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.message, serializer); - serde::Serializable::serialize(obj.output, serializer); +void serde::Serializable::serialize(const Circuit::BlackBoxFuncCall::BigIntToLeBytes &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.input, serializer); + serde::Serializable::serialize(obj.outputs, serializer); } template <> template -Circuit::BlackBoxOp::Keccakf1600 serde::Deserializable::deserialize(Deserializer &deserializer) { - Circuit::BlackBoxOp::Keccakf1600 obj; - obj.message = serde::Deserializable::deserialize(deserializer); - obj.output = serde::Deserializable::deserialize(deserializer); +Circuit::BlackBoxFuncCall::BigIntToLeBytes serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxFuncCall::BigIntToLeBytes obj; + obj.input = serde::Deserializable::deserialize(deserializer); + obj.outputs = serde::Deserializable::deserialize(deserializer); return obj; } namespace Circuit { - inline bool operator==(const BlackBoxOp::EcdsaSecp256k1 &lhs, const BlackBoxOp::EcdsaSecp256k1 &rhs) { - if (!(lhs.hashed_msg == rhs.hashed_msg)) { return false; } - if (!(lhs.public_key_x == rhs.public_key_x)) { return false; } - if (!(lhs.public_key_y == rhs.public_key_y)) { return false; } - if (!(lhs.signature == rhs.signature)) { return false; } - if (!(lhs.result == rhs.result)) { return false; } + inline bool operator==(const BlackBoxFuncCall::Poseidon2Permutation &lhs, const BlackBoxFuncCall::Poseidon2Permutation &rhs) { + if (!(lhs.inputs == rhs.inputs)) { return false; } + if (!(lhs.outputs == rhs.outputs)) { return false; } + if (!(lhs.len == rhs.len)) { return false; } return true; } - inline std::vector BlackBoxOp::EcdsaSecp256k1::bincodeSerialize() const { + inline std::vector BlackBoxFuncCall::Poseidon2Permutation::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline BlackBoxOp::EcdsaSecp256k1 BlackBoxOp::EcdsaSecp256k1::bincodeDeserialize(std::vector input) { + inline BlackBoxFuncCall::Poseidon2Permutation BlackBoxFuncCall::Poseidon2Permutation::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -2746,46 +2852,615 @@ namespace Circuit { template <> template -void serde::Serializable::serialize(const Circuit::BlackBoxOp::EcdsaSecp256k1 &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.hashed_msg, serializer); - serde::Serializable::serialize(obj.public_key_x, serializer); - serde::Serializable::serialize(obj.public_key_y, serializer); - serde::Serializable::serialize(obj.signature, serializer); - serde::Serializable::serialize(obj.result, serializer); +void serde::Serializable::serialize(const Circuit::BlackBoxFuncCall::Poseidon2Permutation &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.inputs, serializer); + serde::Serializable::serialize(obj.outputs, serializer); + serde::Serializable::serialize(obj.len, serializer); } template <> template -Circuit::BlackBoxOp::EcdsaSecp256k1 serde::Deserializable::deserialize(Deserializer &deserializer) { - Circuit::BlackBoxOp::EcdsaSecp256k1 obj; - obj.hashed_msg = serde::Deserializable::deserialize(deserializer); - obj.public_key_x = serde::Deserializable::deserialize(deserializer); - obj.public_key_y = serde::Deserializable::deserialize(deserializer); - obj.signature = serde::Deserializable::deserialize(deserializer); +Circuit::BlackBoxFuncCall::Poseidon2Permutation serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxFuncCall::Poseidon2Permutation obj; + obj.inputs = serde::Deserializable::deserialize(deserializer); + obj.outputs = serde::Deserializable::deserialize(deserializer); + obj.len = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxFuncCall::Sha256Compression &lhs, const BlackBoxFuncCall::Sha256Compression &rhs) { + if (!(lhs.inputs == rhs.inputs)) { return false; } + if (!(lhs.hash_values == rhs.hash_values)) { return false; } + if (!(lhs.outputs == rhs.outputs)) { return false; } + return true; + } + + inline std::vector BlackBoxFuncCall::Sha256Compression::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxFuncCall::Sha256Compression BlackBoxFuncCall::Sha256Compression::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxFuncCall::Sha256Compression &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.inputs, serializer); + serde::Serializable::serialize(obj.hash_values, serializer); + serde::Serializable::serialize(obj.outputs, serializer); +} + +template <> +template +Circuit::BlackBoxFuncCall::Sha256Compression serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxFuncCall::Sha256Compression obj; + obj.inputs = serde::Deserializable::deserialize(deserializer); + obj.hash_values = serde::Deserializable::deserialize(deserializer); + obj.outputs = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxOp &lhs, const BlackBoxOp &rhs) { + if (!(lhs.value == rhs.value)) { return false; } + return true; + } + + inline std::vector BlackBoxOp::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp BlackBoxOp::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxOp &obj, Serializer &serializer) { + serializer.increase_container_depth(); + serde::Serializable::serialize(obj.value, serializer); + serializer.decrease_container_depth(); +} + +template <> +template +Circuit::BlackBoxOp serde::Deserializable::deserialize(Deserializer &deserializer) { + deserializer.increase_container_depth(); + Circuit::BlackBoxOp obj; + obj.value = serde::Deserializable::deserialize(deserializer); + deserializer.decrease_container_depth(); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxOp::Sha256 &lhs, const BlackBoxOp::Sha256 &rhs) { + if (!(lhs.message == rhs.message)) { return false; } + if (!(lhs.output == rhs.output)) { return false; } + return true; + } + + inline std::vector BlackBoxOp::Sha256::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp::Sha256 BlackBoxOp::Sha256::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxOp::Sha256 &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.message, serializer); + serde::Serializable::serialize(obj.output, serializer); +} + +template <> +template +Circuit::BlackBoxOp::Sha256 serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::Sha256 obj; + obj.message = serde::Deserializable::deserialize(deserializer); + obj.output = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxOp::Blake2s &lhs, const BlackBoxOp::Blake2s &rhs) { + if (!(lhs.message == rhs.message)) { return false; } + if (!(lhs.output == rhs.output)) { return false; } + return true; + } + + inline std::vector BlackBoxOp::Blake2s::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp::Blake2s BlackBoxOp::Blake2s::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxOp::Blake2s &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.message, serializer); + serde::Serializable::serialize(obj.output, serializer); +} + +template <> +template +Circuit::BlackBoxOp::Blake2s serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::Blake2s obj; + obj.message = serde::Deserializable::deserialize(deserializer); + obj.output = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxOp::Blake3 &lhs, const BlackBoxOp::Blake3 &rhs) { + if (!(lhs.message == rhs.message)) { return false; } + if (!(lhs.output == rhs.output)) { return false; } + return true; + } + + inline std::vector BlackBoxOp::Blake3::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp::Blake3 BlackBoxOp::Blake3::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxOp::Blake3 &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.message, serializer); + serde::Serializable::serialize(obj.output, serializer); +} + +template <> +template +Circuit::BlackBoxOp::Blake3 serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::Blake3 obj; + obj.message = serde::Deserializable::deserialize(deserializer); + obj.output = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxOp::Keccak256 &lhs, const BlackBoxOp::Keccak256 &rhs) { + if (!(lhs.message == rhs.message)) { return false; } + if (!(lhs.output == rhs.output)) { return false; } + return true; + } + + inline std::vector BlackBoxOp::Keccak256::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp::Keccak256 BlackBoxOp::Keccak256::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxOp::Keccak256 &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.message, serializer); + serde::Serializable::serialize(obj.output, serializer); +} + +template <> +template +Circuit::BlackBoxOp::Keccak256 serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::Keccak256 obj; + obj.message = serde::Deserializable::deserialize(deserializer); + obj.output = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxOp::Keccakf1600 &lhs, const BlackBoxOp::Keccakf1600 &rhs) { + if (!(lhs.message == rhs.message)) { return false; } + if (!(lhs.output == rhs.output)) { return false; } + return true; + } + + inline std::vector BlackBoxOp::Keccakf1600::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp::Keccakf1600 BlackBoxOp::Keccakf1600::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxOp::Keccakf1600 &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.message, serializer); + serde::Serializable::serialize(obj.output, serializer); +} + +template <> +template +Circuit::BlackBoxOp::Keccakf1600 serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::Keccakf1600 obj; + obj.message = serde::Deserializable::deserialize(deserializer); + obj.output = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxOp::EcdsaSecp256k1 &lhs, const BlackBoxOp::EcdsaSecp256k1 &rhs) { + if (!(lhs.hashed_msg == rhs.hashed_msg)) { return false; } + if (!(lhs.public_key_x == rhs.public_key_x)) { return false; } + if (!(lhs.public_key_y == rhs.public_key_y)) { return false; } + if (!(lhs.signature == rhs.signature)) { return false; } + if (!(lhs.result == rhs.result)) { return false; } + return true; + } + + inline std::vector BlackBoxOp::EcdsaSecp256k1::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp::EcdsaSecp256k1 BlackBoxOp::EcdsaSecp256k1::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxOp::EcdsaSecp256k1 &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.hashed_msg, serializer); + serde::Serializable::serialize(obj.public_key_x, serializer); + serde::Serializable::serialize(obj.public_key_y, serializer); + serde::Serializable::serialize(obj.signature, serializer); + serde::Serializable::serialize(obj.result, serializer); +} + +template <> +template +Circuit::BlackBoxOp::EcdsaSecp256k1 serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::EcdsaSecp256k1 obj; + obj.hashed_msg = serde::Deserializable::deserialize(deserializer); + obj.public_key_x = serde::Deserializable::deserialize(deserializer); + obj.public_key_y = serde::Deserializable::deserialize(deserializer); + obj.signature = serde::Deserializable::deserialize(deserializer); + obj.result = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxOp::EcdsaSecp256r1 &lhs, const BlackBoxOp::EcdsaSecp256r1 &rhs) { + if (!(lhs.hashed_msg == rhs.hashed_msg)) { return false; } + if (!(lhs.public_key_x == rhs.public_key_x)) { return false; } + if (!(lhs.public_key_y == rhs.public_key_y)) { return false; } + if (!(lhs.signature == rhs.signature)) { return false; } + if (!(lhs.result == rhs.result)) { return false; } + return true; + } + + inline std::vector BlackBoxOp::EcdsaSecp256r1::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp::EcdsaSecp256r1 BlackBoxOp::EcdsaSecp256r1::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxOp::EcdsaSecp256r1 &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.hashed_msg, serializer); + serde::Serializable::serialize(obj.public_key_x, serializer); + serde::Serializable::serialize(obj.public_key_y, serializer); + serde::Serializable::serialize(obj.signature, serializer); + serde::Serializable::serialize(obj.result, serializer); +} + +template <> +template +Circuit::BlackBoxOp::EcdsaSecp256r1 serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::EcdsaSecp256r1 obj; + obj.hashed_msg = serde::Deserializable::deserialize(deserializer); + obj.public_key_x = serde::Deserializable::deserialize(deserializer); + obj.public_key_y = serde::Deserializable::deserialize(deserializer); + obj.signature = serde::Deserializable::deserialize(deserializer); + obj.result = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxOp::SchnorrVerify &lhs, const BlackBoxOp::SchnorrVerify &rhs) { + if (!(lhs.public_key_x == rhs.public_key_x)) { return false; } + if (!(lhs.public_key_y == rhs.public_key_y)) { return false; } + if (!(lhs.message == rhs.message)) { return false; } + if (!(lhs.signature == rhs.signature)) { return false; } + if (!(lhs.result == rhs.result)) { return false; } + return true; + } + + inline std::vector BlackBoxOp::SchnorrVerify::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp::SchnorrVerify BlackBoxOp::SchnorrVerify::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxOp::SchnorrVerify &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.public_key_x, serializer); + serde::Serializable::serialize(obj.public_key_y, serializer); + serde::Serializable::serialize(obj.message, serializer); + serde::Serializable::serialize(obj.signature, serializer); + serde::Serializable::serialize(obj.result, serializer); +} + +template <> +template +Circuit::BlackBoxOp::SchnorrVerify serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::SchnorrVerify obj; + obj.public_key_x = serde::Deserializable::deserialize(deserializer); + obj.public_key_y = serde::Deserializable::deserialize(deserializer); + obj.message = serde::Deserializable::deserialize(deserializer); + obj.signature = serde::Deserializable::deserialize(deserializer); + obj.result = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxOp::PedersenCommitment &lhs, const BlackBoxOp::PedersenCommitment &rhs) { + if (!(lhs.inputs == rhs.inputs)) { return false; } + if (!(lhs.domain_separator == rhs.domain_separator)) { return false; } + if (!(lhs.output == rhs.output)) { return false; } + return true; + } + + inline std::vector BlackBoxOp::PedersenCommitment::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp::PedersenCommitment BlackBoxOp::PedersenCommitment::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxOp::PedersenCommitment &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.inputs, serializer); + serde::Serializable::serialize(obj.domain_separator, serializer); + serde::Serializable::serialize(obj.output, serializer); +} + +template <> +template +Circuit::BlackBoxOp::PedersenCommitment serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::PedersenCommitment obj; + obj.inputs = serde::Deserializable::deserialize(deserializer); + obj.domain_separator = serde::Deserializable::deserialize(deserializer); + obj.output = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxOp::PedersenHash &lhs, const BlackBoxOp::PedersenHash &rhs) { + if (!(lhs.inputs == rhs.inputs)) { return false; } + if (!(lhs.domain_separator == rhs.domain_separator)) { return false; } + if (!(lhs.output == rhs.output)) { return false; } + return true; + } + + inline std::vector BlackBoxOp::PedersenHash::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp::PedersenHash BlackBoxOp::PedersenHash::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxOp::PedersenHash &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.inputs, serializer); + serde::Serializable::serialize(obj.domain_separator, serializer); + serde::Serializable::serialize(obj.output, serializer); +} + +template <> +template +Circuit::BlackBoxOp::PedersenHash serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::PedersenHash obj; + obj.inputs = serde::Deserializable::deserialize(deserializer); + obj.domain_separator = serde::Deserializable::deserialize(deserializer); + obj.output = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxOp::FixedBaseScalarMul &lhs, const BlackBoxOp::FixedBaseScalarMul &rhs) { + if (!(lhs.low == rhs.low)) { return false; } + if (!(lhs.high == rhs.high)) { return false; } + if (!(lhs.result == rhs.result)) { return false; } + return true; + } + + inline std::vector BlackBoxOp::FixedBaseScalarMul::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp::FixedBaseScalarMul BlackBoxOp::FixedBaseScalarMul::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxOp::FixedBaseScalarMul &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.low, serializer); + serde::Serializable::serialize(obj.high, serializer); + serde::Serializable::serialize(obj.result, serializer); +} + +template <> +template +Circuit::BlackBoxOp::FixedBaseScalarMul serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::FixedBaseScalarMul obj; + obj.low = serde::Deserializable::deserialize(deserializer); + obj.high = serde::Deserializable::deserialize(deserializer); obj.result = serde::Deserializable::deserialize(deserializer); return obj; } namespace Circuit { - inline bool operator==(const BlackBoxOp::EcdsaSecp256r1 &lhs, const BlackBoxOp::EcdsaSecp256r1 &rhs) { - if (!(lhs.hashed_msg == rhs.hashed_msg)) { return false; } - if (!(lhs.public_key_x == rhs.public_key_x)) { return false; } - if (!(lhs.public_key_y == rhs.public_key_y)) { return false; } - if (!(lhs.signature == rhs.signature)) { return false; } + inline bool operator==(const BlackBoxOp::EmbeddedCurveAdd &lhs, const BlackBoxOp::EmbeddedCurveAdd &rhs) { + if (!(lhs.input1_x == rhs.input1_x)) { return false; } + if (!(lhs.input1_y == rhs.input1_y)) { return false; } + if (!(lhs.input2_x == rhs.input2_x)) { return false; } + if (!(lhs.input2_y == rhs.input2_y)) { return false; } if (!(lhs.result == rhs.result)) { return false; } return true; } - inline std::vector BlackBoxOp::EcdsaSecp256r1::bincodeSerialize() const { + inline std::vector BlackBoxOp::EmbeddedCurveAdd::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline BlackBoxOp::EcdsaSecp256r1 BlackBoxOp::EcdsaSecp256r1::bincodeDeserialize(std::vector input) { + inline BlackBoxOp::EmbeddedCurveAdd BlackBoxOp::EmbeddedCurveAdd::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -2796,46 +3471,44 @@ namespace Circuit { template <> template -void serde::Serializable::serialize(const Circuit::BlackBoxOp::EcdsaSecp256r1 &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.hashed_msg, serializer); - serde::Serializable::serialize(obj.public_key_x, serializer); - serde::Serializable::serialize(obj.public_key_y, serializer); - serde::Serializable::serialize(obj.signature, serializer); +void serde::Serializable::serialize(const Circuit::BlackBoxOp::EmbeddedCurveAdd &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.input1_x, serializer); + serde::Serializable::serialize(obj.input1_y, serializer); + serde::Serializable::serialize(obj.input2_x, serializer); + serde::Serializable::serialize(obj.input2_y, serializer); serde::Serializable::serialize(obj.result, serializer); } template <> template -Circuit::BlackBoxOp::EcdsaSecp256r1 serde::Deserializable::deserialize(Deserializer &deserializer) { - Circuit::BlackBoxOp::EcdsaSecp256r1 obj; - obj.hashed_msg = serde::Deserializable::deserialize(deserializer); - obj.public_key_x = serde::Deserializable::deserialize(deserializer); - obj.public_key_y = serde::Deserializable::deserialize(deserializer); - obj.signature = serde::Deserializable::deserialize(deserializer); +Circuit::BlackBoxOp::EmbeddedCurveAdd serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::EmbeddedCurveAdd obj; + obj.input1_x = serde::Deserializable::deserialize(deserializer); + obj.input1_y = serde::Deserializable::deserialize(deserializer); + obj.input2_x = serde::Deserializable::deserialize(deserializer); + obj.input2_y = serde::Deserializable::deserialize(deserializer); obj.result = serde::Deserializable::deserialize(deserializer); return obj; } namespace Circuit { - inline bool operator==(const BlackBoxOp::SchnorrVerify &lhs, const BlackBoxOp::SchnorrVerify &rhs) { - if (!(lhs.public_key_x == rhs.public_key_x)) { return false; } - if (!(lhs.public_key_y == rhs.public_key_y)) { return false; } - if (!(lhs.message == rhs.message)) { return false; } - if (!(lhs.signature == rhs.signature)) { return false; } - if (!(lhs.result == rhs.result)) { return false; } + inline bool operator==(const BlackBoxOp::BigIntAdd &lhs, const BlackBoxOp::BigIntAdd &rhs) { + if (!(lhs.lhs == rhs.lhs)) { return false; } + if (!(lhs.rhs == rhs.rhs)) { return false; } + if (!(lhs.output == rhs.output)) { return false; } return true; } - inline std::vector BlackBoxOp::SchnorrVerify::bincodeSerialize() const { + inline std::vector BlackBoxOp::BigIntAdd::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline BlackBoxOp::SchnorrVerify BlackBoxOp::SchnorrVerify::bincodeDeserialize(std::vector input) { + inline BlackBoxOp::BigIntAdd BlackBoxOp::BigIntAdd::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -2846,44 +3519,40 @@ namespace Circuit { template <> template -void serde::Serializable::serialize(const Circuit::BlackBoxOp::SchnorrVerify &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.public_key_x, serializer); - serde::Serializable::serialize(obj.public_key_y, serializer); - serde::Serializable::serialize(obj.message, serializer); - serde::Serializable::serialize(obj.signature, serializer); - serde::Serializable::serialize(obj.result, serializer); +void serde::Serializable::serialize(const Circuit::BlackBoxOp::BigIntAdd &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.lhs, serializer); + serde::Serializable::serialize(obj.rhs, serializer); + serde::Serializable::serialize(obj.output, serializer); } template <> template -Circuit::BlackBoxOp::SchnorrVerify serde::Deserializable::deserialize(Deserializer &deserializer) { - Circuit::BlackBoxOp::SchnorrVerify obj; - obj.public_key_x = serde::Deserializable::deserialize(deserializer); - obj.public_key_y = serde::Deserializable::deserialize(deserializer); - obj.message = serde::Deserializable::deserialize(deserializer); - obj.signature = serde::Deserializable::deserialize(deserializer); - obj.result = serde::Deserializable::deserialize(deserializer); +Circuit::BlackBoxOp::BigIntAdd serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::BigIntAdd obj; + obj.lhs = serde::Deserializable::deserialize(deserializer); + obj.rhs = serde::Deserializable::deserialize(deserializer); + obj.output = serde::Deserializable::deserialize(deserializer); return obj; } namespace Circuit { - inline bool operator==(const BlackBoxOp::PedersenCommitment &lhs, const BlackBoxOp::PedersenCommitment &rhs) { - if (!(lhs.inputs == rhs.inputs)) { return false; } - if (!(lhs.domain_separator == rhs.domain_separator)) { return false; } + inline bool operator==(const BlackBoxOp::BigIntNeg &lhs, const BlackBoxOp::BigIntNeg &rhs) { + if (!(lhs.lhs == rhs.lhs)) { return false; } + if (!(lhs.rhs == rhs.rhs)) { return false; } if (!(lhs.output == rhs.output)) { return false; } return true; } - inline std::vector BlackBoxOp::PedersenCommitment::bincodeSerialize() const { + inline std::vector BlackBoxOp::BigIntNeg::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline BlackBoxOp::PedersenCommitment BlackBoxOp::PedersenCommitment::bincodeDeserialize(std::vector input) { + inline BlackBoxOp::BigIntNeg BlackBoxOp::BigIntNeg::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -2894,40 +3563,128 @@ namespace Circuit { template <> template -void serde::Serializable::serialize(const Circuit::BlackBoxOp::PedersenCommitment &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.inputs, serializer); - serde::Serializable::serialize(obj.domain_separator, serializer); +void serde::Serializable::serialize(const Circuit::BlackBoxOp::BigIntNeg &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.lhs, serializer); + serde::Serializable::serialize(obj.rhs, serializer); serde::Serializable::serialize(obj.output, serializer); } template <> template -Circuit::BlackBoxOp::PedersenCommitment serde::Deserializable::deserialize(Deserializer &deserializer) { - Circuit::BlackBoxOp::PedersenCommitment obj; - obj.inputs = serde::Deserializable::deserialize(deserializer); - obj.domain_separator = serde::Deserializable::deserialize(deserializer); +Circuit::BlackBoxOp::BigIntNeg serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::BigIntNeg obj; + obj.lhs = serde::Deserializable::deserialize(deserializer); + obj.rhs = serde::Deserializable::deserialize(deserializer); obj.output = serde::Deserializable::deserialize(deserializer); return obj; } namespace Circuit { - inline bool operator==(const BlackBoxOp::PedersenHash &lhs, const BlackBoxOp::PedersenHash &rhs) { + inline bool operator==(const BlackBoxOp::BigIntMul &lhs, const BlackBoxOp::BigIntMul &rhs) { + if (!(lhs.lhs == rhs.lhs)) { return false; } + if (!(lhs.rhs == rhs.rhs)) { return false; } + if (!(lhs.output == rhs.output)) { return false; } + return true; + } + + inline std::vector BlackBoxOp::BigIntMul::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp::BigIntMul BlackBoxOp::BigIntMul::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxOp::BigIntMul &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.lhs, serializer); + serde::Serializable::serialize(obj.rhs, serializer); + serde::Serializable::serialize(obj.output, serializer); +} + +template <> +template +Circuit::BlackBoxOp::BigIntMul serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::BigIntMul obj; + obj.lhs = serde::Deserializable::deserialize(deserializer); + obj.rhs = serde::Deserializable::deserialize(deserializer); + obj.output = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxOp::BigIntDiv &lhs, const BlackBoxOp::BigIntDiv &rhs) { + if (!(lhs.lhs == rhs.lhs)) { return false; } + if (!(lhs.rhs == rhs.rhs)) { return false; } + if (!(lhs.output == rhs.output)) { return false; } + return true; + } + + inline std::vector BlackBoxOp::BigIntDiv::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BlackBoxOp::BigIntDiv BlackBoxOp::BigIntDiv::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Circuit + +template <> +template +void serde::Serializable::serialize(const Circuit::BlackBoxOp::BigIntDiv &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.lhs, serializer); + serde::Serializable::serialize(obj.rhs, serializer); + serde::Serializable::serialize(obj.output, serializer); +} + +template <> +template +Circuit::BlackBoxOp::BigIntDiv serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::BigIntDiv obj; + obj.lhs = serde::Deserializable::deserialize(deserializer); + obj.rhs = serde::Deserializable::deserialize(deserializer); + obj.output = serde::Deserializable::deserialize(deserializer); + return obj; +} + +namespace Circuit { + + inline bool operator==(const BlackBoxOp::BigIntFromLeBytes &lhs, const BlackBoxOp::BigIntFromLeBytes &rhs) { if (!(lhs.inputs == rhs.inputs)) { return false; } - if (!(lhs.domain_separator == rhs.domain_separator)) { return false; } + if (!(lhs.modulus == rhs.modulus)) { return false; } if (!(lhs.output == rhs.output)) { return false; } return true; } - inline std::vector BlackBoxOp::PedersenHash::bincodeSerialize() const { + inline std::vector BlackBoxOp::BigIntFromLeBytes::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline BlackBoxOp::PedersenHash BlackBoxOp::PedersenHash::bincodeDeserialize(std::vector input) { + inline BlackBoxOp::BigIntFromLeBytes BlackBoxOp::BigIntFromLeBytes::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -2938,40 +3695,39 @@ namespace Circuit { template <> template -void serde::Serializable::serialize(const Circuit::BlackBoxOp::PedersenHash &obj, Serializer &serializer) { +void serde::Serializable::serialize(const Circuit::BlackBoxOp::BigIntFromLeBytes &obj, Serializer &serializer) { serde::Serializable::serialize(obj.inputs, serializer); - serde::Serializable::serialize(obj.domain_separator, serializer); + serde::Serializable::serialize(obj.modulus, serializer); serde::Serializable::serialize(obj.output, serializer); } template <> template -Circuit::BlackBoxOp::PedersenHash serde::Deserializable::deserialize(Deserializer &deserializer) { - Circuit::BlackBoxOp::PedersenHash obj; +Circuit::BlackBoxOp::BigIntFromLeBytes serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::BigIntFromLeBytes obj; obj.inputs = serde::Deserializable::deserialize(deserializer); - obj.domain_separator = serde::Deserializable::deserialize(deserializer); + obj.modulus = serde::Deserializable::deserialize(deserializer); obj.output = serde::Deserializable::deserialize(deserializer); return obj; } namespace Circuit { - inline bool operator==(const BlackBoxOp::FixedBaseScalarMul &lhs, const BlackBoxOp::FixedBaseScalarMul &rhs) { - if (!(lhs.low == rhs.low)) { return false; } - if (!(lhs.high == rhs.high)) { return false; } - if (!(lhs.result == rhs.result)) { return false; } + inline bool operator==(const BlackBoxOp::BigIntToLeBytes &lhs, const BlackBoxOp::BigIntToLeBytes &rhs) { + if (!(lhs.input == rhs.input)) { return false; } + if (!(lhs.output == rhs.output)) { return false; } return true; } - inline std::vector BlackBoxOp::FixedBaseScalarMul::bincodeSerialize() const { + inline std::vector BlackBoxOp::BigIntToLeBytes::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline BlackBoxOp::FixedBaseScalarMul BlackBoxOp::FixedBaseScalarMul::bincodeDeserialize(std::vector input) { + inline BlackBoxOp::BigIntToLeBytes BlackBoxOp::BigIntToLeBytes::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -2982,42 +3738,38 @@ namespace Circuit { template <> template -void serde::Serializable::serialize(const Circuit::BlackBoxOp::FixedBaseScalarMul &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.low, serializer); - serde::Serializable::serialize(obj.high, serializer); - serde::Serializable::serialize(obj.result, serializer); +void serde::Serializable::serialize(const Circuit::BlackBoxOp::BigIntToLeBytes &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.input, serializer); + serde::Serializable::serialize(obj.output, serializer); } template <> template -Circuit::BlackBoxOp::FixedBaseScalarMul serde::Deserializable::deserialize(Deserializer &deserializer) { - Circuit::BlackBoxOp::FixedBaseScalarMul obj; - obj.low = serde::Deserializable::deserialize(deserializer); - obj.high = serde::Deserializable::deserialize(deserializer); - obj.result = serde::Deserializable::deserialize(deserializer); +Circuit::BlackBoxOp::BigIntToLeBytes serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::BigIntToLeBytes obj; + obj.input = serde::Deserializable::deserialize(deserializer); + obj.output = serde::Deserializable::deserialize(deserializer); return obj; } namespace Circuit { - inline bool operator==(const BlackBoxOp::EmbeddedCurveAdd &lhs, const BlackBoxOp::EmbeddedCurveAdd &rhs) { - if (!(lhs.input1_x == rhs.input1_x)) { return false; } - if (!(lhs.input1_y == rhs.input1_y)) { return false; } - if (!(lhs.input2_x == rhs.input2_x)) { return false; } - if (!(lhs.input2_y == rhs.input2_y)) { return false; } - if (!(lhs.result == rhs.result)) { return false; } + inline bool operator==(const BlackBoxOp::Poseidon2Permutation &lhs, const BlackBoxOp::Poseidon2Permutation &rhs) { + if (!(lhs.message == rhs.message)) { return false; } + if (!(lhs.output == rhs.output)) { return false; } + if (!(lhs.len == rhs.len)) { return false; } return true; } - inline std::vector BlackBoxOp::EmbeddedCurveAdd::bincodeSerialize() const { + inline std::vector BlackBoxOp::Poseidon2Permutation::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline BlackBoxOp::EmbeddedCurveAdd BlackBoxOp::EmbeddedCurveAdd::bincodeDeserialize(std::vector input) { + inline BlackBoxOp::Poseidon2Permutation BlackBoxOp::Poseidon2Permutation::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -3028,44 +3780,40 @@ namespace Circuit { template <> template -void serde::Serializable::serialize(const Circuit::BlackBoxOp::EmbeddedCurveAdd &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.input1_x, serializer); - serde::Serializable::serialize(obj.input1_y, serializer); - serde::Serializable::serialize(obj.input2_x, serializer); - serde::Serializable::serialize(obj.input2_y, serializer); - serde::Serializable::serialize(obj.result, serializer); +void serde::Serializable::serialize(const Circuit::BlackBoxOp::Poseidon2Permutation &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.message, serializer); + serde::Serializable::serialize(obj.output, serializer); + serde::Serializable::serialize(obj.len, serializer); } template <> template -Circuit::BlackBoxOp::EmbeddedCurveAdd serde::Deserializable::deserialize(Deserializer &deserializer) { - Circuit::BlackBoxOp::EmbeddedCurveAdd obj; - obj.input1_x = serde::Deserializable::deserialize(deserializer); - obj.input1_y = serde::Deserializable::deserialize(deserializer); - obj.input2_x = serde::Deserializable::deserialize(deserializer); - obj.input2_y = serde::Deserializable::deserialize(deserializer); - obj.result = serde::Deserializable::deserialize(deserializer); +Circuit::BlackBoxOp::Poseidon2Permutation serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::Poseidon2Permutation obj; + obj.message = serde::Deserializable::deserialize(deserializer); + obj.output = serde::Deserializable::deserialize(deserializer); + obj.len = serde::Deserializable::deserialize(deserializer); return obj; } namespace Circuit { - inline bool operator==(const BlackBoxOp::EmbeddedCurveDouble &lhs, const BlackBoxOp::EmbeddedCurveDouble &rhs) { - if (!(lhs.input1_x == rhs.input1_x)) { return false; } - if (!(lhs.input1_y == rhs.input1_y)) { return false; } - if (!(lhs.result == rhs.result)) { return false; } + inline bool operator==(const BlackBoxOp::Sha256Compression &lhs, const BlackBoxOp::Sha256Compression &rhs) { + if (!(lhs.input == rhs.input)) { return false; } + if (!(lhs.hash_values == rhs.hash_values)) { return false; } + if (!(lhs.output == rhs.output)) { return false; } return true; } - inline std::vector BlackBoxOp::EmbeddedCurveDouble::bincodeSerialize() const { + inline std::vector BlackBoxOp::Sha256Compression::bincodeSerialize() const { auto serializer = serde::BincodeSerializer(); - serde::Serializable::serialize(*this, serializer); + serde::Serializable::serialize(*this, serializer); return std::move(serializer).bytes(); } - inline BlackBoxOp::EmbeddedCurveDouble BlackBoxOp::EmbeddedCurveDouble::bincodeDeserialize(std::vector input) { + inline BlackBoxOp::Sha256Compression BlackBoxOp::Sha256Compression::bincodeDeserialize(std::vector input) { auto deserializer = serde::BincodeDeserializer(input); - auto value = serde::Deserializable::deserialize(deserializer); + auto value = serde::Deserializable::deserialize(deserializer); if (deserializer.get_buffer_offset() < input.size()) { throw serde::deserialization_error("Some input bytes were not read"); } @@ -3076,19 +3824,19 @@ namespace Circuit { template <> template -void serde::Serializable::serialize(const Circuit::BlackBoxOp::EmbeddedCurveDouble &obj, Serializer &serializer) { - serde::Serializable::serialize(obj.input1_x, serializer); - serde::Serializable::serialize(obj.input1_y, serializer); - serde::Serializable::serialize(obj.result, serializer); +void serde::Serializable::serialize(const Circuit::BlackBoxOp::Sha256Compression &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.input, serializer); + serde::Serializable::serialize(obj.hash_values, serializer); + serde::Serializable::serialize(obj.output, serializer); } template <> template -Circuit::BlackBoxOp::EmbeddedCurveDouble serde::Deserializable::deserialize(Deserializer &deserializer) { - Circuit::BlackBoxOp::EmbeddedCurveDouble obj; - obj.input1_x = serde::Deserializable::deserialize(deserializer); - obj.input1_y = serde::Deserializable::deserialize(deserializer); - obj.result = serde::Deserializable::deserialize(deserializer); +Circuit::BlackBoxOp::Sha256Compression serde::Deserializable::deserialize(Deserializer &deserializer) { + Circuit::BlackBoxOp::Sha256Compression obj; + obj.input = serde::Deserializable::deserialize(deserializer); + obj.hash_values = serde::Deserializable::deserialize(deserializer); + obj.output = serde::Deserializable::deserialize(deserializer); return obj; } diff --git a/acvm-repo/acir/src/circuit/black_box_functions.rs b/acvm-repo/acir/src/circuit/black_box_functions.rs index d1f5560313b..97b4759d350 100644 --- a/acvm-repo/acir/src/circuit/black_box_functions.rs +++ b/acvm-repo/acir/src/circuit/black_box_functions.rs @@ -47,8 +47,22 @@ pub enum BlackBoxFunc { RecursiveAggregation, /// Addition over the embedded curve on which [`FieldElement`][acir_field::FieldElement] is defined. EmbeddedCurveAdd, - /// Point doubling over the embedded curve on which [`FieldElement`][acir_field::FieldElement] is defined. - EmbeddedCurveDouble, + /// BigInt addition + BigIntAdd, + /// BigInt subtraction + BigIntNeg, + /// BigInt multiplication + BigIntMul, + /// BigInt division + BigIntDiv, + /// BigInt from le bytes + BigIntFromLeBytes, + /// BigInt to le bytes + BigIntToLeBytes, + /// Permutation function of Poseidon2 + Poseidon2Permutation, + /// SHA256 compression function + Sha256Compression, } impl std::fmt::Display for BlackBoxFunc { @@ -68,8 +82,7 @@ impl BlackBoxFunc { BlackBoxFunc::PedersenHash => "pedersen_hash", BlackBoxFunc::EcdsaSecp256k1 => "ecdsa_secp256k1", BlackBoxFunc::FixedBaseScalarMul => "fixed_base_scalar_mul", - BlackBoxFunc::EmbeddedCurveAdd => "ec_add", - BlackBoxFunc::EmbeddedCurveDouble => "ec_double", + BlackBoxFunc::EmbeddedCurveAdd => "embedded_curve_add", BlackBoxFunc::AND => "and", BlackBoxFunc::XOR => "xor", BlackBoxFunc::RANGE => "range", @@ -77,8 +90,17 @@ impl BlackBoxFunc { BlackBoxFunc::Keccakf1600 => "keccakf1600", BlackBoxFunc::RecursiveAggregation => "recursive_aggregation", BlackBoxFunc::EcdsaSecp256r1 => "ecdsa_secp256r1", + BlackBoxFunc::BigIntAdd => "bigint_add", + BlackBoxFunc::BigIntNeg => "bigint_neg", + BlackBoxFunc::BigIntMul => "bigint_mul", + BlackBoxFunc::BigIntDiv => "bigint_div", + BlackBoxFunc::BigIntFromLeBytes => "bigint_from_le_bytes", + BlackBoxFunc::BigIntToLeBytes => "bigint_to_le_bytes", + BlackBoxFunc::Poseidon2Permutation => "poseidon2_permutation", + BlackBoxFunc::Sha256Compression => "sha256_compression", } } + pub fn lookup(op_name: &str) -> Option { match op_name { "sha256" => Some(BlackBoxFunc::SHA256), @@ -90,17 +112,25 @@ impl BlackBoxFunc { "ecdsa_secp256k1" => Some(BlackBoxFunc::EcdsaSecp256k1), "ecdsa_secp256r1" => Some(BlackBoxFunc::EcdsaSecp256r1), "fixed_base_scalar_mul" => Some(BlackBoxFunc::FixedBaseScalarMul), - "ec_add" => Some(BlackBoxFunc::EmbeddedCurveAdd), - "ec_double" => Some(BlackBoxFunc::EmbeddedCurveDouble), + "embedded_curve_add" => Some(BlackBoxFunc::EmbeddedCurveAdd), "and" => Some(BlackBoxFunc::AND), "xor" => Some(BlackBoxFunc::XOR), "range" => Some(BlackBoxFunc::RANGE), "keccak256" => Some(BlackBoxFunc::Keccak256), "keccakf1600" => Some(BlackBoxFunc::Keccakf1600), "recursive_aggregation" => Some(BlackBoxFunc::RecursiveAggregation), + "bigint_add" => Some(BlackBoxFunc::BigIntAdd), + "bigint_neg" => Some(BlackBoxFunc::BigIntNeg), + "bigint_mul" => Some(BlackBoxFunc::BigIntMul), + "bigint_div" => Some(BlackBoxFunc::BigIntDiv), + "bigint_from_le_bytes" => Some(BlackBoxFunc::BigIntFromLeBytes), + "bigint_to_le_bytes" => Some(BlackBoxFunc::BigIntToLeBytes), + "poseidon2_permutation" => Some(BlackBoxFunc::Poseidon2Permutation), + "sha256_compression" => Some(BlackBoxFunc::Sha256Compression), _ => None, } } + pub fn is_valid_black_box_func_name(op_name: &str) -> bool { BlackBoxFunc::lookup(op_name).is_some() } diff --git a/acvm-repo/acir/src/circuit/mod.rs b/acvm-repo/acir/src/circuit/mod.rs index b248b30b1d9..dcb09d429eb 100644 --- a/acvm-repo/acir/src/circuit/mod.rs +++ b/acvm-repo/acir/src/circuit/mod.rs @@ -38,6 +38,10 @@ pub struct Circuit { // 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. + // + // TODO: These are only used for constraints that are explicitly created during code generation (such as index out of bounds on slices) + // TODO: We should move towards having all the checks being evaluated in the same manner + // TODO: as runtime assert messages specified by the user. This will also be a breaking change as the `Circuit` structure will change. pub assert_messages: Vec<(OpcodeLocation, String)>, } 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 index 7ee4e2498a5..ba4964c8912 100644 --- a/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs @@ -86,11 +86,6 @@ pub enum BlackBoxFuncCall { input2_y: FunctionInput, outputs: (Witness, Witness), }, - EmbeddedCurveDouble { - input_x: FunctionInput, - input_y: FunctionInput, - outputs: (Witness, Witness), - }, Keccak256 { inputs: Vec, outputs: Vec, @@ -120,6 +115,61 @@ pub enum BlackBoxFuncCall { /// key provided to the circuit matches the key produced by the circuit creator key_hash: FunctionInput, }, + BigIntAdd { + lhs: u32, + rhs: u32, + output: u32, + }, + BigIntNeg { + lhs: u32, + rhs: u32, + output: u32, + }, + BigIntMul { + lhs: u32, + rhs: u32, + output: u32, + }, + BigIntDiv { + lhs: u32, + rhs: u32, + output: u32, + }, + BigIntFromLeBytes { + inputs: Vec, + modulus: Vec, + output: u32, + }, + BigIntToLeBytes { + input: u32, + outputs: Vec, + }, + /// Applies the Poseidon2 permutation function to the given state, + /// outputting the permuted state. + Poseidon2Permutation { + /// Input state for the permutation of Poseidon2 + inputs: Vec, + /// Permuted state + outputs: Vec, + /// State length (in number of field elements) + /// It is the length of inputs and outputs vectors + len: u32, + }, + /// Applies the SHA-256 compression function to the input message + /// + /// # Arguments + /// + /// * `inputs` - input message block + /// * `hash_values` - state from the previous compression + /// * `outputs` - result of the input compressed into 256 bits + Sha256Compression { + /// 512 bits of the input message, represented by 16 u32s + inputs: Vec, + /// Vector of 8 u32s used to compress the input + hash_values: Vec, + /// Output of the compression, represented by 8 u32s + outputs: Vec, + }, } impl BlackBoxFuncCall { @@ -138,11 +188,18 @@ impl BlackBoxFuncCall { BlackBoxFuncCall::EcdsaSecp256r1 { .. } => BlackBoxFunc::EcdsaSecp256r1, BlackBoxFuncCall::FixedBaseScalarMul { .. } => BlackBoxFunc::FixedBaseScalarMul, BlackBoxFuncCall::EmbeddedCurveAdd { .. } => BlackBoxFunc::EmbeddedCurveAdd, - BlackBoxFuncCall::EmbeddedCurveDouble { .. } => BlackBoxFunc::EmbeddedCurveDouble, BlackBoxFuncCall::Keccak256 { .. } => BlackBoxFunc::Keccak256, BlackBoxFuncCall::Keccak256VariableLength { .. } => BlackBoxFunc::Keccak256, BlackBoxFuncCall::Keccakf1600 { .. } => BlackBoxFunc::Keccakf1600, BlackBoxFuncCall::RecursiveAggregation { .. } => BlackBoxFunc::RecursiveAggregation, + BlackBoxFuncCall::BigIntAdd { .. } => BlackBoxFunc::BigIntAdd, + BlackBoxFuncCall::BigIntNeg { .. } => BlackBoxFunc::BigIntNeg, + BlackBoxFuncCall::BigIntMul { .. } => BlackBoxFunc::BigIntMul, + BlackBoxFuncCall::BigIntDiv { .. } => BlackBoxFunc::BigIntDiv, + BlackBoxFuncCall::BigIntFromLeBytes { .. } => BlackBoxFunc::BigIntFromLeBytes, + BlackBoxFuncCall::BigIntToLeBytes { .. } => BlackBoxFunc::BigIntToLeBytes, + BlackBoxFuncCall::Poseidon2Permutation { .. } => BlackBoxFunc::Poseidon2Permutation, + BlackBoxFuncCall::Sha256Compression { .. } => BlackBoxFunc::Sha256Compression, } } @@ -158,17 +215,22 @@ impl BlackBoxFuncCall { | BlackBoxFuncCall::Keccak256 { inputs, .. } | BlackBoxFuncCall::Keccakf1600 { inputs, .. } | BlackBoxFuncCall::PedersenCommitment { inputs, .. } - | BlackBoxFuncCall::PedersenHash { inputs, .. } => inputs.to_vec(), + | BlackBoxFuncCall::PedersenHash { inputs, .. } + | BlackBoxFuncCall::BigIntFromLeBytes { inputs, .. } + | BlackBoxFuncCall::Poseidon2Permutation { inputs, .. } + | BlackBoxFuncCall::Sha256Compression { inputs, .. } => inputs.to_vec(), BlackBoxFuncCall::AND { lhs, rhs, .. } | BlackBoxFuncCall::XOR { lhs, rhs, .. } => { vec![*lhs, *rhs] } + BlackBoxFuncCall::BigIntAdd { .. } + | BlackBoxFuncCall::BigIntNeg { .. } + | BlackBoxFuncCall::BigIntMul { .. } + | BlackBoxFuncCall::BigIntDiv { .. } + | BlackBoxFuncCall::BigIntToLeBytes { .. } => Vec::new(), BlackBoxFuncCall::FixedBaseScalarMul { low, high, .. } => vec![*low, *high], BlackBoxFuncCall::EmbeddedCurveAdd { input1_x, input1_y, input2_x, input2_y, .. } => vec![*input1_x, *input1_y, *input2_x, *input2_y], - BlackBoxFuncCall::EmbeddedCurveDouble { input_x, input_y, .. } => { - vec![*input_x, *input_y] - } BlackBoxFuncCall::RANGE { input } => vec![*input], BlackBoxFuncCall::SchnorrVerify { public_key_x, @@ -249,7 +311,10 @@ impl BlackBoxFuncCall { | BlackBoxFuncCall::Blake2s { outputs, .. } | BlackBoxFuncCall::Blake3 { outputs, .. } | BlackBoxFuncCall::Keccak256 { outputs, .. } - | BlackBoxFuncCall::Keccakf1600 { outputs, .. } => outputs.to_vec(), + | BlackBoxFuncCall::Keccakf1600 { outputs, .. } + | BlackBoxFuncCall::Keccak256VariableLength { outputs, .. } + | BlackBoxFuncCall::Poseidon2Permutation { outputs, .. } + | BlackBoxFuncCall::Sha256Compression { outputs, .. } => outputs.to_vec(), BlackBoxFuncCall::AND { output, .. } | BlackBoxFuncCall::XOR { output, .. } | BlackBoxFuncCall::SchnorrVerify { output, .. } @@ -258,12 +323,17 @@ impl BlackBoxFuncCall { | BlackBoxFuncCall::EcdsaSecp256r1 { output, .. } => vec![*output], BlackBoxFuncCall::FixedBaseScalarMul { outputs, .. } | BlackBoxFuncCall::PedersenCommitment { outputs, .. } - | BlackBoxFuncCall::EmbeddedCurveAdd { outputs, .. } - | BlackBoxFuncCall::EmbeddedCurveDouble { outputs, .. } => vec![outputs.0, outputs.1], - BlackBoxFuncCall::RANGE { .. } | BlackBoxFuncCall::RecursiveAggregation { .. } => { + | BlackBoxFuncCall::EmbeddedCurveAdd { outputs, .. } => vec![outputs.0, outputs.1], + BlackBoxFuncCall::RANGE { .. } + | BlackBoxFuncCall::RecursiveAggregation { .. } + | BlackBoxFuncCall::BigIntFromLeBytes { .. } + | BlackBoxFuncCall::BigIntAdd { .. } + | BlackBoxFuncCall::BigIntNeg { .. } + | BlackBoxFuncCall::BigIntMul { .. } + | BlackBoxFuncCall::BigIntDiv { .. } => { vec![] } - BlackBoxFuncCall::Keccak256VariableLength { outputs, .. } => outputs.to_vec(), + BlackBoxFuncCall::BigIntToLeBytes { outputs, .. } => outputs.to_vec(), } } } diff --git a/acvm-repo/acir_field/Cargo.toml b/acvm-repo/acir_field/Cargo.toml index cedfc66e734..dde121f4029 100644 --- a/acvm-repo/acir_field/Cargo.toml +++ b/acvm-repo/acir_field/Cargo.toml @@ -2,7 +2,7 @@ name = "acir_field" description = "The field implementation being used by ACIR." # x-release-please-start-version -version = "0.38.0" +version = "0.39.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/acvm-repo/acvm/Cargo.toml b/acvm-repo/acvm/Cargo.toml index be2391a3216..a40148a01ef 100644 --- a/acvm-repo/acvm/Cargo.toml +++ b/acvm-repo/acvm/Cargo.toml @@ -2,7 +2,7 @@ name = "acvm" description = "The virtual machine that processes ACIR given a backend/proof system." # x-release-please-start-version -version = "0.38.0" +version = "0.39.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs b/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs index 5d19f9629ba..ecabd98b3b1 100644 --- a/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs +++ b/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs @@ -3,8 +3,7 @@ use acir::{ opcodes::{BlackBoxFuncCall, FunctionInput}, Circuit, Opcode, }, - native_types::{Expression, Witness}, - FieldElement, + native_types::Witness, }; use std::collections::{BTreeMap, HashSet}; @@ -105,9 +104,11 @@ impl RangeOptimizer { 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.into_iter().enumerate() { - let (witness, num_bits) = match extract_range_opcode(&opcode) { - Some(range_opcode) => range_opcode, - None => { + let (witness, num_bits) = match &opcode { + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { input }) => { + (input.witness, input.num_bits) + } + _ => { // If its not the range opcode, add it to the opcode // list and continue; optimized_opcodes.push(opcode); @@ -131,7 +132,7 @@ impl RangeOptimizer { if is_lowest_bit_size { already_seen_witness.insert(witness); new_order_list.push(order_list[idx]); - optimized_opcodes.push(optimized_range_opcode(witness, num_bits)); + optimized_opcodes.push(opcode); } } @@ -139,36 +140,11 @@ impl RangeOptimizer { } } -/// 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)> { - match opcode { - Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { input }) => { - Some((input.witness, input.num_bits)) - } - _ => None, - } -} - -fn optimized_range_opcode(witness: Witness, num_bits: u32) -> Opcode { - if num_bits == 1 { - Opcode::AssertZero(Expression { - mul_terms: vec![(FieldElement::one(), witness, witness)], - linear_combinations: vec![(-FieldElement::one(), witness)], - q_c: FieldElement::zero(), - }) - } else { - Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { - input: FunctionInput { witness, num_bits }, - }) - } -} - #[cfg(test)] mod tests { use std::collections::BTreeSet; - use crate::compiler::optimizers::redundant_range::{extract_range_opcode, RangeOptimizer}; + use crate::compiler::optimizers::redundant_range::RangeOptimizer; use acir::{ circuit::{ opcodes::{BlackBoxFuncCall, FunctionInput}, @@ -218,11 +194,12 @@ mod tests { 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); + assert_eq!( + optimized_circuit.opcodes[0], + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness: Witness(1), num_bits: 16 } + }) + ); } #[test] @@ -240,15 +217,18 @@ mod tests { 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); + assert_eq!( + optimized_circuit.opcodes[0], + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness: Witness(1), num_bits: 16 } + }) + ); + assert_eq!( + optimized_circuit.opcodes[1], + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { + input: FunctionInput { witness: Witness(2), num_bits: 23 } + }) + ); } #[test] diff --git a/acvm-repo/acvm/src/compiler/transformers/mod.rs b/acvm-repo/acvm/src/compiler/transformers/mod.rs index 306ea1b7c12..e184401c5d4 100644 --- a/acvm-repo/acvm/src/compiler/transformers/mod.rs +++ b/acvm-repo/acvm/src/compiler/transformers/mod.rs @@ -99,50 +99,8 @@ pub(super) fn transform_internal( } } Opcode::BlackBoxFuncCall(ref 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::RecursiveAggregation { .. } => (), - acir::circuit::opcodes::BlackBoxFuncCall::SHA256 { outputs, .. } - | acir::circuit::opcodes::BlackBoxFuncCall::Keccak256 { outputs, .. } - | acir::circuit::opcodes::BlackBoxFuncCall::Keccak256VariableLength { - outputs, - .. - } - | acir::circuit::opcodes::BlackBoxFuncCall::Keccakf1600 { outputs, .. } - | acir::circuit::opcodes::BlackBoxFuncCall::Blake2s { outputs, .. } - | acir::circuit::opcodes::BlackBoxFuncCall::Blake3 { outputs, .. } => { - for witness in outputs { - transformer.mark_solvable(*witness); - } - } - acir::circuit::opcodes::BlackBoxFuncCall::FixedBaseScalarMul { - outputs, - .. - } - | acir::circuit::opcodes::BlackBoxFuncCall::EmbeddedCurveAdd { - outputs, .. - } - | acir::circuit::opcodes::BlackBoxFuncCall::EmbeddedCurveDouble { - outputs, - .. - } - | acir::circuit::opcodes::BlackBoxFuncCall::PedersenCommitment { - outputs, - .. - } => { - transformer.mark_solvable(outputs.0); - transformer.mark_solvable(outputs.1); - } - acir::circuit::opcodes::BlackBoxFuncCall::EcdsaSecp256k1 { output, .. } - | acir::circuit::opcodes::BlackBoxFuncCall::EcdsaSecp256r1 { output, .. } - | acir::circuit::opcodes::BlackBoxFuncCall::SchnorrVerify { output, .. } - | acir::circuit::opcodes::BlackBoxFuncCall::PedersenHash { output, .. } => { - transformer.mark_solvable(*output); - } + for witness in func.get_outputs_vec() { + transformer.mark_solvable(witness); } new_acir_opcode_positions.push(acir_opcode_positions[index]); diff --git a/acvm-repo/acvm/src/lib.rs b/acvm-repo/acvm/src/lib.rs index 626bb2c9b91..264479d8a12 100644 --- a/acvm-repo/acvm/src/lib.rs +++ b/acvm-repo/acvm/src/lib.rs @@ -31,3 +31,13 @@ pub enum ExpressionWidth { Unbounded, Bounded { width: usize }, } + +impl From for ExpressionWidth { + fn from(width: usize) -> ExpressionWidth { + if width == 0 { + ExpressionWidth::Unbounded + } else { + ExpressionWidth::Bounded { width } + } + } +} 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 index 582ed56584b..c5bfd1d5646 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/fixed_base_scalar_mul.rs @@ -23,3 +23,24 @@ pub(super) fn fixed_base_scalar_mul( Ok(()) } + +pub(super) fn embedded_curve_add( + backend: &impl BlackBoxFunctionSolver, + initial_witness: &mut WitnessMap, + input1_x: FunctionInput, + input1_y: FunctionInput, + input2_x: FunctionInput, + input2_y: FunctionInput, + outputs: (Witness, Witness), +) -> Result<(), OpcodeResolutionError> { + let input1_x = witness_to_value(initial_witness, input1_x.witness)?; + let input1_y = witness_to_value(initial_witness, input1_y.witness)?; + let input2_x = witness_to_value(initial_witness, input2_x.witness)?; + let input2_y = witness_to_value(initial_witness, input2_y.witness)?; + let (res_x, res_y) = backend.ec_add(input1_x, input1_y, input2_x, input2_y)?; + + 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/mod.rs b/acvm-repo/acvm/src/pwg/blackbox/mod.rs index 5eea234885c..0f026cd274a 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/mod.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/mod.rs @@ -17,7 +17,7 @@ mod pedersen; mod range; mod signature; -use fixed_base_scalar_mul::fixed_base_scalar_mul; +use fixed_base_scalar_mul::{embedded_curve_add, fixed_base_scalar_mul}; // Hash functions should eventually be exposed for external consumers. use hash::solve_generic_256_hash_opcode; use logic::{and, xor}; @@ -177,13 +177,26 @@ pub(crate) fn solve( BlackBoxFuncCall::FixedBaseScalarMul { low, high, outputs } => { fixed_base_scalar_mul(backend, initial_witness, *low, *high, *outputs) } - BlackBoxFuncCall::EmbeddedCurveAdd { .. } => { - todo!(); - } - BlackBoxFuncCall::EmbeddedCurveDouble { .. } => { - todo!(); + BlackBoxFuncCall::EmbeddedCurveAdd { input1_x, input1_y, input2_x, input2_y, outputs } => { + embedded_curve_add( + backend, + initial_witness, + *input1_x, + *input1_y, + *input2_x, + *input2_y, + *outputs, + ) } // Recursive aggregation will be entirely handled by the backend and is not solved by the ACVM BlackBoxFuncCall::RecursiveAggregation { .. } => Ok(()), + BlackBoxFuncCall::BigIntAdd { .. } => todo!(), + BlackBoxFuncCall::BigIntNeg { .. } => todo!(), + BlackBoxFuncCall::BigIntMul { .. } => todo!(), + BlackBoxFuncCall::BigIntDiv { .. } => todo!(), + BlackBoxFuncCall::BigIntFromLeBytes { .. } => todo!(), + BlackBoxFuncCall::BigIntToLeBytes { .. } => todo!(), + BlackBoxFuncCall::Poseidon2Permutation { .. } => todo!(), + BlackBoxFuncCall::Sha256Compression { .. } => todo!(), } } diff --git a/acvm-repo/acvm_js/Cargo.toml b/acvm-repo/acvm_js/Cargo.toml index e8d46b9717e..226e273c306 100644 --- a/acvm-repo/acvm_js/Cargo.toml +++ b/acvm-repo/acvm_js/Cargo.toml @@ -2,7 +2,7 @@ name = "acvm_js" description = "Typescript wrapper around the ACVM allowing execution of ACIR code" # x-release-please-start-version -version = "0.38.0" +version = "0.39.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/acvm-repo/acvm_js/package.json b/acvm-repo/acvm_js/package.json index 2d878e961da..4ec9b1a2da3 100644 --- a/acvm-repo/acvm_js/package.json +++ b/acvm-repo/acvm_js/package.json @@ -1,6 +1,6 @@ { "name": "@noir-lang/acvm_js", - "version": "0.38.0", + "version": "0.39.0", "publishConfig": { "access": "public" }, diff --git a/acvm-repo/acvm_js/src/logging.rs b/acvm-repo/acvm_js/src/logging.rs index f5d71fae067..483c23ab624 100644 --- a/acvm-repo/acvm_js/src/logging.rs +++ b/acvm-repo/acvm_js/src/logging.rs @@ -1,3 +1,4 @@ +use js_sys::{Error, JsString}; use tracing_subscriber::prelude::*; use tracing_subscriber::EnvFilter; use tracing_web::MakeWebConsoleWriter; @@ -7,12 +8,15 @@ use wasm_bindgen::prelude::*; /// /// @param {LogLevel} level - The maximum level of logging to be emitted. #[wasm_bindgen(js_name = initLogLevel, skip_jsdoc)] -pub fn init_log_level(filter: String) { +pub fn init_log_level(filter: String) -> Result<(), JsLogInitError> { // Set the static variable from Rust use std::sync::Once; - let filter: EnvFilter = - filter.parse().expect("Could not parse log filter while initializing logger"); + let filter: EnvFilter = filter.parse().map_err(|err| { + JsLogInitError::constructor( + format!("Could not parse log filter while initializing logger: {err}").into(), + ) + })?; static SET_HOOK: Once = Once::new(); SET_HOOK.call_once(|| { @@ -23,4 +27,19 @@ pub fn init_log_level(filter: String) { tracing_subscriber::registry().with(fmt_layer.with_filter(filter)).init(); }); + + Ok(()) +} + +/// `LogInitError` is a raw js error. +/// It'd be ideal that `LogInitError` 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 = "LogInitError", typescript_type = "LogInitError")] + #[derive(Clone, Debug, PartialEq, Eq)] + pub type JsLogInitError; + + #[wasm_bindgen(constructor, js_class = "Error")] + fn constructor(message: JsString) -> JsLogInitError; } diff --git a/acvm-repo/blackbox_solver/Cargo.toml b/acvm-repo/blackbox_solver/Cargo.toml index 749ef8f289a..7359cf307e4 100644 --- a/acvm-repo/blackbox_solver/Cargo.toml +++ b/acvm-repo/blackbox_solver/Cargo.toml @@ -2,7 +2,7 @@ name = "acvm_blackbox_solver" description = "A solver for the blackbox functions found in ACIR and Brillig" # x-release-please-start-version -version = "0.38.0" +version = "0.39.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/acvm-repo/blackbox_solver/src/curve_specific_solver.rs b/acvm-repo/blackbox_solver/src/curve_specific_solver.rs index 82941e91d61..2234710dec0 100644 --- a/acvm-repo/blackbox_solver/src/curve_specific_solver.rs +++ b/acvm-repo/blackbox_solver/src/curve_specific_solver.rs @@ -36,11 +36,6 @@ pub trait BlackBoxFunctionSolver { input2_x: &FieldElement, input2_y: &FieldElement, ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError>; - fn ec_double( - &self, - input_x: &FieldElement, - input_x: &FieldElement, - ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError>; } pub struct StubbedBlackBoxSolver; @@ -94,11 +89,4 @@ impl BlackBoxFunctionSolver for StubbedBlackBoxSolver { ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { Err(Self::fail(BlackBoxFunc::EmbeddedCurveAdd)) } - fn ec_double( - &self, - _input_x: &FieldElement, - _input_y: &FieldElement, - ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { - Err(Self::fail(BlackBoxFunc::EmbeddedCurveDouble)) - } } diff --git a/acvm-repo/bn254_blackbox_solver/Cargo.toml b/acvm-repo/bn254_blackbox_solver/Cargo.toml index b98bb370f74..a73aded231f 100644 --- a/acvm-repo/bn254_blackbox_solver/Cargo.toml +++ b/acvm-repo/bn254_blackbox_solver/Cargo.toml @@ -2,7 +2,7 @@ name = "bn254_blackbox_solver" description = "Solvers for black box functions which are specific for the bn254 curve" # x-release-please-start-version -version = "0.38.0" +version = "0.39.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/acvm-repo/bn254_blackbox_solver/src/fixed_base_scalar_mul.rs b/acvm-repo/bn254_blackbox_solver/src/fixed_base_scalar_mul.rs index 7f004de0fe9..5e68c7d4030 100644 --- a/acvm-repo/bn254_blackbox_solver/src/fixed_base_scalar_mul.rs +++ b/acvm-repo/bn254_blackbox_solver/src/fixed_base_scalar_mul.rs @@ -47,6 +47,26 @@ pub fn fixed_base_scalar_mul( } } +pub fn embedded_curve_add( + input1_x: FieldElement, + input1_y: FieldElement, + input2_x: FieldElement, + input2_y: FieldElement, +) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { + let mut point1 = grumpkin::SWAffine::new(input1_x.into_repr(), input1_y.into_repr()); + let point2 = grumpkin::SWAffine::new(input2_x.into_repr(), input2_y.into_repr()); + let res = point1 + point2; + point1 = res.into(); + if let Some((res_x, res_y)) = point1.xy() { + Ok((FieldElement::from_repr(*res_x), FieldElement::from_repr(*res_y))) + } else { + Err(BlackBoxResolutionError::Failed( + BlackBoxFunc::EmbeddedCurveAdd, + "Point is not on curve".to_string(), + )) + } +} + #[cfg(test)] mod grumpkin_fixed_base_scalar_mul { use ark_ff::BigInteger; diff --git a/acvm-repo/bn254_blackbox_solver/src/lib.rs b/acvm-repo/bn254_blackbox_solver/src/lib.rs index 92c45e93dea..13aa956f9e1 100644 --- a/acvm-repo/bn254_blackbox_solver/src/lib.rs +++ b/acvm-repo/bn254_blackbox_solver/src/lib.rs @@ -8,7 +8,7 @@ use acvm_blackbox_solver::{BlackBoxFunctionSolver, BlackBoxResolutionError}; mod fixed_base_scalar_mul; mod wasm; -pub use fixed_base_scalar_mul::fixed_base_scalar_mul; +pub use fixed_base_scalar_mul::{embedded_curve_add, fixed_base_scalar_mul}; use wasm::Barretenberg; use self::wasm::{Pedersen, SchnorrSig}; @@ -90,19 +90,11 @@ impl BlackBoxFunctionSolver for Bn254BlackBoxSolver { fn ec_add( &self, - _input1_x: &FieldElement, - _input1_y: &FieldElement, - _input2_x: &FieldElement, - _input2_y: &FieldElement, + input1_x: &FieldElement, + input1_y: &FieldElement, + input2_x: &FieldElement, + input2_y: &FieldElement, ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { - todo!(); - } - - fn ec_double( - &self, - _input_x: &FieldElement, - _input_y: &FieldElement, - ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { - todo!(); + embedded_curve_add(*input1_x, *input1_y, *input2_x, *input2_y) } } diff --git a/acvm-repo/brillig/Cargo.toml b/acvm-repo/brillig/Cargo.toml index ee8651faeec..b9cedfe8d60 100644 --- a/acvm-repo/brillig/Cargo.toml +++ b/acvm-repo/brillig/Cargo.toml @@ -2,7 +2,7 @@ name = "brillig" description = "Brillig is the bytecode ACIR uses for non-determinism." # x-release-please-start-version -version = "0.38.0" +version = "0.39.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/acvm-repo/brillig/src/black_box.rs b/acvm-repo/brillig/src/black_box.rs index c007e78b785..22fac6f3ba3 100644 --- a/acvm-repo/brillig/src/black_box.rs +++ b/acvm-repo/brillig/src/black_box.rs @@ -6,15 +6,30 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum BlackBoxOp { /// Calculates the SHA256 hash of the inputs. - Sha256 { message: HeapVector, output: HeapArray }, + Sha256 { + message: HeapVector, + output: HeapArray, + }, /// Calculates the Blake2s hash of the inputs. - Blake2s { message: HeapVector, output: HeapArray }, + Blake2s { + message: HeapVector, + output: HeapArray, + }, /// Calculates the Blake3 hash of the inputs. - Blake3 { message: HeapVector, output: HeapArray }, + Blake3 { + message: HeapVector, + output: HeapArray, + }, /// Calculates the Keccak256 hash of the inputs. - Keccak256 { message: HeapVector, output: HeapArray }, + Keccak256 { + message: HeapVector, + output: HeapArray, + }, /// Keccak Permutation function of 1600 width - Keccakf1600 { message: HeapVector, output: HeapArray }, + Keccakf1600 { + message: HeapVector, + output: HeapArray, + }, /// Verifies a ECDSA signature over the secp256k1 curve. EcdsaSecp256k1 { hashed_msg: HeapVector, @@ -40,11 +55,23 @@ pub enum BlackBoxOp { result: RegisterIndex, }, /// Calculates a Pedersen commitment to the inputs. - PedersenCommitment { inputs: HeapVector, domain_separator: RegisterIndex, output: HeapArray }, + PedersenCommitment { + inputs: HeapVector, + domain_separator: RegisterIndex, + output: HeapArray, + }, /// Calculates a Pedersen hash to the inputs. - PedersenHash { inputs: HeapVector, domain_separator: RegisterIndex, output: RegisterIndex }, + PedersenHash { + inputs: HeapVector, + domain_separator: RegisterIndex, + output: RegisterIndex, + }, /// Performs scalar multiplication over the embedded curve. - FixedBaseScalarMul { low: RegisterIndex, high: RegisterIndex, result: HeapArray }, + FixedBaseScalarMul { + low: RegisterIndex, + high: RegisterIndex, + result: HeapArray, + }, /// Performs addition over the embedded curve. EmbeddedCurveAdd { input1_x: RegisterIndex, @@ -53,6 +80,43 @@ pub enum BlackBoxOp { input2_y: RegisterIndex, result: HeapArray, }, - /// Performs point doubling over the embedded curve. - EmbeddedCurveDouble { input1_x: RegisterIndex, input1_y: RegisterIndex, result: HeapArray }, + BigIntAdd { + lhs: RegisterIndex, + rhs: RegisterIndex, + output: RegisterIndex, + }, + BigIntNeg { + lhs: RegisterIndex, + rhs: RegisterIndex, + output: RegisterIndex, + }, + BigIntMul { + lhs: RegisterIndex, + rhs: RegisterIndex, + output: RegisterIndex, + }, + BigIntDiv { + lhs: RegisterIndex, + rhs: RegisterIndex, + output: RegisterIndex, + }, + BigIntFromLeBytes { + inputs: HeapVector, + modulus: HeapVector, + output: RegisterIndex, + }, + BigIntToLeBytes { + input: RegisterIndex, + output: HeapVector, + }, + Poseidon2Permutation { + message: HeapVector, + output: HeapArray, + len: RegisterIndex, + }, + Sha256Compression { + input: HeapVector, + hash_values: HeapVector, + output: HeapArray, + }, } diff --git a/acvm-repo/brillig/src/foreign_call.rs b/acvm-repo/brillig/src/foreign_call.rs index 1359d7d604d..3f124a9a0a7 100644 --- a/acvm-repo/brillig/src/foreign_call.rs +++ b/acvm-repo/brillig/src/foreign_call.rs @@ -37,7 +37,7 @@ impl ForeignCallParam { } /// Represents the full output of a [foreign call][crate::Opcode::ForeignCall]. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Default)] pub struct ForeignCallResult { /// Resolved output values of the foreign call. pub values: Vec, diff --git a/acvm-repo/brillig_vm/Cargo.toml b/acvm-repo/brillig_vm/Cargo.toml index 91bef2572bb..5a8a34be881 100644 --- a/acvm-repo/brillig_vm/Cargo.toml +++ b/acvm-repo/brillig_vm/Cargo.toml @@ -2,7 +2,7 @@ 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.38.0" +version = "0.39.0" # x-release-please-end authors.workspace = true edition.workspace = true diff --git a/acvm-repo/brillig_vm/src/black_box.rs b/acvm-repo/brillig_vm/src/black_box.rs index 463038509e1..e9c25200c47 100644 --- a/acvm-repo/brillig_vm/src/black_box.rs +++ b/acvm-repo/brillig_vm/src/black_box.rs @@ -165,13 +165,6 @@ pub(crate) fn evaluate_black_box( memory.write_slice(registers.get(result.pointer).to_usize(), &[x.into(), y.into()]); Ok(()) } - BlackBoxOp::EmbeddedCurveDouble { input1_x, input1_y, result } => { - let input1_x = registers.get(*input1_x).to_field(); - let input1_y = registers.get(*input1_y).to_field(); - let (x, y) = solver.ec_double(&input1_x, &input1_y)?; - memory.write_slice(registers.get(result.pointer).to_usize(), &[x.into(), y.into()]); - Ok(()) - } BlackBoxOp::PedersenCommitment { inputs, domain_separator, output } => { let inputs: Vec = read_heap_vector(memory, registers, inputs).iter().map(|x| x.to_field()).collect(); @@ -200,6 +193,14 @@ pub(crate) fn evaluate_black_box( registers.set(*output, hash.into()); Ok(()) } + BlackBoxOp::BigIntAdd { .. } => todo!(), + BlackBoxOp::BigIntNeg { .. } => todo!(), + BlackBoxOp::BigIntMul { .. } => todo!(), + BlackBoxOp::BigIntDiv { .. } => todo!(), + BlackBoxOp::BigIntFromLeBytes { .. } => todo!(), + BlackBoxOp::BigIntToLeBytes { .. } => todo!(), + BlackBoxOp::Poseidon2Permutation { .. } => todo!(), + BlackBoxOp::Sha256Compression { .. } => todo!(), } } @@ -217,7 +218,14 @@ fn black_box_function_from_op(op: &BlackBoxOp) -> BlackBoxFunc { BlackBoxOp::PedersenHash { .. } => BlackBoxFunc::PedersenHash, BlackBoxOp::FixedBaseScalarMul { .. } => BlackBoxFunc::FixedBaseScalarMul, BlackBoxOp::EmbeddedCurveAdd { .. } => BlackBoxFunc::EmbeddedCurveAdd, - BlackBoxOp::EmbeddedCurveDouble { .. } => BlackBoxFunc::EmbeddedCurveDouble, + BlackBoxOp::BigIntAdd { .. } => BlackBoxFunc::BigIntAdd, + BlackBoxOp::BigIntNeg { .. } => BlackBoxFunc::BigIntNeg, + BlackBoxOp::BigIntMul { .. } => BlackBoxFunc::BigIntMul, + BlackBoxOp::BigIntDiv { .. } => BlackBoxFunc::BigIntDiv, + BlackBoxOp::BigIntFromLeBytes { .. } => BlackBoxFunc::BigIntFromLeBytes, + BlackBoxOp::BigIntToLeBytes { .. } => BlackBoxFunc::BigIntToLeBytes, + BlackBoxOp::Poseidon2Permutation { .. } => BlackBoxFunc::Poseidon2Permutation, + BlackBoxOp::Sha256Compression { .. } => BlackBoxFunc::Sha256Compression, } } diff --git a/acvm-repo/brillig_vm/src/lib.rs b/acvm-repo/brillig_vm/src/lib.rs index df4c8135bce..38a562e65a1 100644 --- a/acvm-repo/brillig_vm/src/lib.rs +++ b/acvm-repo/brillig_vm/src/lib.rs @@ -168,7 +168,7 @@ impl<'a, B: BlackBoxFunctionSolver> VM<'a, B> { self.registers.set(register_index, value); } - pub fn get_memory(&self) -> &Vec { + pub fn get_memory(&self) -> &[Value] { self.memory.values() } @@ -242,7 +242,7 @@ impl<'a, B: BlackBoxFunctionSolver> VM<'a, B> { self.registers.set(*value_index, *value); } _ => unreachable!( - "Function result size does not match brillig bytecode (expected 1 result)" + "Function result size does not match brillig bytecode. Expected 1 result but got {output:?}" ), }, RegisterOrMemory::HeapArray(HeapArray { pointer: pointer_index, size }) => { @@ -460,13 +460,6 @@ impl BlackBoxFunctionSolver for DummyBlackBoxSolver { ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { Ok((5_u128.into(), 6_u128.into())) } - fn ec_double( - &self, - _input1_x: &FieldElement, - _input1_y: &FieldElement, - ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { - Ok((7_u128.into(), 8_u128.into())) - } } #[cfg(test)] @@ -748,7 +741,7 @@ mod tests { let opcodes = [&start[..], &loop_body[..]].concat(); let vm = brillig_execute_and_get_vm(memory, &opcodes); - vm.get_memory().clone() + vm.get_memory().to_vec() } let memory = brillig_write_memory(vec![Value::from(0u128); 5]); @@ -904,7 +897,7 @@ mod tests { let opcodes = [&start[..], &recursive_fn[..]].concat(); let vm = brillig_execute_and_get_vm(memory, &opcodes); - vm.get_memory().clone() + vm.get_memory().to_vec() } let memory = brillig_recursive_write_memory(vec![Value::from(0u128); 5]); diff --git a/acvm-repo/brillig_vm/src/memory.rs b/acvm-repo/brillig_vm/src/memory.rs index e2309537283..8a6993f1353 100644 --- a/acvm-repo/brillig_vm/src/memory.rs +++ b/acvm-repo/brillig_vm/src/memory.rs @@ -39,7 +39,7 @@ impl Memory { } /// Returns the values of the memory - pub fn values(&self) -> &Vec { + pub fn values(&self) -> &[Value] { &self.inner } } diff --git a/aztec_macros/src/lib.rs b/aztec_macros/src/lib.rs index 7c62f8f8169..baabd9aa1dc 100644 --- a/aztec_macros/src/lib.rs +++ b/aztec_macros/src/lib.rs @@ -26,8 +26,12 @@ impl MacroProcessor for AztecMacro { transform(ast, crate_id, context) } - fn process_typed_ast(&self, crate_id: &CrateId, context: &mut HirContext) { - transform_hir(crate_id, context) + fn process_typed_ast( + &self, + crate_id: &CrateId, + context: &mut HirContext, + ) -> Result<(), (MacroError, FileId)> { + transform_hir(crate_id, context).map_err(|(err, file_id)| (err.into(), file_id)) } } @@ -36,32 +40,33 @@ const MAX_CONTRACT_FUNCTIONS: usize = 2_usize.pow(FUNCTION_TREE_HEIGHT); #[derive(Debug, Clone)] pub enum AztecMacroError { - AztecNotFound, - AztecComputeNoteHashAndNullifierNotFound { span: Span }, - AztecContractHasTooManyFunctions { span: Span }, - AztecContractConstructorMissing { span: Span }, + AztecDepNotFound, + ComputeNoteHashAndNullifierNotFound { span: Span }, + ContractHasTooManyFunctions { span: Span }, + ContractConstructorMissing { span: Span }, UnsupportedFunctionArgumentType { span: Span, typ: UnresolvedTypeData }, + EventError { span: Span, message: String }, } impl From for MacroError { fn from(err: AztecMacroError) -> Self { match err { - AztecMacroError::AztecNotFound {} => MacroError { + AztecMacroError::AztecDepNotFound {} => MacroError { primary_message: "Aztec dependency not found. Please add aztec as a dependency in your Cargo.toml. For more information go to https://docs.aztec.network/dev_docs/debugging/aztecnr-errors#aztec-dependency-not-found-please-add-aztec-as-a-dependency-in-your-nargotoml".to_owned(), secondary_message: None, span: None, }, - AztecMacroError::AztecComputeNoteHashAndNullifierNotFound { span } => MacroError { + AztecMacroError::ComputeNoteHashAndNullifierNotFound { span } => MacroError { primary_message: "compute_note_hash_and_nullifier function not found. Define it in your contract. For more information go to https://docs.aztec.network/dev_docs/debugging/aztecnr-errors#compute_note_hash_and_nullifier-function-not-found-define-it-in-your-contract".to_owned(), secondary_message: None, span: Some(span), }, - AztecMacroError::AztecContractHasTooManyFunctions { span } => MacroError { + AztecMacroError::ContractHasTooManyFunctions { span } => MacroError { primary_message: format!("Contract can only have a maximum of {} functions", MAX_CONTRACT_FUNCTIONS), secondary_message: None, span: Some(span), }, - AztecMacroError::AztecContractConstructorMissing { span } => MacroError { + AztecMacroError::ContractConstructorMissing { span } => MacroError { primary_message: "Contract must have a constructor function".to_owned(), secondary_message: None, span: Some(span), @@ -71,6 +76,11 @@ impl From for MacroError { secondary_message: None, span: Some(span), }, + AztecMacroError::EventError { span, message } => MacroError { + primary_message: message, + secondary_message: None, + span: Some(span), + }, } } } @@ -222,7 +232,9 @@ fn transform( // Covers all functions in the ast for submodule in ast.submodules.iter_mut().filter(|submodule| submodule.is_contract) { - if transform_module(&mut submodule.contents, crate_id, context)? { + if transform_module(&mut submodule.contents, crate_id, context) + .map_err(|(err, file_id)| (err.into(), file_id))? + { check_for_aztec_dependency(crate_id, context)?; include_relevant_imports(&mut submodule.contents); } @@ -235,8 +247,11 @@ fn transform( // /// Completes the Hir with data gathered from type resolution -fn transform_hir(crate_id: &CrateId, context: &mut HirContext) { - transform_events(crate_id, context); +fn transform_hir( + crate_id: &CrateId, + context: &mut HirContext, +) -> Result<(), (AztecMacroError, FileId)> { + transform_events(crate_id, context) } /// Includes an import to the aztec library if it has not been included yet @@ -264,7 +279,7 @@ fn check_for_aztec_dependency( if has_aztec_dependency { Ok(()) } else { - Err((AztecMacroError::AztecNotFound.into(), crate_graph.root_file_id)) + Err((AztecMacroError::AztecDepNotFound.into(), crate_graph.root_file_id)) } } @@ -323,7 +338,7 @@ fn transform_module( module: &mut SortedModule, crate_id: &CrateId, context: &HirContext, -) -> Result { +) -> Result { let mut has_transformed_module = false; // Check for a user defined storage struct @@ -332,8 +347,7 @@ fn transform_module( if storage_defined && !check_for_compute_note_hash_and_nullifier_definition(module) { let crate_graph = &context.crate_graph[crate_id]; return Err(( - AztecMacroError::AztecComputeNoteHashAndNullifierNotFound { span: Span::default() } - .into(), + AztecMacroError::ComputeNoteHashAndNullifierNotFound { span: Span::default() }, crate_graph.root_file_id, )); } @@ -350,11 +364,11 @@ fn transform_module( let crate_graph = &context.crate_graph[crate_id]; if is_custom_attribute(&secondary_attribute, "aztec(private)") { transform_function("Private", func, storage_defined) - .map_err(|err| (err.into(), crate_graph.root_file_id))?; + .map_err(|err| (err, crate_graph.root_file_id))?; has_transformed_module = true; } else if is_custom_attribute(&secondary_attribute, "aztec(public)") { transform_function("Public", func, storage_defined) - .map_err(|err| (err.into(), crate_graph.root_file_id))?; + .map_err(|err| (err, crate_graph.root_file_id))?; has_transformed_module = true; } } @@ -371,7 +385,7 @@ fn transform_module( if module.functions.len() > MAX_CONTRACT_FUNCTIONS { let crate_graph = &context.crate_graph[crate_id]; return Err(( - AztecMacroError::AztecContractHasTooManyFunctions { span: Span::default() }.into(), + AztecMacroError::ContractHasTooManyFunctions { span: Span::default() }, crate_graph.root_file_id, )); } @@ -380,7 +394,7 @@ fn transform_module( if !constructor_defined { let crate_graph = &context.crate_graph[crate_id]; return Err(( - AztecMacroError::AztecContractConstructorMissing { span: Span::default() }.into(), + AztecMacroError::ContractConstructorMissing { span: Span::default() }, crate_graph.root_file_id, )); } @@ -471,19 +485,30 @@ fn collect_crate_structs(crate_id: &CrateId, context: &HirContext) -> Vec Result<(), (AztecMacroError, FileId)> { let struct_type = interner.get_struct(struct_id); let selector_id = interner - .lookup_method(&Type::Struct(struct_type, vec![]), struct_id, "selector", false) - .expect("Selector method not found"); + .lookup_method(&Type::Struct(struct_type.clone(), vec![]), struct_id, "selector", false) + .ok_or_else(|| { + let error = AztecMacroError::EventError { + span: struct_type.borrow().location.span, + message: "Selector method not found".to_owned(), + }; + (error, struct_type.borrow().location.file) + })?; let selector_function = interner.function(&selector_id); let compute_selector_statement = interner.statement( - selector_function - .block(interner) - .statements() - .first() - .expect("Compute selector statement not found"), + selector_function.block(interner).statements().first().ok_or_else(|| { + let error = AztecMacroError::EventError { + span: struct_type.borrow().location.span, + message: "Compute selector statement not found".to_owned(), + }; + (error, struct_type.borrow().location.file) + })?, ); let compute_selector_expression = match compute_selector_statement { @@ -493,12 +518,21 @@ fn transform_event(struct_id: StructId, interner: &mut NodeInterner) { }, _ => None, } - .expect("Compute selector statement is not a call expression"); - - let first_arg_id = compute_selector_expression - .arguments - .first() - .expect("Missing argument for compute selector"); + .ok_or_else(|| { + let error = AztecMacroError::EventError { + span: struct_type.borrow().location.span, + message: "Compute selector statement is not a call expression".to_owned(), + }; + (error, struct_type.borrow().location.file) + })?; + + let first_arg_id = compute_selector_expression.arguments.first().ok_or_else(|| { + let error = AztecMacroError::EventError { + span: struct_type.borrow().location.span, + message: "Compute selector statement is not a call expression".to_owned(), + }; + (error, struct_type.borrow().location.file) + })?; match interner.expression(first_arg_id) { HirExpression::Literal(HirLiteral::Str(signature)) @@ -517,18 +551,29 @@ fn transform_event(struct_id: StructId, interner: &mut NodeInterner) { selector_literal_id, Type::String(Box::new(Type::Constant(signature.len() as u64))), ); + Ok(()) } - _ => unreachable!("Signature placeholder literal does not match"), + _ => Err(( + AztecMacroError::EventError { + span: struct_type.borrow().location.span, + message: "Signature placeholder literal does not match".to_owned(), + }, + struct_type.borrow().location.file, + )), } } -fn transform_events(crate_id: &CrateId, context: &mut HirContext) { +fn transform_events( + crate_id: &CrateId, + context: &mut HirContext, +) -> Result<(), (AztecMacroError, FileId)> { for struct_id in collect_crate_structs(crate_id, context) { let attributes = context.def_interner.struct_attributes(&struct_id); if attributes.iter().any(|attr| matches!(attr, SecondaryAttribute::Event)) { - transform_event(struct_id, &mut context.def_interner); + transform_event(struct_id, &mut context.def_interner)?; } } + Ok(()) } const SIGNATURE_PLACEHOLDER: &str = "SIGNATURE_PLACEHOLDER"; diff --git a/compiler/noirc_driver/Cargo.toml b/compiler/noirc_driver/Cargo.toml index eb9650e8aec..d9b240101d8 100644 --- a/compiler/noirc_driver/Cargo.toml +++ b/compiler/noirc_driver/Cargo.toml @@ -25,3 +25,4 @@ rust-embed.workspace = true tracing.workspace = true aztec_macros = { path = "../../aztec_macros" } +noirc_macros = { path = "../../noirc_macros" } diff --git a/compiler/noirc_driver/src/abi_gen.rs b/compiler/noirc_driver/src/abi_gen.rs index e546cd822b7..7fafa719186 100644 --- a/compiler/noirc_driver/src/abi_gen.rs +++ b/compiler/noirc_driver/src/abi_gen.rs @@ -63,7 +63,7 @@ fn into_abi_params(context: &Context, params: Vec) -> Vec { // Takes each abi parameter and shallowly maps to the expected witness range in which the // parameter's constituent values live. fn param_witnesses_from_abi_param( - abi_params: &Vec, + abi_params: &[AbiParameter], input_witnesses: Vec, ) -> BTreeMap>> { let mut idx = 0_usize; diff --git a/compiler/noirc_driver/src/contract.rs b/compiler/noirc_driver/src/contract.rs index ae55d239cf3..5f4b66e7dd2 100644 --- a/compiler/noirc_driver/src/contract.rs +++ b/compiler/noirc_driver/src/contract.rs @@ -26,7 +26,7 @@ pub enum ContractFunctionType { Unconstrained, } -#[derive(Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct CompiledContract { pub noir_version: String, @@ -51,7 +51,7 @@ pub struct CompiledContract { /// A contract function unlike a regular Noir program /// however can have additional properties. /// One of these being a function type. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ContractFunction { pub name: String, diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index db69d41c704..25d4229048e 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -3,6 +3,7 @@ #![warn(unreachable_pub)] #![warn(clippy::semicolon_if_nothing_returned)] +use acvm::ExpressionWidth; use clap::Args; use fm::{FileId, FileManager}; use iter_extended::vecmap; @@ -16,7 +17,6 @@ use noirc_frontend::hir::Context; use noirc_frontend::macros_api::MacroProcessor; use noirc_frontend::monomorphization::monomorphize; use noirc_frontend::node_interner::FuncId; -use serde::{Deserialize, Serialize}; use std::path::Path; use tracing::info; @@ -43,8 +43,12 @@ pub const NOIRC_VERSION: &str = env!("CARGO_PKG_VERSION"); pub const NOIR_ARTIFACT_VERSION_STRING: &str = concat!(env!("CARGO_PKG_VERSION"), "+", env!("GIT_COMMIT")); -#[derive(Args, Clone, Debug, Default, Serialize, Deserialize)] +#[derive(Args, Clone, Debug, Default)] pub struct CompileOptions { + /// Override the expression width requested by the backend. + #[arg(long, value_parser = parse_expression_width)] + pub expression_width: Option, + /// Force a full recompilation. #[arg(long = "force")] pub force_compile: bool, @@ -72,9 +76,23 @@ pub struct CompileOptions { #[arg(long, hide = true)] pub only_acir: bool, - /// Disables the builtin macros being used in the compiler + /// Disables the builtin Aztec macros being used in the compiler #[arg(long, hide = true)] pub disable_macros: bool, + + /// Outputs the monomorphized IR to stdout for debugging + #[arg(long, hide = true)] + pub show_monomorphized: bool, +} + +fn parse_expression_width(input: &str) -> Result { + use std::io::{Error, ErrorKind}; + + let width = input + .parse::() + .map_err(|err| Error::new(ErrorKind::InvalidInput, err.to_string()))?; + + Ok(ExpressionWidth::from(width)) } /// Helper type used to signify where only warnings are expected in file diagnostics @@ -173,9 +191,12 @@ pub fn check_crate( disable_macros: bool, ) -> CompilationResult<()> { let macros: Vec<&dyn MacroProcessor> = if disable_macros { - vec![] + vec![&noirc_macros::AssertMessageMacro as &dyn MacroProcessor] } else { - vec![&aztec_macros::AztecMacro as &dyn MacroProcessor] + vec![ + &aztec_macros::AztecMacro as &dyn MacroProcessor, + &noirc_macros::AssertMessageMacro as &dyn MacroProcessor, + ] }; let mut errors = vec![]; @@ -226,6 +247,7 @@ pub fn compile_main( let compiled_program = compile_no_check(context, options, main, cached_program, options.force_compile) .map_err(FileDiagnostic::from)?; + let compilation_warnings = vecmap(compiled_program.warnings.clone(), FileDiagnostic::from); if options.deny_warnings && !compilation_warnings.is_empty() { return Err(compilation_warnings); @@ -391,6 +413,9 @@ pub fn compile_no_check( let hash = fxhash::hash64(&program); let hashes_match = cached_program.as_ref().map_or(false, |program| program.hash == hash); + if options.show_monomorphized { + println!("{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. diff --git a/compiler/noirc_driver/tests/contracts.rs b/compiler/noirc_driver/tests/contracts.rs new file mode 100644 index 00000000000..c3041292352 --- /dev/null +++ b/compiler/noirc_driver/tests/contracts.rs @@ -0,0 +1,42 @@ +use std::path::Path; + +use fm::FileId; +use noirc_driver::{file_manager_with_stdlib, prepare_crate, CompileOptions, ErrorsAndWarnings}; +use noirc_errors::CustomDiagnostic; +use noirc_frontend::hir::{def_map::parse_file, Context}; + +#[test] +fn reject_crates_containing_multiple_contracts() -> Result<(), ErrorsAndWarnings> { + let source = " +contract Foo {} + +contract Bar {}"; + + let root = Path::new(""); + let file_name = Path::new("main.nr"); + let mut file_manager = file_manager_with_stdlib(root); + file_manager.add_file_with_source(file_name, source.to_owned()).expect( + "Adding source buffer to file manager should never fail when file manager is empty", + ); + let parsed_files = file_manager + .as_file_map() + .all_file_ids() + .map(|&file_id| (file_id, parse_file(&file_manager, file_id))) + .collect(); + + let mut context = Context::new(file_manager, parsed_files); + let root_crate_id = prepare_crate(&mut context, file_name); + + let errors = + noirc_driver::compile_contract(&mut context, root_crate_id, &CompileOptions::default()) + .unwrap_err(); + + assert_eq!( + errors, + vec![CustomDiagnostic::from_message("Packages are limited to a single contract") + .in_file(FileId::default())], + "stdlib is producing warnings" + ); + + Ok(()) +} diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs index c081806f4a7..96d80cb8131 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_black_box.rs @@ -215,23 +215,6 @@ pub(crate) fn convert_black_box_call( ) } } - BlackBoxFunc::EmbeddedCurveDouble => { - if let ( - [BrilligVariable::Simple(input1_x), BrilligVariable::Simple(input1_y)], - [BrilligVariable::BrilligArray(result_array)], - ) = (function_arguments, function_results) - { - brillig_context.black_box_op_instruction(BlackBoxOp::EmbeddedCurveDouble { - input1_x: *input1_x, - input1_y: *input1_y, - result: result_array.to_heap_array(), - }); - } else { - unreachable!( - "ICE: EmbeddedCurveAdd expects two register arguments and one array result" - ) - } - } BlackBoxFunc::AND => { unreachable!("ICE: `BlackBoxFunc::AND` calls should be transformed into a `BinaryOp`") } @@ -244,6 +227,138 @@ pub(crate) fn convert_black_box_call( BlackBoxFunc::RecursiveAggregation => unimplemented!( "ICE: `BlackBoxFunc::RecursiveAggregation` is not implemented by the Brillig VM" ), + BlackBoxFunc::BigIntAdd => { + if let ( + [BrilligVariable::Simple(lhs), BrilligVariable::Simple(rhs)], + [BrilligVariable::Simple(output)], + ) = (function_arguments, function_results) + { + brillig_context.black_box_op_instruction(BlackBoxOp::BigIntAdd { + lhs: *lhs, + rhs: *rhs, + output: *output, + }); + } else { + unreachable!( + "ICE: EmbeddedCurveAdd expects two register arguments and one array result" + ) + } + } + BlackBoxFunc::BigIntNeg => { + if let ( + [BrilligVariable::Simple(lhs), BrilligVariable::Simple(rhs)], + [BrilligVariable::Simple(output)], + ) = (function_arguments, function_results) + { + brillig_context.black_box_op_instruction(BlackBoxOp::BigIntNeg { + lhs: *lhs, + rhs: *rhs, + output: *output, + }); + } else { + unreachable!( + "ICE: EmbeddedCurveAdd expects two register arguments and one array result" + ) + } + } + BlackBoxFunc::BigIntMul => { + if let ( + [BrilligVariable::Simple(lhs), BrilligVariable::Simple(rhs)], + [BrilligVariable::Simple(output)], + ) = (function_arguments, function_results) + { + brillig_context.black_box_op_instruction(BlackBoxOp::BigIntMul { + lhs: *lhs, + rhs: *rhs, + output: *output, + }); + } else { + unreachable!( + "ICE: EmbeddedCurveAdd expects two register arguments and one array result" + ) + } + } + BlackBoxFunc::BigIntDiv => { + if let ( + [BrilligVariable::Simple(lhs), BrilligVariable::Simple(rhs)], + [BrilligVariable::Simple(output)], + ) = (function_arguments, function_results) + { + brillig_context.black_box_op_instruction(BlackBoxOp::BigIntDiv { + lhs: *lhs, + rhs: *rhs, + output: *output, + }); + } else { + unreachable!( + "ICE: EmbeddedCurveAdd expects two register arguments and one array result" + ) + } + } + BlackBoxFunc::BigIntFromLeBytes => { + if let ([inputs, modulus], [BrilligVariable::Simple(output)]) = + (function_arguments, function_results) + { + let inputs_vector = convert_array_or_vector(brillig_context, inputs, bb_func); + let modulus_vector = convert_array_or_vector(brillig_context, modulus, bb_func); + brillig_context.black_box_op_instruction(BlackBoxOp::BigIntFromLeBytes { + inputs: inputs_vector.to_heap_vector(), + modulus: modulus_vector.to_heap_vector(), + output: *output, + }); + } else { + unreachable!( + "ICE: EmbeddedCurveAdd expects two register arguments and one array result" + ) + } + } + BlackBoxFunc::BigIntToLeBytes => { + if let ( + [BrilligVariable::Simple(input)], + [BrilligVariable::BrilligVector(result_vector)], + ) = (function_arguments, function_results) + { + brillig_context.black_box_op_instruction(BlackBoxOp::BigIntToLeBytes { + input: *input, + output: result_vector.to_heap_vector(), + }); + } else { + unreachable!( + "ICE: EmbeddedCurveAdd expects two register arguments and one array result" + ) + } + } + BlackBoxFunc::Poseidon2Permutation => { + if let ( + [message, BrilligVariable::Simple(state_len)], + [BrilligVariable::BrilligArray(result_array)], + ) = (function_arguments, function_results) + { + let message_vector = convert_array_or_vector(brillig_context, message, bb_func); + brillig_context.black_box_op_instruction(BlackBoxOp::Poseidon2Permutation { + message: message_vector.to_heap_vector(), + output: result_array.to_heap_array(), + len: *state_len, + }); + } else { + unreachable!("ICE: Poseidon2Permutation expects one array argument, a length and one array result") + } + } + BlackBoxFunc::Sha256Compression => { + if let ([message, hash_values], [BrilligVariable::BrilligArray(result_array)]) = + (function_arguments, function_results) + { + let message_vector = convert_array_or_vector(brillig_context, message, bb_func); + let hash_vector = convert_array_or_vector(brillig_context, hash_values, bb_func); + brillig_context.black_box_op_instruction(BlackBoxOp::Sha256Compression { + input: message_vector.to_heap_vector(), + hash_values: hash_vector.to_heap_vector(), + output: result_array.to_heap_array(), + }); + } else { + unreachable!("ICE: Sha256Compression expects two array argument, one array result") + } + } } } 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 db005d9d438..632add74e6d 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -3,6 +3,7 @@ use crate::brillig::brillig_ir::{ BrilligBinaryOp, BrilligContext, BRILLIG_INTEGER_ARITHMETIC_BIT_SIZE, }; use crate::ssa::ir::dfg::CallStack; +use crate::ssa::ir::instruction::ConstrainError; use crate::ssa::ir::{ basic_block::{BasicBlock, BasicBlockId}, dfg::DataFlowGraph, @@ -265,7 +266,30 @@ impl<'block> BrilligBlock<'block> { condition, ); - self.brillig_context.constrain_instruction(condition, assert_message.clone()); + let assert_message = if let Some(error) = assert_message { + match error.as_ref() { + ConstrainError::Static(string) => Some(string.clone()), + ConstrainError::Dynamic(call_instruction) => { + let Instruction::Call { func, arguments } = call_instruction else { + unreachable!("expected a call instruction") + }; + + let Value::Function(func_id) = &dfg[*func] else { + unreachable!("expected a function value") + }; + + self.convert_ssa_function_call(*func_id, arguments, dfg, &[]); + + // Dynamic assert messages are handled in the generated function call. + // We then don't need to attach one to the constrain instruction. + None + } + } + } else { + None + }; + + self.brillig_context.constrain_instruction(condition, assert_message); self.brillig_context.deallocate_register(condition); } Instruction::Allocate => { @@ -369,7 +393,8 @@ impl<'block> BrilligBlock<'block> { } } Value::Function(func_id) => { - self.convert_ssa_function_call(*func_id, arguments, dfg, instruction_id); + let result_ids = dfg.instruction_results(instruction_id); + self.convert_ssa_function_call(*func_id, arguments, dfg, result_ids); } Value::Intrinsic(Intrinsic::BlackBox(bb_func)) => { // Slices are represented as a tuple of (length, slice contents). @@ -564,6 +589,7 @@ impl<'block> BrilligBlock<'block> { }; let index_register = self.convert_ssa_register_value(*index, dfg); + self.validate_array_index(array_variable, index_register); self.retrieve_variable_from_array( array_pointer, index_register, @@ -582,6 +608,7 @@ impl<'block> BrilligBlock<'block> { result_ids[0], dfg, ); + self.validate_array_index(source_variable, index_register); self.convert_ssa_array_set( source_variable, @@ -636,7 +663,7 @@ impl<'block> BrilligBlock<'block> { func_id: FunctionId, arguments: &[ValueId], dfg: &DataFlowGraph, - instruction_id: InstructionId, + result_ids: &[ValueId], ) { // Convert the arguments to registers casting those to the types of the receiving function let argument_registers: Vec = arguments @@ -644,8 +671,6 @@ impl<'block> BrilligBlock<'block> { .flat_map(|argument_id| self.convert_ssa_value(*argument_id, dfg).extract_registers()) .collect(); - let result_ids = dfg.instruction_results(instruction_id); - // Create label for the function that will be called let label_of_function_to_call = FunctionContext::function_id_to_function_label(func_id); @@ -690,6 +715,37 @@ impl<'block> BrilligBlock<'block> { .post_call_prep_returns_load_registers(&returned_registers, &saved_registers); } + fn validate_array_index( + &mut self, + array_variable: BrilligVariable, + index_register: RegisterIndex, + ) { + let (size_as_register, should_deallocate_size) = match array_variable { + BrilligVariable::BrilligArray(BrilligArray { size, .. }) => { + (self.brillig_context.make_constant(size.into()), true) + } + BrilligVariable::BrilligVector(BrilligVector { size, .. }) => (size, false), + _ => unreachable!("ICE: validate array index on non-array"), + }; + + let condition = self.brillig_context.allocate_register(); + + self.brillig_context.memory_op( + index_register, + size_as_register, + condition, + BinaryIntOp::LessThan, + ); + + self.brillig_context + .constrain_instruction(condition, Some("Array index out of bounds".to_owned())); + + if should_deallocate_size { + self.brillig_context.deallocate_register(size_as_register); + } + self.brillig_context.deallocate_register(condition); + } + pub(crate) fn retrieve_variable_from_array( &mut self, array_pointer: RegisterIndex, diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs index db3cd501ca0..3e6c3d4d7de 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs @@ -1096,14 +1096,6 @@ pub(crate) mod tests { ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { panic!("Path not trodden by this test") } - - fn ec_double( - &self, - _input_x: &FieldElement, - _input_y: &FieldElement, - ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { - panic!("Path not trodden by this test") - } } pub(crate) fn create_context() -> BrilligContext { diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs index dc8c6b6694c..a8563dc9efe 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs @@ -413,15 +413,6 @@ impl DebugShow { result ); } - BlackBoxOp::EmbeddedCurveDouble { input1_x, input1_y, result } => { - debug_println!( - self.enable_debug_trace, - " EMBEDDED_CURVE_DOUBLE ({} {}) -> {}", - input1_x, - input1_y, - result - ); - } BlackBoxOp::PedersenCommitment { inputs, domain_separator, output } => { debug_println!( self.enable_debug_trace, @@ -457,6 +448,77 @@ impl DebugShow { result ); } + BlackBoxOp::BigIntAdd { lhs, rhs, output } => { + debug_println!( + self.enable_debug_trace, + " BIGINT_ADD {} {} -> {}", + lhs, + rhs, + output + ); + } + BlackBoxOp::BigIntNeg { lhs, rhs, output } => { + debug_println!( + self.enable_debug_trace, + " BIGINT_NEG {} {} -> {}", + lhs, + rhs, + output + ); + } + BlackBoxOp::BigIntMul { lhs, rhs, output } => { + debug_println!( + self.enable_debug_trace, + " BIGINT_MUL {} {} -> {}", + lhs, + rhs, + output + ); + } + BlackBoxOp::BigIntDiv { lhs, rhs, output } => { + debug_println!( + self.enable_debug_trace, + " BIGINT_DIV {} {} -> {}", + lhs, + rhs, + output + ); + } + BlackBoxOp::BigIntFromLeBytes { inputs, modulus, output } => { + debug_println!( + self.enable_debug_trace, + " BIGINT_FROM_LE_BYTES {} {} -> {}", + inputs, + modulus, + output + ); + } + BlackBoxOp::BigIntToLeBytes { input, output } => { + debug_println!( + self.enable_debug_trace, + " BIGINT_TO_LE_BYTES {} -> {}", + input, + output + ); + } + BlackBoxOp::Poseidon2Permutation { message, output, len } => { + debug_println!( + self.enable_debug_trace, + " POSEIDON2_PERMUTATION {} {} -> {}", + message, + len, + output + ); + } + BlackBoxOp::Sha256Compression { input, hash_values, output } => { + debug_println!( + self.enable_debug_trace, + " SHA256COMPRESSION {} {} -> {}", + input, + hash_values, + output + ); + } } } diff --git a/compiler/noirc_evaluator/src/errors.rs b/compiler/noirc_evaluator/src/errors.rs index d7229c0adc5..73b6e671bd5 100644 --- a/compiler/noirc_evaluator/src/errors.rs +++ b/compiler/noirc_evaluator/src/errors.rs @@ -44,6 +44,8 @@ pub enum RuntimeError { AssertConstantFailed { call_stack: CallStack }, #[error("Nested slices are not supported")] NestedSlice { call_stack: CallStack }, + #[error("Big Integer modulus do no match")] + BigIntModulus { call_stack: CallStack }, } // We avoid showing the actual lhs and rhs since most of the time they are just 0 @@ -68,7 +70,7 @@ impl From for FileDiagnostic { let message = warning.to_string(); let (secondary_message, call_stack) = match warning { InternalWarning::ReturnConstant { call_stack } => { - ("constant value".to_string(), call_stack) + ("This variable contains a value which is constrained to be a constant. Consider removing this value as additional return values increase proving/verification time".to_string(), call_stack) }, InternalWarning::VerifyProof { call_stack } => { ("verify_proof(...) aggregates data for the verifier, the actual verification will be done when the full proof is verified using nargo verify. nargo prove may generate an invalid proof if bad data is used as input to verify_proof".to_string(), call_stack) @@ -87,7 +89,7 @@ impl From for FileDiagnostic { #[derive(Debug, PartialEq, Eq, Clone, Error, Serialize, Deserialize)] pub enum InternalWarning { - #[error("Returning a constant value is not allowed")] + #[error("Return variable contains a constant value")] ReturnConstant { call_stack: CallStack }, #[error("Calling std::verify_proof(...) does not verify a proof")] VerifyProof { call_stack: CallStack }, @@ -132,7 +134,8 @@ impl RuntimeError { | RuntimeError::AssertConstantFailed { call_stack } | RuntimeError::IntegerOutOfBounds { call_stack, .. } | RuntimeError::UnsupportedIntegerSize { call_stack, .. } - | RuntimeError::NestedSlice { call_stack, .. } => call_stack, + | RuntimeError::NestedSlice { call_stack, .. } + | RuntimeError::BigIntModulus { call_stack, .. } => call_stack, } } } diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index e2da5652faf..0e3076923e0 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -63,7 +63,6 @@ pub(crate) fn optimize_into_acir( .run_pass(Ssa::mem2reg, "After Mem2Reg:") .run_pass(Ssa::fold_constants, "After Constant Folding:") .run_pass(Ssa::dead_instruction_elimination, "After Dead Instruction Elimination:") - .run_pass(Ssa::bubble_up_constrains, "After Constraint Bubbling:") .finish(); let brillig = ssa.to_brillig(print_brillig_trace); diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir.rs index 96800b22ad0..1ddbae0f339 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir.rs @@ -1,3 +1,4 @@ pub(crate) mod acir_variable; +pub(crate) mod big_int; pub(crate) mod generated_acir; pub(crate) mod sort; 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 ca1da74d43e..fd8c4b52228 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 @@ -1,3 +1,4 @@ +use super::big_int::BigIntContext; use super::generated_acir::GeneratedAcir; use crate::brillig::brillig_gen::brillig_directive; use crate::brillig::brillig_ir::artifact::GeneratedBrillig; @@ -107,6 +108,9 @@ pub(crate) struct AcirContext { /// then the `acir_ir` will be populated to assert this /// addition. acir_ir: GeneratedAcir, + + /// The BigIntContext, used to generate identifiers for BigIntegers + big_int_ctx: BigIntContext, } impl AcirContext { @@ -396,8 +400,8 @@ impl AcirContext { // Operands are booleans. // // a ^ b == a + b - 2*a*b - let sum = self.add_var(lhs, rhs)?; let prod = self.mul_var(lhs, rhs)?; + let sum = self.add_var(lhs, rhs)?; self.add_mul_var(sum, -FieldElement::from(2_i128), prod) } else { let inputs = vec![AcirValue::Var(lhs, typ.clone()), AcirValue::Var(rhs, typ)]; @@ -457,8 +461,8 @@ impl AcirContext { if bit_size == 1 { // Operands are booleans // a + b - ab - let sum = self.add_var(lhs, rhs)?; let mul = self.mul_var(lhs, rhs)?; + let sum = self.add_var(lhs, rhs)?; self.sub_var(sum, mul) } else { // Implement OR in terms of AND @@ -1137,10 +1141,10 @@ impl AcirContext { &mut self, name: BlackBoxFunc, mut inputs: Vec, - output_count: usize, + mut output_count: usize, ) -> Result, RuntimeError> { // Separate out any arguments that should be constants - let constants = match name { + let (constant_inputs, constant_outputs) = match name { BlackBoxFunc::PedersenCommitment | BlackBoxFunc::PedersenHash => { // The last argument of pedersen is the domain separator, which must be a constant let domain_var = match inputs.pop() { @@ -1164,23 +1168,140 @@ impl AcirContext { } }; - vec![domain_constant] + (vec![domain_constant], Vec::new()) + } + BlackBoxFunc::Poseidon2Permutation => { + // The last argument is the state length, which must be a constant + let state_len = match inputs.pop() { + Some(state_len) => state_len.into_var()?, + None => { + return Err(RuntimeError::InternalError(InternalError::MissingArg { + name: "poseidon_2_permutation call".to_string(), + arg: "length".to_string(), + call_stack: self.get_call_stack(), + })) + } + }; + + let state_len = match self.vars[&state_len].as_constant() { + Some(state_len) => state_len, + None => { + return Err(RuntimeError::InternalError(InternalError::NotAConstant { + name: "length".to_string(), + call_stack: self.get_call_stack(), + })) + } + }; + + (vec![state_len], Vec::new()) + } + BlackBoxFunc::BigIntAdd + | BlackBoxFunc::BigIntNeg + | BlackBoxFunc::BigIntMul + | BlackBoxFunc::BigIntDiv => { + assert_eq!(inputs.len(), 4, "ICE - bigint operation requires 4 inputs"); + let const_inputs = vecmap(inputs, |i| { + let var = i.into_var()?; + match self.vars[&var].as_constant() { + Some(const_var) => Ok(const_var), + None => Err(RuntimeError::InternalError(InternalError::NotAConstant { + name: "big integer".to_string(), + call_stack: self.get_call_stack(), + })), + } + }); + inputs = Vec::new(); + output_count = 0; + let mut field_inputs = Vec::new(); + for i in const_inputs { + field_inputs.push(i?); + } + if field_inputs[1] != field_inputs[3] { + return Err(RuntimeError::BigIntModulus { call_stack: self.get_call_stack() }); + } + + let result_id = self.big_int_ctx.new_big_int(field_inputs[1]); + ( + vec![field_inputs[0], field_inputs[2]], + vec![result_id.bigint_id(), result_id.modulus_id()], + ) + } + BlackBoxFunc::BigIntToLeBytes => { + let const_inputs = vecmap(inputs, |i| { + let var = i.into_var()?; + match self.vars[&var].as_constant() { + Some(const_var) => Ok(const_var), + None => Err(RuntimeError::InternalError(InternalError::NotAConstant { + name: "big integer".to_string(), + call_stack: self.get_call_stack(), + })), + } + }); + inputs = Vec::new(); + let mut field_inputs = Vec::new(); + for i in const_inputs { + field_inputs.push(i?); + } + let modulus = self.big_int_ctx.modulus(field_inputs[0]); + let bytes_len = ((modulus - BigUint::from(1_u32)).bits() - 1) / 8 + 1; + output_count = bytes_len as usize; + (field_inputs, vec![FieldElement::from(bytes_len as u128)]) + } + BlackBoxFunc::BigIntFromLeBytes => { + let invalid_input = "ICE - bigint operation requires 2 inputs"; + assert_eq!(inputs.len(), 2, "{invalid_input}"); + let mut modulus = Vec::new(); + match inputs.pop().expect(invalid_input) { + AcirValue::Array(values) => { + for value in values { + modulus.push(self.vars[&value.into_var()?].as_constant().ok_or( + RuntimeError::InternalError(InternalError::NotAConstant { + name: "big integer".to_string(), + call_stack: self.get_call_stack(), + }), + )?); + } + } + _ => { + return Err(RuntimeError::InternalError(InternalError::MissingArg { + name: "big_int_from_le_bytes".to_owned(), + arg: "modulus".to_owned(), + call_stack: self.get_call_stack(), + })); + } + } + let big_modulus = BigUint::from_bytes_le(&vecmap(&modulus, |b| b.to_u128() as u8)); + output_count = 0; + + let modulus_id = self.big_int_ctx.get_or_insert_modulus(big_modulus); + let result_id = + self.big_int_ctx.new_big_int(FieldElement::from(modulus_id as u128)); + (modulus, vec![result_id.bigint_id(), result_id.modulus_id()]) } - _ => vec![], + _ => (vec![], vec![]), }; // Convert `AcirVar` to `FunctionInput` let inputs = self.prepare_inputs_for_black_box_func_call(inputs)?; - // Call Black box with `FunctionInput` - let outputs = self.acir_ir.call_black_box(name, &inputs, constants, output_count)?; + let mut results = vecmap(&constant_outputs, |c| self.add_constant(*c)); + let outputs = self.acir_ir.call_black_box( + name, + &inputs, + constant_inputs, + constant_outputs, + output_count, + )?; // Convert `Witness` values which are now constrained to be the output of the // black box function call into `AcirVar`s. // // We do not apply range information on the output of the black box function. // See issue #1439 - Ok(vecmap(&outputs, |witness_index| self.add_data(AcirVarData::Witness(*witness_index)))) + results.extend(vecmap(&outputs, |witness_index| { + self.add_data(AcirVarData::Witness(*witness_index)) + })); + Ok(results) } /// Black box function calls expect their inputs to be in a specific data structure (FunctionInput). @@ -1317,20 +1438,22 @@ impl AcirContext { inputs: Vec, outputs: Vec, attempt_execution: bool, - ) -> Result, InternalError> { - let b_inputs = try_vecmap(inputs, |i| match i { - AcirValue::Var(var, _) => Ok(BrilligInputs::Single(self.var_to_expression(var)?)), - AcirValue::Array(vars) => { - let mut var_expressions: Vec = Vec::new(); - for var in vars { - self.brillig_array_input(&mut var_expressions, var)?; + ) -> Result, RuntimeError> { + let b_inputs = try_vecmap(inputs, |i| -> Result<_, InternalError> { + match i { + AcirValue::Var(var, _) => Ok(BrilligInputs::Single(self.var_to_expression(var)?)), + AcirValue::Array(vars) => { + let mut var_expressions: Vec = Vec::new(); + for var in vars { + self.brillig_array_input(&mut var_expressions, var)?; + } + Ok(BrilligInputs::Array(var_expressions)) + } + AcirValue::DynamicArray(_) => { + let mut var_expressions = Vec::new(); + self.brillig_array_input(&mut var_expressions, i)?; + Ok(BrilligInputs::Array(var_expressions)) } - Ok(BrilligInputs::Array(var_expressions)) - } - AcirValue::DynamicArray(_) => { - let mut var_expressions = Vec::new(); - self.brillig_array_input(&mut var_expressions, i)?; - Ok(BrilligInputs::Array(var_expressions)) } })?; @@ -1365,6 +1488,34 @@ impl AcirContext { let predicate = self.var_to_expression(predicate)?; self.acir_ir.brillig(Some(predicate), generated_brillig, b_inputs, b_outputs); + fn range_constraint_value( + context: &mut AcirContext, + value: &AcirValue, + ) -> Result<(), RuntimeError> { + match value { + AcirValue::Var(var, typ) => { + let numeric_type = match typ { + AcirType::NumericType(numeric_type) => numeric_type, + _ => unreachable!("`AcirValue::Var` may only hold primitive values"), + }; + context.range_constrain_var(*var, numeric_type, None)?; + } + AcirValue::Array(values) => { + for value in values { + range_constraint_value(context, value)?; + } + } + AcirValue::DynamicArray(_) => { + unreachable!("Brillig opcodes cannot return dynamic arrays") + } + } + Ok(()) + } + + for output_var in &outputs_var { + range_constraint_value(self, output_var)?; + } + Ok(outputs_var) } @@ -1495,7 +1646,6 @@ impl AcirContext { &mut self, inputs: Vec, bit_size: u32, - predicate: AcirVar, ) -> Result, RuntimeError> { let len = inputs.len(); // Convert the inputs into expressions @@ -1512,25 +1662,16 @@ impl AcirContext { self.acir_ir.permutation(&inputs_expr, &output_expr)?; // Enforce the outputs to be sorted + let true_var = self.add_constant(true); for i in 0..(outputs_var.len() - 1) { - self.less_than_constrain(outputs_var[i], outputs_var[i + 1], bit_size, predicate)?; + let less_than_next_element = + self.more_than_eq_var(outputs_var[i + 1], outputs_var[i], bit_size)?; + self.assert_eq_var(less_than_next_element, true_var, None)?; } Ok(outputs_var) } - /// Constrain lhs to be less than rhs - fn less_than_constrain( - &mut self, - lhs: AcirVar, - rhs: AcirVar, - bit_size: u32, - predicate: AcirVar, - ) -> Result<(), RuntimeError> { - let lhs_less_than_rhs = self.more_than_eq_var(rhs, lhs, bit_size)?; - self.maybe_eq_predicate(lhs_less_than_rhs, predicate) - } - /// Returns a Variable that is constrained to be the result of reading /// from the memory `block_id` at the given `index`. pub(crate) fn read_from_memory( @@ -1748,7 +1889,7 @@ fn execute_brillig( // It may be finished, in-progress, failed, or may be waiting for results of a foreign call. // If it's finished then we can omit the opcode and just write in the return values. match vm_status { - VMStatus::Finished => Some((vm.get_registers().clone(), vm.get_memory().clone())), + VMStatus::Finished => Some((vm.get_registers().clone(), vm.get_memory().to_vec())), VMStatus::InProgress => unreachable!("Brillig VM has not completed execution"), VMStatus::Failure { .. } => { // TODO: Return an error stating that the brillig function failed. diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/big_int.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/big_int.rs new file mode 100644 index 00000000000..c21188a8dbc --- /dev/null +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/big_int.rs @@ -0,0 +1,57 @@ +use acvm::FieldElement; +use num_bigint::BigUint; + +/// Represents a bigint value in the form (id, modulus) where +/// id is the identifier of the big integer number, and +/// modulus is the identifier of the big integer size +#[derive(Default, Clone, Copy, Debug)] +pub(crate) struct BigIntId { + pub(crate) bigint_id: u32, + pub(crate) modulus_id: u32, +} + +impl BigIntId { + pub(crate) fn bigint_id(&self) -> FieldElement { + FieldElement::from(self.bigint_id as u128) + } + + pub(crate) fn modulus_id(&self) -> FieldElement { + FieldElement::from(self.modulus_id as u128) + } +} + +/// BigIntContext is used to generate identifiers for big integers and their modulus +#[derive(Default, Debug)] +pub(crate) struct BigIntContext { + modulus: Vec, + big_integers: Vec, +} + +impl BigIntContext { + /// Creates a new BigIntId for the given modulus identifier and returns it. + pub(crate) fn new_big_int(&mut self, modulus_id: FieldElement) -> BigIntId { + let id = self.big_integers.len() as u32; + let result = BigIntId { bigint_id: id, modulus_id: modulus_id.to_u128() as u32 }; + self.big_integers.push(result); + result + } + + /// Returns the modulus corresponding to the given modulus index + pub(crate) fn modulus(&self, idx: FieldElement) -> BigUint { + self.modulus[idx.to_u128() as usize].clone() + } + + /// Returns the BigIntId corresponding to the given identifier + pub(crate) fn get(&self, id: FieldElement) -> BigIntId { + self.big_integers[id.to_u128() as usize] + } + + /// Adds a modulus to the context (if it is not already present) + pub(crate) fn get_or_insert_modulus(&mut self, modulus: BigUint) -> u32 { + if let Some(pos) = self.modulus.iter().position(|x| x == &modulus) { + return pos as u32; + } + self.modulus.push(modulus); + (self.modulus.len() - 1) as u32 + } +} 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 efc64c5286e..b86fc4eeb5f 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 @@ -138,7 +138,8 @@ impl GeneratedAcir { &mut self, func_name: BlackBoxFunc, inputs: &[Vec], - constants: Vec, + constant_inputs: Vec, + constant_outputs: Vec, output_count: usize, ) -> Result, InternalError> { let input_count = inputs.iter().fold(0usize, |sum, val| sum + val.len()); @@ -176,12 +177,12 @@ impl GeneratedAcir { BlackBoxFunc::PedersenCommitment => BlackBoxFuncCall::PedersenCommitment { inputs: inputs[0].clone(), outputs: (outputs[0], outputs[1]), - domain_separator: constants[0].to_u128() as u32, + domain_separator: constant_inputs[0].to_u128() as u32, }, BlackBoxFunc::PedersenHash => BlackBoxFuncCall::PedersenHash { inputs: inputs[0].clone(), output: outputs[0], - domain_separator: constants[0].to_u128() as u32, + domain_separator: constant_inputs[0].to_u128() as u32, }, BlackBoxFunc::EcdsaSecp256k1 => { BlackBoxFuncCall::EcdsaSecp256k1 { @@ -219,11 +220,6 @@ impl GeneratedAcir { input2_y: inputs[3][0], outputs: (outputs[0], outputs[1]), }, - BlackBoxFunc::EmbeddedCurveDouble => BlackBoxFuncCall::EmbeddedCurveDouble { - input_x: inputs[0][0], - input_y: inputs[1][0], - outputs: (outputs[0], outputs[1]), - }, BlackBoxFunc::Keccak256 => { let var_message_size = match inputs.to_vec().pop() { Some(var_message_size) => var_message_size[0], @@ -251,6 +247,45 @@ impl GeneratedAcir { public_inputs: inputs[2].clone(), key_hash: inputs[3][0], }, + BlackBoxFunc::BigIntAdd => BlackBoxFuncCall::BigIntAdd { + lhs: constant_inputs[0].to_u128() as u32, + rhs: constant_inputs[1].to_u128() as u32, + output: constant_outputs[0].to_u128() as u32, + }, + BlackBoxFunc::BigIntNeg => BlackBoxFuncCall::BigIntNeg { + lhs: constant_inputs[0].to_u128() as u32, + rhs: constant_inputs[1].to_u128() as u32, + output: constant_outputs[0].to_u128() as u32, + }, + BlackBoxFunc::BigIntMul => BlackBoxFuncCall::BigIntMul { + lhs: constant_inputs[0].to_u128() as u32, + rhs: constant_inputs[1].to_u128() as u32, + output: constant_outputs[0].to_u128() as u32, + }, + BlackBoxFunc::BigIntDiv => BlackBoxFuncCall::BigIntDiv { + lhs: constant_inputs[0].to_u128() as u32, + rhs: constant_inputs[1].to_u128() as u32, + output: constant_outputs[0].to_u128() as u32, + }, + BlackBoxFunc::BigIntFromLeBytes => BlackBoxFuncCall::BigIntFromLeBytes { + inputs: inputs[0].clone(), + modulus: vecmap(constant_inputs, |c| c.to_u128() as u8), + output: constant_outputs[0].to_u128() as u32, + }, + BlackBoxFunc::BigIntToLeBytes => BlackBoxFuncCall::BigIntToLeBytes { + input: constant_inputs[0].to_u128() as u32, + outputs, + }, + BlackBoxFunc::Poseidon2Permutation => BlackBoxFuncCall::Poseidon2Permutation { + inputs: inputs[0].clone(), + outputs, + len: constant_inputs[0].to_u128() as u32, + }, + BlackBoxFunc::Sha256Compression => BlackBoxFuncCall::Sha256Compression { + inputs: inputs[0].clone(), + hash_values: inputs[1].clone(), + outputs, + }, }; self.push_opcode(AcirOpcode::BlackBoxFuncCall(black_box_func_call)); @@ -573,6 +608,7 @@ fn black_box_func_expected_input_size(name: BlackBoxFunc) -> Option { match name { // Bitwise opcodes will take in 2 parameters BlackBoxFunc::AND | BlackBoxFunc::XOR => Some(2), + // All of the hash/cipher methods will take in a // variable number of inputs. BlackBoxFunc::Keccak256 @@ -583,7 +619,11 @@ fn black_box_func_expected_input_size(name: BlackBoxFunc) -> Option { | BlackBoxFunc::PedersenHash => None, BlackBoxFunc::Keccakf1600 => Some(25), + // The permutation takes a fixed number of inputs, but the inputs length depends on the proving system implementation. + BlackBoxFunc::Poseidon2Permutation => None, + // SHA256 compression requires 16 u32s as input message and 8 u32s for the hash state. + BlackBoxFunc::Sha256Compression => Some(24), // Can only apply a range constraint to one // witness at a time. BlackBoxFunc::RANGE => Some(1), @@ -593,15 +633,26 @@ fn black_box_func_expected_input_size(name: BlackBoxFunc) -> Option { BlackBoxFunc::SchnorrVerify | BlackBoxFunc::EcdsaSecp256k1 | BlackBoxFunc::EcdsaSecp256r1 => None, + // Inputs for fixed based scalar multiplication // is the low and high limbs of the scalar BlackBoxFunc::FixedBaseScalarMul => Some(2), + // Recursive aggregation has a variable number of inputs BlackBoxFunc::RecursiveAggregation => None, + // Addition over the embedded curve: input are coordinates (x1,y1) and (x2,y2) of the Grumpkin points BlackBoxFunc::EmbeddedCurveAdd => Some(4), - // Doubling over the embedded curve: input is (x,y) coordinate of the point. - BlackBoxFunc::EmbeddedCurveDouble => Some(2), + + // Big integer operations take in 0 inputs. They use constants for their inputs. + BlackBoxFunc::BigIntAdd + | BlackBoxFunc::BigIntNeg + | BlackBoxFunc::BigIntMul + | BlackBoxFunc::BigIntDiv + | BlackBoxFunc::BigIntToLeBytes => Some(0), + + // FromLeBytes takes a variable array of bytes as input + BlackBoxFunc::BigIntFromLeBytes => None, } } @@ -612,28 +663,47 @@ fn black_box_expected_output_size(name: BlackBoxFunc) -> Option { // Bitwise opcodes will return 1 parameter which is the output // or the operation. BlackBoxFunc::AND | BlackBoxFunc::XOR => Some(1), + // 32 byte hash algorithms BlackBoxFunc::Keccak256 | BlackBoxFunc::SHA256 | BlackBoxFunc::Blake2s | BlackBoxFunc::Blake3 => Some(32), + BlackBoxFunc::Keccakf1600 => Some(25), + // The permutation returns a fixed number of outputs, equals to the inputs length which depends on the proving system implementation. + BlackBoxFunc::Poseidon2Permutation => None, + + BlackBoxFunc::Sha256Compression => Some(8), // Pedersen commitment returns a point BlackBoxFunc::PedersenCommitment => Some(2), + // Pedersen hash returns a field BlackBoxFunc::PedersenHash => Some(1), + // Can only apply a range constraint to one // witness at a time. BlackBoxFunc::RANGE => Some(0), + // Signature verification algorithms will return a boolean BlackBoxFunc::SchnorrVerify | BlackBoxFunc::EcdsaSecp256k1 | BlackBoxFunc::EcdsaSecp256r1 => Some(1), + // Output of operations over the embedded curve // will be 2 field elements representing the point. - BlackBoxFunc::FixedBaseScalarMul - | BlackBoxFunc::EmbeddedCurveAdd - | BlackBoxFunc::EmbeddedCurveDouble => Some(2), + BlackBoxFunc::FixedBaseScalarMul | BlackBoxFunc::EmbeddedCurveAdd => Some(2), + + // Big integer operations return a big integer + BlackBoxFunc::BigIntAdd + | BlackBoxFunc::BigIntNeg + | BlackBoxFunc::BigIntMul + | BlackBoxFunc::BigIntDiv + | BlackBoxFunc::BigIntFromLeBytes => Some(0), + + // ToLeBytes returns a variable array of bytes + BlackBoxFunc::BigIntToLeBytes => None, + // Recursive aggregation has a variable number of outputs BlackBoxFunc::RecursiveAggregation => None, } @@ -690,5 +760,5 @@ fn intrinsics_check_outputs(name: BlackBoxFunc, output_count: usize) { None => return, }; - assert_eq!(expected_num_outputs,output_count,"Tried to call black box function {name} with {output_count} inputs, but this function's definition requires {expected_num_outputs} inputs"); + assert_eq!(expected_num_outputs,output_count,"Tried to call black box function {name} with {output_count} outputs, but this function's definition requires {expected_num_outputs} outputs"); } diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index d832b8d0fbb..211506a6d15 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -7,6 +7,7 @@ use std::fmt::Debug; use self::acir_ir::acir_variable::{AcirContext, AcirType, AcirVar}; use super::function_builder::data_bus::DataBus; use super::ir::dfg::CallStack; +use super::ir::instruction::ConstrainError; use super::{ ir::{ dfg::DataFlowGraph, @@ -413,15 +414,94 @@ impl Context { let lhs = self.convert_numeric_value(*lhs, dfg)?; let rhs = self.convert_numeric_value(*rhs, dfg)?; - self.acir_context.assert_eq_var(lhs, rhs, assert_message.clone())?; + let assert_message = if let Some(error) = assert_message { + match error.as_ref() { + ConstrainError::Static(string) => Some(string.clone()), + ConstrainError::Dynamic(call_instruction) => { + self.convert_ssa_call(call_instruction, dfg, ssa, brillig, &[])?; + None + } + } + } else { + None + }; + + self.acir_context.assert_eq_var(lhs, rhs, assert_message)?; } Instruction::Cast(value_id, _) => { let acir_var = self.convert_numeric_value(*value_id, dfg)?; self.define_result_var(dfg, instruction_id, acir_var); } - Instruction::Call { func, arguments } => { + Instruction::Call { .. } => { let result_ids = dfg.instruction_results(instruction_id); - match &dfg[*func] { + warnings.extend(self.convert_ssa_call( + instruction, + dfg, + ssa, + brillig, + result_ids, + )?); + } + Instruction::Not(value_id) => { + let (acir_var, typ) = match self.convert_value(*value_id, dfg) { + AcirValue::Var(acir_var, typ) => (acir_var, typ), + _ => unreachable!("NOT is only applied to numerics"), + }; + let result_acir_var = self.acir_context.not_var(acir_var, typ)?; + self.define_result_var(dfg, instruction_id, result_acir_var); + } + Instruction::Truncate { value, bit_size, max_bit_size } => { + let result_acir_var = + self.convert_ssa_truncate(*value, *bit_size, *max_bit_size, dfg)?; + self.define_result_var(dfg, instruction_id, result_acir_var); + } + Instruction::EnableSideEffects { condition } => { + let acir_var = self.convert_numeric_value(*condition, dfg)?; + self.current_side_effects_enabled_var = acir_var; + } + Instruction::ArrayGet { .. } | Instruction::ArraySet { .. } => { + self.handle_array_operation(instruction_id, dfg, last_array_uses)?; + } + Instruction::Allocate => { + unreachable!("Expected all allocate instructions to be removed before acir_gen") + } + Instruction::Store { .. } => { + unreachable!("Expected all store instructions to be removed before acir_gen") + } + Instruction::Load { .. } => { + unreachable!("Expected all load instructions to be removed before acir_gen") + } + Instruction::IncrementRc { .. } => { + // Do nothing. Only Brillig needs to worry about reference counted arrays + } + Instruction::RangeCheck { value, max_bit_size, assert_message } => { + let acir_var = self.convert_numeric_value(*value, dfg)?; + self.acir_context.range_constrain_var( + acir_var, + &NumericType::Unsigned { bit_size: *max_bit_size }, + assert_message.clone(), + )?; + } + } + + self.acir_context.set_call_stack(CallStack::new()); + Ok(warnings) + } + + fn convert_ssa_call( + &mut self, + instruction: &Instruction, + dfg: &DataFlowGraph, + ssa: &Ssa, + brillig: &Brillig, + result_ids: &[ValueId], + ) -> Result, RuntimeError> { + let mut warnings = Vec::new(); + + match instruction { + Instruction::Call { func, arguments } => { + let function_value = &dfg[*func]; + match function_value { Value::Function(id) => { let func = &ssa.functions[id]; match func.runtime() { @@ -495,51 +575,11 @@ impl Context { Value::ForeignFunction(_) => unreachable!( "All `oracle` methods should be wrapped in an unconstrained fn" ), - _ => unreachable!("expected calling a function"), + _ => unreachable!("expected calling a function but got {function_value:?}"), } } - Instruction::Not(value_id) => { - let (acir_var, typ) = match self.convert_value(*value_id, dfg) { - AcirValue::Var(acir_var, typ) => (acir_var, typ), - _ => unreachable!("NOT is only applied to numerics"), - }; - let result_acir_var = self.acir_context.not_var(acir_var, typ)?; - self.define_result_var(dfg, instruction_id, result_acir_var); - } - Instruction::Truncate { value, bit_size, max_bit_size } => { - let result_acir_var = - self.convert_ssa_truncate(*value, *bit_size, *max_bit_size, dfg)?; - self.define_result_var(dfg, instruction_id, result_acir_var); - } - Instruction::EnableSideEffects { condition } => { - let acir_var = self.convert_numeric_value(*condition, dfg)?; - self.current_side_effects_enabled_var = acir_var; - } - Instruction::ArrayGet { .. } | Instruction::ArraySet { .. } => { - self.handle_array_operation(instruction_id, dfg, last_array_uses)?; - } - Instruction::Allocate => { - unreachable!("Expected all allocate instructions to be removed before acir_gen") - } - Instruction::Store { .. } => { - unreachable!("Expected all store instructions to be removed before acir_gen") - } - Instruction::Load { .. } => { - unreachable!("Expected all load instructions to be removed before acir_gen") - } - Instruction::IncrementRc { .. } => { - // Do nothing. Only Brillig needs to worry about reference counted arrays - } - Instruction::RangeCheck { value, max_bit_size, assert_message } => { - let acir_var = self.convert_numeric_value(*value, dfg)?; - self.acir_context.range_constrain_var( - acir_var, - &NumericType::Unsigned { bit_size: *max_bit_size }, - assert_message.clone(), - )?; - } + _ => unreachable!("expected calling a call instruction"), } - self.acir_context.set_call_stack(CallStack::new()); Ok(warnings) } @@ -1541,7 +1581,7 @@ impl Context { let vars = self.acir_context.black_box_function(black_box, inputs, output_count)?; - Ok(Self::convert_vars_to_values(vars, dfg, result_ids)) + Ok(self.convert_vars_to_values(vars, dfg, result_ids)) } Intrinsic::ApplyRangeConstraint => { unreachable!("ICE: `Intrinsic::ApplyRangeConstraint` calls should be transformed into an `Instruction::RangeCheck`"); @@ -1583,12 +1623,9 @@ impl Context { } } // Generate the sorted output variables - let out_vars = self - .acir_context - .sort(input_vars, bit_size, self.current_side_effects_enabled_var) - .expect("Could not sort"); + let out_vars = self.acir_context.sort(input_vars, bit_size)?; - Ok(Self::convert_vars_to_values(out_vars, dfg, result_ids)) + Ok(self.convert_vars_to_values(out_vars, dfg, result_ids)) } Intrinsic::ArrayLen => { let len = match self.convert_value(arguments[0], dfg) { @@ -2101,16 +2138,35 @@ impl Context { /// 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. + /// If the type of a result id is a slice, the slice length must precede it and we can + /// convert to an AcirValue::Array when the length is known (constant). fn convert_vars_to_values( + &self, vars: Vec, dfg: &DataFlowGraph, result_ids: &[ValueId], ) -> Vec { let mut vars = vars.into_iter(); - vecmap(result_ids, |result| { + let mut values: Vec = Vec::new(); + for result in result_ids { let result_type = dfg.type_of_value(*result); - Self::convert_var_type_to_values(&result_type, &mut vars) - }) + if let Type::Slice(elements_type) = result_type { + let error = "ICE - cannot get slice length when converting slice to AcirValue"; + let len = values.last().expect(error).borrow_var().expect(error); + let len = self.acir_context.constant(len).to_u128(); + let mut element_values = im::Vector::new(); + for _ in 0..len { + for element_type in elements_type.iter() { + let element = Self::convert_var_type_to_values(element_type, &mut vars); + element_values.push_back(element); + } + } + values.push(AcirValue::Array(element_values)); + } else { + values.push(Self::convert_var_type_to_values(&result_type, &mut vars)); + } + } + values } /// Recursive helper for convert_vars_to_values. diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 852848afb81..1cac0aa67ed 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -18,7 +18,7 @@ use super::{ basic_block::BasicBlock, dfg::{CallStack, InsertInstructionResult}, function::RuntimeType, - instruction::{Endian, InstructionId, Intrinsic}, + instruction::{ConstrainError, Endian, InstructionId, Intrinsic}, types::NumericType, }, ssa_gen::Ssa, @@ -250,7 +250,7 @@ impl FunctionBuilder { &mut self, lhs: ValueId, rhs: ValueId, - assert_message: Option, + assert_message: Option>, ) { self.insert_instruction(Instruction::Constrain(lhs, rhs, assert_message), None); } @@ -367,10 +367,12 @@ impl FunctionBuilder { let r_squared = self.insert_binary(r, BinaryOp::Mul, r); let a = self.insert_binary(r_squared, BinaryOp::Mul, lhs); let idx = self.field_constant(FieldElement::from((bit_size - i) as i128)); - let b = self.insert_array_get(rhs_bits, idx, Type::field()); + let b = self.insert_array_get(rhs_bits, idx, Type::bool()); + let not_b = self.insert_not(b); + let b = self.insert_cast(b, Type::field()); + let not_b = self.insert_cast(not_b, Type::field()); let r1 = self.insert_binary(a, BinaryOp::Mul, b); - let c = self.insert_binary(one, BinaryOp::Sub, b); - let r2 = self.insert_binary(c, BinaryOp::Mul, r_squared); + let r2 = self.insert_binary(r_squared, BinaryOp::Mul, not_b); r = self.insert_binary(r1, BinaryOp::Add, r2); } r diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 457fe41de93..fa5bc0f6697 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -1,6 +1,5 @@ use acvm::{acir::BlackBoxFunc, FieldElement}; use iter_extended::vecmap; -use num_bigint::BigUint; use super::{ basic_block::BasicBlockId, @@ -10,9 +9,15 @@ use super::{ value::{Value, ValueId}, }; +mod binary; mod call; +mod cast; +mod constrain; +pub(crate) use binary::{Binary, BinaryOp}; use call::simplify_call; +use cast::simplify_cast; +use constrain::decompose_constrain; /// Reference to an instruction /// @@ -82,6 +87,9 @@ impl Intrinsic { match self { Intrinsic::AssertConstant | Intrinsic::ApplyRangeConstraint => true, + // These apply a constraint that the input must fit into a specified number of limbs. + Intrinsic::ToBits(_) | Intrinsic::ToRadix(_) => true, + Intrinsic::Sort | Intrinsic::ArrayLen | Intrinsic::SlicePushBack @@ -91,8 +99,6 @@ impl Intrinsic { | Intrinsic::SliceInsert | Intrinsic::SliceRemove | Intrinsic::StrAsBytes - | Intrinsic::ToBits(_) - | Intrinsic::ToRadix(_) | Intrinsic::FromField | Intrinsic::AsField => false, @@ -151,7 +157,7 @@ pub(crate) enum Instruction { Truncate { value: ValueId, bit_size: u32, max_bit_size: u32 }, /// Constrains two values to be equal to one another. - Constrain(ValueId, ValueId, Option), + Constrain(ValueId, ValueId, Option>), /// Range constrain `value` to `max_bit_size` RangeCheck { value: ValueId, max_bit_size: u32, assert_message: Option }, @@ -320,7 +326,17 @@ impl Instruction { max_bit_size: *max_bit_size, }, Instruction::Constrain(lhs, rhs, assert_message) => { - Instruction::Constrain(f(*lhs), f(*rhs), assert_message.clone()) + // Must map the `lhs` and `rhs` first as the value `f` is moved with the closure + let lhs = f(*lhs); + let rhs = f(*rhs); + let assert_message = assert_message.as_ref().map(|error| match error.as_ref() { + ConstrainError::Dynamic(call_instr) => { + let new_instr = call_instr.map_values(f); + Box::new(ConstrainError::Dynamic(new_instr)) + } + _ => error.clone(), + }); + Instruction::Constrain(lhs, rhs, assert_message) } Instruction::Call { func, arguments } => Instruction::Call { func: f(*func), @@ -370,9 +386,14 @@ impl Instruction { | Instruction::Load { address: value } => { f(*value); } - Instruction::Constrain(lhs, rhs, _) => { + Instruction::Constrain(lhs, rhs, assert_error) => { f(*lhs); f(*rhs); + if let Some(error) = assert_error.as_ref() { + if let ConstrainError::Dynamic(call_instr) = error.as_ref() { + call_instr.for_each_value(f); + } + } } Instruction::Store { address, value } => { @@ -435,7 +456,7 @@ impl Instruction { } } Instruction::Constrain(lhs, rhs, msg) => { - let constraints = decompose_constrain(*lhs, *rhs, msg.clone(), dfg); + let constraints = decompose_constrain(*lhs, *rhs, msg, dfg); if constraints.is_empty() { Remove } else { @@ -469,6 +490,9 @@ impl Instruction { None } Instruction::Truncate { value, bit_size, max_bit_size } => { + if bit_size == max_bit_size { + return SimplifiedTo(*value); + } if let Some((numeric_constant, typ)) = dfg.get_numeric_constant_with_type(*value) { let integer_modulus = 2_u128.pow(*bit_size); let truncated = numeric_constant.to_u128() % integer_modulus; @@ -545,176 +569,25 @@ impl Instruction { } } -/// Try to simplify this cast instruction. If the instruction can be simplified to a known value, -/// that value is returned. Otherwise None is returned. -fn simplify_cast(value: ValueId, dst_typ: &Type, dfg: &mut DataFlowGraph) -> SimplifyResult { - use SimplifyResult::*; - let value = dfg.resolve(value); - - if let Value::Instruction { instruction, .. } = &dfg[value] { - if let Instruction::Cast(original_value, _) = &dfg[*instruction] { - return SimplifiedToInstruction(Instruction::Cast(*original_value, dst_typ.clone())); - } - } +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub(crate) enum ConstrainError { + // These are errors which have been hardcoded during SSA gen + Static(String), + // These are errors which come from runtime expressions specified by a Noir program + // We store an `Instruction` as we want this Instruction to be atomic in SSA with + // a constrain instruction, and leave codegen of this instruction to lower level passes. + Dynamic(Instruction), +} - if let Some(constant) = dfg.get_numeric_constant(value) { - let src_typ = dfg.type_of_value(value); - match (src_typ, dst_typ) { - (Type::Numeric(NumericType::NativeField), Type::Numeric(NumericType::NativeField)) => { - // Field -> Field: use src value - SimplifiedTo(value) - } - ( - Type::Numeric(NumericType::Unsigned { .. }), - Type::Numeric(NumericType::NativeField), - ) => { - // Unsigned -> Field: redefine same constant as Field - SimplifiedTo(dfg.make_constant(constant, dst_typ.clone())) - } - ( - Type::Numeric( - NumericType::NativeField - | NumericType::Unsigned { .. } - | NumericType::Signed { .. }, - ), - Type::Numeric(NumericType::Unsigned { bit_size }), - ) => { - // Field/Unsigned -> unsigned: truncate - let integer_modulus = BigUint::from(2u128).pow(*bit_size); - let constant: BigUint = BigUint::from_bytes_be(&constant.to_be_bytes()); - let truncated = constant % integer_modulus; - let truncated = FieldElement::from_be_bytes_reduce(&truncated.to_bytes_be()); - SimplifiedTo(dfg.make_constant(truncated, dst_typ.clone())) - } - _ => None, - } - } else if *dst_typ == dfg.type_of_value(value) { - SimplifiedTo(value) - } else { - None +impl From for ConstrainError { + fn from(value: String) -> Self { + ConstrainError::Static(value) } } -/// Try to decompose this constrain instruction. This constraint will be broken down such that it instead constrains -/// all the values which are used to compute the values which were being constrained. -fn decompose_constrain( - lhs: ValueId, - rhs: ValueId, - msg: Option, - dfg: &mut DataFlowGraph, -) -> Vec { - let lhs = dfg.resolve(lhs); - let rhs = dfg.resolve(rhs); - - if lhs == rhs { - // Remove trivial case `assert_eq(x, x)` - Vec::new() - } else { - match (&dfg[lhs], &dfg[rhs]) { - (Value::NumericConstant { constant, typ }, Value::Instruction { instruction, .. }) - | (Value::Instruction { instruction, .. }, Value::NumericConstant { constant, typ }) - if *typ == Type::bool() => - { - match dfg[*instruction] { - Instruction::Binary(Binary { lhs, rhs, operator: BinaryOp::Eq }) - if constant.is_one() => - { - // Replace an explicit two step equality assertion - // - // v2 = eq v0, u32 v1 - // constrain v2 == u1 1 - // - // with a direct assertion of equality between the two values - // - // v2 = eq v0, u32 v1 - // constrain v0 == v1 - // - // Note that this doesn't remove the value `v2` as it may be used in other instructions, but it - // will likely be removed through dead instruction elimination. - - vec![Instruction::Constrain(lhs, rhs, msg)] - } - - Instruction::Binary(Binary { lhs, rhs, operator: BinaryOp::Mul }) - if constant.is_one() && dfg.type_of_value(lhs) == Type::bool() => - { - // Replace an equality assertion on a boolean multiplication - // - // v2 = mul v0, v1 - // constrain v2 == u1 1 - // - // with a direct assertion that each value is equal to 1 - // - // v2 = mul v0, v1 - // constrain v0 == 1 - // constrain v1 == 1 - // - // This is due to the fact that for `v2` to be 1 then both `v0` and `v1` are 1. - // - // Note that this doesn't remove the value `v2` as it may be used in other instructions, but it - // will likely be removed through dead instruction elimination. - let one = FieldElement::one(); - let one = dfg.make_constant(one, Type::bool()); - - [ - decompose_constrain(lhs, one, msg.clone(), dfg), - decompose_constrain(rhs, one, msg, dfg), - ] - .concat() - } - - Instruction::Binary(Binary { lhs, rhs, operator: BinaryOp::Or }) - if constant.is_zero() => - { - // Replace an equality assertion on an OR - // - // v2 = or v0, v1 - // constrain v2 == u1 0 - // - // with a direct assertion that each value is equal to 0 - // - // v2 = or v0, v1 - // constrain v0 == 0 - // constrain v1 == 0 - // - // This is due to the fact that for `v2` to be 0 then both `v0` and `v1` are 0. - // - // Note that this doesn't remove the value `v2` as it may be used in other instructions, but it - // will likely be removed through dead instruction elimination. - let zero = FieldElement::zero(); - let zero = dfg.make_constant(zero, dfg.type_of_value(lhs)); - - [ - decompose_constrain(lhs, zero, msg.clone(), dfg), - decompose_constrain(rhs, zero, msg, dfg), - ] - .concat() - } - - Instruction::Not(value) => { - // Replace an assertion that a not instruction is truthy - // - // v1 = not v0 - // constrain v1 == u1 1 - // - // with an assertion that the not instruction input is falsy - // - // v1 = not v0 - // constrain v0 == u1 0 - // - // Note that this doesn't remove the value `v1` as it may be used in other instructions, but it - // will likely be removed through dead instruction elimination. - let reversed_constant = FieldElement::from(!constant.is_one()); - let reversed_constant = dfg.make_constant(reversed_constant, Type::bool()); - decompose_constrain(value, reversed_constant, msg, dfg) - } - - _ => vec![Instruction::Constrain(lhs, rhs, msg)], - } - } - - _ => vec![Instruction::Constrain(lhs, rhs, msg)], - } +impl From for Box { + fn from(value: String) -> Self { + Box::new(value.into()) } } @@ -848,350 +721,6 @@ impl TerminatorInstruction { } } -/// A binary instruction in the IR. -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub(crate) struct Binary { - /// Left hand side of the binary operation - pub(crate) lhs: ValueId, - /// Right hand side of the binary operation - pub(crate) rhs: ValueId, - /// The binary operation to apply - pub(crate) operator: BinaryOp, -} - -impl Binary { - /// The type of this Binary instruction's result - pub(crate) fn result_type(&self) -> InstructionResultType { - match self.operator { - BinaryOp::Eq | BinaryOp::Lt => InstructionResultType::Known(Type::bool()), - _ => InstructionResultType::Operand(self.lhs), - } - } - - /// Try to simplify this binary instruction, returning the new value if possible. - fn simplify(&self, dfg: &mut DataFlowGraph) -> SimplifyResult { - let lhs = dfg.get_numeric_constant(self.lhs); - let rhs = dfg.get_numeric_constant(self.rhs); - let operand_type = dfg.type_of_value(self.lhs); - - if let (Some(lhs), Some(rhs)) = (lhs, rhs) { - return match eval_constant_binary_op(lhs, rhs, self.operator, operand_type) { - Some((result, result_type)) => { - let value = dfg.make_constant(result, result_type); - SimplifyResult::SimplifiedTo(value) - } - None => SimplifyResult::None, - }; - } - - let lhs_is_zero = lhs.map_or(false, |lhs| lhs.is_zero()); - let rhs_is_zero = rhs.map_or(false, |rhs| rhs.is_zero()); - - let lhs_is_one = lhs.map_or(false, |lhs| lhs.is_one()); - let rhs_is_one = rhs.map_or(false, |rhs| rhs.is_one()); - - match self.operator { - BinaryOp::Add => { - if lhs_is_zero { - return SimplifyResult::SimplifiedTo(self.rhs); - } - if rhs_is_zero { - return SimplifyResult::SimplifiedTo(self.lhs); - } - } - BinaryOp::Sub => { - if rhs_is_zero { - return SimplifyResult::SimplifiedTo(self.lhs); - } - } - BinaryOp::Mul => { - if lhs_is_one { - return SimplifyResult::SimplifiedTo(self.rhs); - } - if rhs_is_one { - return SimplifyResult::SimplifiedTo(self.lhs); - } - if lhs_is_zero || rhs_is_zero { - let zero = dfg.make_constant(FieldElement::zero(), operand_type); - return SimplifyResult::SimplifiedTo(zero); - } - } - BinaryOp::Div => { - if rhs_is_one { - return SimplifyResult::SimplifiedTo(self.lhs); - } - } - BinaryOp::Mod => { - if rhs_is_one { - let zero = dfg.make_constant(FieldElement::zero(), operand_type); - return SimplifyResult::SimplifiedTo(zero); - } - } - BinaryOp::Eq => { - if dfg.resolve(self.lhs) == dfg.resolve(self.rhs) { - let one = dfg.make_constant(FieldElement::one(), Type::bool()); - return SimplifyResult::SimplifiedTo(one); - } - if operand_type == Type::bool() { - // Simplify forms of `(boolean == true)` into `boolean` - if lhs_is_one { - return SimplifyResult::SimplifiedTo(self.rhs); - } - if rhs_is_one { - return SimplifyResult::SimplifiedTo(self.lhs); - } - // Simplify forms of `(boolean == false)` into `!boolean` - if lhs_is_zero { - return SimplifyResult::SimplifiedToInstruction(Instruction::Not(self.rhs)); - } - if rhs_is_zero { - return SimplifyResult::SimplifiedToInstruction(Instruction::Not(self.lhs)); - } - } - } - BinaryOp::Lt => { - if dfg.resolve(self.lhs) == dfg.resolve(self.rhs) { - let zero = dfg.make_constant(FieldElement::zero(), Type::bool()); - return SimplifyResult::SimplifiedTo(zero); - } - if operand_type.is_unsigned() { - if rhs_is_zero { - // Unsigned values cannot be less than zero. - let zero = dfg.make_constant(FieldElement::zero(), Type::bool()); - return SimplifyResult::SimplifiedTo(zero); - } else if rhs_is_one { - let zero = dfg.make_constant(FieldElement::zero(), operand_type); - return SimplifyResult::SimplifiedToInstruction(Instruction::binary( - BinaryOp::Eq, - self.lhs, - zero, - )); - } - } - } - BinaryOp::And => { - if lhs_is_zero || rhs_is_zero { - let zero = dfg.make_constant(FieldElement::zero(), operand_type); - return SimplifyResult::SimplifiedTo(zero); - } - if dfg.resolve(self.lhs) == dfg.resolve(self.rhs) { - return SimplifyResult::SimplifiedTo(self.lhs); - } - if operand_type == Type::bool() { - // Boolean AND is equivalent to multiplication, which is a cheaper operation. - let instruction = Instruction::binary(BinaryOp::Mul, self.lhs, self.rhs); - return SimplifyResult::SimplifiedToInstruction(instruction); - } - } - BinaryOp::Or => { - if lhs_is_zero { - return SimplifyResult::SimplifiedTo(self.rhs); - } - if rhs_is_zero { - return SimplifyResult::SimplifiedTo(self.lhs); - } - if dfg.resolve(self.lhs) == dfg.resolve(self.rhs) { - return SimplifyResult::SimplifiedTo(self.lhs); - } - } - BinaryOp::Xor => { - if lhs_is_zero { - return SimplifyResult::SimplifiedTo(self.rhs); - } - if rhs_is_zero { - return SimplifyResult::SimplifiedTo(self.lhs); - } - if dfg.resolve(self.lhs) == dfg.resolve(self.rhs) { - let zero = dfg.make_constant(FieldElement::zero(), Type::bool()); - return SimplifyResult::SimplifiedTo(zero); - } - } - } - SimplifyResult::None - } -} - -/// Evaluate a binary operation with constant arguments. -fn eval_constant_binary_op( - lhs: FieldElement, - rhs: FieldElement, - operator: BinaryOp, - mut operand_type: Type, -) -> Option<(FieldElement, Type)> { - let value = match &operand_type { - Type::Numeric(NumericType::NativeField) => { - // If the rhs of a division is zero, attempting to evaluate the division will cause a compiler panic. - // Thus, we do not evaluate the division in this method, as we want to avoid triggering a panic, - // and the operation should be handled by ACIR generation. - if matches!(operator, BinaryOp::Div | BinaryOp::Mod) && rhs == FieldElement::zero() { - return None; - } - operator.get_field_function()?(lhs, rhs) - } - Type::Numeric(NumericType::Unsigned { bit_size }) => { - let function = operator.get_u128_function(); - - let lhs = truncate(lhs.try_into_u128()?, *bit_size); - let rhs = truncate(rhs.try_into_u128()?, *bit_size); - - // The divisor is being truncated into the type of the operand, which can potentially - // lead to the rhs being zero. - // If the rhs of a division is zero, attempting to evaluate the division will cause a compiler panic. - // Thus, we do not evaluate the division in this method, as we want to avoid triggering a panic, - // and the operation should be handled by ACIR generation. - if matches!(operator, BinaryOp::Div | BinaryOp::Mod) && rhs == 0 { - return None; - } - let result = function(lhs, rhs)?; - // Check for overflow - if result >= 2u128.pow(*bit_size) { - return None; - } - result.into() - } - Type::Numeric(NumericType::Signed { bit_size }) => { - let function = operator.get_i128_function(); - - let lhs = truncate(lhs.try_into_u128()?, *bit_size); - let rhs = truncate(rhs.try_into_u128()?, *bit_size); - let l_pos = lhs < 2u128.pow(bit_size - 1); - let r_pos = rhs < 2u128.pow(bit_size - 1); - let lhs = if l_pos { lhs as i128 } else { -((2u128.pow(*bit_size) - lhs) as i128) }; - let rhs = if r_pos { rhs as i128 } else { -((2u128.pow(*bit_size) - rhs) as i128) }; - // The divisor is being truncated into the type of the operand, which can potentially - // lead to the rhs being zero. - // If the rhs of a division is zero, attempting to evaluate the division will cause a compiler panic. - // Thus, we do not evaluate the division in this method, as we want to avoid triggering a panic, - // and the operation should be handled by ACIR generation. - if matches!(operator, BinaryOp::Div | BinaryOp::Mod) && rhs == 0 { - return None; - } - - let result = function(lhs, rhs)?; - // Check for overflow - if result >= 2i128.pow(*bit_size - 1) || result < -(2i128.pow(*bit_size - 1)) { - return None; - } - let result = - if result >= 0 { result as u128 } else { (2i128.pow(*bit_size) + result) as u128 }; - result.into() - } - _ => return None, - }; - - if matches!(operator, BinaryOp::Eq | BinaryOp::Lt) { - operand_type = Type::bool(); - } - - Some((value, operand_type)) -} - -fn truncate(int: u128, bit_size: u32) -> u128 { - let max = 2u128.pow(bit_size); - int % max -} - -impl BinaryOp { - fn get_field_function(self) -> Option FieldElement> { - match self { - BinaryOp::Add => Some(std::ops::Add::add), - BinaryOp::Sub => Some(std::ops::Sub::sub), - BinaryOp::Mul => Some(std::ops::Mul::mul), - BinaryOp::Div => Some(std::ops::Div::div), - BinaryOp::Eq => Some(|x, y| (x == y).into()), - BinaryOp::Lt => Some(|x, y| (x < y).into()), - // Bitwise operators are unsupported for Fields - BinaryOp::Mod => None, - BinaryOp::And => None, - BinaryOp::Or => None, - BinaryOp::Xor => None, - } - } - - fn get_u128_function(self) -> fn(u128, u128) -> Option { - match self { - BinaryOp::Add => u128::checked_add, - BinaryOp::Sub => u128::checked_sub, - BinaryOp::Mul => u128::checked_mul, - BinaryOp::Div => u128::checked_div, - BinaryOp::Mod => u128::checked_rem, - BinaryOp::And => |x, y| Some(x & y), - BinaryOp::Or => |x, y| Some(x | y), - BinaryOp::Xor => |x, y| Some(x ^ y), - BinaryOp::Eq => |x, y| Some((x == y) as u128), - BinaryOp::Lt => |x, y| Some((x < y) as u128), - } - } - - fn get_i128_function(self) -> fn(i128, i128) -> Option { - match self { - BinaryOp::Add => i128::checked_add, - BinaryOp::Sub => i128::checked_sub, - BinaryOp::Mul => i128::checked_mul, - BinaryOp::Div => i128::checked_div, - BinaryOp::Mod => i128::checked_rem, - BinaryOp::And => |x, y| Some(x & y), - BinaryOp::Or => |x, y| Some(x | y), - BinaryOp::Xor => |x, y| Some(x ^ y), - BinaryOp::Eq => |x, y| Some((x == y) as i128), - BinaryOp::Lt => |x, y| Some((x < y) as i128), - } - } -} - -/// Binary Operations allowed in the IR. -/// Aside from the comparison operators (Eq and Lt), all operators -/// will return the same type as their operands. -/// The operand types must match for all binary operators. -/// All binary operators are also only for numeric types. To implement -/// e.g. equality for a compound type like a struct, one must add a -/// separate Eq operation for each field and combine them later with And. -#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] -pub(crate) enum BinaryOp { - /// Addition of lhs + rhs. - Add, - /// Subtraction of lhs - rhs. - Sub, - /// Multiplication of lhs * rhs. - Mul, - /// Division of lhs / rhs. - Div, - /// Modulus of lhs % rhs. - Mod, - /// Checks whether two types are equal. - /// Returns true if the types were equal and - /// false otherwise. - Eq, - /// Checks whether the lhs is less than the rhs. - /// All other comparison operators should be translated - /// to less than. For example (a > b) = (b < a) = !(a >= b) = !(b <= a). - /// The result will always be a u1. - Lt, - /// Bitwise and (&) - And, - /// Bitwise or (|) - Or, - /// Bitwise xor (^) - Xor, -} - -impl std::fmt::Display for BinaryOp { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BinaryOp::Add => write!(f, "add"), - BinaryOp::Sub => write!(f, "sub"), - BinaryOp::Mul => write!(f, "mul"), - BinaryOp::Div => write!(f, "div"), - BinaryOp::Eq => write!(f, "eq"), - BinaryOp::Mod => write!(f, "mod"), - BinaryOp::Lt => write!(f, "lt"), - BinaryOp::And => write!(f, "and"), - BinaryOp::Or => write!(f, "or"), - BinaryOp::Xor => write!(f, "xor"), - } - } -} - /// Contains the result to Instruction::simplify, specifying how the instruction /// should be simplified. pub(crate) enum SimplifyResult { diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs new file mode 100644 index 00000000000..1cb32d94148 --- /dev/null +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs @@ -0,0 +1,349 @@ +use acvm::FieldElement; + +use super::{ + DataFlowGraph, Instruction, InstructionResultType, NumericType, SimplifyResult, Type, ValueId, +}; + +/// Binary Operations allowed in the IR. +/// Aside from the comparison operators (Eq and Lt), all operators +/// will return the same type as their operands. +/// The operand types must match for all binary operators. +/// All binary operators are also only for numeric types. To implement +/// e.g. equality for a compound type like a struct, one must add a +/// separate Eq operation for each field and combine them later with And. +#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +pub(crate) enum BinaryOp { + /// Addition of lhs + rhs. + Add, + /// Subtraction of lhs - rhs. + Sub, + /// Multiplication of lhs * rhs. + Mul, + /// Division of lhs / rhs. + Div, + /// Modulus of lhs % rhs. + Mod, + /// Checks whether two types are equal. + /// Returns true if the types were equal and + /// false otherwise. + Eq, + /// Checks whether the lhs is less than the rhs. + /// All other comparison operators should be translated + /// to less than. For example (a > b) = (b < a) = !(a >= b) = !(b <= a). + /// The result will always be a u1. + Lt, + /// Bitwise and (&) + And, + /// Bitwise or (|) + Or, + /// Bitwise xor (^) + Xor, +} + +impl std::fmt::Display for BinaryOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BinaryOp::Add => write!(f, "add"), + BinaryOp::Sub => write!(f, "sub"), + BinaryOp::Mul => write!(f, "mul"), + BinaryOp::Div => write!(f, "div"), + BinaryOp::Eq => write!(f, "eq"), + BinaryOp::Mod => write!(f, "mod"), + BinaryOp::Lt => write!(f, "lt"), + BinaryOp::And => write!(f, "and"), + BinaryOp::Or => write!(f, "or"), + BinaryOp::Xor => write!(f, "xor"), + } + } +} + +/// A binary instruction in the IR. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub(crate) struct Binary { + /// Left hand side of the binary operation + pub(crate) lhs: ValueId, + /// Right hand side of the binary operation + pub(crate) rhs: ValueId, + /// The binary operation to apply + pub(crate) operator: BinaryOp, +} + +impl Binary { + /// The type of this Binary instruction's result + pub(crate) fn result_type(&self) -> InstructionResultType { + match self.operator { + BinaryOp::Eq | BinaryOp::Lt => InstructionResultType::Known(Type::bool()), + _ => InstructionResultType::Operand(self.lhs), + } + } + + /// Try to simplify this binary instruction, returning the new value if possible. + pub(super) fn simplify(&self, dfg: &mut DataFlowGraph) -> SimplifyResult { + let lhs = dfg.get_numeric_constant(self.lhs); + let rhs = dfg.get_numeric_constant(self.rhs); + let operand_type = dfg.type_of_value(self.lhs); + + if let (Some(lhs), Some(rhs)) = (lhs, rhs) { + return match eval_constant_binary_op(lhs, rhs, self.operator, operand_type) { + Some((result, result_type)) => { + let value = dfg.make_constant(result, result_type); + SimplifyResult::SimplifiedTo(value) + } + None => SimplifyResult::None, + }; + } + + let lhs_is_zero = lhs.map_or(false, |lhs| lhs.is_zero()); + let rhs_is_zero = rhs.map_or(false, |rhs| rhs.is_zero()); + + let lhs_is_one = lhs.map_or(false, |lhs| lhs.is_one()); + let rhs_is_one = rhs.map_or(false, |rhs| rhs.is_one()); + + match self.operator { + BinaryOp::Add => { + if lhs_is_zero { + return SimplifyResult::SimplifiedTo(self.rhs); + } + if rhs_is_zero { + return SimplifyResult::SimplifiedTo(self.lhs); + } + } + BinaryOp::Sub => { + if rhs_is_zero { + return SimplifyResult::SimplifiedTo(self.lhs); + } + } + BinaryOp::Mul => { + if lhs_is_one { + return SimplifyResult::SimplifiedTo(self.rhs); + } + if rhs_is_one { + return SimplifyResult::SimplifiedTo(self.lhs); + } + if lhs_is_zero || rhs_is_zero { + let zero = dfg.make_constant(FieldElement::zero(), operand_type); + return SimplifyResult::SimplifiedTo(zero); + } + } + BinaryOp::Div => { + if rhs_is_one { + return SimplifyResult::SimplifiedTo(self.lhs); + } + } + BinaryOp::Mod => { + if rhs_is_one { + let zero = dfg.make_constant(FieldElement::zero(), operand_type); + return SimplifyResult::SimplifiedTo(zero); + } + } + BinaryOp::Eq => { + if dfg.resolve(self.lhs) == dfg.resolve(self.rhs) { + let one = dfg.make_constant(FieldElement::one(), Type::bool()); + return SimplifyResult::SimplifiedTo(one); + } + if operand_type == Type::bool() { + // Simplify forms of `(boolean == true)` into `boolean` + if lhs_is_one { + return SimplifyResult::SimplifiedTo(self.rhs); + } + if rhs_is_one { + return SimplifyResult::SimplifiedTo(self.lhs); + } + // Simplify forms of `(boolean == false)` into `!boolean` + if lhs_is_zero { + return SimplifyResult::SimplifiedToInstruction(Instruction::Not(self.rhs)); + } + if rhs_is_zero { + return SimplifyResult::SimplifiedToInstruction(Instruction::Not(self.lhs)); + } + } + } + BinaryOp::Lt => { + if dfg.resolve(self.lhs) == dfg.resolve(self.rhs) { + let zero = dfg.make_constant(FieldElement::zero(), Type::bool()); + return SimplifyResult::SimplifiedTo(zero); + } + if operand_type.is_unsigned() { + if rhs_is_zero { + // Unsigned values cannot be less than zero. + let zero = dfg.make_constant(FieldElement::zero(), Type::bool()); + return SimplifyResult::SimplifiedTo(zero); + } else if rhs_is_one { + let zero = dfg.make_constant(FieldElement::zero(), operand_type); + return SimplifyResult::SimplifiedToInstruction(Instruction::binary( + BinaryOp::Eq, + self.lhs, + zero, + )); + } + } + } + BinaryOp::And => { + if lhs_is_zero || rhs_is_zero { + let zero = dfg.make_constant(FieldElement::zero(), operand_type); + return SimplifyResult::SimplifiedTo(zero); + } + if dfg.resolve(self.lhs) == dfg.resolve(self.rhs) { + return SimplifyResult::SimplifiedTo(self.lhs); + } + if operand_type == Type::bool() { + // Boolean AND is equivalent to multiplication, which is a cheaper operation. + let instruction = Instruction::binary(BinaryOp::Mul, self.lhs, self.rhs); + return SimplifyResult::SimplifiedToInstruction(instruction); + } + } + BinaryOp::Or => { + if lhs_is_zero { + return SimplifyResult::SimplifiedTo(self.rhs); + } + if rhs_is_zero { + return SimplifyResult::SimplifiedTo(self.lhs); + } + if dfg.resolve(self.lhs) == dfg.resolve(self.rhs) { + return SimplifyResult::SimplifiedTo(self.lhs); + } + } + BinaryOp::Xor => { + if lhs_is_zero { + return SimplifyResult::SimplifiedTo(self.rhs); + } + if rhs_is_zero { + return SimplifyResult::SimplifiedTo(self.lhs); + } + if dfg.resolve(self.lhs) == dfg.resolve(self.rhs) { + let zero = dfg.make_constant(FieldElement::zero(), operand_type); + return SimplifyResult::SimplifiedTo(zero); + } + } + } + SimplifyResult::None + } +} + +/// Evaluate a binary operation with constant arguments. +fn eval_constant_binary_op( + lhs: FieldElement, + rhs: FieldElement, + operator: BinaryOp, + mut operand_type: Type, +) -> Option<(FieldElement, Type)> { + let value = match &operand_type { + Type::Numeric(NumericType::NativeField) => { + // If the rhs of a division is zero, attempting to evaluate the division will cause a compiler panic. + // Thus, we do not evaluate the division in this method, as we want to avoid triggering a panic, + // and the operation should be handled by ACIR generation. + if matches!(operator, BinaryOp::Div | BinaryOp::Mod) && rhs == FieldElement::zero() { + return None; + } + operator.get_field_function()?(lhs, rhs) + } + Type::Numeric(NumericType::Unsigned { bit_size }) => { + let function = operator.get_u128_function(); + + let lhs = truncate(lhs.try_into_u128()?, *bit_size); + let rhs = truncate(rhs.try_into_u128()?, *bit_size); + + // The divisor is being truncated into the type of the operand, which can potentially + // lead to the rhs being zero. + // If the rhs of a division is zero, attempting to evaluate the division will cause a compiler panic. + // Thus, we do not evaluate the division in this method, as we want to avoid triggering a panic, + // and the operation should be handled by ACIR generation. + if matches!(operator, BinaryOp::Div | BinaryOp::Mod) && rhs == 0 { + return None; + } + let result = function(lhs, rhs)?; + // Check for overflow + if result >= 2u128.pow(*bit_size) { + return None; + } + result.into() + } + Type::Numeric(NumericType::Signed { bit_size }) => { + let function = operator.get_i128_function(); + + let lhs = truncate(lhs.try_into_u128()?, *bit_size); + let rhs = truncate(rhs.try_into_u128()?, *bit_size); + let l_pos = lhs < 2u128.pow(bit_size - 1); + let r_pos = rhs < 2u128.pow(bit_size - 1); + let lhs = if l_pos { lhs as i128 } else { -((2u128.pow(*bit_size) - lhs) as i128) }; + let rhs = if r_pos { rhs as i128 } else { -((2u128.pow(*bit_size) - rhs) as i128) }; + // The divisor is being truncated into the type of the operand, which can potentially + // lead to the rhs being zero. + // If the rhs of a division is zero, attempting to evaluate the division will cause a compiler panic. + // Thus, we do not evaluate the division in this method, as we want to avoid triggering a panic, + // and the operation should be handled by ACIR generation. + if matches!(operator, BinaryOp::Div | BinaryOp::Mod) && rhs == 0 { + return None; + } + + let result = function(lhs, rhs)?; + // Check for overflow + if result >= 2i128.pow(*bit_size - 1) || result < -(2i128.pow(*bit_size - 1)) { + return None; + } + let result = + if result >= 0 { result as u128 } else { (2i128.pow(*bit_size) + result) as u128 }; + result.into() + } + _ => return None, + }; + + if matches!(operator, BinaryOp::Eq | BinaryOp::Lt) { + operand_type = Type::bool(); + } + + Some((value, operand_type)) +} + +fn truncate(int: u128, bit_size: u32) -> u128 { + let max = 2u128.pow(bit_size); + int % max +} + +impl BinaryOp { + fn get_field_function(self) -> Option FieldElement> { + match self { + BinaryOp::Add => Some(std::ops::Add::add), + BinaryOp::Sub => Some(std::ops::Sub::sub), + BinaryOp::Mul => Some(std::ops::Mul::mul), + BinaryOp::Div => Some(std::ops::Div::div), + BinaryOp::Eq => Some(|x, y| (x == y).into()), + BinaryOp::Lt => Some(|x, y| (x < y).into()), + // Bitwise operators are unsupported for Fields + BinaryOp::Mod => None, + BinaryOp::And => None, + BinaryOp::Or => None, + BinaryOp::Xor => None, + } + } + + fn get_u128_function(self) -> fn(u128, u128) -> Option { + match self { + BinaryOp::Add => u128::checked_add, + BinaryOp::Sub => u128::checked_sub, + BinaryOp::Mul => u128::checked_mul, + BinaryOp::Div => u128::checked_div, + BinaryOp::Mod => u128::checked_rem, + BinaryOp::And => |x, y| Some(x & y), + BinaryOp::Or => |x, y| Some(x | y), + BinaryOp::Xor => |x, y| Some(x ^ y), + BinaryOp::Eq => |x, y| Some((x == y) as u128), + BinaryOp::Lt => |x, y| Some((x < y) as u128), + } + } + + fn get_i128_function(self) -> fn(i128, i128) -> Option { + match self { + BinaryOp::Add => i128::checked_add, + BinaryOp::Sub => i128::checked_sub, + BinaryOp::Mul => i128::checked_mul, + BinaryOp::Div => i128::checked_div, + BinaryOp::Mod => i128::checked_rem, + BinaryOp::And => |x, y| Some(x & y), + BinaryOp::Or => |x, y| Some(x | y), + BinaryOp::Xor => |x, y| Some(x ^ y), + BinaryOp::Eq => |x, y| Some((x == y) as i128), + BinaryOp::Lt => |x, y| Some((x < y) as i128), + } + } +} diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index d1991abab37..0178ae9dba1 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -417,7 +417,7 @@ fn simplify_black_box_func( _ => SimplifyResult::None, } } - + BlackBoxFunc::Poseidon2Permutation => SimplifyResult::None, //TODO(Guillaume) BlackBoxFunc::EcdsaSecp256k1 => { simplify_signature(dfg, arguments, acvm::blackbox_solver::ecdsa_secp256k1_verify) } @@ -429,13 +429,17 @@ fn simplify_black_box_func( | BlackBoxFunc::SchnorrVerify | BlackBoxFunc::PedersenCommitment | BlackBoxFunc::PedersenHash - | BlackBoxFunc::EmbeddedCurveAdd - | BlackBoxFunc::EmbeddedCurveDouble => { + | BlackBoxFunc::EmbeddedCurveAdd => { // Currently unsolvable here as we rely on an implementation in the backend. SimplifyResult::None } - - BlackBoxFunc::RecursiveAggregation => SimplifyResult::None, + BlackBoxFunc::BigIntAdd + | BlackBoxFunc::BigIntNeg + | BlackBoxFunc::BigIntMul + | BlackBoxFunc::BigIntDiv + | BlackBoxFunc::RecursiveAggregation + | BlackBoxFunc::BigIntFromLeBytes + | BlackBoxFunc::BigIntToLeBytes => SimplifyResult::None, BlackBoxFunc::AND => { unreachable!("ICE: `BlackBoxFunc::AND` calls should be transformed into a `BinaryOp`") @@ -448,6 +452,7 @@ fn simplify_black_box_func( "ICE: `BlackBoxFunc::RANGE` calls should be transformed into a `Instruction::Cast`" ) } + BlackBoxFunc::Sha256Compression => SimplifyResult::None, //TODO(Guillaume) } } diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/cast.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/cast.rs new file mode 100644 index 00000000000..671820e801d --- /dev/null +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/cast.rs @@ -0,0 +1,58 @@ +use acvm::FieldElement; +use num_bigint::BigUint; + +use super::{DataFlowGraph, Instruction, NumericType, SimplifyResult, Type, Value, ValueId}; + +/// Try to simplify this cast instruction. If the instruction can be simplified to a known value, +/// that value is returned. Otherwise None is returned. +pub(super) fn simplify_cast( + value: ValueId, + dst_typ: &Type, + dfg: &mut DataFlowGraph, +) -> SimplifyResult { + use SimplifyResult::*; + let value = dfg.resolve(value); + + if let Value::Instruction { instruction, .. } = &dfg[value] { + if let Instruction::Cast(original_value, _) = &dfg[*instruction] { + return SimplifiedToInstruction(Instruction::Cast(*original_value, dst_typ.clone())); + } + } + + if let Some(constant) = dfg.get_numeric_constant(value) { + let src_typ = dfg.type_of_value(value); + match (src_typ, dst_typ) { + (Type::Numeric(NumericType::NativeField), Type::Numeric(NumericType::NativeField)) => { + // Field -> Field: use src value + SimplifiedTo(value) + } + ( + Type::Numeric(NumericType::Unsigned { .. }), + Type::Numeric(NumericType::NativeField), + ) => { + // Unsigned -> Field: redefine same constant as Field + SimplifiedTo(dfg.make_constant(constant, dst_typ.clone())) + } + ( + Type::Numeric( + NumericType::NativeField + | NumericType::Unsigned { .. } + | NumericType::Signed { .. }, + ), + Type::Numeric(NumericType::Unsigned { bit_size }), + ) => { + // Field/Unsigned -> unsigned: truncate + let integer_modulus = BigUint::from(2u128).pow(*bit_size); + let constant: BigUint = BigUint::from_bytes_be(&constant.to_be_bytes()); + let truncated = constant % integer_modulus; + let truncated = FieldElement::from_be_bytes_reduce(&truncated.to_bytes_be()); + SimplifiedTo(dfg.make_constant(truncated, dst_typ.clone())) + } + _ => None, + } + } else if *dst_typ == dfg.type_of_value(value) { + SimplifiedTo(value) + } else { + None + } +} diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs new file mode 100644 index 00000000000..0ae0f8f2892 --- /dev/null +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/constrain.rs @@ -0,0 +1,126 @@ +use acvm::FieldElement; + +use super::{Binary, BinaryOp, ConstrainError, DataFlowGraph, Instruction, Type, Value, ValueId}; + +/// Try to decompose this constrain instruction. This constraint will be broken down such that it instead constrains +/// all the values which are used to compute the values which were being constrained. +pub(super) fn decompose_constrain( + lhs: ValueId, + rhs: ValueId, + msg: &Option>, + dfg: &mut DataFlowGraph, +) -> Vec { + let lhs = dfg.resolve(lhs); + let rhs = dfg.resolve(rhs); + + if lhs == rhs { + // Remove trivial case `assert_eq(x, x)` + Vec::new() + } else { + match (&dfg[lhs], &dfg[rhs]) { + (Value::NumericConstant { constant, typ }, Value::Instruction { instruction, .. }) + | (Value::Instruction { instruction, .. }, Value::NumericConstant { constant, typ }) + if *typ == Type::bool() => + { + match dfg[*instruction] { + Instruction::Binary(Binary { lhs, rhs, operator: BinaryOp::Eq }) + if constant.is_one() => + { + // Replace an explicit two step equality assertion + // + // v2 = eq v0, u32 v1 + // constrain v2 == u1 1 + // + // with a direct assertion of equality between the two values + // + // v2 = eq v0, u32 v1 + // constrain v0 == v1 + // + // Note that this doesn't remove the value `v2` as it may be used in other instructions, but it + // will likely be removed through dead instruction elimination. + + vec![Instruction::Constrain(lhs, rhs, msg.clone())] + } + + Instruction::Binary(Binary { lhs, rhs, operator: BinaryOp::Mul }) + if constant.is_one() && dfg.type_of_value(lhs) == Type::bool() => + { + // Replace an equality assertion on a boolean multiplication + // + // v2 = mul v0, v1 + // constrain v2 == u1 1 + // + // with a direct assertion that each value is equal to 1 + // + // v2 = mul v0, v1 + // constrain v0 == 1 + // constrain v1 == 1 + // + // This is due to the fact that for `v2` to be 1 then both `v0` and `v1` are 1. + // + // Note that this doesn't remove the value `v2` as it may be used in other instructions, but it + // will likely be removed through dead instruction elimination. + let one = FieldElement::one(); + let one = dfg.make_constant(one, Type::bool()); + + [ + decompose_constrain(lhs, one, msg, dfg), + decompose_constrain(rhs, one, msg, dfg), + ] + .concat() + } + + Instruction::Binary(Binary { lhs, rhs, operator: BinaryOp::Or }) + if constant.is_zero() => + { + // Replace an equality assertion on an OR + // + // v2 = or v0, v1 + // constrain v2 == u1 0 + // + // with a direct assertion that each value is equal to 0 + // + // v2 = or v0, v1 + // constrain v0 == 0 + // constrain v1 == 0 + // + // This is due to the fact that for `v2` to be 0 then both `v0` and `v1` are 0. + // + // Note that this doesn't remove the value `v2` as it may be used in other instructions, but it + // will likely be removed through dead instruction elimination. + let zero = FieldElement::zero(); + let zero = dfg.make_constant(zero, dfg.type_of_value(lhs)); + + [ + decompose_constrain(lhs, zero, msg, dfg), + decompose_constrain(rhs, zero, msg, dfg), + ] + .concat() + } + + Instruction::Not(value) => { + // Replace an assertion that a not instruction is truthy + // + // v1 = not v0 + // constrain v1 == u1 1 + // + // with an assertion that the not instruction input is falsy + // + // v1 = not v0 + // constrain v0 == u1 0 + // + // Note that this doesn't remove the value `v1` as it may be used in other instructions, but it + // will likely be removed through dead instruction elimination. + let reversed_constant = FieldElement::from(!constant.is_one()); + let reversed_constant = dfg.make_constant(reversed_constant, Type::bool()); + decompose_constrain(value, reversed_constant, msg, dfg) + } + + _ => vec![Instruction::Constrain(lhs, rhs, msg.clone())], + } + } + + _ => vec![Instruction::Constrain(lhs, rhs, msg.clone())], + } + } +} diff --git a/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/compiler/noirc_evaluator/src/ssa/ir/printer.rs index 2899b987c1d..9bd43fab1ff 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -9,7 +9,7 @@ use iter_extended::vecmap; use super::{ basic_block::BasicBlockId, function::Function, - instruction::{Instruction, InstructionId, TerminatorInstruction}, + instruction::{ConstrainError, Instruction, InstructionId, TerminatorInstruction}, value::ValueId, }; @@ -133,9 +133,17 @@ pub(crate) fn display_instruction( write!(f, "{} = ", value_list(function, results))?; } + display_instruction_inner(function, &function.dfg[instruction], f) +} + +fn display_instruction_inner( + function: &Function, + instruction: &Instruction, + f: &mut Formatter, +) -> Result { let show = |id| value(function, id); - match &function.dfg[instruction] { + match instruction { Instruction::Binary(binary) => { writeln!(f, "{} {}, {}", binary.operator, show(binary.lhs), show(binary.rhs)) } @@ -145,10 +153,15 @@ pub(crate) fn display_instruction( let value = show(*value); writeln!(f, "truncate {value} to {bit_size} bits, max_bit_size: {max_bit_size}",) } - Instruction::Constrain(lhs, rhs, message) => match message { - Some(message) => writeln!(f, "constrain {} == {} '{message}'", show(*lhs), show(*rhs)), - None => writeln!(f, "constrain {} == {}", show(*lhs), show(*rhs)), - }, + Instruction::Constrain(lhs, rhs, error) => { + write!(f, "constrain {} == {}", show(*lhs), show(*rhs))?; + if let Some(error) = error { + write!(f, " ")?; + display_constrain_error(function, error, f) + } else { + writeln!(f) + } + } Instruction::Call { func, arguments } => { writeln!(f, "call {}({})", show(*func), value_list(function, arguments)) } @@ -180,3 +193,18 @@ pub(crate) fn display_instruction( } } } + +fn display_constrain_error( + function: &Function, + error: &ConstrainError, + f: &mut Formatter, +) -> Result { + match error { + ConstrainError::Static(assert_message_string) => { + writeln!(f, "{assert_message_string:?}") + } + ConstrainError::Dynamic(assert_message_call) => { + display_instruction_inner(function, assert_message_call, f) + } + } +} diff --git a/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs b/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs index cb9519eeb2e..b737c51d145 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs @@ -1,4 +1,9 @@ -use crate::ssa::{ir::instruction::Instruction, ssa_gen::Ssa}; +use std::collections::HashMap; + +use crate::ssa::{ + ir::instruction::{Instruction, InstructionId}, + ssa_gen::Ssa, +}; impl Ssa { /// A simple SSA pass to go through each instruction and move every `Instruction::Constrain` to immediately @@ -10,6 +15,12 @@ impl Ssa { let instructions = function.dfg[block].take_instructions(); let mut filtered_instructions = Vec::with_capacity(instructions.len()); + // Multiple constrains can bubble up to sit under a single instruction. We want to maintain the ordering of these constraints, + // so we need to keep track of how many constraints are attached to a given instruction. + // Some assertions don't operate on instruction results, so we use Option so we also track the None case + let mut inserted_at_instruction: HashMap, usize> = + HashMap::with_capacity(instructions.len()); + let dfg = &function.dfg; for instruction in instructions { let (lhs, rhs) = match dfg[instruction] { @@ -20,19 +31,38 @@ impl Ssa { } }; - let index = filtered_instructions + let last_instruction_that_creates_inputs = filtered_instructions .iter() .rev() - .position(|instruction_id| { - let results = dfg.instruction_results(*instruction_id).to_vec(); + .position(|&instruction_id| { + let results = dfg.instruction_results(instruction_id).to_vec(); results.contains(&lhs) || results.contains(&rhs) }) // We iterate through the previous instructions in reverse order so the index is from the - // back of the vector. Subtract from vector length to get correct index. - .map(|reversed_index| filtered_instructions.len() - reversed_index) - .unwrap_or(0); + // back of the vector + .map(|reversed_index| filtered_instructions.len() - reversed_index - 1); + + let insertion_index = last_instruction_that_creates_inputs + .map(|index| { + // We want to insert just after the last instruction that creates the inputs + index + 1 + }) + // If it doesn't depend from the previous instructions, then we insert at the start + .unwrap_or_default(); + + let already_inserted_for_this_instruction = inserted_at_instruction + .entry( + last_instruction_that_creates_inputs + .map(|index| filtered_instructions[index]), + ) + .or_default(); - filtered_instructions.insert(index, instruction); + filtered_instructions.insert( + insertion_index + *already_inserted_for_this_instruction, + instruction, + ); + + *already_inserted_for_this_instruction += 1; } *function.dfg[block].instructions_mut() = filtered_instructions; @@ -41,3 +71,83 @@ impl Ssa { self } } + +#[cfg(test)] +mod test { + use crate::ssa::{ + function_builder::FunctionBuilder, + ir::{ + function::RuntimeType, + instruction::{Binary, BinaryOp, Instruction}, + map::Id, + types::Type, + }, + }; + + #[test] + fn check_bubble_up_constrains() { + // fn main f0 { + // b0(v0: Field): + // v1 = add v0, Field 1 + // v2 = add v1, Field 1 + // constrain v0 == Field 1 'With message' + // constrain v2 == Field 3 + // constrain v0 == Field 1 + // constrain v1 == Field 2 + // constrain v1 == Field 2 'With message' + // } + // + 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 one = builder.field_constant(1u128); + let two = builder.field_constant(2u128); + let three = builder.field_constant(3u128); + + let v1 = builder.insert_binary(v0, BinaryOp::Add, one); + let v2 = builder.insert_binary(v1, BinaryOp::Add, one); + builder.insert_constrain(v0, one, Some("With message".to_string().into())); + builder.insert_constrain(v2, three, None); + builder.insert_constrain(v0, one, None); + builder.insert_constrain(v1, two, None); + builder.insert_constrain(v1, two, Some("With message".to_string().into())); + builder.terminate_with_return(vec![]); + + let ssa = builder.finish(); + + // Expected output: + // + // fn main f0 { + // b0(v0: Field): + // constrain v0 == Field 1 'With message' + // constrain v0 == Field 1 + // v1 = add v0, Field 1 + // constrain v1 == Field 2 + // constrain v1 == Field 2 'With message' + // v2 = add v1, Field 1 + // constrain v2 == Field 3 + // } + // + let ssa = ssa.bubble_up_constrains(); + let main = ssa.main(); + let block = &main.dfg[main.entry_block()]; + assert_eq!(block.instructions().len(), 7); + + let expected_instructions = vec![ + Instruction::Constrain(v0, one, Some("With message".to_string().into())), + Instruction::Constrain(v0, one, None), + Instruction::Binary(Binary { lhs: v0, rhs: one, operator: BinaryOp::Add }), + Instruction::Constrain(v1, two, None), + Instruction::Constrain(v1, two, Some("With message".to_string().into())), + Instruction::Binary(Binary { lhs: v1, rhs: one, operator: BinaryOp::Add }), + Instruction::Constrain(v2, three, None), + ]; + + for (index, instruction) in block.instructions().iter().enumerate() { + assert_eq!(&main.dfg[*instruction], &expected_instructions[index]); + } + } +} diff --git a/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs b/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs index b7f154397a6..1f09a132132 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs @@ -14,7 +14,7 @@ use crate::ssa::{ ir::{ basic_block::BasicBlockId, function::{Function, FunctionId, RuntimeType, Signature}, - instruction::{BinaryOp, Instruction}, + instruction::{BinaryOp, ConstrainError, Instruction}, types::{NumericType, Type}, value::{Value, ValueId}, }, @@ -86,9 +86,22 @@ impl DefunctionalizationContext { let instruction = func.dfg[instruction_id].clone(); let mut replacement_instruction = None; // Operate on call instructions - let (target_func_id, mut arguments) = match instruction { + let (target_func_id, arguments) = match &instruction { Instruction::Call { func: target_func_id, arguments } => { - (target_func_id, arguments) + (*target_func_id, arguments) + } + // Constrain instruction potentially hold a call instruction themselves + // thus we need to account for them. + Instruction::Constrain(_, _, Some(constrain_error)) => { + if let ConstrainError::Dynamic(Instruction::Call { + func: target_func_id, + arguments, + }) = constrain_error.as_ref() + { + (*target_func_id, arguments) + } else { + continue; + } } _ => continue, }; @@ -96,6 +109,7 @@ impl DefunctionalizationContext { match func.dfg[target_func_id] { // If the target is a function used as value Value::Param { .. } | Value::Instruction { .. } => { + let mut arguments = arguments.clone(); let results = func.dfg.instruction_results(instruction_id); let signature = Signature { params: vecmap(&arguments, |param| func.dfg.type_of_value(*param)), @@ -120,7 +134,20 @@ impl DefunctionalizationContext { } _ => {} } - if let Some(new_instruction) = replacement_instruction { + if let Some(mut new_instruction) = replacement_instruction { + if let Instruction::Constrain(lhs, rhs, constrain_error_call) = instruction { + let new_error_call = if let Some(error) = constrain_error_call { + match error.as_ref() { + ConstrainError::Dynamic(_) => { + Some(Box::new(ConstrainError::Dynamic(new_instruction))) + } + _ => None, + } + } else { + None + }; + new_instruction = Instruction::Constrain(lhs, rhs, new_error_call); + } func.dfg[instruction_id] = new_instruction; } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 6bdf2ab1c0a..1059994b9be 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -144,9 +144,9 @@ use crate::ssa::{ dfg::{CallStack, InsertInstructionResult}, function::Function, function_inserter::FunctionInserter, - instruction::{BinaryOp, Instruction, InstructionId, TerminatorInstruction}, + instruction::{BinaryOp, Instruction, InstructionId, Intrinsic, TerminatorInstruction}, types::Type, - value::ValueId, + value::{Value, ValueId}, }, ssa_gen::Ssa, }; @@ -683,6 +683,28 @@ impl<'f> Context<'f> { ); Instruction::RangeCheck { value, max_bit_size, assert_message } } + Instruction::Call { func, mut arguments } => match self.inserter.function.dfg[func] + { + Value::Intrinsic(Intrinsic::ToBits(_) | Intrinsic::ToRadix(_)) => { + let field = arguments[0]; + let argument_type = self.inserter.function.dfg.type_of_value(field); + + let casted_condition = self.insert_instruction( + Instruction::Cast(condition, argument_type), + call_stack.clone(), + ); + let field = self.insert_instruction( + Instruction::binary(BinaryOp::Mul, field, casted_condition), + call_stack.clone(), + ); + + arguments[0] = field; + + Instruction::Call { func, arguments } + } + + _ => Instruction::Call { func, arguments }, + }, other => other, } } else { diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs index f1a2154d3a8..adf68bacba4 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -270,11 +270,12 @@ impl<'a> FunctionContext<'a> { /// helper function which add instructions to the block computing the absolute value of the /// given signed integer input. When the input is negative, we return its two complement, and itself when it is positive. fn absolute_value_helper(&mut self, input: ValueId, sign: ValueId, bit_size: u32) -> ValueId { + assert_eq!(self.builder.type_of_value(sign), Type::bool()); + // We compute the absolute value of lhs - let one = self.builder.numeric_constant(FieldElement::one(), Type::bool()); let bit_width = self.builder.numeric_constant(FieldElement::from(2_i128.pow(bit_size)), Type::field()); - let sign_not = self.builder.insert_binary(one, BinaryOp::Sub, sign); + let sign_not = self.builder.insert_not(sign); // We use unsafe casts here, this is fine as we're casting to a `field` type. let as_field = self.builder.insert_cast(input, Type::field()); @@ -434,7 +435,7 @@ impl<'a> FunctionContext<'a> { self.builder.set_location(location).insert_constrain( sign, one, - Some("attempt to bit-shift with overflow".to_string()), + Some("attempt to bit-shift with overflow".to_owned().into()), ); } @@ -445,7 +446,7 @@ impl<'a> FunctionContext<'a> { self.builder.set_location(location).insert_constrain( overflow, one, - Some("attempt to bit-shift with overflow".to_owned()), + Some("attempt to bit-shift with overflow".to_owned().into()), ); self.builder.insert_truncate(result, bit_size, bit_size + 1) } @@ -472,7 +473,6 @@ impl<'a> FunctionContext<'a> { location: Location, ) { let is_sub = operator == BinaryOpKind::Subtract; - let one = self.builder.numeric_constant(FieldElement::one(), Type::bool()); let half_width = self.builder.numeric_constant( FieldElement::from(2_i128.pow(bit_size - 1)), Type::unsigned(bit_size), @@ -484,7 +484,7 @@ impl<'a> FunctionContext<'a> { let mut rhs_sign = self.builder.insert_binary(rhs_as_unsigned, BinaryOp::Lt, half_width); let message = if is_sub { // lhs - rhs = lhs + (-rhs) - rhs_sign = self.builder.insert_binary(one, BinaryOp::Sub, rhs_sign); + rhs_sign = self.builder.insert_not(rhs_sign); "attempt to subtract with overflow".to_string() } else { "attempt to add with overflow".to_string() @@ -498,8 +498,11 @@ impl<'a> FunctionContext<'a> { let sign_diff = self.builder.insert_binary(result_sign, BinaryOp::Eq, lhs_sign); let sign_diff_with_predicate = self.builder.insert_binary(sign_diff, BinaryOp::Mul, same_sign); - let overflow_check = - Instruction::Constrain(sign_diff_with_predicate, same_sign, Some(message)); + let overflow_check = Instruction::Constrain( + sign_diff_with_predicate, + same_sign, + Some(message.into()), + ); self.builder.set_location(location).insert_instruction(overflow_check, None); } BinaryOpKind::Multiply => { @@ -509,26 +512,27 @@ impl<'a> FunctionContext<'a> { let rhs_abs = self.absolute_value_helper(rhs, rhs_sign, bit_size); let product_field = self.builder.insert_binary(lhs_abs, BinaryOp::Mul, rhs_abs); // It must not already overflow the bit_size - let message = "attempt to multiply with overflow".to_string(); self.builder.set_location(location).insert_range_check( product_field, bit_size, - Some(message.clone()), + Some("attempt to multiply with overflow".to_string()), ); let product = self.builder.insert_cast(product_field, Type::unsigned(bit_size)); // Then we check the signed product fits in a signed integer of bit_size-bits - let not_same = self.builder.insert_binary(one, BinaryOp::Sub, same_sign); + let not_same = self.builder.insert_not(same_sign); let not_same_sign_field = self.insert_safe_cast(not_same, Type::unsigned(bit_size), location); let positive_maximum_with_offset = self.builder.insert_binary(half_width, BinaryOp::Add, not_same_sign_field); let product_overflow_check = self.builder.insert_binary(product, BinaryOp::Lt, positive_maximum_with_offset); + + let one = self.builder.numeric_constant(FieldElement::one(), Type::bool()); self.builder.set_location(location).insert_constrain( product_overflow_check, one, - Some(message), + Some(message.into()), ); } _ => unreachable!("operator {} should not overflow", operator), diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index eb35ba9a65b..0efc73fb85b 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -13,7 +13,7 @@ use noirc_frontend::{ }; use crate::{ - errors::RuntimeError, + errors::{InternalError, RuntimeError}, ssa::{ function_builder::data_bus::DataBusBuilder, ir::{instruction::Intrinsic, types::NumericType}, @@ -29,7 +29,7 @@ use super::{ function_builder::data_bus::DataBus, ir::{ function::RuntimeType, - instruction::{BinaryOp, TerminatorInstruction}, + instruction::{BinaryOp, ConstrainError, Instruction, TerminatorInstruction}, types::Type, value::ValueId, }, @@ -96,6 +96,9 @@ pub(crate) fn generate_ssa(program: Program) -> Result { _ => unreachable!("ICE - expect return on the last block"), } } + // we save the data bus inside the dfg + function_context.builder.current_function.dfg.data_bus = + DataBus::get_data_bus(call_data, return_data); // Main has now been compiled and any other functions referenced within have been added to the // function queue as they were found in codegen_ident. This queueing will happen each time a @@ -106,9 +109,6 @@ pub(crate) fn generate_ssa(program: Program) -> Result { function_context.new_function(dest_id, function); function_context.codegen_function_body(&function.body)?; } - // we save the data bus inside the dfg - function_context.builder.current_function.dfg.data_bus = - DataBus::get_data_bus(call_data, return_data); Ok(function_context.builder.finish()) } @@ -141,7 +141,7 @@ impl<'a> FunctionContext<'a> { Expression::Call(call) => self.codegen_call(call), Expression::Let(let_expr) => self.codegen_let(let_expr), Expression::Constrain(expr, location, assert_message) => { - self.codegen_constrain(expr, *location, assert_message.clone()) + self.codegen_constrain(expr, *location, assert_message) } Expression::Assign(assign) => self.codegen_assign(assign), Expression::Semi(semi) => self.codegen_semi(semi), @@ -438,10 +438,11 @@ impl<'a> FunctionContext<'a> { let is_offset_out_of_bounds = self.builder.insert_binary(index, BinaryOp::Lt, array_len); let true_const = self.builder.numeric_constant(true, Type::bool()); + self.builder.insert_constrain( is_offset_out_of_bounds, true_const, - Some("Index out of bounds".to_owned()), + Some(Box::new("Index out of bounds".to_owned().into())), ); } @@ -665,15 +666,59 @@ impl<'a> FunctionContext<'a> { &mut self, expr: &Expression, location: Location, - assert_message: Option, + assert_message: &Option>, ) -> Result { let expr = self.codegen_non_tuple_expression(expr)?; let true_literal = self.builder.numeric_constant(true, Type::bool()); - self.builder.set_location(location).insert_constrain(expr, true_literal, assert_message); + + // Set the location here for any errors that may occur when we codegen the assert message + self.builder.set_location(location); + + let assert_message = self.codegen_constrain_error(assert_message)?; + + self.builder.insert_constrain(expr, true_literal, assert_message); Ok(Self::unit_value()) } + // This method does not necessary codegen the full assert message expression, thus it does not + // return a `Values` object. Instead we check the internals of the expression to make sure + // we have an `Expression::Call` as expected. An `Instruction::Call` is then constructed but not + // inserted to the SSA as we want that instruction to be atomic in SSA with a constrain instruction. + fn codegen_constrain_error( + &mut self, + assert_message: &Option>, + ) -> Result>, RuntimeError> { + let Some(assert_message_expr) = assert_message else { + return Ok(None) + }; + + let ast::Expression::Call(call) = assert_message_expr.as_ref() else { + return Err(InternalError::Unexpected { + expected: "Expected a call expression".to_owned(), + found: "Instead found {expr:?}".to_owned(), + call_stack: self.builder.get_call_stack(), + } + .into()); + }; + + let func = self.codegen_non_tuple_expression(&call.func)?; + let mut arguments = Vec::with_capacity(call.arguments.len()); + + for argument in &call.arguments { + let mut values = self.codegen_expression(argument)?.into_value_list(self); + arguments.append(&mut values); + } + + // If an array is passed as an argument we increase its reference count + for argument in &arguments { + self.builder.increment_array_reference_count(*argument); + } + + let instr = Instruction::Call { func, arguments }; + Ok(Some(Box::new(ConstrainError::Dynamic(instr)))) + } + fn codegen_assign(&mut self, assign: &ast::Assign) -> Result { let lhs = self.extract_current_value(&assign.lvalue)?; let rhs = self.codegen_expression(&assign.expression)?; diff --git a/compiler/noirc_frontend/src/ast/function.rs b/compiler/noirc_frontend/src/ast/function.rs index b8f385f52d3..f20fc54b101 100644 --- a/compiler/noirc_frontend/src/ast/function.rs +++ b/compiler/noirc_frontend/src/ast/function.rs @@ -72,7 +72,7 @@ impl NoirFunction { pub fn function_attribute(&self) -> Option<&FunctionAttribute> { self.def.attributes.function.as_ref() } - pub fn secondary_attributes(&self) -> &Vec { + pub fn secondary_attributes(&self) -> &[SecondaryAttribute] { self.def.attributes.secondary.as_ref() } pub fn def(&self) -> &FunctionDefinition { diff --git a/compiler/noirc_frontend/src/ast/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index 73b1f68778d..a2c29a8c52e 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -416,7 +416,7 @@ pub enum LValue { } #[derive(Debug, PartialEq, Eq, Clone)] -pub struct ConstrainStatement(pub Expression, pub Option, pub ConstrainKind); +pub struct ConstrainStatement(pub Expression, pub Option, pub ConstrainKind); #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum ConstrainKind { 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 c768ea96f8f..f7441750fc8 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -14,7 +14,7 @@ use crate::hir::resolution::{ use crate::hir::type_check::{type_check_func, TypeCheckError, TypeChecker}; use crate::hir::Context; -use crate::macros_api::MacroProcessor; +use crate::macros_api::{MacroError, MacroProcessor}; use crate::node_interner::{FuncId, NodeInterner, StmtId, StructId, TraitId, TypeAliasId}; use crate::parser::{ParserError, SortedModule}; @@ -155,6 +155,12 @@ impl From for CustomDiagnostic { } } +impl From for CompilationError { + fn from(value: MacroError) -> Self { + CompilationError::DefinitionError(DefCollectorErrorKind::MacroError(value)) + } +} + impl From for CompilationError { fn from(value: ParserError) -> Self { CompilationError::ParseError(value) @@ -359,7 +365,11 @@ impl DefCollector { errors.extend(resolved_globals.errors); for macro_processor in macro_processors { - macro_processor.process_typed_ast(&crate_id, context); + macro_processor.process_typed_ast(&crate_id, context).unwrap_or_else( + |(macro_err, file_id)| { + errors.push((macro_err.into(), file_id)); + }, + ); } errors.extend(type_check_globals(&mut context.def_interner, resolved_globals.globals)); @@ -460,7 +470,7 @@ fn type_check_functions( #[allow(clippy::too_many_arguments)] pub(crate) fn check_methods_signatures( resolver: &mut Resolver, - impl_methods: &Vec<(FileId, FuncId)>, + impl_methods: &[(FileId, FuncId)], trait_id: TraitId, trait_name_span: Span, // These are the generics on the trait itself from the impl. diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index 390807afd17..7bd4de77e84 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -84,6 +84,8 @@ pub enum ResolverError { InvalidTypeForEntryPoint { span: Span }, #[error("Nested slices are not supported")] NestedSlices { span: Span }, + #[error("Usage of the `#[foreign]` or `#[builtin]` function attributes are not allowed outside of the Noir standard library")] + LowLevelFunctionOutsideOfStdlib { ident: Ident }, } impl ResolverError { @@ -311,6 +313,11 @@ impl From for Diagnostic { "Try to use a constant sized array instead".into(), span, ), + ResolverError::LowLevelFunctionOutsideOfStdlib { ident } => Diagnostic::simple_error( + "Definition of low-level function outside of standard library".into(), + "Usage of the `#[foreign]` or `#[builtin]` function attributes are not allowed outside of the Noir standard library".into(), + ident.span(), + ), } } } diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 1d4f60ffd51..7b876244601 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -191,10 +191,18 @@ impl<'a> Resolver<'a> { self.add_generics(&func.def.generics); self.trait_bounds = func.def.where_clause.clone(); + let is_low_level_or_oracle = func + .attributes() + .function + .as_ref() + .map_or(false, |func| func.is_low_level() || func.is_oracle()); let (hir_func, func_meta) = self.intern_function(func, func_id); let func_scope_tree = self.scopes.end_function(); - self.check_for_unused_variables_in_scope_tree(func_scope_tree); + // The arguments to low-level and oracle functions are always unused so we do not produce warnings for them. + if !is_low_level_or_oracle { + self.check_for_unused_variables_in_scope_tree(func_scope_tree); + } self.trait_bounds.clear(); (hir_func, func_meta, self.errors) @@ -449,7 +457,7 @@ impl<'a> Resolver<'a> { fn resolve_type_inner(&mut self, typ: UnresolvedType, new_variables: &mut Generics) -> Type { use UnresolvedTypeData::*; - match typ.typ { + let resolved_type = match typ.typ { FieldElement => Type::FieldElement, Array(size, elem) => { let elem = Box::new(self.resolve_type_inner(*elem, new_variables)); @@ -510,7 +518,18 @@ impl<'a> Resolver<'a> { Type::MutableReference(Box::new(self.resolve_type_inner(*element, new_variables))) } Parenthesized(typ) => self.resolve_type_inner(*typ, new_variables), + }; + + if let Type::Struct(_, _) = resolved_type { + if let Some(unresolved_span) = typ.span { + // Record the location of the type reference + self.interner.push_type_ref_location( + resolved_type.clone(), + Location::new(unresolved_span, self.file), + ); + } } + resolved_type } fn find_generic(&self, target_name: &str) -> Option<&(Rc, TypeVariable, Span)> { @@ -714,6 +733,7 @@ impl<'a> Resolver<'a> { if resolved_type.is_nested_slice() { self.errors.push(ResolverError::NestedSlices { span: span.unwrap() }); } + resolved_type } @@ -888,6 +908,13 @@ impl<'a> Resolver<'a> { position: PubPosition::ReturnType, }); } + let is_low_level_function = + func.attributes().function.as_ref().map_or(false, |func| func.is_low_level()); + if !self.path_resolver.module_id().krate.is_stdlib() && is_low_level_function { + let error = + ResolverError::LowLevelFunctionOutsideOfStdlib { ident: func.name_ident().clone() }; + self.push_err(error); + } // 'pub' is required on return types for entry point functions if self.is_entry_point_function(func) @@ -1106,9 +1133,16 @@ impl<'a> Resolver<'a> { }) } StatementKind::Constrain(constrain_stmt) => { + let span = constrain_stmt.0.span; + let assert_msg_call_expr_id = + self.resolve_assert_message(constrain_stmt.1, span, constrain_stmt.0.clone()); let expr_id = self.resolve_expression(constrain_stmt.0); - let assert_message = constrain_stmt.1; - HirStatement::Constrain(HirConstrainStatement(expr_id, self.file, assert_message)) + + HirStatement::Constrain(HirConstrainStatement( + expr_id, + self.file, + assert_msg_call_expr_id, + )) } StatementKind::Expression(expr) => { HirStatement::Expression(self.resolve_expression(expr)) @@ -1157,6 +1191,41 @@ impl<'a> Resolver<'a> { } } + fn resolve_assert_message( + &mut self, + assert_message_expr: Option, + span: Span, + condition: Expression, + ) -> Option { + let mut assert_msg_call_args = if let Some(assert_message_expr) = assert_message_expr { + vec![assert_message_expr.clone()] + } else { + return None; + }; + assert_msg_call_args.push(condition); + + let is_in_stdlib = self.path_resolver.module_id().krate.is_stdlib(); + let assert_msg_call_path = if is_in_stdlib { + ExpressionKind::Variable(Path { + segments: vec![Ident::from("resolve_assert_message")], + kind: PathKind::Crate, + span, + }) + } else { + ExpressionKind::Variable(Path { + segments: vec![Ident::from("std"), Ident::from("resolve_assert_message")], + kind: PathKind::Dep, + span, + }) + }; + let assert_msg_call_expr = Expression::call( + Expression { kind: assert_msg_call_path, span }, + assert_msg_call_args, + span, + ); + Some(self.resolve_expression(assert_msg_call_expr)) + } + pub fn intern_stmt(&mut self, stmt: StatementKind) -> StmtId { let hir_stmt = self.resolve_stmt(stmt); self.interner.push_stmt(hir_stmt) @@ -1438,6 +1507,8 @@ impl<'a> Resolver<'a> { HirExpression::MemberAccess(HirMemberAccess { lhs: self.resolve_expression(access.lhs), rhs: access.rhs, + // This is only used when lhs is a reference and we want to return a reference to rhs + is_offset: false, }) } ExpressionKind::Error => HirExpression::Error, @@ -1734,7 +1805,8 @@ impl<'a> Resolver<'a> { // This resolves a static trait method T::trait_method by iterating over the where clause // - // Returns the trait method, object type, and the trait generics. + // Returns the trait method, trait constraint, and whether the impl is assumed from a where + // clause. This is always true since this helper searches where clauses for a generic constraint. // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` fn resolve_trait_method_by_named_generic( &mut self, @@ -1777,7 +1849,7 @@ impl<'a> Resolver<'a> { // Try to resolve the given trait method path. // - // Returns the trait method, object type, and the trait generics. + // Returns the trait method, trait constraint, and whether the impl is assumed to exist by a where clause or not // E.g. `t.method()` with `where T: Foo` in scope will return `(Foo::method, T, vec![Bar])` fn resolve_trait_generic_path( &mut self, diff --git a/compiler/noirc_frontend/src/hir/type_check/expr.rs b/compiler/noirc_frontend/src/hir/type_check/expr.rs index 58cf4e7b289..5885707a9b7 100644 --- a/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ b/compiler/noirc_frontend/src/hir/type_check/expr.rs @@ -5,8 +5,8 @@ use crate::{ hir::{resolution::resolver::verify_mutable_reference, type_check::errors::Source}, hir_def::{ expr::{ - self, HirArrayLiteral, HirBinaryOp, HirExpression, HirLiteral, HirMethodCallExpression, - HirMethodReference, HirPrefixExpression, ImplKind, + self, HirArrayLiteral, HirBinaryOp, HirExpression, HirIdent, HirLiteral, + HirMethodCallExpression, HirMethodReference, HirPrefixExpression, ImplKind, }, types::Type, }, @@ -46,50 +46,7 @@ impl<'interner> TypeChecker<'interner> { /// function `foo` to refer to. pub(crate) fn check_expression(&mut self, expr_id: &ExprId) -> Type { let typ = match self.interner.expression(expr_id) { - HirExpression::Ident(ident) => { - // An identifiers type may be forall-quantified in the case of generic functions. - // E.g. `fn foo(t: T, field: Field) -> T` has type `forall T. fn(T, Field) -> T`. - // We must instantiate identifiers at every call site to replace this T with a new type - // variable to handle generic functions. - let t = self.interner.id_type_substitute_trait_as_type(ident.id); - - // This instantiate's a trait's generics as well which need to be set - // when the constraint below is later solved for when the function is - // finished. How to link the two? - let (typ, bindings) = t.instantiate(self.interner); - - // Push any trait constraints required by this definition to the context - // to be checked later when the type of this variable is further constrained. - if let Some(definition) = self.interner.try_definition(ident.id) { - if let DefinitionKind::Function(function) = definition.kind { - let function = self.interner.function_meta(&function); - - for mut constraint in function.trait_constraints.clone() { - constraint.apply_bindings(&bindings); - self.trait_constraints.push((constraint, *expr_id)); - } - } - } - - if let ImplKind::TraitMethod(_, mut constraint, assumed) = ident.impl_kind { - constraint.apply_bindings(&bindings); - if assumed { - let trait_impl = TraitImplKind::Assumed { - object_type: constraint.typ, - trait_generics: constraint.trait_generics, - }; - self.interner.select_impl_for_expression(*expr_id, trait_impl); - } else { - // Currently only one impl can be selected per expr_id, so this - // constraint needs to be pushed after any other constraints so - // that monomorphization can resolve this trait method to the correct impl. - self.trait_constraints.push((constraint, *expr_id)); - } - } - - self.interner.store_instantiation_bindings(*expr_id, bindings); - typ - } + HirExpression::Ident(ident) => self.check_ident(ident, expr_id), HirExpression::Literal(literal) => { match literal { HirLiteral::Array(HirArrayLiteral::Standard(arr)) => { @@ -194,74 +151,42 @@ impl<'interner> TypeChecker<'interner> { self.bind_function_type(function, args, span) } HirExpression::MethodCall(mut method_call) => { - let object_type = self.check_expression(&method_call.object).follow_bindings(); + let mut 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_ref) => { - let mut args = vec![( - object_type.clone(), - method_call.object, - self.interner.expr_span(&method_call.object), - )]; - - for arg in &method_call.arguments { - let typ = self.check_expression(arg); - args.push((typ, *arg, self.interner.expr_span(arg))); - } - // Desugar the method call into a normal, resolved function call // so that the backend doesn't need to worry about methods let location = method_call.location; - let trait_id = match &method_ref { - HirMethodReference::FuncId(func_id) => { - // Automatically add `&mut` if the method expects a mutable reference and - // the object is not already one. - if *func_id != FuncId::dummy_id() { - let function_type = - self.interner.function_meta(func_id).typ.clone(); - self.try_add_mutable_reference_to_object( - &mut method_call, - &function_type, - &mut args, - ); - } - - let meta = self.interner.function_meta(func_id); - meta.trait_impl.map(|impl_id| { - self.interner - .get_trait_implementation(impl_id) - .borrow() - .trait_id - }) + // Automatically add `&mut` if the method expects a mutable reference and + // the object is not already one. + if let HirMethodReference::FuncId(func_id) = &method_ref { + if *func_id != FuncId::dummy_id() { + let function_type = + self.interner.function_meta(func_id).typ.clone(); + + self.try_add_mutable_reference_to_object( + &mut method_call, + &function_type, + &mut object_type, + ); } - HirMethodReference::TraitMethodId(method, _) => Some(method.trait_id), - }; + } - let (function_id, function_call) = method_call.into_function_call( + // TODO: update object_type here? + let function_call = method_call.into_function_call( &method_ref, - object_type.clone(), + object_type, location, self.interner, ); - let span = self.interner.expr_span(expr_id); - let ret = self.check_method_call(&function_id, method_ref, args, span); - - if let Some(trait_id) = trait_id { - // Assume no trait generics were specified - // TODO: Fill in type variables - self.verify_trait_constraint( - &object_type, - trait_id, - &[], - function_id, - span, - ); - } - self.interner.replace_expr(expr_id, function_call); - ret + + // Type check the new call now that it has been changed from a method call + // to a function call. This way we avoid duplicating code. + self.check_expression(expr_id) } None => Type::Error, } @@ -341,6 +266,67 @@ impl<'interner> TypeChecker<'interner> { typ } + /// Returns the type of the given identifier + fn check_ident(&mut self, ident: HirIdent, expr_id: &ExprId) -> Type { + let mut bindings = TypeBindings::new(); + + // Add type bindings from any constraints that were used. + // We need to do this first since otherwise instantiating the type below + // will replace each trait generic with a fresh type variable, rather than + // the type used in the trait constraint (if it exists). See #4088. + if let ImplKind::TraitMethod(_, constraint, _) = &ident.impl_kind { + let the_trait = self.interner.get_trait(constraint.trait_id); + assert_eq!(the_trait.generics.len(), constraint.trait_generics.len()); + + for (param, arg) in the_trait.generics.iter().zip(&constraint.trait_generics) { + bindings.insert(param.id(), (param.clone(), arg.clone())); + } + } + + // An identifiers type may be forall-quantified in the case of generic functions. + // E.g. `fn foo(t: T, field: Field) -> T` has type `forall T. fn(T, Field) -> T`. + // We must instantiate identifiers at every call site to replace this T with a new type + // variable to handle generic functions. + let t = self.interner.id_type_substitute_trait_as_type(ident.id); + + // This instantiates a trait's generics as well which need to be set + // when the constraint below is later solved for when the function is + // finished. How to link the two? + let (typ, bindings) = t.instantiate_with_bindings(bindings, self.interner); + + // Push any trait constraints required by this definition to the context + // to be checked later when the type of this variable is further constrained. + if let Some(definition) = self.interner.try_definition(ident.id) { + if let DefinitionKind::Function(function) = definition.kind { + let function = self.interner.function_meta(&function); + + for mut constraint in function.trait_constraints.clone() { + constraint.apply_bindings(&bindings); + self.trait_constraints.push((constraint, *expr_id)); + } + } + } + + if let ImplKind::TraitMethod(_, mut constraint, assumed) = ident.impl_kind { + constraint.apply_bindings(&bindings); + if assumed { + let trait_impl = TraitImplKind::Assumed { + object_type: constraint.typ, + trait_generics: constraint.trait_generics, + }; + self.interner.select_impl_for_expression(*expr_id, trait_impl); + } else { + // Currently only one impl can be selected per expr_id, so this + // constraint needs to be pushed after any other constraints so + // that monomorphization can resolve this trait method to the correct impl. + self.trait_constraints.push((constraint, *expr_id)); + } + } + + self.interner.store_instantiation_bindings(*expr_id, bindings); + typ + } + pub fn verify_trait_constraint( &mut self, object_type: &Type, @@ -390,7 +376,7 @@ impl<'interner> TypeChecker<'interner> { &mut self, method_call: &mut HirMethodCallExpression, function_type: &Type, - argument_types: &mut [(Type, ExprId, noirc_errors::Span)], + object_type: &mut Type, ) { let expected_object_type = match function_type { Type::Function(args, _, _) => args.first(), @@ -402,7 +388,7 @@ impl<'interner> TypeChecker<'interner> { }; if let Some(expected_object_type) = expected_object_type { - let actual_type = argument_types[0].0.follow_bindings(); + let actual_type = object_type.follow_bindings(); if matches!(expected_object_type.follow_bindings(), Type::MutableReference(_)) { if !matches!(actual_type, Type::MutableReference(_)) { @@ -412,34 +398,33 @@ impl<'interner> TypeChecker<'interner> { } let new_type = Type::MutableReference(Box::new(actual_type)); - argument_types[0].0 = new_type.clone(); + *object_type = new_type.clone(); // First try to remove a dereference operator that may have been implicitly // inserted by a field access expression `foo.bar` on a mutable reference `foo`. - if self.try_remove_implicit_dereference(method_call.object).is_none() { - // If that didn't work, then wrap the whole expression in an `&mut` + let new_object = self.try_remove_implicit_dereference(method_call.object); + + // If that didn't work, then wrap the whole expression in an `&mut` + method_call.object = new_object.unwrap_or_else(|| { let location = self.interner.id_location(method_call.object); - method_call.object = + let new_object = self.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { operator: UnaryOp::MutableReference, rhs: method_call.object, })); - self.interner.push_expr_type(&method_call.object, new_type); - self.interner.push_expr_location( - method_call.object, - location.span, - location.file, - ); - } + self.interner.push_expr_type(&new_object, new_type); + self.interner.push_expr_location(new_object, location.span, location.file); + new_object + }); } // Otherwise if the object type is a mutable reference and the method is not, insert as // many dereferences as needed. } else if matches!(actual_type, Type::MutableReference(_)) { let (object, new_type) = self.insert_auto_dereferences(method_call.object, actual_type); + *object_type = new_type; method_call.object = object; - argument_types[0].0 = new_type; } } } @@ -467,29 +452,22 @@ impl<'interner> TypeChecker<'interner> { /// Given a method object: `(*foo).bar` of a method call `(*foo).bar.baz()`, remove the /// implicitly added dereference operator if one is found. /// - /// Returns Some(()) if a dereference was removed and None otherwise. - fn try_remove_implicit_dereference(&mut self, object: ExprId) -> Option<()> { + /// Returns Some(new_expr_id) if a dereference was removed and None otherwise. + fn try_remove_implicit_dereference(&mut self, object: ExprId) -> Option { match self.interner.expression(&object) { - HirExpression::MemberAccess(access) => { - self.try_remove_implicit_dereference(access.lhs)?; - - // Since we removed a dereference, instead of returning the field directly, - // we expect to be returning a reference to the field, so update the type accordingly. - let current_type = self.interner.id_type(object); - let reference_type = Type::MutableReference(Box::new(current_type)); - self.interner.push_expr_type(&object, reference_type); - Some(()) + HirExpression::MemberAccess(mut access) => { + let new_lhs = self.try_remove_implicit_dereference(access.lhs)?; + access.lhs = new_lhs; + access.is_offset = true; + + // `object` will have a different type now, which will be filled in + // later when type checking the method call as a function call. + self.interner.replace_expr(&object, HirExpression::MemberAccess(access)); + Some(object) } HirExpression::Prefix(prefix) => match prefix.operator { - UnaryOp::Dereference { implicitly_added: true } => { - // Found a dereference we can remove. Now just replace it with its rhs to remove it. - let rhs = self.interner.expression(&prefix.rhs); - self.interner.replace_expr(&object, rhs); - - let rhs_type = self.interner.id_type(prefix.rhs); - self.interner.push_expr_type(&object, rhs_type); - Some(()) - } + // Found a dereference we can remove. Now just replace it with its rhs to remove it. + UnaryOp::Dereference { implicitly_added: true } => Some(prefix.rhs), _ => None, }, _ => None, @@ -566,60 +544,6 @@ impl<'interner> TypeChecker<'interner> { } } - // We need a special function to type check method calls since the method - // is not a Expression::Ident it must be manually instantiated here - fn check_method_call( - &mut self, - function_ident_id: &ExprId, - method_ref: HirMethodReference, - arguments: Vec<(Type, ExprId, Span)>, - span: Span, - ) -> Type { - let (fn_typ, param_len, generic_bindings) = match method_ref { - HirMethodReference::FuncId(func_id) => { - if func_id == FuncId::dummy_id() { - return Type::Error; - } - - let func_meta = self.interner.function_meta(&func_id); - let param_len = func_meta.parameters.len(); - (func_meta.typ.clone(), param_len, TypeBindings::new()) - } - HirMethodReference::TraitMethodId(method, generics) => { - let the_trait = self.interner.get_trait(method.trait_id); - let method = &the_trait.methods[method.method_index]; - - // These are any bindings from the trait's generics itself, - // rather than an impl or method's generics. - let generic_bindings = the_trait - .generics - .iter() - .zip(generics) - .map(|(var, arg)| (var.id(), (var.clone(), arg))) - .collect(); - - (method.typ.clone(), method.arguments().len(), generic_bindings) - } - }; - - let arg_len = arguments.len(); - - 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) = - fn_typ.instantiate_with_bindings(generic_bindings, 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.clone(), arguments, span) - } - fn check_if_expr(&mut self, if_expr: &expr::HirIfExpression, expr_id: &ExprId) -> Type { let cond_type = self.check_expression(&if_expr.condition); let then_type = self.check_expression(&if_expr.consequence); @@ -718,6 +642,9 @@ impl<'interner> TypeChecker<'interner> { this.interner.push_expr_location(*access_lhs, span, old_location.file); }; + // If this access is just a field offset, we want to avoid dereferencing + let dereference_lhs = (!access.is_offset).then_some(dereference_lhs); + match self.check_field_access(&lhs_type, &access.rhs.0.contents, span, dereference_lhs) { Some((element_type, index)) => { self.interner.set_field_index(expr_id, index); @@ -742,12 +669,16 @@ impl<'interner> TypeChecker<'interner> { /// expression. The second parameter of this function represents the lhs_type (which should /// always be a Type::MutableReference if `dereference_lhs` is called) and the third /// represents the element type. + /// + /// If `dereference_lhs` is None, this will assume we're taking the offset of a struct field + /// rather than dereferencing it. So the result of `foo.bar` with a `foo : &mut Foo` will + /// be a `&mut Bar` rather than just a `Bar`. pub(super) fn check_field_access( &mut self, lhs_type: &Type, field_name: &str, span: Span, - mut dereference_lhs: impl FnMut(&mut Self, Type, Type), + dereference_lhs: Option, ) -> Option<(Type, usize)> { let lhs_type = lhs_type.follow_bindings(); @@ -777,8 +708,19 @@ impl<'interner> TypeChecker<'interner> { // If the lhs is a mutable reference we automatically transform // lhs.field into (*lhs).field Type::MutableReference(element) => { - dereference_lhs(self, lhs_type.clone(), element.as_ref().clone()); - return self.check_field_access(element, field_name, span, dereference_lhs); + if let Some(mut dereference_lhs) = dereference_lhs { + dereference_lhs(self, lhs_type.clone(), element.as_ref().clone()); + return self.check_field_access( + element, + field_name, + span, + Some(dereference_lhs), + ); + } else { + let (element, index) = + self.check_field_access(element, field_name, span, dereference_lhs)?; + return Some((Type::MutableReference(Box::new(element)), index)); + } } _ => (), } @@ -1010,9 +952,9 @@ impl<'interner> TypeChecker<'interner> { fn bind_function_type_impl( &mut self, - fn_params: &Vec, + fn_params: &[Type], fn_ret: &Type, - callsite_args: &Vec<(Type, ExprId, Span)>, + callsite_args: &[(Type, ExprId, Span)], span: Span, ) -> Type { if fn_params.len() != callsite_args.len() { diff --git a/compiler/noirc_frontend/src/hir/type_check/stmt.rs b/compiler/noirc_frontend/src/hir/type_check/stmt.rs index c4e72f48c50..76bd064bf83 100644 --- a/compiler/noirc_frontend/src/hir/type_check/stmt.rs +++ b/compiler/noirc_frontend/src/hir/type_check/stmt.rs @@ -8,6 +8,7 @@ use crate::hir_def::stmt::{ }; use crate::hir_def::types::Type; use crate::node_interner::{DefinitionId, ExprId, StmtId}; +use crate::UnaryOp; use super::errors::{Source, TypeCheckError}; use super::TypeChecker; @@ -206,24 +207,22 @@ impl<'interner> TypeChecker<'interner> { let object_ref = &mut object; let mutable_ref = &mut mutable; + let dereference_lhs = move |_: &mut Self, _, element_type| { + // We must create a temporary value first to move out of object_ref before + // we eventually reassign to it. + let id = DefinitionId::dummy_id(); + let location = Location::new(span, fm::FileId::dummy()); + let ident = HirIdent::non_trait_method(id, location); + let tmp_value = HirLValue::Ident(ident, Type::Error); + + let lvalue = std::mem::replace(object_ref, Box::new(tmp_value)); + *object_ref = Box::new(HirLValue::Dereference { lvalue, element_type }); + *mutable_ref = true; + }; + + let name = &field_name.0.contents; let (object_type, field_index) = self - .check_field_access( - &lhs_type, - &field_name.0.contents, - span, - move |_, _, element_type| { - // We must create a temporary value first to move out of object_ref before - // we eventually reassign to it. - let id = DefinitionId::dummy_id(); - let location = Location::new(span, fm::FileId::dummy()); - let ident = HirIdent::non_trait_method(id, location); - let tmp_value = HirLValue::Ident(ident, Type::Error); - - let lvalue = std::mem::replace(object_ref, Box::new(tmp_value)); - *object_ref = Box::new(HirLValue::Dereference { lvalue, element_type }); - *mutable_ref = true; - }, - ) + .check_field_access(&lhs_type, name, span, Some(dereference_lhs)) .unwrap_or((Type::Error, 0)); let field_index = Some(field_index); @@ -305,6 +304,9 @@ impl<'interner> TypeChecker<'interner> { let expr_type = self.check_expression(&stmt.0); let expr_span = self.interner.expr_span(&stmt.0); + // Must type check the assertion message expression so that we instantiate bindings + stmt.2.map(|assert_msg_expr| self.check_expression(&assert_msg_expr)); + self.unify(&expr_type, &Type::Bool, || TypeCheckError::TypeMismatch { expr_typ: expr_type.to_string(), expected_typ: Type::Bool.to_string(), @@ -325,6 +327,7 @@ impl<'interner> TypeChecker<'interner> { // Now check if LHS is the same type as the RHS // Importantly, we do not coerce any types implicitly let expr_span = self.interner.expr_span(&rhs_expr); + self.unify_with_coercions(&expr_type, &annotated_type, rhs_expr, || { TypeCheckError::TypeMismatch { expected_typ: annotated_type.to_string(), @@ -335,10 +338,8 @@ impl<'interner> TypeChecker<'interner> { if annotated_type.is_unsigned() { self.lint_overflowing_uint(&rhs_expr, &annotated_type); } - annotated_type - } else { - expr_type } + expr_type } /// Check if an assignment is overflowing with respect to `annotated_type` @@ -361,9 +362,15 @@ impl<'interner> TypeChecker<'interner> { }; }; } - HirExpression::Prefix(_) => self - .errors - .push(TypeCheckError::InvalidUnaryOp { kind: annotated_type.to_string(), span }), + HirExpression::Prefix(expr) => { + self.lint_overflowing_uint(&expr.rhs, annotated_type); + if matches!(expr.operator, UnaryOp::Minus) { + self.errors.push(TypeCheckError::InvalidUnaryOp { + kind: "annotated_type".to_string(), + span, + }); + } + } HirExpression::Infix(expr) => { self.lint_overflowing_uint(&expr.lhs, annotated_type); self.lint_overflowing_uint(&expr.rhs, annotated_type); diff --git a/compiler/noirc_frontend/src/hir_def/expr.rs b/compiler/noirc_frontend/src/hir_def/expr.rs index fe1cd78b5ed..75ed68af0f6 100644 --- a/compiler/noirc_frontend/src/hir_def/expr.rs +++ b/compiler/noirc_frontend/src/hir_def/expr.rs @@ -152,6 +152,12 @@ pub struct HirMemberAccess { // This field is not an IdentId since the rhs of a field // access has no corresponding definition pub rhs: Ident, + + /// True if we should return an offset of the field rather than the field itself. + /// For most cases this is false, corresponding to `foo.bar` in source code. + /// This is true when calling methods or when we have an lvalue we want to preserve such + /// that if `foo : &mut Foo` has a field `bar : Bar`, we can return an `&mut Bar`. + pub is_offset: bool, } #[derive(Debug, Clone)] @@ -201,13 +207,14 @@ pub enum HirMethodReference { } impl HirMethodCallExpression { + /// Converts a method call into a function call pub fn into_function_call( mut self, method: &HirMethodReference, object_type: Type, location: Location, interner: &mut NodeInterner, - ) -> (ExprId, HirExpression) { + ) -> HirExpression { let mut arguments = vec![self.object]; arguments.append(&mut self.arguments); @@ -225,9 +232,10 @@ impl HirMethodCallExpression { (id, ImplKind::TraitMethod(*method_id, constraint, false)) } }; - let expr = HirExpression::Ident(HirIdent { location, id, impl_kind }); - let func = interner.push_expr(expr); - (func, HirExpression::Call(HirCallExpression { func, arguments, location })) + let func = HirExpression::Ident(HirIdent { location, id, impl_kind }); + let func = interner.push_expr(func); + interner.push_expr_location(func, location.span, location.file); + HirExpression::Call(HirCallExpression { func, arguments, location }) } } diff --git a/compiler/noirc_frontend/src/hir_def/stmt.rs b/compiler/noirc_frontend/src/hir_def/stmt.rs index 34c9302c251..4d946690151 100644 --- a/compiler/noirc_frontend/src/hir_def/stmt.rs +++ b/compiler/noirc_frontend/src/hir_def/stmt.rs @@ -55,7 +55,7 @@ pub struct HirAssignStatement { /// originates from. This is used later in the SSA pass to issue /// an error if a constrain is found to be always false. #[derive(Debug, Clone)] -pub struct HirConstrainStatement(pub ExprId, pub FileId, pub Option); +pub struct HirConstrainStatement(pub ExprId, pub FileId, pub Option); #[derive(Debug, Clone, Hash)] pub enum HirPattern { diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index 8979d60c005..0ba4cb2da65 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -17,7 +17,7 @@ use crate::{node_interner::StructId, Ident, Signedness}; use super::expr::{HirCallExpression, HirExpression, HirIdent}; -#[derive(Debug, PartialEq, Eq, Clone, Hash)] +#[derive(PartialEq, Eq, Clone, Hash)] pub enum Type { /// A primitive Field type FieldElement, @@ -64,7 +64,7 @@ pub enum Type { TypeVariable(TypeVariable, TypeVariableKind), /// `impl Trait` when used in a type position. - /// These are only matched based on the TraitId. The trait name paramer is only + /// These are only matched based on the TraitId. The trait name parameter is only /// used for displaying error messages using the name of the trait. TraitAsType(TraitId, /*name:*/ Rc, /*generics:*/ Vec), @@ -190,7 +190,7 @@ pub type TypeBindings = HashMap; /// Represents a struct type in the type system. Each instance of this /// rust struct will be shared across all Type::Struct variants that represent /// the same struct type. -#[derive(Debug, Eq)] +#[derive(Eq)] pub struct StructType { /// A unique id representing this struct type. Used to check if two /// struct types are equal. @@ -449,7 +449,7 @@ pub enum TypeVariableKind { /// A TypeVariable is a mutable reference that is either /// bound to some type, or unbound with a given TypeVariableId. -#[derive(Debug, PartialEq, Eq, Clone, Hash)] +#[derive(PartialEq, Eq, Clone, Hash)] pub struct TypeVariable(TypeVariableId, Shared); impl TypeVariable { @@ -516,7 +516,7 @@ impl TypeVariable { /// TypeBindings are the mutable insides of a TypeVariable. /// They are either bound to some type, or are unbound. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Clone, PartialEq, Eq, Hash)] pub enum TypeBinding { Bound(Type), Unbound(TypeVariableId), @@ -529,7 +529,7 @@ impl TypeBinding { } /// A unique ID used to differentiate different type variables -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct TypeVariableId(pub usize); impl Type { @@ -804,7 +804,7 @@ impl std::fmt::Display for Type { Type::Function(args, ret, env) => { let closure_env_text = match **env { Type::Unit => "".to_string(), - _ => format!(" with closure environment {env}"), + _ => format!(" with env {env}"), }; let args = vecmap(args.iter(), ToString::to_string); @@ -1661,3 +1661,101 @@ impl From<&Type> for PrintableType { } } } + +impl std::fmt::Debug for Type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Type::FieldElement => { + write!(f, "Field") + } + Type::Array(len, typ) => { + if matches!(len.follow_bindings(), Type::NotConstant) { + write!(f, "[{typ:?}]") + } else { + write!(f, "[{typ:?}; {len:?}]") + } + } + Type::Integer(sign, num_bits) => match sign { + Signedness::Signed => write!(f, "i{num_bits}"), + Signedness::Unsigned => write!(f, "u{num_bits}"), + }, + Type::TypeVariable(var, TypeVariableKind::Normal) => write!(f, "{:?}", var), + Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { + write!(f, "IntOrField{:?}", binding) + } + Type::TypeVariable(binding, TypeVariableKind::Constant(n)) => { + write!(f, "{}{:?}", n, binding) + } + Type::Struct(s, args) => { + let args = vecmap(args, |arg| format!("{:?}", arg)); + if args.is_empty() { + write!(f, "{:?}", s.borrow()) + } else { + write!(f, "{:?}<{}>", s.borrow(), args.join(", ")) + } + } + Type::TraitAsType(_id, name, generics) => { + write!(f, "impl {}", name)?; + if !generics.is_empty() { + let generics = vecmap(generics, |arg| format!("{:?}", arg)).join(", "); + write!(f, "<{generics}>")?; + } + Ok(()) + } + Type::Tuple(elements) => { + let elements = vecmap(elements, |arg| format!("{:?}", arg)); + write!(f, "({})", elements.join(", ")) + } + Type::Bool => write!(f, "bool"), + Type::String(len) => write!(f, "str<{len:?}>"), + Type::FmtString(len, elements) => { + write!(f, "fmtstr<{len:?}, {elements:?}>") + } + Type::Unit => write!(f, "()"), + Type::Error => write!(f, "error"), + Type::NamedGeneric(binding, name) => write!(f, "{}{:?}", name, binding), + Type::Constant(x) => x.fmt(f), + Type::Forall(typevars, typ) => { + let typevars = vecmap(typevars, |var| format!("{:?}", var)); + write!(f, "forall {}. {:?}", typevars.join(" "), typ) + } + Type::Function(args, ret, env) => { + let closure_env_text = match **env { + Type::Unit => "".to_string(), + _ => format!(" with env {env:?}"), + }; + + let args = vecmap(args.iter(), |arg| format!("{:?}", arg)); + + write!(f, "fn({}) -> {ret:?}{closure_env_text}", args.join(", ")) + } + Type::MutableReference(element) => { + write!(f, "&mut {element:?}") + } + Type::NotConstant => write!(f, "NotConstant"), + } + } +} + +impl std::fmt::Debug for TypeVariableId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "'{}", self.0) + } +} + +impl std::fmt::Debug for TypeVariable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.id())?; + + if let TypeBinding::Bound(typ) = &*self.borrow() { + write!(f, " -> {typ:?}")?; + } + Ok(()) + } +} + +impl std::fmt::Debug for StructType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index ab131ccd880..835a0baae3f 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -562,6 +562,10 @@ impl FunctionAttribute { matches!(self, FunctionAttribute::Foreign(_)) } + pub fn is_oracle(&self) -> bool { + matches!(self, FunctionAttribute::Oracle(_)) + } + pub fn is_low_level(&self) -> bool { matches!(self, FunctionAttribute::Foreign(_) | FunctionAttribute::Builtin(_)) } diff --git a/compiler/noirc_frontend/src/lib.rs b/compiler/noirc_frontend/src/lib.rs index 9582b80dcba..b14cb632f81 100644 --- a/compiler/noirc_frontend/src/lib.rs +++ b/compiler/noirc_frontend/src/lib.rs @@ -48,7 +48,7 @@ pub mod macros_api { pub use crate::hir_def::expr::{HirExpression, HirLiteral}; pub use crate::hir_def::stmt::HirStatement; pub use crate::node_interner::{NodeInterner, StructId}; - pub use crate::parser::SortedModule; + pub use crate::parser::{parse_program, SortedModule}; pub use crate::token::SecondaryAttribute; pub use crate::hir::def_map::ModuleDefId; @@ -75,6 +75,10 @@ pub mod macros_api { ) -> Result; /// Function to manipulate the AST after type checking has been completed. /// The AST after type checking has been done is called the HIR. - fn process_typed_ast(&self, crate_id: &CrateId, context: &mut HirContext); + fn process_typed_ast( + &self, + crate_id: &CrateId, + context: &mut HirContext, + ) -> Result<(), (MacroError, FileId)>; } } diff --git a/compiler/noirc_frontend/src/monomorphization/ast.rs b/compiler/noirc_frontend/src/monomorphization/ast.rs index 42a618e7d77..5172eabde46 100644 --- a/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -31,7 +31,7 @@ pub enum Expression { ExtractTupleField(Box, usize), Call(Call), Let(Let), - Constrain(Box, Location, Option), + Constrain(Box, Location, Option>), Assign(Assign), Semi(Box), } diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 67b246a02ce..7e06b94ca5a 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -479,7 +479,9 @@ impl<'interner> Monomorphizer<'interner> { HirStatement::Constrain(constrain) => { let expr = self.expr(constrain.0); let location = self.interner.expr_location(&constrain.0); - ast::Expression::Constrain(Box::new(expr), location, constrain.2) + let assert_message = + constrain.2.map(|assert_msg_expr| Box::new(self.expr(assert_msg_expr))); + ast::Expression::Constrain(Box::new(expr), location, assert_message) } HirStatement::Assign(assign) => self.assign(assign), HirStatement::For(for_loop) => { @@ -929,6 +931,9 @@ impl<'interner> Monomorphizer<'interner> { // The first argument to the `print` oracle is a bool, indicating a newline to be inserted at the end of the input // The second argument is expected to always be an ident self.append_printable_type_info(&hir_arguments[1], &mut arguments); + } else if name.as_str() == "assert_message" { + // The first argument to the `assert_message` oracle is the expression passed as a mesage to an `assert` or `assert_eq` statement + self.append_printable_type_info(&hir_arguments[0], &mut arguments); } } } @@ -1024,7 +1029,7 @@ impl<'interner> Monomorphizer<'interner> { // The caller needs information as to whether it is handling a format string or a single type arguments.push(ast::Expression::Literal(ast::Literal::Bool(is_fmt_str))); } - _ => unreachable!("logging expr {:?} is not supported", arguments[0]), + _ => unreachable!("logging expr {:?} is not supported", hir_argument), } } @@ -1033,10 +1038,10 @@ impl<'interner> Monomorphizer<'interner> { // since they cannot be passed from ACIR into Brillig if let HirType::Array(size, _) = typ { if let HirType::NotConstant = **size { - unreachable!("println does not support slices. Convert the slice to an array before passing it to println"); + unreachable!("println and format strings do not support slices. Convert the slice to an array before passing it to println"); } } else if matches!(typ, HirType::MutableReference(_)) { - unreachable!("println does not support mutable references."); + unreachable!("println and format strings do not support mutable references."); } let printable_type: PrintableType = typ.into(); diff --git a/compiler/noirc_frontend/src/monomorphization/printer.rs b/compiler/noirc_frontend/src/monomorphization/printer.rs index e79330de6f8..7aec2193494 100644 --- a/compiler/noirc_frontend/src/monomorphization/printer.rs +++ b/compiler/noirc_frontend/src/monomorphization/printer.rs @@ -30,7 +30,9 @@ impl AstPrinter { pub fn print_expr(&mut self, expr: &Expression, f: &mut Formatter) -> std::fmt::Result { match expr { - Expression::Ident(ident) => write!(f, "{}${}", ident.name, ident.definition), + Expression::Ident(ident) => { + write!(f, "{}${}", ident.name, ident.definition) + } Expression::Literal(literal) => self.print_literal(literal, f), Expression::Block(exprs) => self.print_block(exprs, f), Expression::Unary(unary) => self.print_unary(unary, f), diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index e734161e360..b856b54f6ca 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -146,6 +146,9 @@ pub struct NodeInterner { /// A list of all type aliases that are referenced in the program. /// Searched by LSP to resolve [Location]s of [TypeAliasType]s pub(crate) type_alias_ref: Vec<(TypeAliasId, Location)>, + + /// Stores the [Location] of a [Type] reference + pub(crate) type_ref_locations: Vec<(Type, Location)>, } /// A trait implementation is either a normal implementation that is present in the source @@ -455,6 +458,7 @@ impl Default for NodeInterner { struct_methods: HashMap::new(), primitive_methods: HashMap::new(), type_alias_ref: Vec::new(), + type_ref_locations: Vec::new(), }; // An empty block expression is used often, we add this into the `node` on startup @@ -607,6 +611,11 @@ impl NodeInterner { self.id_to_type.insert(definition_id.into(), typ); } + /// Store [Location] of [Type] reference + pub fn push_type_ref_location(&mut self, typ: Type, location: Location) { + self.type_ref_locations.push((typ, location)); + } + pub fn push_global(&mut self, stmt_id: StmtId, ident: Ident, local_id: LocalModuleId) { self.globals.insert(stmt_id, GlobalInfo { ident, local_id }); } @@ -1186,7 +1195,6 @@ impl NodeInterner { } /// Adds a trait implementation to the list of known implementations. - #[tracing::instrument(skip(self))] pub fn add_trait_implementation( &mut self, object_type: Type, diff --git a/compiler/noirc_frontend/src/parser/errors.rs b/compiler/noirc_frontend/src/parser/errors.rs index 5c869ff4719..9158c68db72 100644 --- a/compiler/noirc_frontend/src/parser/errors.rs +++ b/compiler/noirc_frontend/src/parser/errors.rs @@ -40,6 +40,8 @@ pub enum ParserErrorReason { NoFunctionAttributesAllowedOnStruct, #[error("Assert statements can only accept string literals")] AssertMessageNotString, + #[error("Integer bit size {0} won't be supported")] + DeprecatedBitSize(u32), #[error("{0}")] Lexer(LexerErrorKind), } @@ -130,6 +132,8 @@ impl std::fmt::Display for ParserError { } } +pub(crate) static ALLOWED_INTEGER_BIT_SIZES: &[u32] = &[1, 8, 32, 64]; + impl From for Diagnostic { fn from(error: ParserError) -> Diagnostic { match error.reason { @@ -145,6 +149,11 @@ impl From for Diagnostic { "The 'comptime' keyword has been deprecated. It can be removed without affecting your program".into(), error.span, ), + ParserErrorReason::DeprecatedBitSize(bit_size) => Diagnostic::simple_warning( + format!("Use of deprecated bit size {}", bit_size), + format!("Bit sizes for integers will be restricted to {}", ALLOWED_INTEGER_BIT_SIZES.iter().map(|n| n.to_string()).collect::>().join(", ")), + 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 cdfdc570949..1f41a29dd4f 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -23,6 +23,7 @@ //! prevent other parsers from being tried afterward since there is no longer an error. Thus, they should //! be limited to cases like the above `fn` example where it is clear we shouldn't back out of the //! current parser to try alternative parsers in a `choice` expression. +use super::errors::ALLOWED_INTEGER_BIT_SIZES; use super::{ foldl_with_span, labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, parenthesized, then_commit, then_commit_ignore, top_level_statement_recovery, ExprParser, @@ -35,7 +36,7 @@ use crate::ast::{ }; use crate::lexer::Lexer; use crate::parser::{force, ignore_then_commit, statement_recovery}; -use crate::token::{Attribute, Attributes, Keyword, SecondaryAttribute, Token, TokenKind}; +use crate::token::{Attribute, Attributes, IntType, Keyword, SecondaryAttribute, Token, TokenKind}; use crate::{ BinaryOp, BinaryOpKind, BlockExpression, ConstrainKind, ConstrainStatement, Distinctness, ForLoopStatement, ForRange, FunctionDefinition, FunctionReturnType, FunctionVisibility, Ident, @@ -61,12 +62,12 @@ pub fn parse_program(source_program: &str) -> (ParsedModule, Vec) { parsing_errors.extend(lexing_errors.into_iter().map(Into::into)); - (module.unwrap(), parsing_errors) + (module.unwrap_or(ParsedModule { items: vec![] }), parsing_errors) } /// program: module EOF fn program() -> impl NoirParser { - module().then_ignore(force(just(Token::EOF))) + module().then_ignore(just(Token::EOF)) } /// module: top_level_statement module @@ -74,7 +75,7 @@ fn program() -> impl NoirParser { fn module() -> impl NoirParser { recursive(|module_parser| { empty() - .map(|_| ParsedModule::default()) + .to(ParsedModule::default()) .then(spanned(top_level_statement(module_parser)).repeated()) .foldl(|mut program, (statement, span)| { let mut push_item = |kind| program.items.push(Item { kind, span }); @@ -164,7 +165,6 @@ fn contract(module_parser: impl NoirParser) -> impl NoirParser impl NoirParser { attributes() - .or_not() .then(function_modifiers()) .then_ignore(keyword(Keyword::Fn)) .then(ident()) @@ -224,6 +224,7 @@ fn function_modifiers() -> impl NoirParser<(bool, bool, bool, bool, bool)> { ) }) } + fn is_pub_crate() -> impl NoirParser { (keyword(Keyword::Pub) .then_ignore(just(Token::LeftParen)) @@ -260,18 +261,14 @@ fn struct_definition() -> impl NoirParser { [(LeftParen, RightParen), (LeftBracket, RightBracket)], |_| vec![], )) - .or(just(Semicolon).map(|_| Vec::new())); + .or(just(Semicolon).to(Vec::new())); - attributes() - .or_not() - .then_ignore(keyword(Struct)) - .then(ident()) - .then(generics()) - .then(fields) - .validate(|(((raw_attributes, name), generics), fields), span, emit| { + attributes().then_ignore(keyword(Struct)).then(ident()).then(generics()).then(fields).validate( + |(((raw_attributes, name), generics), fields), span, emit| { let attributes = validate_struct_attributes(raw_attributes, span, emit); TopLevelStatement::Struct(NoirStruct { name, attributes, generics, fields, span }) - }) + }, + ) } fn type_alias_definition() -> impl NoirParser { @@ -448,7 +445,7 @@ fn trait_constant_declaration() -> impl NoirParser { /// trait_function_declaration: 'fn' ident generics '(' declaration_parameters ')' function_return_type fn trait_function_declaration() -> impl NoirParser { let trait_function_body_or_semicolon = - block(fresh_statement()).map(Option::from).or(just(Token::Semicolon).map(|_| Option::None)); + block(fresh_statement()).map(Option::from).or(just(Token::Semicolon).to(Option::None)); keyword(Keyword::Fn) .ignore_then(ident()) @@ -463,20 +460,14 @@ fn trait_function_declaration() -> impl NoirParser { } fn validate_attributes( - attributes: Option>, + attributes: Vec, span: Span, emit: &mut dyn FnMut(ParserError), ) -> Attributes { - if attributes.is_none() { - return Attributes::empty(); - } - - let attrs = attributes.unwrap(); - let mut primary = None; let mut secondary = Vec::new(); - for attribute in attrs { + for attribute in attributes { match attribute { Attribute::Function(attr) => { if primary.is_some() { @@ -495,14 +486,13 @@ fn validate_attributes( } fn validate_struct_attributes( - attributes: Option>, + attributes: Vec, span: Span, emit: &mut dyn FnMut(ParserError), ) -> Vec { - let attrs = attributes.unwrap_or_default(); let mut struct_attributes = vec![]; - for attribute in attrs { + for attribute in attributes { match attribute { Attribute::Function(..) => { emit(ParserError::with_reason( @@ -843,23 +833,10 @@ where ignore_then_commit(keyword(Keyword::Assert), parenthesized(argument_parser)) .labelled(ParsingRuleLabel::Statement) - .validate(|expressions, span, emit| { + .validate(|expressions, span, _| { let condition = expressions.get(0).unwrap_or(&Expression::error(span)).clone(); - let mut message_str = None; - - if let Some(message) = expressions.get(1) { - if let ExpressionKind::Literal(Literal::Str(message)) = &message.kind { - message_str = Some(message.clone()); - } else { - emit(ParserError::with_reason(ParserErrorReason::AssertMessageNotString, span)); - } - } - - StatementKind::Constrain(ConstrainStatement( - condition, - message_str, - ConstrainKind::Assert, - )) + let message = expressions.get(1).cloned(); + StatementKind::Constrain(ConstrainStatement(condition, message, ConstrainKind::Assert)) }) } @@ -872,7 +849,7 @@ where ignore_then_commit(keyword(Keyword::AssertEq), parenthesized(argument_parser)) .labelled(ParsingRuleLabel::Statement) - .validate(|exprs: Vec, span, emit| { + .validate(|exprs: Vec, span, _| { let predicate = Expression::new( ExpressionKind::Infix(Box::new(InfixExpression { lhs: exprs.get(0).unwrap_or(&Expression::error(span)).clone(), @@ -881,18 +858,10 @@ where })), span, ); - let mut message_str = None; - - if let Some(message) = exprs.get(2) { - if let ExpressionKind::Literal(Literal::Str(message)) = &message.kind { - message_str = Some(message.clone()); - } else { - emit(ParserError::with_reason(ParserErrorReason::AssertMessageNotString, span)); - } - } + let message = exprs.get(2).cloned(); StatementKind::Constrain(ConstrainStatement( predicate, - message_str, + message, ConstrainKind::AssertEq, )) }) @@ -976,7 +945,7 @@ fn assign_operator() -> impl NoirParser { // Since >> is lexed as two separate "greater-than"s, >>= is lexed as > >=, so // we need to account for that case here as well. let right_shift_fix = - just(Token::Greater).then(just(Token::GreaterEqual)).map(|_| Token::ShiftRight); + just(Token::Greater).then(just(Token::GreaterEqual)).to(Token::ShiftRight); let shorthand_syntax = shorthand_syntax.or(right_shift_fix); just(Token::Assign).or(shorthand_syntax) @@ -1124,6 +1093,18 @@ fn int_type() -> impl NoirParser { Err(ParserError::expected_label(ParsingRuleLabel::IntegerType, unexpected, span)) } })) + .validate(|int_type, span, emit| { + let bit_size = match int_type.1 { + IntType::Signed(bit_size) | IntType::Unsigned(bit_size) => bit_size, + }; + if !ALLOWED_INTEGER_BIT_SIZES.contains(&bit_size) { + emit(ParserError::with_reason( + ParserErrorReason::DeprecatedBitSize(bit_size), + span, + )); + } + int_type + }) .map_with_span(|(_, token), span| UnresolvedTypeData::from_int_token(token).with_span(span)) } @@ -1337,7 +1318,7 @@ fn create_infix_expression(lhs: Expression, (operator, rhs): (BinaryOp, Expressi // to parse nested generic types. For normal expressions however, it means we have to manually // parse two greater-than tokens as a single right-shift here. fn right_shift_operator() -> impl NoirParser { - just(Token::Greater).then(just(Token::Greater)).map(|_| Token::ShiftRight) + just(Token::Greater).then(just(Token::Greater)).to(Token::ShiftRight) } fn operator_with_precedence(precedence: Precedence) -> impl NoirParser> { @@ -2103,7 +2084,13 @@ mod test { match parse_with(assertion(expression()), "assert(x == y, \"assertion message\")").unwrap() { StatementKind::Constrain(ConstrainStatement(_, message, _)) => { - assert_eq!(message, Some("assertion message".to_owned())); + let message = message.unwrap(); + match message.kind { + ExpressionKind::Literal(Literal::Str(message_string)) => { + assert_eq!(message_string, "assertion message".to_owned()); + } + _ => unreachable!(), + } } _ => unreachable!(), } @@ -2127,7 +2114,13 @@ mod test { .unwrap() { StatementKind::Constrain(ConstrainStatement(_, message, _)) => { - assert_eq!(message, Some("assertion message".to_owned())); + let message = message.unwrap(); + match message.kind { + ExpressionKind::Literal(Literal::Str(message_string)) => { + assert_eq!(message_string, "assertion message".to_owned()); + } + _ => unreachable!(), + } } _ => unreachable!(), } @@ -2494,7 +2487,7 @@ mod test { Case { source: "assert(x == x, x)", expect: "constrain (plain::x == plain::x)", - errors: 1, + errors: 0, }, Case { source: "assert_eq(x,)", expect: "constrain (Error == Error)", errors: 1 }, Case { @@ -2505,7 +2498,7 @@ mod test { Case { source: "assert_eq(x, x, x)", expect: "constrain (plain::x == plain::x)", - errors: 1, + errors: 0, }, ]; diff --git a/compiler/noirc_frontend/src/resolve_locations.rs b/compiler/noirc_frontend/src/resolve_locations.rs index 02325de4da8..cfb88966b9d 100644 --- a/compiler/noirc_frontend/src/resolve_locations.rs +++ b/compiler/noirc_frontend/src/resolve_locations.rs @@ -42,6 +42,7 @@ impl NodeInterner { .and_then(|index| self.resolve_location(index, return_type_location_instead)) .or_else(|| self.try_resolve_trait_impl_location(location)) .or_else(|| self.try_resolve_trait_method_declaration(location)) + .or_else(|| self.try_resolve_type_ref(location)) .or_else(|| self.try_resolve_type_alias(location)) } @@ -196,7 +197,17 @@ impl NodeInterner { }) } - #[tracing::instrument(skip(self), ret)] + /// Attempts to resolve [Location] of [Type] based on [Location] of reference in code + pub(crate) fn try_resolve_type_ref(&self, location: Location) -> Option { + self.type_ref_locations + .iter() + .find(|(_typ, type_ref_location)| type_ref_location.contains(&location)) + .and_then(|(typ, _)| match typ { + Type::Struct(struct_typ, _) => Some(struct_typ.borrow().location), + _ => None, + }) + } + fn try_resolve_type_alias(&self, location: Location) -> Option { self.type_alias_ref .iter() diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 9ccbddab9ec..a4246a9fe7d 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -52,10 +52,12 @@ mod test { ) -> (ParsedModule, Context, Vec<(CompilationError, FileId)>) { let root = std::path::Path::new("/"); let fm = FileManager::new(root); + let mut context = Context::new(fm, Default::default()); context.def_interner.populate_dummy_operator_traits(); let root_file_id = FileId::dummy(); let root_crate_id = context.crate_graph.add_crate_root(root_file_id); + let (program, parser_errors) = parse_program(src); let mut errors = vecmap(parser_errors, |e| (e.into(), root_file_id)); remove_experimental_warnings(&mut errors); diff --git a/compiler/noirc_printable_type/src/lib.rs b/compiler/noirc_printable_type/src/lib.rs index 18f2fe0a873..eb206f0af3d 100644 --- a/compiler/noirc_printable_type/src/lib.rs +++ b/compiler/noirc_printable_type/src/lib.rs @@ -69,6 +69,9 @@ pub enum ForeignCallError { #[error("Failed calling external resolver. {0}")] ExternalResolverError(#[from] jsonrpc::Error), + + #[error("Assert message resolved after an unsatisified constrain. {0}")] + ResolvedAssertMessage(String), } impl TryFrom<&[ForeignCallParam]> for PrintableValueDisplay { diff --git a/compiler/wasm/package.json b/compiler/wasm/package.json index 412e9c82f9a..2aaf4a494df 100644 --- a/compiler/wasm/package.json +++ b/compiler/wasm/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.22.0", + "version": "0.23.0", "license": "(MIT OR Apache-2.0)", "main": "dist/main.js", "types": "./dist/types/src/index.d.cts", diff --git a/compiler/wasm/src/compile.rs b/compiler/wasm/src/compile.rs index 498ffe447ce..b39a27a7931 100644 --- a/compiler/wasm/src/compile.rs +++ b/compiler/wasm/src/compile.rs @@ -190,7 +190,8 @@ pub fn compile( })? .0; - let optimized_contract = nargo::ops::optimize_contract(compiled_contract, expression_width); + let optimized_contract = + nargo::ops::transform_contract(compiled_contract, expression_width); let compile_output = generate_contract_artifact(optimized_contract); Ok(JsCompileResult::new(compile_output)) @@ -205,7 +206,7 @@ pub fn compile( })? .0; - let optimized_program = nargo::ops::optimize_program(compiled_program, expression_width); + let optimized_program = nargo::ops::transform_program(compiled_program, expression_width); let compile_output = generate_program_artifact(optimized_program); Ok(JsCompileResult::new(compile_output)) diff --git a/compiler/wasm/src/compile_new.rs b/compiler/wasm/src/compile_new.rs index 6476f6d29bc..4616004ae2b 100644 --- a/compiler/wasm/src/compile_new.rs +++ b/compiler/wasm/src/compile_new.rs @@ -109,7 +109,7 @@ impl CompilerContext { })? .0; - let optimized_program = nargo::ops::optimize_program(compiled_program, np_language); + let optimized_program = nargo::ops::transform_program(compiled_program, np_language); let compile_output = generate_program_artifact(optimized_program); Ok(JsCompileResult::new(compile_output)) @@ -134,7 +134,7 @@ impl CompilerContext { })? .0; - let optimized_contract = nargo::ops::optimize_contract(compiled_contract, np_language); + let optimized_contract = nargo::ops::transform_contract(compiled_contract, np_language); let compile_output = generate_contract_artifact(optimized_contract); Ok(JsCompileResult::new(compile_output)) diff --git a/cspell.json b/cspell.json index 0547b956d72..12b1e3f63d3 100644 --- a/cspell.json +++ b/cspell.json @@ -20,6 +20,8 @@ "bindgen", "bitand", "blackbox", + "boilerplate", + "boilerplates", "bridgekeeper", "brillig", "bytecount", @@ -56,6 +58,7 @@ "defunctionalized", "deque", "desugared", + "devcontainer", "direnv", "eddsa", "Elligator", @@ -96,6 +99,7 @@ "keccak", "keccakf", "krate", + "losslessly", "lvalue", "Maddiaa", "mathbb", @@ -121,6 +125,7 @@ "noirup", "nomicfoundation", "noncanonical", + "nouner", "pedersen", "peekable", "plonkc", @@ -172,9 +177,7 @@ "wasi", "wasmer", "Weierstraß", - "zshell", - "nouner", - "devcontainer" + "zshell" ], "ignorePaths": [ "./**/node_modules/**", diff --git a/docs/.gitignore b/docs/.gitignore index 4f6eee8284e..501e7e465ea 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -3,6 +3,8 @@ # Production /build +processed-docs +processed-docs-cache # Generated files .docusaurus diff --git a/docs/README.md b/docs/README.md index 92f6c5f41f7..c1d2bbd6d4e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,7 +2,7 @@ This is the source code for the Noir documentation site at [noir-lang.org](https://noir-lang.org). -This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website +This website is built using [Docusaurus 3](https://docusaurus.io/), a modern static website generator. ## Contributing @@ -15,14 +15,20 @@ Check out the contributing guide [here](../CONTRIBUTING.md). ### Installation +This project requires recent versions of rust and cargo to be installed. +Any build errors should indicate dependencies that need installing, and at what version. + +On the root folder of the repository, run: + ``` yarn +yarn build ``` ### Local Development ``` -yarn start +yarn workspace docs start ``` This command starts a local development server and opens up a browser window. Most changes are @@ -31,8 +37,12 @@ reflected live without having to restart the server. ### Build ``` -yarn build +yarn workspace docs build ``` This command generates static content into the `build` directory and can be served using any static -contents hosting service. +contents hosting service. You can see a preview by running: + +``` +yarn workspace docs serve +``` diff --git a/docs/docs/explainers/explainer-recursion.md b/docs/docs/explainers/explainer-recursion.md index 8f992ec29fd..18846176ca7 100644 --- a/docs/docs/explainers/explainer-recursion.md +++ b/docs/docs/explainers/explainer-recursion.md @@ -16,12 +16,13 @@ keywords: "Optimizing Computational Resources", "Improving Efficiency", "Verification Key", - "Aggregation Objects", + "Aggregation", "Recursive zkSNARK schemes", "PLONK", "Proving and Verification Keys" ] sidebar_position: 1 +pagination_next: how_to/how-to-recursion --- In programming, we tend to think of recursion as something calling itself. A classic example would be the calculation of the factorial of a number: @@ -64,7 +65,7 @@ So, they use zero-knowledge proofs. Alice tries to guess Bob's number, and Bob w This ZK proof can go on a smart contract, revealing the winner and even giving prizes. However, this means every turn needs to be verified on-chain. This incurs some cost and waiting time that may simply make the game too expensive or time-consuming to be worth it. -As a solution, Alice proposes the following: "what if Bob generates his proof, and instead of sending it on-chain, I verify it *within* my own proof before playing my own turn?". +As a solution, Alice proposes the following: "what if Bob generates his proof, and instead of sending it on-chain, I verify it *within* my own proof before playing my own turn?". She can then generate a proof that she verified his proof, and so on. @@ -116,25 +117,19 @@ As you can see in the [recursion reference](noir/standard_library/recursion.md), - The Verification Key of the circuit that generated the proof - A hash of this verification key, as it's needed for some backends - The public inputs for the proof -- The input aggregation object -It also returns the `output aggregation object`. These aggregation objects can be confusing at times, so let's dive in a little bit. - -### Aggregation objects +:::info Recursive zkSNARK schemes do not necessarily "verify a proof" in the sense that you expect a true or false to be spit out by the verifier. Rather an aggregation object is built over the public inputs. -In the case of PLONK the recursive aggregation object is two G1 points (expressed as 16 witness values). The final verifier (in our case this is most often the smart contract verifier) has to be aware of this aggregation object to execute a pairing and check the validity of these points. - So, taking the example of Alice and Bob and their guessing game: - Alice makes her guess. Her proof is *not* recursive: it doesn't verify any proof within it! It's just a standard `assert(x != y)` circuit -- Bob verifies Alice's proof and makes his own guess. In this circuit, he is verifying a proof, so it needs to output an `aggregation object`: he is generating a recursive proof! -- Alice verifies Bob's *recursive proof*, and uses Bob's `output aggregation object` as the `input aggregation object` in her proof... Which in turn, generates another `output aggregation object`. +- Bob verifies Alice's proof and makes his own guess. In this circuit, he doesn't exactly *prove* the verification of Alice's proof. Instead, he *aggregates* his proof to Alice's proof. The actual verification is done when the full proof is verified, for example when using `nargo verify` or through the verifier smart contract. -One should notice that when Bob generates his first proof, he has no input aggregation object. Because he is not verifying an recursive proof, he has no `input aggregation object`. In this case, he may use zeros instead. +We can imagine recursive proofs a [relay race](https://en.wikipedia.org/wiki/Relay_race). The first runner doesn't have to receive the baton from anyone else, as he/she already starts with it. But when his/her turn is over, the next runner needs to receive it, run a bit more, and pass it along. Even though every runner could theoretically verify the baton mid-run (why not? 🏃🔍), only at the end of the race does the referee verify that the whole race is valid. -We can imagine the `aggregation object` as the baton in a [relay race](https://en.wikipedia.org/wiki/Relay_race). The first runner doesn't have to receive the baton from anyone else, as he/she already starts with it. But when his/her turn is over, the next runner needs to receive it, run a bit more, and pass it along. Even though every runner could theoretically verify the baton mid-run (why not? 🏃🔍), only at the end of the race does the referee verify that the whole race is valid. +::: ## Some architecture @@ -175,3 +170,7 @@ In this example, a regulator could verify that taxes were paid for a specific pu At the time of writing, verifying recursive proofs is surprisingly fast. This is because most of the time is spent on generating the verification key that will be used to generate the next proof. So you are able to cache the verification key and reuse it later. Currently, Noir JS packages don't expose the functionality of loading proving and verification keys, but that feature exists in the underlying `bb.js` package. + +## How can I try it + +Learn more about using recursion in Nargo and NoirJS in the [how-to guide](../how_to/how-to-recursion.md) and see a full example in [noir-examples](https://github.com/noir-lang/noir-examples). diff --git a/docs/docs/getting_started/hello_noir/_category_.json b/docs/docs/getting_started/hello_noir/_category_.json new file mode 100644 index 00000000000..23b560f610b --- /dev/null +++ b/docs/docs/getting_started/hello_noir/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 1, + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/getting_started/create_a_project.md b/docs/docs/getting_started/hello_noir/index.md similarity index 92% rename from docs/docs/getting_started/create_a_project.md rename to docs/docs/getting_started/hello_noir/index.md index 26ff265c389..743c4d8d634 100644 --- a/docs/docs/getting_started/create_a_project.md +++ b/docs/docs/getting_started/hello_noir/index.md @@ -1,5 +1,5 @@ --- -title: Creating A Project +title: Creating a Project description: Learn how to create and verify your first Noir program using Nargo, a programming language for zero-knowledge proofs. @@ -48,7 +48,7 @@ nargo new hello_world > `test`). A `hello_world` folder would be created. Similar to Rust, the folder houses _src/main.nr_ and -_Nargo.toml_ that contains the source code and environmental options of your Noir program +_Nargo.toml_ which contain the source code and environmental options of your Noir program respectively. ### Intro to Noir Syntax @@ -69,7 +69,7 @@ x : Field, y : pub Field Program inputs in Noir are private by default (e.g. `x`), but can be labeled public using the keyword `pub` (e.g. `y`). To learn more about private and public values, check the -[Data Types](../noir/concepts/data_types/index.md) section. +[Data Types](../../noir/concepts/data_types/index.md) section. The next line of the program specifies its body: @@ -79,7 +79,7 @@ assert(x != y); The Noir syntax `assert` can be interpreted as something similar to constraints in other zk-contract languages. -For more Noir syntax, check the [Language Concepts](../noir/concepts/comments.md) chapter. +For more Noir syntax, check the [Language Concepts](../../noir/concepts/comments.md) chapter. ## Build In/Output Files @@ -96,7 +96,7 @@ _Prover.toml_ houses input values, and _Verifier.toml_ houses public values. ## Prove Our Noir Program -Now that the project is set up, we can create a proof of correct execution on our Noir program. +Now that the project is set up, we can create a proof of correct execution of our Noir program. Fill in input values for execution in the _Prover.toml_ file. For example: diff --git a/docs/docs/getting_started/project_breakdown.md b/docs/docs/getting_started/hello_noir/project_breakdown.md similarity index 86% rename from docs/docs/getting_started/project_breakdown.md rename to docs/docs/getting_started/hello_noir/project_breakdown.md index c4e2a9ae003..6160a102c6c 100644 --- a/docs/docs/getting_started/project_breakdown.md +++ b/docs/docs/getting_started/hello_noir/project_breakdown.md @@ -8,8 +8,8 @@ keywords: sidebar_position: 2 --- -This section breaks down our hello world program in section _1.2_. We elaborate on the project -structure and what the `prove` and `verify` commands did in the previous section. +This section breaks down our hello world program from the previous section. We elaborate on the project +structure and what the `prove` and `verify` commands did. ## Anatomy of a Nargo Project @@ -52,7 +52,7 @@ license = "MIT" ecrecover = {tag = "v0.9.0", git = "https://github.com/colinnielsen/ecrecover-noir.git"} ``` -Nargo.toml for a [workspace](../noir/modules_packages_crates/workspaces.md) will look a bit different. For example: +Nargo.toml for a [workspace](../../noir/modules_packages_crates/workspaces.md) will look a bit different. For example: ```toml [workspace] @@ -62,7 +62,7 @@ default-member = "crates/a" #### Package section -The package section requires a number of fields including: +The package section defines a number of fields including: - `name` (**required**) - the name of the package - `type` (**required**) - can be "bin", "lib", or "contract" to specify whether its a binary, library or Aztec contract @@ -75,7 +75,7 @@ The package section requires a number of fields including: #### Dependencies section -This is where you will specify any dependencies for your project. See the [Dependencies page](../noir/modules_packages_crates/dependencies.md) for more info. +This is where you will specify any dependencies for your project. See the [Dependencies page](../../noir/modules_packages_crates/dependencies.md) for more info. `./proofs/` and `./contract/` directories will not be immediately visible until you create a proof or verifier contract respectively. @@ -98,7 +98,7 @@ verifying the proof. The prover supplies the values for `x` and `y` in the _Prover.toml_ file. -As for the program body, `assert` ensures the satisfaction of the condition (e.g. `x != y`) is +As for the program body, `assert` ensures that the condition to be satisfied (e.g. `x != y`) is constrained by the proof of the execution of said program (i.e. if the condition was not met, the verifier would reject the proof as an invalid proof). @@ -116,8 +116,8 @@ y = "2" When the command `nargo prove` is executed, two processes happen: -1. Noir creates a proof that `x` which holds the value of `1` and `y` which holds the value of `2` - is not equal. This not equal constraint is due to the line `assert(x != y)`. +1. Noir creates a proof that `x`, which holds the value of `1`, and `y`, which holds the value of `2`, + is not equal. This inequality constraint is due to the line `assert(x != y)`. 2. Noir creates and stores the proof of this statement in the _proofs_ directory in a file called your-project.proof. So if your project is named "private_voting" (defined in the project Nargo.toml), the proof will be saved at `./proofs/private_voting.proof`. Opening this file will display the proof in hex format. @@ -183,12 +183,12 @@ When the command `nargo verify` is executed, two processes happen: In production, the prover and the verifier are usually two separate entities. A prover would retrieve the necessary inputs, execute the Noir program, generate a proof and pass it to the -verifier. The verifier would then retrieve the public inputs from usually external sources and -verifies the validity of the proof against it. +verifier. The verifier would then retrieve the public inputs, usually from external sources, and +verify the validity of the proof against it. Take a private asset transfer as an example: -A user on browser as the prover would retrieve private inputs (e.g. the user's private key) and +A person using a browser as the prover would retrieve private inputs locally (e.g. the user's private key) and public inputs (e.g. the user's encrypted balance on-chain), compute the transfer, generate a proof and submit it to the verifier smart contract. diff --git a/docs/docs/getting_started/installation/index.md b/docs/docs/getting_started/installation/index.md index 27eeeca88ed..4ef86aa5914 100644 --- a/docs/docs/getting_started/installation/index.md +++ b/docs/docs/getting_started/installation/index.md @@ -16,6 +16,7 @@ keywords: [ Branches Noirup Repository ] +pagination_next: getting_started/hello_noir/index --- `nargo` is the one-stop-shop for almost everything related with Noir. The name comes from our love for Rust and its package manager `cargo`. @@ -43,3 +44,5 @@ Done. That's it. You should have the latest version working. You can check with You can also install nightlies, specific versions or branches. Check out the [noirup repository](https://github.com/noir-lang/noirup) for more information. + +Now we're ready to start working on [our first Noir program!](../hello_noir/index.md) diff --git a/docs/docs/getting_started/tooling/_category_.json b/docs/docs/getting_started/tooling/_category_.json index dff520ebc41..55804c03a71 100644 --- a/docs/docs/getting_started/tooling/_category_.json +++ b/docs/docs/getting_started/tooling/_category_.json @@ -1,5 +1,5 @@ { - "position": 3, + "position": 2, "label": "Tooling", "collapsible": true, "collapsed": true diff --git a/docs/docs/how_to/how-to-oracles.md b/docs/docs/how_to/how-to-oracles.md index bdb2b1332ba..0d84d992320 100644 --- a/docs/docs/how_to/how-to-oracles.md +++ b/docs/docs/how_to/how-to-oracles.md @@ -18,7 +18,7 @@ This guide shows you how to use oracles in your Noir program. For the sake of cl - You understand the concept of a JSON-RPC server. Visit the [JSON-RPC website](https://www.jsonrpc.org/) if you need a refresher. - You are comfortable with server-side JavaScript (e.g. Node.js, managing packages, etc.). -For reference, you can find the snippets used in this tutorial on the [Aztec DevRel Repository](https://github.com/AztecProtocol/dev-rel/tree/main/how_to_oracles/code-snippets/how-to-oracles). +For reference, you can find the snippets used in this tutorial on the [Aztec DevRel Repository](https://github.com/AztecProtocol/dev-rel/tree/main/code-snippets/how-to-oracles). ## Rundown diff --git a/docs/docs/how_to/how-to-recursion.md b/docs/docs/how_to/how-to-recursion.md index f60aa3ff2d9..4c45bb87ae2 100644 --- a/docs/docs/how_to/how-to-recursion.md +++ b/docs/docs/how_to/how-to-recursion.md @@ -42,12 +42,12 @@ In short: ::: -In a standard recursive app, you're also dealing with at least two circuits. For the purpose of this guide, we will assume these two: +In a standard recursive app, you're also dealing with at least two circuits. For the purpose of this guide, we will assume the following: -- `main`: a circuit of type `assert(x != y)` +- `main`: a circuit of type `assert(x != y)`, where `main` is marked with a `#[recursive]` attribute. This attribute states that the backend should generate proofs that are friendly for verification within another circuit. - `recursive`: a circuit that verifies `main` -For a full example on how recursive proofs work, please refer to the [noir-examples](https://github.com/noir/noir-examples) repository. We will *not* be using it as a reference for this guide. +For a full example on how recursive proofs work, please refer to the [noir-examples](https://github.com/noir-lang/noir-examples) repository. We will *not* be using it as a reference for this guide. ## Step 1: Setup @@ -77,7 +77,7 @@ const { witness } = noir.execute(input) With this witness, you are now able to generate the intermediate proof for the main circuit: ```js -const { proof, publicInputs } = await backend.generateIntermediateProof(witness) +const { proof, publicInputs } = await backend.generateProof(witness) ``` :::warning @@ -95,24 +95,20 @@ With this in mind, it becomes clear that our intermediate proof is the one *mean Optionally, you are able to verify the intermediate proof: ```js -const verified = await backend.verifyIntermediateProof({ proof, publicInputs }) +const verified = await backend.verifyProof({ proof, publicInputs }) ``` -This can be useful to make sure our intermediate proof was correctly generated. But the real goal is to do it within another circuit. For that, we need to generate the intermediate artifacts: +This can be useful to make sure our intermediate proof was correctly generated. But the real goal is to do it within another circuit. For that, we need to generate recursive proof artifacts that will be passed to the circuit that is verifying the proof we just generated. Instead of passing the proof and verification key as a byte array, we pass them as fields which makes it cheaper to verify in a circuit: ```js -const { proofAsFields, vkAsFields, vkHash } = await backend.generateIntermediateProofArtifacts( { publicInputs, proof }, publicInputsCount) +const { proofAsFields, vkAsFields, vkHash } = await backend.generateRecursiveProofArtifacts( { publicInputs, proof }, publicInputsCount) ``` This call takes the public inputs and the proof, but also the public inputs count. While this is easily retrievable by simply counting the `publicInputs` length, the backend interface doesn't currently abstract it away. :::info -The `proofAsFields` has a constant size `[Field; 93]`. However, currently the backend doesn't remove the public inputs from the proof when converting it. - -This means that if your `main` circuit has two public inputs, then you should also modify the recursive circuit to accept a proof with the public inputs appended. This means that in our example, since `y` is a public input, our `proofAsFields` is of type `[Field; 94]`. - -Verification keys in Barretenberg are always of size 114. +The `proofAsFields` has a constant size `[Field; 93]` and verification keys in Barretenberg are always `[Field; 114]`. ::: @@ -136,15 +132,14 @@ const recursiveInputs = { proof: proofAsFields, // array of length 93 + size of public inputs publicInputs: [mainInput.y], // using the example above, where `y` is the only public input key_hash: vkHash, - input_aggregation_object: Array(16).fill(0) // this circuit is verifying a non-recursive proof, so there's no input aggregation object: just use zero } const { witness, returnValue } = noir.execute(recursiveInputs) // we're executing the recursive circuit now! -const { proof, publicInputs } = backend.generateFinalProof(witness) -const verified = backend.verifyFinalProof({ proof, publicInputs }) +const { proof, publicInputs } = backend.generateProof(witness) +const verified = backend.verifyProof({ proof, publicInputs }) ``` -You can obviously chain this proof into another proof. In fact, if you're using recursive proofs, you're probably interested of using them this way! In that case, you should keep in mind the `returnValue`, as it will contain the `input_aggregation_object` for the next proof. +You can obviously chain this proof into another proof. In fact, if you're using recursive proofs, you're probably interested of using them this way! :::tip @@ -152,16 +147,16 @@ Managing circuits and "who does what" can be confusing. To make sure your naming ```js const circuits = { -main: mainJSON, -recursive: recursiveJSON + main: mainJSON, + recursive: recursiveJSON } const backends = { -main: new BarretenbergBackend(circuits.main), -recursive: new BarretenbergBackend(circuits.recursive) + main: new BarretenbergBackend(circuits.main), + recursive: new BarretenbergBackend(circuits.recursive) } const noir_programs = { -main: new Noir(circuits.main, backends.main), -recursive: new Noir(circuits.recursive, backends.recursive) + main: new Noir(circuits.main, backends.main), + recursive: new Noir(circuits.recursive, backends.recursive) } ``` @@ -170,15 +165,15 @@ This allows you to neatly call exactly the method you want without conflicting n ```js // Alice runs this 👇 const { witness: mainWitness } = await noir_programs.main.execute(input) -const proof = await backends.main.generateIntermediateProof(mainWitness) +const proof = await backends.main.generateProof(mainWitness) // Bob runs this 👇 -const verified = await backends.main.verifyIntermediateProof(proof) -const { proofAsFields, vkAsFields, vkHash } = await backends.main.generateIntermediateProofArtifacts( +const verified = await backends.main.verifyProof(proof) +const { proofAsFields, vkAsFields, vkHash } = await backends.main.generateRecursiveProofArtifacts( proof, numPublicInputs, ); -const recursiveProof = await noir_programs.recursive.generateFinalProof(recursiveInputs) +const recursiveProof = await noir_programs.recursive.generateProof(recursiveInputs) ``` ::: diff --git a/docs/docs/how_to/how-to-solidity-verifier.md b/docs/docs/how_to/how-to-solidity-verifier.md new file mode 100644 index 00000000000..e3c7c1065da --- /dev/null +++ b/docs/docs/how_to/how-to-solidity-verifier.md @@ -0,0 +1,231 @@ +--- +title: Generate a Solidity Verifier +description: + Learn how to run the verifier as a smart contract on the blockchain. Compile a Solidity verifier + contract for your Noir program and deploy it on any EVM blockchain acting as a verifier smart + contract. Read more to find out +keywords: + [ + solidity verifier, + smart contract, + blockchain, + compiler, + plonk_vk.sol, + EVM blockchain, + verifying Noir programs, + proving backend, + Barretenberg, + ] +sidebar_position: 0 +pagination_next: tutorials/noirjs_app +--- + +Noir has the ability to generate a verifier contract in Solidity, which can be deployed in many EVM-compatible blockchains such as Ethereum. + +This allows for a powerful feature set, as one can make use of the conciseness and the privacy provided by Noir in an immutable ledger. Applications can range from simple P2P guessing games, to complex private DeFi interactions. + +This guide shows you how to generate a Solidity Verifier and deploy it on the [Remix IDE](https://remix.ethereum.org/). It is assumed that: + +- You are comfortable with the Solidity programming language and understand how contracts are deployed on the Ethereum network +- You have Noir installed and you have a Noir program. If you don't, [get started](../getting_started/installation/index.md) with Nargo and the example Hello Noir circuit +- You are comfortable navigating RemixIDE. If you aren't or you need a refresher, you can find some video tutorials [here](https://www.youtube.com/channel/UCjTUPyFEr2xDGN6Cg8nKDaA) that could help you. + +## Rundown + +Generating a Solidity Verifier contract is actually a one-command process. However, compiling it and deploying it can have some caveats. Here's the rundown of this guide: + +1. How to generate a solidity smart contract +2. How to compile the smart contract in the RemixIDE +3. How to deploy it to a testnet + +## Step 1 - Generate a contract + +This is by far the most straight-forward step. Just run: + +```sh +nargo codegen-verifier +``` + +A new `contract` folder would then be generated in your project directory, containing the Solidity +file `plonk_vk.sol`. It can be deployed to any EVM blockchain acting as a verifier smart contract. + +:::info + +It is possible to generate verifier contracts of Noir programs for other smart contract platforms as long as the proving backend supplies an implementation. + +Barretenberg, the default proving backend for Nargo, supports generation of verifier contracts, for the time being these are only in Solidity. +::: + +## Step 2 - Compiling + +We will mostly skip the details of RemixIDE, as the UI can change from version to version. For now, we can just open +Remix and create a blank workspace. + +![Create Workspace](@site/static/img/how-tos/solidity_verifier_1.png) + +We will create a new file to contain the contract Nargo generated, and copy-paste its content. + +:::warning + +You'll likely see a warning advising you to not trust pasted code. While it is an important warning, it is irrelevant in the context of this guide and can be ignored. We will not be deploying anywhere near a mainnet. + +::: + +To compile our the verifier, we can navigate to the compilation tab: + +![Compilation Tab](@site/static/img/how-tos/solidity_verifier_2.png) + +Remix should automatically match a suitable compiler version. However, hitting the "Compile" button will most likely generate a "Stack too deep" error: + +![Stack too deep](@site/static/img/how-tos/solidity_verifier_3.png) + +This is due to the verify function needing to put many variables on the stack, but enabling the optimizer resolves the issue. To do this, let's open the "Advanced Configurations" tab and enable optimization. The default 200 runs will suffice. + +:::info + +This time we will see a warning about an unused function parameter. This is expected, as the `verify` function doesn't use the `_proof` parameter inside a solidity block, it is loaded from calldata and used in assembly. + +::: + +![Compilation success](@site/static/img/how-tos/solidity_verifier_4.png) + +## Step 3 - Deploying + +At this point we should have a compiled contract read to deploy. If we navigate to the deploy section in Remix, we will see many different environments we can deploy to. The steps to deploy on each environment would be out-of-scope for this guide, so we will just use the default Remix VM. + +Looking closely, we will notice that our "Solidity Verifier" is actually three contracts working together: + +- An `UltraVerificationKey` library which simply stores the verification key for our circuit. +- An abstract contract `BaseUltraVerifier` containing most of the verifying logic. +- A main `UltraVerifier` contract that inherits from the Base and uses the Key contract. + +Remix will take care of the dependencies for us so we can simply deploy the UltraVerifier contract by selecting it and hitting "deploy": + +![Deploying UltraVerifier](@site/static/img/how-tos/solidity_verifier_5.png) + +A contract will show up in the "Deployed Contracts" section, where we can retrieve the Verification Key Hash. This is particularly useful for double-checking the deployer contract is the correct one. + +:::note + +Why "UltraVerifier"? + +To be precise, the Noir compiler (`nargo`) doesn't generate the verifier contract directly. It compiles the Noir code into an intermediate language (ACIR), which is then executed by the backend. So it is the backend that returns the verifier smart contract, not Noir. + +In this case, the Barretenberg Backend uses the UltraPlonk proving system, hence the "UltraVerifier" name. + +::: + +## Step 4 - Verifying + +To verify a proof using the Solidity verifier contract, we call the `verify` function in this extended contract: + +```solidity +function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool) +``` + +When using the default example in the [Hello Noir](../getting_started/hello_noir/index.md) guide, the easiest way to confirm that the verifier contract is doing its job is by calling the `verify` function via remix with the required parameters. For `_proof`, run `nargo prove` and use the string in `proof/.proof` (adding the hex `0x` prefix). We can also copy the public input from `Verifier.toml`, as it will be properly formatted as 32-byte strings: + +``` +0x...... , [0x0000.....02] +``` + +A programmatic example of how the `verify` function is called can be seen in the example zk voting application [here](https://github.com/noir-lang/noir-examples/blob/33e598c257e2402ea3a6b68dd4c5ad492bce1b0a/foundry-voting/src/zkVote.sol#L35): + +```solidity +function castVote(bytes calldata proof, uint proposalId, uint vote, bytes32 nullifierHash) public returns (bool) { + // ... + bytes32[] memory publicInputs = new bytes32[](4); + publicInputs[0] = merkleRoot; + publicInputs[1] = bytes32(proposalId); + publicInputs[2] = bytes32(vote); + publicInputs[3] = nullifierHash; + require(verifier.verify(proof, publicInputs), "Invalid proof"); +``` + +:::info[Return Values] + +A circuit doesn't have the concept of a return value. Return values are just syntactic sugar in +Noir. + +Under the hood, the return value is passed as an input to the circuit and is checked at the end of +the circuit program. + +For example, if you have Noir program like this: + +```rust +fn main( + // Public inputs + pubkey_x: pub Field, + pubkey_y: pub Field, + // Private inputs + priv_key: Field, +) -> pub Field +``` + +the `verify` function will expect the public inputs array (second function parameter) to be of length 3, the two inputs and the return value. Like before, these values are populated in Verifier.toml after running `nargo prove`. + +Passing only two inputs will result in an error such as `PUBLIC_INPUT_COUNT_INVALID(3, 2)`. + +In this case, the inputs parameter to `verify` would be an array ordered as `[pubkey_x, pubkey_y, return]`. + +::: + +:::tip[Structs] + +You can pass structs to the verifier contract. They will be flattened so that the array of inputs is 1-dimensional array. + +For example, consider the following program: + +```rust +struct Type1 { + val1: Field, + val2: Field, +} + +struct Nested { + t1: Type1, + is_true: bool, +} + +fn main(x: pub Field, nested: pub Nested, y: pub Field) { + //... +} +``` + +The order of these inputs would be flattened to: `[x, nested.t1.val1, nested.t1.val2, nested.is_true, y]` + +::: + +The other function you can call is our entrypoint `verify` function, as defined above. + +:::tip + +It's worth noticing that the `verify` function is actually a `view` function. A `view` function does not alter the blockchain state, so it doesn't need to be distributed (i.e. it will run only on the executing node), and therefore doesn't cost any gas. + +This can be particularly useful in some situations. If Alice generated a proof and wants Bob to verify its correctness, Bob doesn't need to run Nargo, NoirJS, or any Noir specific infrastructure. He can simply make a call to the blockchain with the proof and verify it is correct without paying any gas. + +It would be incorrect to say that a Noir proof verification costs any gas at all. However, most of the time the result of `verify` is used to modify state (for example, to update a balance, a game state, etc). In that case the whole network needs to execute it, which does incur gas costs (calldata and execution, but not storage). + +::: + +## A Note on EVM chains + +ZK-SNARK verification depends on some precompiled cryptographic primitives such as Elliptic Curve Pairings (if you like complex math, you can read about EC Pairings [here](https://medium.com/@VitalikButerin/exploring-elliptic-curve-pairings-c73c1864e627)). Not all EVM chains support EC Pairings, notably some of the ZK-EVMs. This means that you won't be able to use the verifier contract in all of them. + +For example, chains like `zkSync ERA` and `Polygon zkEVM` do not currently support these precompiles, so proof verification via Solidity verifier contracts won't work. Here's a quick list of EVM chains that have been tested and are known to work: + +- Optimism +- Arbitrum +- Polygon PoS +- Scroll +- Celo + +If you test any other chains, please open a PR on this page to update the list. See [this doc](https://github.com/noir-lang/noir-starter/tree/main/with-foundry#testing-on-chain) for more info about testing verifier contracts on different EVM chains. + +## What's next + +Now that you know how to call a Noir Solidity Verifier on a smart contract using Remix, you should be comfortable with using it with some programmatic frameworks, such as [hardhat](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat) and [foundry](https://github.com/noir-lang/noir-starter/tree/main/with-foundry). + +You can find other tools, examples, boilerplates and libraries in the [awesome-noir](https://github.com/noir-lang/awesome-noir) repository. + +You should also be ready to write and deploy your first NoirJS app and start generating proofs on websites, phones, and NodeJS environments! Head on to the [NoirJS tutorial](../tutorials/noirjs_app.md) to learn how to do that. diff --git a/docs/docs/how_to/solidity_verifier.md b/docs/docs/how_to/solidity_verifier.md deleted file mode 100644 index 8022b0e5f20..00000000000 --- a/docs/docs/how_to/solidity_verifier.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -title: Generate a Solidity Verifier -description: - Learn how to run the verifier as a smart contract on the blockchain. Compile a Solidity verifier - contract for your Noir program and deploy it on any EVM blockchain acting as a verifier smart - contract. Read more to find out -keywords: - [ - solidity verifier, - smart contract, - blockchain, - compiler, - plonk_vk.sol, - EVM blockchain, - verifying Noir programs, - proving backend, - Barretenberg, - ] -sidebar_position: 0 ---- - -For certain applications, it may be desirable to run the verifier as a smart contract instead of on -a local machine. - -Compile a Solidity verifier contract for your Noir program by running: - -```sh -nargo codegen-verifier -``` - -A new `contract` folder would then be generated in your project directory, containing the Solidity -file `plonk_vk.sol`. It can be deployed on any EVM blockchain acting as a verifier smart contract. - -> **Note:** It is possible to compile verifier contracts of Noir programs for other smart contract -> platforms as long as the proving backend supplies an implementation. -> -> Barretenberg, the default proving backend for Nargo, supports compilation of verifier contracts in -> Solidity only for the time being. - -## Verify - -To verify a proof using the Solidity verifier contract, call the `verify` function with the -following signature: - -```solidity -function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool) -``` - -You can see an example of how the `verify` function is called in the example zk voting application [here](https://github.com/noir-lang/noir-examples/blob/33e598c257e2402ea3a6b68dd4c5ad492bce1b0a/foundry-voting/src/zkVote.sol#L35): - -```solidity -function castVote(bytes calldata proof, uint proposalId, uint vote, bytes32 nullifierHash) public returns (bool) { - // ... - bytes32[] memory publicInputs = new bytes32[](4); - publicInputs[0] = merkleRoot; - publicInputs[1] = bytes32(proposalId); - publicInputs[2] = bytes32(vote); - publicInputs[3] = nullifierHash; - require(verifier.verify(proof, publicInputs), "Invalid proof"); -``` - -### Public Inputs - -:::tip - -A circuit doesn't have the concept of a return value. Return values are just syntactic sugar in -Noir. - -Under the hood, the return value is passed as an input to the circuit and is checked at the end of -the circuit program. - -::: - -The verifier contract uses the output (return) value of a Noir program as a public input. So if you -have the following function - -```rust -fn main( - // Public inputs - pubkey_x: pub Field, - pubkey_y: pub Field, - // Private inputs - priv_key: Field, -) -> pub Field -``` - -then `verify` in `plonk_vk.sol` will expect 3 public inputs. Passing two inputs will result in an -error like `Reason: PUBLIC_INPUT_COUNT_INVALID(3, 2)`. - -In this case the 3 inputs to `verify` would be ordered as `[pubkey_x, pubkey_y, return]`. - -#### Struct inputs - -Consider the following program: - -```rust -struct Type1 { - val1: Field, - val2: Field, -} - -struct Nested { - t1: Type1, - is_true: bool, -} - -fn main(x: pub Field, nested: pub Nested, y: pub Field) { - //... -} -``` - -Structs will be flattened so that the array of inputs is 1-dimensional array. The order of these inputs would be flattened to: `[x, nested.t1.val1, nested.t1.val2, nested.is_true, y]` - -## Noir for EVM chains - -You can currently deploy the Solidity verifier contracts to most EVM compatible chains. EVM chains that have been tested and are known to work include: - -- Optimism -- Arbitrum -- Polygon PoS -- Scroll -- Celo - -Other EVM chains should work, but have not been tested directly by our team. If you test any other chains, please open a PR on this page to update the list. See [this doc](https://github.com/noir-lang/noir-starter/tree/main/with-foundry#testing-on-chain) for more info about testing verifier contracts on different EVM chains. - -### Unsupported chains - -Unfortunately not all "EVM" chains are supported. - -**zkSync** and the **Polygon zkEVM** do _not_ currently support proof verification via Solidity verifier contracts. They are missing the bn256 precompile contract that the verifier contract requires. Once these chains support this precompile, they may work. diff --git a/docs/docs/index.md b/docs/docs/index.md deleted file mode 100644 index ab8c2f8acd2..00000000000 --- a/docs/docs/index.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: Noir -description: - Learn about the public alpha release of Noir, a domain specific language heavily influenced by Rust that compiles to - an intermediate language which can be compiled to an arithmetic circuit or a rank-1 constraint system. -keywords: - [ - Noir, - Domain Specific Language, - Rust, - Intermediate Language, - Arithmetic Circuit, - Rank-1 Constraint System, - Ethereum Developers, - Protocol Developers, - Blockchain Developers, - Proving System, - Smart Contract Language, - ] -sidebar_position: 0 ---- - -## What's new about Noir? - -Noir, a domain-specific language crafted for SNARK proving systems, stands out with its simplicity, flexibility, -and robust capabilities. Unlike conventional approaches that compile directly to a fixed NP-complete language, -Noir takes a two-pronged path. First, Noir compiles to an adaptable intermediate language known as ACIR. - -From there, depending on a given project's needs, ACIR can be further compiled into an arithmetic circuit for integration with Aztec's -barretenberg backend, or transformed into a rank-1 constraint system suitable for R1CS backends like Arkworks' Marlin -backend (among others). - -This innovative design introduces unique challenges; however, this approach also strategically separates the programming language from the -backend. Noir's approach echoes the modular philosophy of LLVM, offering developers a versatile toolkit for cryptographic -programming. - -## Who is Noir for? - -### Solidity Developers - -Noir streamlines the creation of Solidity contracts that interface with SNARK systems. -[`Utilize the nargo codegen-verifier`](./reference/nargo_commands.md#nargo-codegen-verifier) command to construct verifier -contracts efficiently. While the current alpha version offers this as a direct feature, future updates aim -to modularize this process for even greater ease of use. - -Noir currently includes a command to create a Solidity contract which verifies your Noir program. This will be -modularized in the future; however, as of the alpha, you can use the `nargo codegen-verifier` command to create a verifier contract. - -### Protocol Developers - -Should the Aztec backend not align with your existing tech stack, or if you're inclined to integrate alternative -proving systems, Noir's agnostic compilation to a proof-agnostic intermediate language offers unmatched flexibility. -This allows protocol engineers the freedom to substitute the default PLONK-based system with an alternative of their -choice, tailoring the proving system to their specific needs. - -### Blockchain developers - -Blockchain developers often face environmental constraints, such as predetermined proving systems and smart contract -languages. Noir addresses this by enabling the implementation of custom proving system backends and smart contract -interfaces, ensuring seamless integration with your blockchain's architecture, and expanding the horizons for innovation -within your projects. - -## Libraries - -Noir does not currently have an official package manager. You can find a list of some of the available Noir libraries in the -[awesome-noir repo here](https://github.com/noir-lang/awesome-noir#libraries). - -Some libraries that are available today include: - -- [Standard Library](https://github.com/noir-lang/noir/tree/master/noir_stdlib) - the Noir Standard Library -- [Ethereum Storage Proof Verification](https://github.com/aragonzkresearch/noir-trie-proofs) - a library that contains - the primitives necessary for RLP decoding (in the form of look-up table construction) and Ethereum state and storage - proof verification (or verification of any trie proof involving 32-byte long keys) -- [BigInt](https://github.com/shuklaayush/noir-bigint) - a library that provides a custom BigUint56 data type, allowing - for computations on large unsigned integers -- [ECrecover](https://github.com/colinnielsen/ecrecover-noir/tree/main) - a library to verify an ECDSA signature and - return the source Ethereum address -- [Sparse Merkle Tree Verifier](https://github.com/vocdoni/smtverifier-noir/tree/main) - a library for verification of - sparse Merkle trees -- [Signed Int](https://github.com/resurgencelabs/signed_int) - a library for accessing a custom Signed Integer data - type, allowing access to negative numbers on Noir -- [Fraction](https://github.com/resurgencelabs/fraction) - a library for accessing fractional number data type in Noir, - allowing results that aren't whole numbers - -See the section on [dependencies](noir/modules_packages_crates/dependencies.md) for more information. diff --git a/docs/docs/index.mdx b/docs/docs/index.mdx new file mode 100644 index 00000000000..2cec2397051 --- /dev/null +++ b/docs/docs/index.mdx @@ -0,0 +1,67 @@ +--- +title: Noir Lang +hide_title: true +description: + Learn about the public alpha release of Noir, a domain specific language heavily influenced by Rust that compiles to + an intermediate language which can be compiled to an arithmetic circuit or a rank-1 constraint system. +keywords: + [Noir, + Domain Specific Language, + Rust, + Intermediate Language, + Arithmetic Circuit, + Rank-1 Constraint System, + Ethereum Developers, + Protocol Developers, + Blockchain Developers, + Proving System, + Smart Contract Language] +sidebar_position: 0 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Noir Logo + +Noir is a Domain-Specific Language for SNARK proving systems developed by [Aztec Labs](https://aztec.network/). It allows you to generate complex Zero-Knowledge Programs (ZKP) by using simple and flexible syntax, requiring no previous knowledge on the underlying mathematics or cryptography. + +ZK programs are programs that can generate short proofs of a certain statement without revealing some details about it. You can read more about ZKPs [here](https://dev.to/spalladino/a-beginners-intro-to-coding-zero-knowledge-proofs-c56). + +## What's new about Noir? + +Noir works differently from most ZK languages by taking a two-pronged path. First, it compiles the program to an adaptable intermediate language known as ACIR. From there, depending on a given project's needs, ACIR can be further compiled into an arithmetic circuit for integration with the proving backend. + +:::info + +Noir is backend agnostic, which means it makes no assumptions on which proving backend powers the ZK proof. Being the language that powers [Aztec Contracts](https://docs.aztec.network/dev_docs/contracts/main), it defaults to Aztec's Barretenberg proving backend. + +However, the ACIR output can be transformed to be compatible with other PLONK-based backends, or into a [rank-1 constraint system](https://www.rareskills.io/post/rank-1-constraint-system) suitable for backends such as Arkwork's Marlin. + +::: + +## Who is Noir for? + +Noir can be used both in complex cloud-based backends and in user's smartphones, requiring no knowledge on the underlying math or cryptography. From authorization systems that keep a password in the user's device, to complex on-chain verification of recursive proofs, Noir is designed to abstract away complexity without any significant overhead. Here are some examples of situations where Noir can be used: + + + + Noir Logo + + Aztec Contracts leverage Noir to allow for the storage and execution of private information. Writing an Aztec Contract is as easy as writing Noir, and Aztec developers can easily interact with the network storage and execution through the [Aztec.nr](https://docs.aztec.network/dev_docs/contracts/main) library. + + + Soliditry Verifier Example + Noir can auto-generate Solidity verifier contracts that verify Noir proofs. This allows for non-interactive verification of proofs containing private information in an immutable system. This feature powers a multitude of use-case scenarios, from P2P chess tournaments, to [Aztec Layer-2 Blockchain](https://docs.aztec.network/) + + + Aztec Labs developed NoirJS, an easy interface to generate and verify Noir proofs in a Javascript environment. This allows for Noir to be used in webpages, mobile apps, games, and any other environment supporting JS execution in a standalone manner. + + + + +## Libraries + +Noir is meant to be easy to extend by simply importing Noir libraries just like in Rust. +The [awesome-noir repo](https://github.com/noir-lang/awesome-noir#libraries) is a collection of libraries developed by the Noir community. +Writing a new library is easy and makes code be composable and easy to reuse. See the section on [dependencies](noir/modules_packages_crates/dependencies.md) for more information. diff --git a/docs/docs/noir/concepts/assert.md b/docs/docs/noir/concepts/assert.md index c5f9aff139c..bcff613a695 100644 --- a/docs/docs/noir/concepts/assert.md +++ b/docs/docs/noir/concepts/assert.md @@ -18,10 +18,28 @@ fn main(x : Field, y : Field) { } ``` +> Assertions only work for predicate operations, such as `==`. If there's any ambiguity on the operation, the program will fail to compile. For example, it is unclear if `assert(x + y)` would check for `x + y == 0` or simply would return `true`. + You can optionally provide a message to be logged when the assertion fails: ```rust assert(x == y, "x and y are not equal"); ``` -> Assertions only work for predicate operations, such as `==`. If there's any ambiguity on the operation, the program will fail to compile. For example, it is unclear if `assert(x + y)` would check for `x + y == 0` or simply would return `true`. +Aside string literals, the optional message can be a format string or any other type supported as input for Noir's [print](../standard_library/logging.md) functions. This feature lets you incorporate runtime variables into your failed assertion logs: + +```rust +assert(x == y, f"Expected x == y, but got {x} == {y}"); +``` + +Using a variable as an assertion message directly: + +```rust +struct myStruct { + myField: Field +} + +let s = myStruct { myField: y }; +assert(s.myField == x, s); +``` + diff --git a/docs/docs/noir/concepts/data_types/arrays.md b/docs/docs/noir/concepts/data_types/arrays.md index 7f275a2d771..a8bd338e736 100644 --- a/docs/docs/noir/concepts/data_types/arrays.md +++ b/docs/docs/noir/concepts/data_types/arrays.md @@ -82,14 +82,16 @@ You can create arrays of primitive types or structs. There is not yet support fo ## Methods -For convenience, the STD provides some ready-to-use, common methods for arrays: +For convenience, the STD provides some ready-to-use, common methods for arrays. +Each of these functions are located within the generic impl `impl [T; N] {`. +So anywhere `self` appears, it refers to the variable `self: [T; N]`. ### len Returns the length of an array ```rust -fn len(_array: [T; N]) -> comptime Field +fn len(self) -> Field ``` example @@ -109,7 +111,7 @@ logic it uses internally is optimized specifically for these values. If you need sort any type, you should use the function `sort_via` described below. ```rust -fn sort(_array: [T; N]) -> [T; N] +fn sort(self) -> [T; N] ``` example @@ -127,7 +129,7 @@ fn main() { Sorts the array with a custom comparison function ```rust -fn sort_via(mut a: [T; N], ordering: fn(T, T) -> bool) -> [T; N] +fn sort_via(self, ordering: fn(T, T) -> bool) -> [T; N] ``` example @@ -148,7 +150,7 @@ fn main() { Applies a function to each element of the array, returning a new array containing the mapped elements. ```rust -fn map(f: fn(T) -> U) -> [U; N] +fn map(self, f: fn(T) -> U) -> [U; N] ``` example @@ -164,7 +166,7 @@ Applies a function to each element of the array, returning the final accumulated parameter is the initial value. ```rust -fn fold(mut accumulator: U, f: fn(U, T) -> U) -> U +fn fold(self, mut accumulator: U, f: fn(U, T) -> U) -> U ``` This is a left fold, so the given function will be applied to the accumulator and first element of @@ -198,7 +200,7 @@ fn main() { Same as fold, but uses the first element as starting element. ```rust -fn reduce(f: fn(T, T) -> T) -> T +fn reduce(self, f: fn(T, T) -> T) -> T ``` example: @@ -216,7 +218,7 @@ fn main() { Returns true if all the elements satisfy the given predicate ```rust -fn all(predicate: fn(T) -> bool) -> bool +fn all(self, predicate: fn(T) -> bool) -> bool ``` example: @@ -234,7 +236,7 @@ fn main() { Returns true if any of the elements satisfy the given predicate ```rust -fn any(predicate: fn(T) -> bool) -> bool +fn any(self, predicate: fn(T) -> bool) -> bool ``` example: diff --git a/docs/docs/noir/concepts/data_types/fields.md b/docs/docs/noir/concepts/data_types/fields.md index a1c67945d66..7870c98c858 100644 --- a/docs/docs/noir/concepts/data_types/fields.md +++ b/docs/docs/noir/concepts/data_types/fields.md @@ -157,6 +157,23 @@ fn main() { } ``` +### assert_max_bit_size + +Adds a constraint to specify that the field can be represented with `bit_size` number of bits + +```rust +fn assert_max_bit_size(self, bit_size: u32) +``` + +example: + +```rust +fn main() { + let field = 2 + field.assert_max_bit_size(32); +} +``` + ### sgn0 Parity of (prime) Field element, i.e. sgn0(x mod p) = 0 if x ∈ \{0, ..., p-1\} is even, otherwise sgn0(x mod p) = 1. diff --git a/docs/docs/noir/concepts/unconstrained.md b/docs/docs/noir/concepts/unconstrained.md index 6b3424f7993..89d12c1c971 100644 --- a/docs/docs/noir/concepts/unconstrained.md +++ b/docs/docs/noir/concepts/unconstrained.md @@ -40,7 +40,7 @@ Total ACIR opcodes generated for language PLONKCSat { width: 3 }: 91 Backend circuit size: 3619 ``` -A lot of the operations in this function are optimized away by the compiler (all the bit-shifts turn into divisions by constants). However we can save a bunch of gates by casting to u8 a bit earlier. This automatically truncates the bit-shifted value to fit in a u8 which allows us to remove the XOR against 0xff. This saves us ~480 gates in total. +A lot of the operations in this function are optimized away by the compiler (all the bit-shifts turn into divisions by constants). However we can save a bunch of gates by casting to u8 a bit earlier. This automatically truncates the bit-shifted value to fit in a u8 which allows us to remove the AND against 0xff. This saves us ~480 gates in total. ```rust fn main(num: u72) -> pub [u8; 8] { diff --git a/docs/docs/noir/standard_library/black_box_fns.md b/docs/docs/noir/standard_library/black_box_fns.md index 4b1efbd17de..6b22d0e7466 100644 --- a/docs/docs/noir/standard_library/black_box_fns.md +++ b/docs/docs/noir/standard_library/black_box_fns.md @@ -6,40 +6,26 @@ keywords: [noir, black box functions] Black box functions are functions in Noir that rely on backends implementing support for specialized constraints. This makes certain zk-snark unfriendly computations cheaper than if they were implemented in Noir. -:::warning - -It is likely that not all backends will support a particular black box function. - -::: - -Because it is not guaranteed that all backends will support black box functions, it is possible that certain Noir programs won't compile against a particular backend if they use an unsupported black box function. It is possible to fallback to less efficient implementations written in Noir/ACIR in some cases. - -Black box functions are specified with the `#[foreign(black_box_fn)]` attribute. For example, the SHA256 function in the Noir [source code](https://github.com/noir-lang/noir/blob/v0.5.1/noir_stdlib/src/hash.nr) looks like: - -```rust -#[foreign(sha256)] -fn sha256(_input : [u8; N]) -> [u8; 32] {} -``` +The ACVM spec defines a set of blackbox functions which backends will be expected to implement. This allows backends to use optimized implementations of these constraints if they have them, however they may also fallback to less efficient naive implementations if not. ## Function list -Here is a list of the current black box functions that are supported by UltraPlonk: +Here is a list of the current black box functions: -- AES - [SHA256](./cryptographic_primitives/hashes#sha256) - [Schnorr signature verification](./cryptographic_primitives/schnorr) - [Blake2s](./cryptographic_primitives/hashes#blake2s) +- [Blake3](./cryptographic_primitives/hashes#blake2s) - [Pedersen Hash](./cryptographic_primitives/hashes#pedersen_hash) - [Pedersen Commitment](./cryptographic_primitives/hashes#pedersen_commitment) - [ECDSA signature verification](./cryptographic_primitives/ecdsa_sig_verification) - [Fixed base scalar multiplication](./cryptographic_primitives/scalar) -- [Compute merkle root](./merkle_trees#compute_merkle_root) - AND - XOR - RANGE - [Keccak256](./cryptographic_primitives/hashes#keccak256) - [Recursive proof verification](./recursion) -Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. To ensure compatibility across backends, the ACVM has fallback implementations of `AND`, `XOR` and `RANGE` defined in its standard library which it can seamlessly fallback to if the backend doesn't support them. +Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/noir/blob/master/acvm-repo/acir/src/circuit/black_box_functions.rs). diff --git a/docs/docs/noir/standard_library/cryptographic_primitives/ec_primitives.md b/docs/docs/noir/standard_library/cryptographic_primitives/ec_primitives.md index 8d573adb3be..d2b42d67b7c 100644 --- a/docs/docs/noir/standard_library/cryptographic_primitives/ec_primitives.md +++ b/docs/docs/noir/standard_library/cryptographic_primitives/ec_primitives.md @@ -72,7 +72,7 @@ does indeed lie on `c` by calling `c.contains(p1)`. ## Examples The -[ec_baby_jubjub test](https://github.com/noir-lang/noir/blob/master/crates/nargo_cli/tests/test_data/ec_baby_jubjub/src/main.nr) +[ec_baby_jubjub test](https://github.com/noir-lang/noir/blob/master/test_programs/compile_success_empty/ec_baby_jubjub/src/main.nr) illustrates all of the above primitives on various forms of the Baby Jubjub curve. A couple of more interesting examples in Noir would be: diff --git a/docs/docs/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx b/docs/docs/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx index 1376c51dfde..d67a1ac94df 100644 --- a/docs/docs/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx +++ b/docs/docs/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx @@ -13,9 +13,7 @@ Noir supports ECDSA signatures verification over the secp256k1 and secp256r1 cur Verifier for ECDSA Secp256k1 signatures -```rust -fn verify_signature(_public_key_x : [u8; 32], _public_key_y : [u8; 32], _signature: [u8; 64], _message: [u8]) -> bool -``` +#include_code ecdsa_secp256k1 noir_stdlib/src/ecdsa_secp256k1.nr rust example: @@ -30,9 +28,7 @@ fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], sign Verifier for ECDSA Secp256r1 signatures -```rust -fn verify_signature(_public_key_x : [u8; 32], _public_key_y : [u8; 32], _signature: [u8; 64], _message: [u8]) -> bool -``` +#include_code ecdsa_secp256r1 noir_stdlib/src/ecdsa_secp256r1.nr rust example: diff --git a/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx b/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx index 3c5f7f79603..85706384eee 100644 --- a/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx +++ b/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -14,9 +14,7 @@ import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; Given an array of bytes, returns the resulting sha256 hash. -```rust -fn sha256(_input : [u8]) -> [u8; 32] -``` +#include_code sha256 noir_stdlib/src/hash.nr rust example: @@ -33,9 +31,7 @@ fn main() { Given an array of bytes, returns an array with the Blake2 hash -```rust -fn blake2s(_input : [u8]) -> [u8; 32] -``` +#include_code blake2s noir_stdlib/src/hash.nr rust example: @@ -48,43 +44,45 @@ fn main() { -## pedersen_hash +## blake3 -Given an array of Fields, returns the Pedersen hash. +Given an array of bytes, returns an array with the Blake3 hash -```rust -fn pedersen_hash(_input : [Field]) -> Field -``` +#include_code blake3 noir_stdlib/src/hash.nr rust example: ```rust fn main() { let x = [163, 117, 178, 149]; // some random bytes - let hash = std::hash::pedersen_hash(x); + let hash = std::hash::blake3(x); } ``` +## pedersen_hash + +Given an array of Fields, returns the Pedersen hash. + +#include_code pedersen_hash noir_stdlib/src/hash.nr rust + +example: + +#include_code pedersen-hash test_programs/execution_success/pedersen_hash/src/main.nr rust + + ## pedersen_commitment Given an array of Fields, returns the Pedersen commitment. -```rust -fn pedersen_commitment(_input : [Field]) -> [Field; 2] -``` +#include_code pedersen_commitment noir_stdlib/src/hash.nr rust example: -```rust -fn main() { - let x = [163, 117, 178, 149]; // some random bytes - let commitment = std::hash::pedersen_commitment(x); -} -``` +#include_code pedersen-commitment test_programs/execution_success/pedersen_commitment/src/main.nr rust @@ -94,19 +92,11 @@ Given an array of bytes (`u8`), returns the resulting keccak hash as an array of (`[u8; 32]`). Specify a message_size to hash only the first `message_size` bytes of the input. -```rust -fn keccak256(_input : [u8; N], _message_size: u32) -> [u8; 32] -``` +#include_code keccak256 noir_stdlib/src/hash.nr rust example: -```rust -fn main() { - let x = [163, 117, 178, 149]; // some random bytes - let message_size = 4; - let hash = std::hash::keccak256(x, message_size); -} -``` +#include_code keccak256 test_programs/execution_success/keccak256/src/main.nr rust @@ -122,13 +112,7 @@ fn hash_1(input: [Field; 1]) -> Field example: -```rust -fn main() -{ - let hash_2 = std::hash::poseidon::bn254::hash_2([1, 2]); - assert(hash2 == 0x115cc0f5e7d690413df64c6b9662e9cf2a3617f2743245519e19607a4417189a); -} -``` +#include_code poseidon test_programs/execution_success/poseidon_bn254_hash/src/main.nr rust ## mimc_bn254 and mimc diff --git a/docs/docs/noir/standard_library/cryptographic_primitives/scalar.mdx b/docs/docs/noir/standard_library/cryptographic_primitives/scalar.mdx index aa4fb8cbaed..c2946b2b73b 100644 --- a/docs/docs/noir/standard_library/cryptographic_primitives/scalar.mdx +++ b/docs/docs/noir/standard_library/cryptographic_primitives/scalar.mdx @@ -12,9 +12,7 @@ import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; Performs scalar multiplication over the embedded curve whose coordinates are defined by the configured noir field. For the BN254 scalar field, this is BabyJubJub or Grumpkin. -```rust -fn fixed_base_embedded_curve(_input : Field) -> [Field; 2] -``` +#include_code fixed_base_embedded_curve noir_stdlib/src/scalar_mul.nr rust example diff --git a/docs/docs/noir/standard_library/cryptographic_primitives/schnorr.mdx b/docs/docs/noir/standard_library/cryptographic_primitives/schnorr.mdx index 7a2c9c20226..0e0c358c6e1 100644 --- a/docs/docs/noir/standard_library/cryptographic_primitives/schnorr.mdx +++ b/docs/docs/noir/standard_library/cryptographic_primitives/schnorr.mdx @@ -11,9 +11,7 @@ import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; Verifier for Schnorr signatures over the embedded curve (for BN254 it is Grumpkin). -```rust -fn verify_signature(_public_key_x: Field, _public_key_y: Field, _signature: [u8; 64], _message: [u8]) -> bool -``` +#include_code schnorr_verify noir_stdlib/src/schnorr.nr rust where `_signature` can be generated like so using the npm package [@noir-lang/barretenberg](https://www.npmjs.com/package/@noir-lang/barretenberg) diff --git a/docs/docs/noir/standard_library/options.md b/docs/docs/noir/standard_library/options.md index 970c9cfbf11..a1bd4e1de5f 100644 --- a/docs/docs/noir/standard_library/options.md +++ b/docs/docs/noir/standard_library/options.md @@ -56,6 +56,10 @@ Returns the wrapped value if `self.is_some()`. Otherwise, returns the given defa Returns the wrapped value if `self.is_some()`. Otherwise, calls the given function to return a default value. +### expect + +Asserts `self.is_some()` with a provided custom message and returns the contained `Some` value. The custom message is expected to be a format string. + ### map If self is `Some(x)`, this returns `Some(f(x))`. Otherwise, this returns `None`. diff --git a/docs/docs/noir/standard_library/recursion.md b/docs/docs/noir/standard_library/recursion.md index 67962082a8f..f252150c8b5 100644 --- a/docs/docs/noir/standard_library/recursion.md +++ b/docs/docs/noir/standard_library/recursion.md @@ -1,16 +1,16 @@ --- title: Recursive Proofs description: Learn about how to write recursive proofs in Noir. -keywords: [recursion, recursive proofs, verification_key, aggregation object, verify_proof] +keywords: [recursion, recursive proofs, verification_key, verify_proof] --- Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. -The `verify_proof` function takes a verification key, proof and public inputs for a zk program, as well as a key hash and an input aggregation object. The key hash is used to check the validity of the verification key and the input aggregation object is required by some proving systems. The `verify_proof` function returns an output aggregation object that can then be fed into future iterations of the proof verification if required. +Read [the explainer on recursion](../../explainers/explainer-recursion.md) to know more about this function and the [guide on how to use it.](../../how_to/how-to-recursion.md) ```rust #[foreign(verify_proof)] -fn verify_proof(_verification_key : [Field], _proof : [Field], _public_input : Field, _key_hash : Field, _input_aggregation_object : [Field]) -> [Field] {} +fn verify_proof(_verification_key : [Field], _proof : [Field], _public_input : Field, _key_hash : Field) {} ``` :::info @@ -26,36 +26,29 @@ use dep::std; fn main( verification_key : [Field; 114], - proof : [Field; 94], + proof : [Field; 93], public_inputs : [Field; 1], key_hash : Field, - input_aggregation_object : [Field; 16], - proof_b : [Field; 94], -) -> pub [Field; 16] { - let output_aggregation_object_a = std::verify_proof( + proof_b : [Field; 93], +) { + std::verify_proof( verification_key.as_slice(), proof.as_slice(), public_inputs.as_slice(), - key_hash, - input_aggregation_object + key_hash ); - let output_aggregation_object = std::verify_proof( + std::verify_proof( verification_key.as_slice(), proof_b.as_slice(), public_inputs.as_slice(), - key_hash, - output_aggregation_object_a + key_hash ); - - let mut output = [0; 16]; - for i in 0..16 { - output[i] = output_aggregation_object[i]; - } - output } ``` +You can see a full example of recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). + ## Parameters ### `verification_key` @@ -68,23 +61,8 @@ The proof for the zk program that is being verified. ### `public_inputs` -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. +These represent the public inputs of the proof we are verifying. ### `key_hash` 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. - -### `input_aggregation_object` - -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. - -## Return value - -### `output_aggregation_object` - -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. - -## Example - -You can see an example of how to do recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). diff --git a/docs/docs/noir/standard_library/traits.md b/docs/docs/noir/standard_library/traits.md index f2960ca5080..fb6d5ae1c48 100644 --- a/docs/docs/noir/standard_library/traits.md +++ b/docs/docs/noir/standard_library/traits.md @@ -8,11 +8,7 @@ keywords: [traits, trait, interface, protocol, default, add, eq] ### `std::default::Default` -```rust -trait Default { - fn default() -> Self; -} -``` +#include_code default-trait noir_stdlib/src/default.nr rust Constructs a default value of a type. @@ -52,15 +48,47 @@ impl Default for (A, B, C, D, E) For primitive integer types, the return value of `default` is `0`. Container types such as arrays are filled with default values of their element type. + +## `std::convert` + +### `std::convert::From` + +#include_code from-trait noir_stdlib/src/convert.nr rust + +The `From` trait defines how to convert from a given type `T` to the type on which the trait is implemented. + +The Noir standard library provides a number of implementations of `From` between primitive types. +#include_code from-impls noir_stdlib/src/convert.nr rust + +#### When to implement `From` + +As a general rule of thumb, `From` may be implemented in the [situations where it would be suitable in Rust](https://doc.rust-lang.org/std/convert/trait.From.html#when-to-implement-from): + +- The conversion is *infallible*: Noir does not provide an equivalent to Rust's `TryFrom`, if the conversion can fail then provide a named method instead. +- The conversion is *lossless*: semantically, it should not lose or discard information. For example, `u32: From` can losslessly convert any `u16` into a valid `u32` such that the original `u16` can be recovered. On the other hand, `u16: From` should not be implemented as `2**16` is a `u32` which cannot be losslessly converted into a `u16`. +- The conversion is *value-preserving*: the conceptual kind and meaning of the resulting value is the same, even though the Noir type and technical representation might be different. While it's possible to infallibly and losslessly convert a `u8` into a `str<2>` hex representation, `4u8` and `"04"` are too different for `str<2>: From` to be implemented. +- The conversion is *obvious*: it's the only reasonable conversion between the two types. If there's ambiguity on how to convert between them such that the same input could potentially map to two different values then a named method should be used. For instance rather than implementing `U128: From<[u8; 16]>`, the methods `U128::from_le_bytes` and `U128::from_be_bytes` are used as otherwise the endianness of the array would be ambiguous, resulting in two potential values of `U128` from the same byte array. + +One additional recommendation specific to Noir is: +- The conversion is *efficient*: it's relatively cheap to convert between the two types. Due to being a ZK DSL, it's more important to avoid unnecessary computation compared to Rust. If the implementation of `From` would encourage users to perform unnecessary conversion, resulting in additional proving time, then it may be preferable to expose functionality such that this conversion may be avoided. + +### `std::convert::Into` + +The `Into` trait is defined as the reciprocal of `From`. It should be easy to convince yourself that if we can convert to type `A` from type `B`, then it's possible to convert type `B` into type `A`. + +For this reason, implementing `From` on a type will automatically generate a matching `Into` implementation. One should always prefer implementing `From` over `Into` as implementing `Into` will not generate a matching `From` implementation. + +#include_code into-trait noir_stdlib/src/convert.nr rust + +`Into` is most useful when passing function arguments where the types don't quite match up with what the function expects. In this case, the compiler has enough type information to perform the necessary conversion by just appending `.into()` onto the arguments in question. + + ## `std::cmp` ### `std::cmp::Eq` -```rust -trait Eq { - fn eq(self, other: Self) -> bool; -} -``` +#include_code eq-trait noir_stdlib/src/cmp.nr rust + Returns `true` if `self` is equal to `other`. Implementing this trait on a type allows the type to be used with `==` and `!=`. @@ -97,13 +125,9 @@ impl Eq for (A, B, C, D, E) where A: Eq, B: Eq, C: Eq, D: Eq, E: Eq { .. } ``` -### `std::cmp::Cmp` +### `std::cmp::Ord` -```rust -trait Cmp { - fn cmp(self, other: Self) -> Ordering; -} -``` +#include_code ord-trait noir_stdlib/src/cmp.nr rust `a.cmp(b)` compares two values returning `Ordering::less()` if `a < b`, `Ordering::equal()` if `a == b`, or `Ordering::greater()` if `a > b`. @@ -151,23 +175,10 @@ These traits abstract over addition, subtraction, multiplication, and division r Implementing these traits for a given type will also allow that type to be used with the corresponding operator for that trait (`+` for Add, etc) in addition to the normal method names. -```rust -trait Add { - fn add(self, other: Self) -> Self; -} - -trait Sub { - fn sub(self, other: Self) -> Self; -} - -trait Mul { - fn mul(self, other: Self) -> Self; -} - -trait Div { - fn div(self, other: Self) -> Self; -} -``` +#include_code add-trait noir_stdlib/src/ops.nr rust +#include_code sub-trait noir_stdlib/src/ops.nr rust +#include_code mul-trait noir_stdlib/src/ops.nr rust +#include_code div-trait noir_stdlib/src/ops.nr rust The implementations block below is given for the `Add` trait, but the same types that implement `Add` also implement `Sub`, `Mul`, and `Div`. @@ -189,11 +200,7 @@ impl Add for u64 { .. } ### `std::ops::Rem` -```rust -trait Rem { - fn rem(self, other: Self) -> Self; -} -``` +#include_code rem-trait noir_stdlib/src/ops.nr rust `Rem::rem(a, b)` is the remainder function returning the result of what is left after dividing `a` and `b`. Implementing `Rem` allows the `%` operator @@ -216,19 +223,9 @@ impl Rem for i64 { fn rem(self, other: i64) -> i64 { self % other } } ### `std::ops::{ BitOr, BitAnd, BitXor }` -```rust -trait BitOr { - fn bitor(self, other: Self) -> Self; -} - -trait BitAnd { - fn bitand(self, other: Self) -> Self; -} - -trait BitXor { - fn bitxor(self, other: Self) -> Self; -} -``` +#include_code bitor-trait noir_stdlib/src/ops.nr rust +#include_code bitand-trait noir_stdlib/src/ops.nr rust +#include_code bitxor-trait noir_stdlib/src/ops.nr rust Traits for the bitwise operations `|`, `&`, and `^`. @@ -255,15 +252,8 @@ impl BitOr for i64 { fn bitor(self, other: i64) -> i64 { self | other } } ### `std::ops::{ Shl, Shr }` -```rust -trait Shl { - fn shl(self, other: Self) -> Self; -} - -trait Shr { - fn shr(self, other: Self) -> Self; -} -``` +#include_code shl-trait noir_stdlib/src/ops.nr rust +#include_code shr-trait noir_stdlib/src/ops.nr rust Traits for a bit shift left and bit shift right. diff --git a/docs/docs/tutorials/noirjs_app.md b/docs/docs/tutorials/noirjs_app.md index 4293aa053fc..23534795dde 100644 --- a/docs/docs/tutorials/noirjs_app.md +++ b/docs/docs/tutorials/noirjs_app.md @@ -3,6 +3,7 @@ title: Building a web app with NoirJS description: Learn how to setup a new app that uses Noir to generate and verify zero-knowledge SNARK proofs in a typescript or javascript environment. keywords: [how to, guide, javascript, typescript, noir, barretenberg, zero-knowledge, proofs, app] sidebar_position: 0 +pagination_next: noir/concepts/data_types/index --- NoirJS is a set of packages meant to work both in a browser and a server environment. In this tutorial, we will build a simple web app using them. From here, you should get an idea on how to proceed with your own Noir projects! @@ -70,7 +71,7 @@ At this point in the tutorial, your folder structure should look like this: ### Node and Vite If you want to explore Nargo, feel free to go on a side-quest now and follow the steps in the -[getting started](../getting_started/create_a_project) guide. However, we want our app to run on the browser, so we need Vite. +[getting started](../getting_started/hello_noir/index.md) guide. However, we want our app to run on the browser, so we need Vite. Vite is a powerful tool to generate static websites. While it provides all kinds of features, let's just go barebones with some good old vanilla JS. @@ -205,7 +206,7 @@ We're starting with the good stuff now. If you've compiled the circuit as descri import circuit from '../circuit/target/circuit.json'; ``` -[Noir is backend-agnostic](../index.md#whats-new-about-noir). We write Noir, but we also need a proving backend. That's why we need to import and instantiate the two dependencies we installed above: `BarretenbergBackend` and `Noir`. Let's import them right below: +[Noir is backend-agnostic](../index.mdx#whats-new-about-noir). We write Noir, but we also need a proving backend. That's why we need to import and instantiate the two dependencies we installed above: `BarretenbergBackend` and `Noir`. Let's import them right below: ```js import { BarretenbergBackend } from '@noir-lang/backend_barretenberg'; @@ -273,6 +274,6 @@ You have successfully generated a client-side Noir web app! ## Further Reading -You can see how noirjs is used in a full stack Next.js hardhat application in the [noir-starter repo here](https://github.com/noir-lang/noir-starter/tree/main/next-hardhat). The example shows how to calculate a proof in the browser and verify it with a deployed Solidity verifier contract from noirjs. +You can see how noirjs is used in a full stack Next.js hardhat application in the [noir-starter repo here](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat). The example shows how to calculate a proof in the browser and verify it with a deployed Solidity verifier contract from noirjs. You should also check out the more advanced examples in the [noir-examples repo](https://github.com/noir-lang/noir-examples), where you'll find reference usage for some cool apps. diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index aacc318f5be..d1d344ba635 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -26,6 +26,7 @@ export default { '@docusaurus/preset-classic', { docs: { + path: "processed-docs", sidebarPath: './sidebars.js', routeBasePath: '/docs', remarkPlugins: [math], @@ -154,7 +155,7 @@ export default { entryPoints: ['../tooling/noir_js/src/index.ts'], tsconfig: '../tooling/noir_js/tsconfig.json', entryPointStrategy: 'resolve', - out: 'docs/reference/NoirJS/noir_js', + out: 'processed-docs/reference/NoirJS/noir_js', plugin: ['typedoc-plugin-markdown'], name: 'noir_js', disableSources: true, @@ -185,7 +186,7 @@ export default { entryPoints: ['../tooling/noir_js_backend_barretenberg/src/index.ts'], tsconfig: '../tooling/noir_js_backend_barretenberg/tsconfig.json', entryPointStrategy: 'resolve', - out: 'docs/reference/NoirJS/backend_barretenberg', + out: 'processed-docs/reference/NoirJS/backend_barretenberg', plugin: ['typedoc-plugin-markdown'], name: 'backend_barretenberg', disableSources: true, diff --git a/docs/package.json b/docs/package.json index 1e3efcfe3d1..71b624ff565 100644 --- a/docs/package.json +++ b/docs/package.json @@ -3,10 +3,12 @@ "version": "0.0.0", "private": true, "scripts": { - "start": "docusaurus start", - "build": "yarn version::stables && docusaurus build", + "preprocess": "yarn node ./scripts/preprocess/index.js", + "start": "yarn preprocess && docusaurus start", + "build": "yarn preprocess && yarn version::stables && docusaurus build", "version::stables": "ts-node ./scripts/setStable.ts", - "serve": "serve build" + "serve": "serve build", + "version": "yarn preprocess && docusaurus docs:version" }, "dependencies": { "@docusaurus/core": "^3.0.1", diff --git a/docs/scripts/preprocess/include_code.js b/docs/scripts/preprocess/include_code.js new file mode 100644 index 00000000000..ffe50065002 --- /dev/null +++ b/docs/scripts/preprocess/include_code.js @@ -0,0 +1,312 @@ +const fs = require('fs'); +const path = require('path'); +const childProcess = require('child_process'); + +const getLineNumberFromIndex = (fileContent, index) => { + return fileContent.substring(0, index).split('\n').length; +}; + +/** + * Search for lines of the form + */ +function processHighlighting(codeSnippet, identifier) { + const lines = codeSnippet.split('\n'); + /** + * For an identifier = bar: + * + * Matches of the form: `highlight-next-line:foo:bar:baz` will be replaced with "highlight-next-line". + * Matches of the form: `highlight-next-line:foo:baz` will be replaced with "". + */ + const regex1 = /highlight-next-line:([a-zA-Z0-9-._:]+)/; + const replacement1 = 'highlight-next-line'; + const regex2 = /highlight-start:([a-zA-Z0-9-._:]+)/; + const replacement2 = 'highlight-start'; + const regex3 = /highlight-end:([a-zA-Z0-9-._:]+)/; + const replacement3 = 'highlight-end'; + const regex4 = /this-will-error:([a-zA-Z0-9-._:]+)/; + const replacement4 = 'this-will-error'; + + let result = ''; + let mutated = false; + + const processLine = (line, regex, replacement) => { + const match = line.match(regex); + if (match) { + mutated = true; + + const identifiers = match[1].split(':'); + if (identifiers.includes(identifier)) { + line = line.replace(match[0], replacement); + } else { + // Remove matched text completely + line = line.replace(match[0], ''); + } + } else { + // No match: it's an ordinary line of code. + } + return line.trim() == '//' || line.trim() == '#' ? '' : line; + }; + + for (let line of lines) { + mutated = false; + line = processLine(line, regex1, replacement1); + line = processLine(line, regex2, replacement2); + line = processLine(line, regex3, replacement3); + line = processLine(line, regex4, replacement4); + result += line === '' && mutated ? '' : line + '\n'; + } + + return result.trim(); +} + +let lastReleasedVersion; + +/** Returns the last released tag */ +function getLatestTag() { + if (!lastReleasedVersion) { + const manifest = path.resolve(__dirname, '../../../.release-please-manifest.json'); + lastReleasedVersion = JSON.parse(fs.readFileSync(manifest).toString())['.']; + } + return lastReleasedVersion ? `v${lastReleasedVersion}` : undefined; +} + +/** Returns whether to use the latest release or the current version of stuff. */ +function useLastRelease() { + return process.env.NETLIFY || process.env.INCLUDE_RELEASED_CODE; +} + +/** + * Returns the contents of a file. If the build is running for publishing, it will load the contents + * of the file in the last released version. + */ +function readFile(filePath, tag) { + if (tag && tag !== 'master') { + try { + const root = path.resolve(__dirname, '../../../'); + const relPath = path.relative(root, filePath); + const taggedPath = `${tag}:${relPath}`; + return childProcess.execSync('git show', taggedPath).toString(); + } catch (err) { + console.error(`Error reading file ${filePath} from version ${tag}. Falling back to current content.`); + } + } + return fs.readFileSync(filePath, 'utf-8'); +} + +/** Extracts a code snippet, trying with the last release if applicable, and falling back to current content. */ +function extractCodeSnippet(filePath, identifier, requesterFile) { + if (useLastRelease()) { + try { + return doExtractCodeSnippet(filePath, identifier, false); + } catch (err) { + console.error( + `Error extracting code snippet ${identifier} from ${path.basename( + filePath, + )} requested by ${requesterFile}: ${err}. Falling back to current content.`, + ); + } + } + + return doExtractCodeSnippet(filePath, identifier, true); +} + +/** + * Parse a code file, looking for identifiers of the form: + * `docs:start:${identifier}` and `docs:end:{identifier}`. + * Extract that section of code. + * + * It's complicated if code snippet identifiers overlap (i.e. the 'start' of one code snippet is in the + * middle of another code snippet). The extra logic in this function searches for all identifiers, and + * removes any which fall within the bounds of the code snippet for this particular `identifier` param. + * @returns the code snippet, and start and end line numbers which can later be used for creating a link to github source code. + */ +function doExtractCodeSnippet(filePath, identifier, useCurrent) { + const tag = useCurrent ? 'master' : getLatestTag(); + let fileContent = readFile(filePath, tag); + let lineRemovalCount = 0; + let linesToRemove = []; + + const startRegex = /(?:\/\/|#)\s+docs:start:([a-zA-Z0-9-._:]+)/g; // `g` will iterate through the regex.exec loop + const endRegex = /(?:\/\/|#)\s+docs:end:([a-zA-Z0-9-._:]+)/g; + + /** + * Search for one of the regex statements in the code file. If it's found, return the line as a string and the line number. + */ + const lookForMatch = (regex) => { + let match; + let matchFound = false; + let matchedLineNum = null; + let actualMatch = null; + let lines = fileContent.split('\n'); + while ((match = regex.exec(fileContent))) { + if (match !== null) { + const identifiers = match[1].split(':'); + let tempMatch = identifiers.includes(identifier) ? match : null; + + if (tempMatch === null) { + // If it's not a match, we'll make a note that we should remove the matched text, because it's from some other identifier and should not appear in the snippet for this identifier. + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; + if (line.trim() == match[0].trim()) { + linesToRemove.push(i + 1); // lines are indexed from 1 + ++lineRemovalCount; + } + } + } else { + if (matchFound === true) { + throw new Error(`Duplicate for regex ${regex} and identifier ${identifier}`); + } + matchFound = true; + matchedLineNum = getLineNumberFromIndex(fileContent, tempMatch.index); + actualMatch = tempMatch; + } + } + } + + return [actualMatch, matchedLineNum]; + }; + + let [startMatch, startLineNum] = lookForMatch(startRegex); + let [endMatch, endLineNum] = lookForMatch(endRegex); + + // Double-check that the extracted line actually contains the required start and end identifier. + if (startMatch !== null) { + const startIdentifiers = startMatch[1].split(':'); + startMatch = startIdentifiers.includes(identifier) ? startMatch : null; + } + if (endMatch !== null) { + const endIdentifiers = endMatch[1].split(':'); + endMatch = endIdentifiers.includes(identifier) ? endMatch : null; + } + + if (startMatch === null || endMatch === null) { + if (startMatch === null && endMatch === null) { + throw new Error(`Identifier "${identifier}" not found in file "${filePath}"`); + } else if (startMatch === null) { + throw new Error(`Start line "docs:start:${identifier}" not found in file "${filePath}"`); + } else { + throw new Error(`End line "docs:end:${identifier}" not found in file "${filePath}"`); + } + } + + let lines = fileContent.split('\n'); + + // We only want to remove lines which actually fall within the bounds of our code snippet, so narrow down the list of lines that we actually want to remove. + linesToRemove = linesToRemove.filter((lineNum) => { + const removal_in_bounds = lineNum >= startLineNum && lineNum <= endLineNum; + return removal_in_bounds; + }); + + // Remove lines which contain `docs:` comments for unrelated identifiers: + lines = lines.filter((l, i) => { + return !linesToRemove.includes(i + 1); // lines are indexed from 1 + }); + + // Remove lines from the snippet which fall outside the `docs:start` and `docs:end` values. + lines = lines.filter((l, i) => { + return i + 1 > startLineNum && i + 1 < endLineNum - linesToRemove.length; // lines are indexed from 1 + }); + + // We have our code snippet! + let codeSnippet = lines.join('\n'); + + // The code snippet might contain some docusaurus highlighting comments for other identifiers. We should remove those. + codeSnippet = processHighlighting(codeSnippet, identifier); + + return [codeSnippet, startLineNum, endLineNum, tag]; +} + +/** + * Explaining this regex: + * + * E.g. `#include_code snippet_identifier /circuits/my_code.cpp cpp` + * + * #include_code\s+(\S+)\s+(\S+)\s+(\S+) + * - This is the main regex to match the above format. + * - \s+: one or more whitespace characters (space or tab) after `include_code` command. + * - (\S+): one or more non-whitespaced characters. Captures this as the first argument, which is a human-readable identifier for the code block. + * - etc. + * + * Lookaheads are needed to allow us to ignore commented-out lines: + * + * ^(?! feet` binding in the example above. diff --git a/docs/versioned_docs/version-v0.23.0/noir/concepts/data_types/tuples.md b/docs/versioned_docs/version-v0.23.0/noir/concepts/data_types/tuples.md new file mode 100644 index 00000000000..2ec5c9c4113 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/concepts/data_types/tuples.md @@ -0,0 +1,48 @@ +--- +title: Tuples +description: + Dive into the Tuple data type in Noir. Understand its methods, practical examples, and best practices for efficiently using Tuples in your Noir code. +keywords: + [ + noir, + tuple type, + methods, + examples, + multi-value containers, + ] +sidebar_position: 7 +--- + +A tuple collects multiple values like an array, but with the added ability to collect values of +different types: + +```rust +fn main() { + let tup: (u8, u64, Field) = (255, 500, 1000); +} +``` + +One way to access tuple elements is via destructuring using pattern matching: + +```rust +fn main() { + let tup = (1, 2); + + let (one, two) = tup; + + let three = one + two; +} +``` + +Another way to access tuple elements is via direct member access, using a period (`.`) followed by +the index of the element we want to access. Index `0` corresponds to the first tuple element, `1` to +the second and so on: + +```rust +fn main() { + let tup = (5, 6, 7, 8); + + let five = tup.0; + let eight = tup.3; +} +``` diff --git a/docs/versioned_docs/version-v0.23.0/noir/concepts/data_types/vectors.mdx b/docs/versioned_docs/version-v0.23.0/noir/concepts/data_types/vectors.mdx new file mode 100644 index 00000000000..aed13183719 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/concepts/data_types/vectors.mdx @@ -0,0 +1,171 @@ +--- +title: Vectors +description: Delve into the Vector data type in Noir. Learn about its methods, practical examples, and best practices for using Vectors in your Noir code. +keywords: [noir, vector type, methods, examples, dynamic arrays] +sidebar_position: 6 +--- + +import Experimental from '@site/src/components/Notes/_experimental.mdx'; + + + +A vector is a collection type similar to Rust's Vector type. It's convenient way to use slices as mutable arrays. + +Example: + +```rust +let mut vector: Vec = Vec::new(); +for i in 0..5 { + vector.push(i); +} +assert(vector.len() == 5); +``` + +## Methods + +### new + +Creates a new, empty vector. + +```rust +pub fn new() -> Self { + Self { slice: [] } +} +``` + +Example: + +```rust +let empty_vector: Vec = Vec::new(); +assert(empty_vector.len() == 0); +``` + +### from_slice + +Creates a vector containing each element from a given slice. Mutations to the resulting vector will not affect the original slice. + +```rust +pub fn from_slice(slice: [T]) -> Self { + Self { slice } +} +``` + +Example: + +```rust +let arr: [Field] = [1, 2, 3]; +let vector_from_slice = Vec::from_slice(arr); +assert(vector_from_slice.len() == 3); +``` + +### get + +Retrieves an element from the vector at a given index. Panics if the index points beyond the vector's end. + +```rust +pub fn get(self, index: Field) -> T { + self.slice[index] +} +``` + +Example: + +```rust +let vector: Vec = Vec::from_slice([10, 20, 30]); +assert(vector.get(1) == 20); +``` + +### push + +Adds a new element to the vector's end, returning a new vector with a length one greater than the original unmodified vector. + +```rust +pub fn push(&mut self, elem: T) { + self.slice = self.slice.push_back(elem); +} +``` + +Example: + +```rust +let mut vector: Vec = Vec::new(); +vector.push(10); +assert(vector.len() == 1); +``` + +### pop + +Removes an element from the vector's end, returning a new vector with a length one less than the original vector, along with the removed element. Panics if the vector's length is zero. + +```rust +pub fn pop(&mut self) -> T { + let (popped_slice, last_elem) = self.slice.pop_back(); + self.slice = popped_slice; + last_elem +} +``` + +Example: + +```rust +let mut vector = Vec::from_slice([10, 20]); +let popped_elem = vector.pop(); +assert(popped_elem == 20); +assert(vector.len() == 1); +``` + +### insert + +Inserts an element at a specified index, shifting subsequent elements to the right. + +```rust +pub fn insert(&mut self, index: Field, elem: T) { + self.slice = self.slice.insert(index, elem); +} +``` + +Example: + +```rust +let mut vector = Vec::from_slice([10, 30]); +vector.insert(1, 20); +assert(vector.get(1) == 20); +``` + +### remove + +Removes an element at a specified index, shifting subsequent elements to the left, and returns the removed element. + +```rust +pub fn remove(&mut self, index: Field) -> T { + let (new_slice, elem) = self.slice.remove(index); + self.slice = new_slice; + elem +} +``` + +Example: + +```rust +let mut vector = Vec::from_slice([10, 20, 30]); +let removed_elem = vector.remove(1); +assert(removed_elem == 20); +assert(vector.len() == 2); +``` + +### len + +Returns the number of elements in the vector. + +```rust +pub fn len(self) -> Field { + self.slice.len() +} +``` + +Example: + +```rust +let empty_vector: Vec = Vec::new(); +assert(empty_vector.len() == 0); +``` diff --git a/docs/versioned_docs/version-v0.23.0/noir/concepts/distinct.md b/docs/versioned_docs/version-v0.23.0/noir/concepts/distinct.md new file mode 100644 index 00000000000..6c993b8b5e0 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/concepts/distinct.md @@ -0,0 +1,64 @@ +--- +title: Distinct Witnesses +sidebar_position: 11 +--- + +The `distinct` keyword prevents repetitions of witness indices in the program's ABI. This ensures +that the witnesses being returned as public inputs are all unique. + +The `distinct` keyword is only used for return values on program entry points (usually the `main()` +function). + +When using `distinct` and `pub` simultaneously, `distinct` comes first. See the example below. + +You can read more about the problem this solves +[here](https://github.com/noir-lang/noir/issues/1183). + +## Example + +Without the `distinct` keyword, the following program + +```rust +fn main(x : pub Field, y : pub Field) -> pub [Field; 4] { + let a = 1; + let b = 1; + [x + 1, y, a, b] +} +``` + +compiles to + +```json +{ + //... + "abi": { + //... + "param_witnesses": { "x": [1], "y": [2] }, + "return_witnesses": [3, 2, 4, 4] + } +} +``` + +Whereas (with the `distinct` keyword) + +```rust +fn main(x : pub Field, y : pub Field) -> distinct pub [Field; 4] { + let a = 1; + let b = 1; + [x + 1, y, a, b] +} +``` + +compiles to + +```json +{ + //... + "abi": { + //... + "param_witnesses": { "x": [1], "y": [2] }, + //... + "return_witnesses": [3, 4, 5, 6] + } +} +``` diff --git a/docs/versioned_docs/version-v0.23.0/noir/concepts/functions.md b/docs/versioned_docs/version-v0.23.0/noir/concepts/functions.md new file mode 100644 index 00000000000..48aba9cd058 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/concepts/functions.md @@ -0,0 +1,226 @@ +--- +title: Functions +description: + Learn how to declare functions and methods in Noir, a programming language with Rust semantics. + This guide covers parameter declaration, return types, call expressions, and more. +keywords: [Noir, Rust, functions, methods, parameter declaration, return types, call expressions] +sidebar_position: 1 +--- + +Functions in Noir follow the same semantics of Rust, though Noir does not support early returns. + +To declare a function the `fn` keyword is used. + +```rust +fn foo() {} +``` + +By default, functions are visible only within the package they are defined. To make them visible outside of that package (for example, as part of a [library](../modules_packages_crates/crates_and_packages.md#libraries)), you should mark them as `pub`: + +```rust +pub fn foo() {} +``` + +You can also restrict the visibility of the function to only the crate it was defined in, by specifying `pub(crate)`: + +```rust +pub(crate) fn foo() {} //foo can only be called within its crate +``` + +All parameters in a function must have a type and all types are known at compile time. The parameter +is pre-pended with a colon and the parameter type. Multiple parameters are separated using a comma. + +```rust +fn foo(x : Field, y : Field){} +``` + +The return type of a function can be stated by using the `->` arrow notation. The function below +states that the foo function must return a `Field`. If the function returns no value, then the arrow +is omitted. + +```rust +fn foo(x : Field, y : Field) -> Field { + x + y +} +``` + +Note that a `return` keyword is unneeded in this case - the last expression in a function's body is +returned. + +## Main function + +If you're writing a binary, the `main` function is the starting point of your program. You can pass all types of expressions to it, as long as they have a fixed size at compile time: + +```rust +fn main(x : Field) // this is fine: passing a Field +fn main(x : [Field; 2]) // this is also fine: passing a Field with known size at compile-time +fn main(x : (Field, bool)) // 👌: passing a (Field, bool) tuple means size 2 +fn main(x : str<5>) // this is fine, as long as you pass a string of size 5 + +fn main(x : Vec) // can't compile, has variable size +fn main(x : [Field]) // can't compile, has variable size +fn main(....// i think you got it by now +``` + +Keep in mind [tests](../../getting_started/tooling/testing.md) don't differentiate between `main` and any other function. The following snippet passes tests, but won't compile or prove: + +```rust +fn main(x : [Field]) { + assert(x[0] == 1); +} + +#[test] +fn test_one() { + main([1, 2]); +} +``` + +```bash +$ nargo test +[testing] Running 1 test functions +[testing] Testing test_one... ok +[testing] All tests passed + +$ nargo check +The application panicked (crashed). +Message: Cannot have variable sized arrays as a parameter to main +``` + +## Call Expressions + +Calling a function in Noir is executed by using the function name and passing in the necessary +arguments. + +Below we show how to call the `foo` function from the `main` function using a call expression: + +```rust +fn main(x : Field, y : Field) { + let z = foo(x); +} + +fn foo(x : Field) -> Field { + x + x +} +``` + +## Methods + +You can define methods in Noir on any struct type in scope. + +```rust +struct MyStruct { + foo: Field, + bar: Field, +} + +impl MyStruct { + fn new(foo: Field) -> MyStruct { + MyStruct { + foo, + bar: 2, + } + } + + fn sum(self) -> Field { + self.foo + self.bar + } +} + +fn main() { + let s = MyStruct::new(40); + assert(s.sum() == 42); +} +``` + +Methods are just syntactic sugar for functions, so if we wanted to we could also call `sum` as +follows: + +```rust +assert(MyStruct::sum(s) == 42); +``` + +It is also possible to specialize which method is chosen depending on the [generic](./generics.md) type that is used. In this example, the `foo` function returns different values depending on its type: + +```rust +struct Foo {} + +impl Foo { + fn foo(self) -> Field { 1 } +} + +impl Foo { + fn foo(self) -> Field { 2 } +} + +fn main() { + let f1: Foo = Foo{}; + let f2: Foo = Foo{}; + assert(f1.foo() + f2.foo() == 3); +} +``` + +Also note that impls with the same method name defined in them cannot overlap. For example, if we already have `foo` defined for `Foo` and `Foo` like we do above, we cannot also define `foo` in an `impl Foo` since it would be ambiguous which version of `foo` to choose. + +```rust +// Including this impl in the same project as the above snippet would +// cause an overlapping impls error +impl Foo { + fn foo(self) -> Field { 3 } +} +``` + +## Lambdas + +Lambdas are anonymous functions. They follow the syntax of Rust - `|arg1, arg2, ..., argN| return_expression`. + +```rust +let add_50 = |val| val + 50; +assert(add_50(100) == 150); +``` + +See [Lambdas](./lambdas.md) for more details. + +## Attributes + +Attributes are metadata that can be applied to a function, using the following syntax: `#[attribute(value)]`. + +Supported attributes include: + +- **builtin**: the function is implemented by the compiler, for efficiency purposes. +- **deprecated**: mark the function as _deprecated_. Calling the function will generate a warning: `warning: use of deprecated function` +- **field**: Used to enable conditional compilation of code depending on the field size. See below for more details +- **oracle**: mark the function as _oracle_; meaning it is an external unconstrained function, implemented in noir_js. See [Unconstrained](./unconstrained.md) and [NoirJS](../../reference/NoirJS/noir_js/index.md) for more details. +- **test**: mark the function as unit tests. See [Tests](../../getting_started/tooling/testing.md) for more details + +### Field Attribute + +The field attribute defines which field the function is compatible for. The function is conditionally compiled, under the condition that the field attribute matches the Noir native field. +The field can be defined implicitly, by using the name of the elliptic curve usually associated to it - for instance bn254, bls12_381 - or explicitly by using the field (prime) order, in decimal or hexadecimal form. +As a result, it is possible to define multiple versions of a function with each version specialized for a different field attribute. This can be useful when a function requires different parameters depending on the underlying elliptic curve. + +Example: we define the function `foo()` three times below. Once for the default Noir bn254 curve, once for the field $\mathbb F_{23}$, which will normally never be used by Noir, and once again for the bls12_381 curve. + +```rust +#[field(bn254)] +fn foo() -> u32 { + 1 +} + +#[field(23)] +fn foo() -> u32 { + 2 +} + +// This commented code would not compile as foo would be defined twice because it is the same field as bn254 +// #[field(21888242871839275222246405745257275088548364400416034343698204186575808495617)] +// fn foo() -> u32 { +// 2 +// } + +#[field(bls12_381)] +fn foo() -> u32 { + 3 +} +``` + +If the field name is not known to Noir, it will discard the function. Field names are case insensitive. diff --git a/docs/versioned_docs/version-v0.23.0/noir/concepts/generics.md b/docs/versioned_docs/version-v0.23.0/noir/concepts/generics.md new file mode 100644 index 00000000000..ddd42bf1f9b --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/concepts/generics.md @@ -0,0 +1,106 @@ +--- +title: Generics +description: Learn how to use Generics in Noir +keywords: [Noir, Rust, generics, functions, structs] +sidebar_position: 7 +--- + +Generics allow you to use the same functions with multiple different concrete data types. You can +read more about the concept of generics in the Rust documentation +[here](https://doc.rust-lang.org/book/ch10-01-syntax.html). + +Here is a trivial example showing the identity function that supports any type. In Rust, it is +common to refer to the most general type as `T`. We follow the same convention in Noir. + +```rust +fn id(x: T) -> T { + x +} +``` + +## In Structs + +Generics are useful for specifying types in structs. For example, we can specify that a field in a +struct will be of a certain generic type. In this case `value` is of type `T`. + +```rust +struct RepeatedValue { + value: T, + count: Field, +} + +impl RepeatedValue { + fn print(self) { + for _i in 0 .. self.count { + println(self.value); + } + } +} + +fn main() { + let repeated = RepeatedValue { value: "Hello!", count: 2 }; + repeated.print(); +} +``` + +The `print` function will print `Hello!` an arbitrary number of times, twice in this case. + +If we want to be generic over array lengths (which are type-level integers), we can use numeric +generics. Using these looks just like using regular generics, but these generics can resolve to +integers at compile-time, rather than resolving to types. Here's an example of a struct that is +generic over the size of the array it contains internally: + +```rust +struct BigInt { + limbs: [u32; N], +} + +impl BigInt { + // `N` is in scope of all methods in the impl + fn first(first: BigInt, second: BigInt) -> Self { + assert(first.limbs != second.limbs); + first + + fn second(first: BigInt, second: Self) -> Self { + assert(first.limbs != second.limbs); + second + } +} +``` + +## Calling functions on generic parameters + +Since a generic type `T` can represent any type, how can we call functions on the underlying type? +In other words, how can we go from "any type `T`" to "any type `T` that has certain methods available?" + +This is what [traits](../concepts/traits) are for in Noir. Here's an example of a function generic over +any type `T` that implements the `Eq` trait for equality: + +```rust +fn first_element_is_equal(array1: [T; N], array2: [T; N]) -> bool + where T: Eq +{ + if (array1.len() == 0) | (array2.len() == 0) { + true + } else { + array1[0] == array2[0] + } +} + +fn main() { + assert(first_element_is_equal([1, 2, 3], [1, 5, 6])); + + // We can use first_element_is_equal for arrays of any type + // as long as we have an Eq impl for the types we pass in + let array = [MyStruct::new(), MyStruct::new()]; + assert(array_eq(array, array, MyStruct::eq)); +} + +impl Eq for MyStruct { + fn eq(self, other: MyStruct) -> bool { + self.foo == other.foo + } +} +``` + +You can find more details on traits and trait implementations on the [traits page](../concepts/traits). diff --git a/docs/versioned_docs/version-v0.23.0/noir/concepts/lambdas.md b/docs/versioned_docs/version-v0.23.0/noir/concepts/lambdas.md new file mode 100644 index 00000000000..be3c7e0b5ca --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/concepts/lambdas.md @@ -0,0 +1,81 @@ +--- +title: Lambdas +description: Learn how to use anonymous functions in Noir programming language. +keywords: [Noir programming language, lambda, closure, function, anonymous function] +sidebar_position: 9 +--- + +## Introduction + +Lambdas are anonymous functions. The syntax is `|arg1, arg2, ..., argN| return_expression`. + +```rust +let add_50 = |val| val + 50; +assert(add_50(100) == 150); +``` + +A block can be used as the body of a lambda, allowing you to declare local variables inside it: + +```rust +let cool = || { + let x = 100; + let y = 100; + x + y +} + +assert(cool() == 200); +``` + +## Closures + +Inside the body of a lambda, you can use variables defined in the enclosing function. Such lambdas are called **closures**. In this example `x` is defined inside `main` and is accessed from within the lambda: + +```rust +fn main() { + let x = 100; + let closure = || x + 150; + assert(closure() == 250); +} +``` + +## Passing closures to higher-order functions + +It may catch you by surprise that the following code fails to compile: + +```rust +fn foo(f: fn () -> Field) -> Field { + f() +} + +fn main() { + let (x, y) = (50, 50); + assert(foo(|| x + y) == 100); // error :( +} +``` + +The reason is that the closure's capture environment affects its type - we have a closure that captures two Fields and `foo` +expects a regular function as an argument - those are incompatible. +:::note + +Variables contained within the `||` are the closure's parameters, and the expression that follows it is the closure's body. The capture environment is comprised of any variables used in the closure's body that are not parameters. + +E.g. in |x| x + y, y would be a captured variable, but x would not be, since it is a parameter of the closure. + +::: +The syntax for the type of a closure is `fn[env](args) -> ret_type`, where `env` is the capture environment of the closure - +in this example that's `(Field, Field)`. + +The best solution in our case is to make `foo` generic over the environment type of its parameter, so that it can be called +with closures with any environment, as well as with regular functions: + +```rust +fn foo(f: fn[Env]() -> Field) -> Field { + f() +} + +fn main() { + let (x, y) = (50, 50); + assert(foo(|| x + y) == 100); // compiles fine + assert(foo(|| 60) == 60); // compiles fine +} +``` diff --git a/docs/versioned_docs/version-v0.23.0/noir/concepts/mutability.md b/docs/versioned_docs/version-v0.23.0/noir/concepts/mutability.md new file mode 100644 index 00000000000..9cc10429cb4 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/concepts/mutability.md @@ -0,0 +1,93 @@ +--- +title: Mutability +description: + Learn about mutable variables, constants, and globals in Noir programming language. Discover how + to declare, modify, and use them in your programs. +keywords: [noir programming language, mutability in noir, mutable variables, constants, globals] +sidebar_position: 8 +--- + +Variables in noir can be declared mutable via the `mut` keyword. Mutable variables can be reassigned +to via an assignment expression. + +```rust +let x = 2; +x = 3; // error: x must be mutable to be assigned to + +let mut y = 3; +let y = 4; // OK +``` + +The `mut` modifier can also apply to patterns: + +```rust +let (a, mut b) = (1, 2); +a = 11; // error: a must be mutable to be assigned to +b = 12; // OK + +let mut (c, d) = (3, 4); +c = 13; // OK +d = 14; // OK + +// etc. +let MyStruct { x: mut y } = MyStruct { x: a }; +// y is now in scope +``` + +Note that mutability in noir is local and everything is passed by value, so if a called function +mutates its parameters then the parent function will keep the old value of the parameters. + +```rust +fn main() -> pub Field { + let x = 3; + helper(x); + x // x is still 3 +} + +fn helper(mut x: i32) { + x = 4; +} +``` + +## Comptime Values + +:::warning + +The 'comptime' keyword was removed in version 0.10. The comptime keyword and syntax are currently still kept and parsed for backwards compatibility, but are now deprecated and will issue a warning when used. `comptime` has been removed because it is no longer needed for accessing arrays. + +::: + +## Globals + +Noir also supports global variables. However, they must be known at compile-time. The global type can also be inferred by the compiler entirely. Globals can also be used to specify array +annotations for function parameters and can be imported from submodules. + +```rust +global N: Field = 5; // Same as `global N: Field = 5` + +fn main(x : Field, y : [Field; N]) { + let res = x * N; + + assert(res == y[0]); + + let res2 = x * my_submodule::N; + assert(res != res2); +} + +mod my_submodule { + use dep::std; + + global N: Field = 10; + + fn my_helper() -> Field { + let x = N; + x + } +} +``` + +## Why only local mutability? + +Witnesses in a proving system are immutable in nature. Noir aims to _closely_ mirror this setting +without applying additional overhead to the user. Modeling a mutable reference is not as +straightforward as on conventional architectures and would incur some possibly unexpected overhead. diff --git a/docs/versioned_docs/version-v0.23.0/noir/concepts/ops.md b/docs/versioned_docs/version-v0.23.0/noir/concepts/ops.md new file mode 100644 index 00000000000..60425cb8994 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/concepts/ops.md @@ -0,0 +1,98 @@ +--- +title: Logical Operations +description: + Learn about the supported arithmetic and logical operations in the Noir programming language. + Discover how to perform operations on private input types, integers, and booleans. +keywords: + [ + Noir programming language, + supported operations, + arithmetic operations, + logical operations, + predicate operators, + bitwise operations, + short-circuiting, + backend, + ] +sidebar_position: 3 +--- + +# Operations + +## Table of Supported Operations + +| Operation | Description | Requirements | +| :-------- | :------------------------------------------------------------: | -------------------------------------: | +| + | Adds two private input types together | Types must be private input | +| - | Subtracts two private input types together | Types must be private input | +| \* | Multiplies two private input types together | Types must be private input | +| / | Divides two private input types together | Types must be private input | +| ^ | XOR two private input types together | Types must be integer | +| & | AND two private input types together | Types must be integer | +| \| | OR two private input types together | Types must be integer | +| \<\< | Left shift an integer by another integer amount | Types must be integer | +| >> | Right shift an integer by another integer amount | Types must be integer | +| ! | Bitwise not of a value | Type must be integer or boolean | +| \< | returns a bool if one value is less than the other | Upper bound must have a known bit size | +| \<= | returns a bool if one value is less than or equal to the other | Upper bound must have a known bit size | +| > | returns a bool if one value is more than the other | Upper bound must have a known bit size | +| >= | returns a bool if one value is more than or equal to the other | Upper bound must have a known bit size | +| == | returns a bool if one value is equal to the other | Both types must not be constants | +| != | returns a bool if one value is not equal to the other | Both types must not be constants | + +### Predicate Operators + +`<,<=, !=, == , >, >=` are known as predicate/comparison operations because they compare two values. +This differs from the operations such as `+` where the operands are used in _computation_. + +### Bitwise Operations Example + +```rust +fn main(x : Field) { + let y = x as u32; + let z = y & y; +} +``` + +`z` is implicitly constrained to be the result of `y & y`. The `&` operand is used to denote bitwise +`&`. + +> `x & x` would not compile as `x` is a `Field` and not an integer type. + +### Logical Operators + +Noir has no support for the logical operators `||` and `&&`. This is because encoding the +short-circuiting that these operators require can be inefficient for Noir's backend. Instead you can +use the bitwise operators `|` and `&` which operate identically for booleans, just without the +short-circuiting. + +```rust +let my_val = 5; + +let mut flag = 1; +if (my_val > 6) | (my_val == 0) { + flag = 0; +} +assert(flag == 1); + +if (my_val != 10) & (my_val < 50) { + flag = 0; +} +assert(flag == 0); +``` + +### Shorthand operators + +Noir shorthand operators for most of the above operators, namely `+=, -=, *=, /=, %=, &=, |=, ^=, <<=`, and `>>=`. These allow for more concise syntax. For example: + +```rust +let mut i = 0; +i = i + 1; +``` + +could be written as: + +```rust +let mut i = 0; +i += 1; +``` diff --git a/docs/versioned_docs/version-v0.22.0/noir/syntax/oracles.md b/docs/versioned_docs/version-v0.23.0/noir/concepts/oracles.md similarity index 100% rename from docs/versioned_docs/version-v0.22.0/noir/syntax/oracles.md rename to docs/versioned_docs/version-v0.23.0/noir/concepts/oracles.md diff --git a/docs/versioned_docs/version-v0.23.0/noir/concepts/shadowing.md b/docs/versioned_docs/version-v0.23.0/noir/concepts/shadowing.md new file mode 100644 index 00000000000..5ce6130d201 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/concepts/shadowing.md @@ -0,0 +1,44 @@ +--- +title: Shadowing +sidebar_position: 12 +--- + +Noir allows for inheriting variables' values and re-declaring them with the same name similar to Rust, known as shadowing. + +For example, the following function is valid in Noir: + +```rust +fn main() { + let x = 5; + + { + let x = x * 2; + assert (x == 10); + } + + assert (x == 5); +} +``` + +In this example, a variable x is first defined with the value 5. + +The local scope that follows shadows the original x, i.e. creates a local mutable x based on the value of the original x. It is given a value of 2 times the original x. + +When we return to the main scope, x once again refers to just the original x, which stays at the value of 5. + +## Temporal mutability + +One way that shadowing is useful, in addition to ergonomics across scopes, is for temporarily mutating variables. + +```rust +fn main() { + let age = 30; + // age = age + 5; // Would error as `age` is immutable by default. + + let mut age = age + 5; // Temporarily mutates `age` with a new value. + + let age = age; // Locks `age`'s mutability again. + + assert (age == 35); +} +``` diff --git a/docs/versioned_docs/version-v0.23.0/noir/concepts/traits.md b/docs/versioned_docs/version-v0.23.0/noir/concepts/traits.md new file mode 100644 index 00000000000..ef1445a5907 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/concepts/traits.md @@ -0,0 +1,389 @@ +--- +title: Traits +description: + Traits in Noir can be used to abstract out a common interface for functions across + several data types. +keywords: [noir programming language, traits, interfaces, generic, protocol] +sidebar_position: 14 +--- + +## Overview + +Traits in Noir are a useful abstraction similar to interfaces or protocols in other languages. Each trait defines +the interface of several methods contained within the trait. Types can then implement this trait by providing +implementations for these methods. For example in the program: + +```rust +struct Rectangle { + width: Field, + height: Field, +} + +impl Rectangle { + fn area(self) -> Field { + self.width * self.height + } +} + +fn log_area(r: Rectangle) { + println(r.area()); +} +``` + +We have a function `log_area` to log the area of a `Rectangle`. Now how should we change the program if we want this +function to work on `Triangle`s as well?: + +```rust +struct Triangle { + width: Field, + height: Field, +} + +impl Triangle { + fn area(self) -> Field { + self.width * self.height / 2 + } +} +``` + +Making `log_area` generic over all types `T` would be invalid since not all types have an `area` method. Instead, we can +introduce a new `Area` trait and make `log_area` generic over all types `T` that implement `Area`: + +```rust +trait Area { + fn area(self) -> Field; +} + +fn log_area(shape: T) where T: Area { + println(shape.area()); +} +``` + +We also need to explicitly implement `Area` for `Rectangle` and `Triangle`. We can do that by changing their existing +impls slightly. Note that the parameter types and return type of each of our `area` methods must match those defined +by the `Area` trait. + +```rust +impl Area for Rectangle { + fn area(self) -> Field { + self.width * self.height + } +} + +impl Area for Triangle { + fn area(self) -> Field { + self.width * self.height / 2 + } +} +``` + +Now we have a working program that is generic over any type of Shape that is used! Others can even use this program +as a library with their own types - such as `Circle` - as long as they also implement `Area` for these types. + +## Where Clauses + +As seen in `log_area` above, when we want to create a function or method that is generic over any type that implements +a trait, we can add a where clause to the generic function. + +```rust +fn log_area(shape: T) where T: Area { + println(shape.area()); +} +``` + +It is also possible to apply multiple trait constraints on the same variable at once by combining traits with the `+` +operator. Similarly, we can have multiple trait constraints by separating each with a comma: + +```rust +fn foo(elements: [T], thing: U) where + T: Default + Add + Eq, + U: Bar, +{ + let mut sum = T::default(); + + for element in elements { + sum += element; + } + + if sum == T::default() { + thing.bar(); + } +} +``` + +## Generic Implementations + +You can add generics to a trait implementation by adding the generic list after the `impl` keyword: + +```rust +trait Second { + fn second(self) -> Field; +} + +impl Second for (T, Field) { + fn second(self) -> Field { + self.1 + } +} +``` + +You can also implement a trait for every type this way: + +```rust +trait Debug { + fn debug(self); +} + +impl Debug for T { + fn debug(self) { + println(self); + } +} + +fn main() { + 1.debug(); +} +``` + +### Generic Trait Implementations With Where Clauses + +Where clauses can also be placed on trait implementations themselves to restrict generics in a similar way. +For example, while `impl Foo for T` implements the trait `Foo` for every type, `impl Foo for T where T: Bar` +will implement `Foo` only for types that also implement `Bar`. This is often used for implementing generic types. +For example, here is the implementation for array equality: + +```rust +impl Eq for [T; N] where T: Eq { + // Test if two arrays have the same elements. + // Because both arrays must have length N, we know their lengths already match. + fn eq(self, other: Self) -> bool { + let mut result = true; + + for i in 0 .. self.len() { + // The T: Eq constraint is needed to call == on the array elements here + result &= self[i] == other[i]; + } + + result + } +} +``` + +## Generic Traits + +Traits themselves can also be generic by placing the generic arguments after the trait name. These generics are in +scope of every item within the trait. + +```rust +trait Into { + // Convert `self` to type `T` + fn into(self) -> T; +} +``` + +When implementing generic traits the generic arguments of the trait must be specified. This is also true anytime +when referencing a generic trait (e.g. in a `where` clause). + +```rust +struct MyStruct { + array: [Field; 2], +} + +impl Into<[Field; 2]> for MyStruct { + fn into(self) -> [Field; 2] { + self.array + } +} + +fn as_array(x: T) -> [Field; 2] + where T: Into<[Field; 2]> +{ + x.into() +} + +fn main() { + let array = [1, 2]; + let my_struct = MyStruct { array }; + + assert_eq(as_array(my_struct), array); +} +``` + +## Trait Methods With No `self` + +A trait can contain any number of methods, each of which have access to the `Self` type which represents each type +that eventually implements the trait. Similarly, the `self` variable is available as well but is not required to be used. +For example, we can define a trait to create a default value for a type. This trait will need to return the `Self` type +but doesn't need to take any parameters: + +```rust +trait Default { + fn default() -> Self; +} +``` + +Implementing this trait can be done similarly to any other trait: + +```rust +impl Default for Field { + fn default() -> Field { + 0 + } +} + +struct MyType {} + +impl Default for MyType { + fn default() -> Field { + MyType {} + } +} +``` + +However, since there is no `self` parameter, we cannot call it via the method call syntax `object.method()`. +Instead, we'll need to refer to the function directly. This can be done either by referring to the +specific impl `MyType::default()` or referring to the trait itself `Default::default()`. In the later +case, type inference determines the impl that is selected. + +```rust +let my_struct = MyStruct::default(); + +let x: Field = Default::default(); +let result = x + Default::default(); +``` + +:::warning + +```rust +let _ = Default::default(); +``` + +If type inference cannot select which impl to use because of an ambiguous `Self` type, an impl will be +arbitrarily selected. This occurs most often when the result of a trait function call with no parameters +is unused. To avoid this, when calling a trait function with no `self` or `Self` parameters or return type, +always refer to it via the implementation type's namespace - e.g. `MyType::default()`. +This is set to change to an error in future Noir versions. + +::: + +## Default Method Implementations + +A trait can also have default implementations of its methods by giving a body to the desired functions. +Note that this body must be valid for all types that may implement the trait. As a result, the only +valid operations on `self` will be operations valid for any type or other operations on the trait itself. + +```rust +trait Numeric { + fn add(self, other: Self) -> Self; + + // Default implementation of double is (self + self) + fn double(self) -> Self { + self.add(self) + } +} +``` + +When implementing a trait with default functions, a type may choose to implement only the required functions: + +```rust +impl Numeric for Field { + fn add(self, other: Field) -> Field { + self + other + } +} +``` + +Or it may implement the optional methods as well: + +```rust +impl Numeric for u32 { + fn add(self, other: u32) -> u32 { + self + other + } + + fn double(self) -> u32 { + self * 2 + } +} +``` + +## Impl Specialization + +When implementing traits for a generic type it is possible to implement the trait for only a certain combination +of generics. This can be either as an optimization or because those specific generics are required to implement the trait. + +```rust +trait Sub { + fn sub(self, other: Self) -> Self; +} + +struct NonZero { + value: T, +} + +impl Sub for NonZero { + fn sub(self, other: Self) -> Self { + let value = self.value - other.value; + assert(value != 0); + NonZero { value } + } +} +``` + +## Overlapping Implementations + +Overlapping implementations are disallowed by Noir to ensure Noir's decision on which impl to select is never ambiguous. +This means if a trait `Foo` is already implemented +by a type `Bar` for all `T`, then we cannot also have a separate impl for `Bar` (or any other +type argument). Similarly, if there is an impl for all `T` such as `impl Debug for T`, we cannot create +any more impls to `Debug` for other types since it would be ambiguous which impl to choose for any given +method call. + +```rust +trait Trait {} + +// Previous impl defined here +impl Trait for (A, B) {} + +// error: Impl for type `(Field, Field)` overlaps with existing impl +impl Trait for (Field, Field) {} +``` + +## Trait Coherence + +Another restriction on trait implementations is coherence. This restriction ensures other crates cannot create +impls that may overlap with other impls, even if several unrelated crates are used as dependencies in the same +program. + +The coherence restriction is: to implement a trait, either the trait itself or the object type must be declared +in the crate the impl is in. + +In practice this often comes up when using types provided by libraries. If a library provides a type `Foo` that does +not implement a trait in the standard library such as `Default`, you may not `impl Default for Foo` in your own crate. +While restrictive, this prevents later issues or silent changes in the program if the `Foo` library later added its +own impl for `Default`. If you are a user of the `Foo` library in this scenario and need a trait not implemented by the +library your choices are to either submit a patch to the library or use the newtype pattern. + +### The Newtype Pattern + +The newtype pattern gets around the coherence restriction by creating a new wrapper type around the library type +that we cannot create `impl`s for. Since the new wrapper type is defined in our current crate, we can create +impls for any trait we need on it. + +```rust +struct Wrapper { + foo: dep::some_library::Foo, +} + +impl Default for Wrapper { + fn default() -> Wrapper { + Wrapper { + foo: dep::some_library::Foo::new(), + } + } +} +``` + +Since we have an impl for our own type, the behavior of this code will not change even if `some_library` is updated +to provide its own `impl Default for Foo`. The downside of this pattern is that it requires extra wrapping and +unwrapping of values when converting to and from the `Wrapper` and `Foo` types. diff --git a/docs/versioned_docs/version-v0.23.0/noir/concepts/unconstrained.md b/docs/versioned_docs/version-v0.23.0/noir/concepts/unconstrained.md new file mode 100644 index 00000000000..6b3424f7993 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/concepts/unconstrained.md @@ -0,0 +1,95 @@ +--- +title: Unconstrained Functions +description: "Learn about what unconstrained functions in Noir are, how to use them and when you'd want to." + +keywords: [Noir programming language, unconstrained, open] +sidebar_position: 5 +--- + +Unconstrained functions are functions which do not constrain any of the included computation and allow for non-deterministic computation. + +## Why? + +Zero-knowledge (ZK) domain-specific languages (DSL) enable developers to generate ZK proofs from their programs by compiling code down to the constraints of an NP complete language (such as R1CS or PLONKish languages). However, the hard bounds of a constraint system can be very limiting to the functionality of a ZK DSL. + +Enabling a circuit language to perform unconstrained execution is a powerful tool. Said another way, unconstrained execution lets developers generate witnesses from code that does not generate any constraints. Being able to execute logic outside of a circuit is critical for both circuit performance and constructing proofs on information that is external to a circuit. + +Fetching information from somewhere external to a circuit can also be used to enable developers to improve circuit efficiency. + +A ZK DSL does not just prove computation, but proves that some computation was handled correctly. Thus, it is necessary that when we switch from performing some operation directly inside of a circuit to inside of an unconstrained environment that the appropriate constraints are still laid down elsewhere in the circuit. + +## Example + +An in depth example might help drive the point home. This example comes from the excellent [post](https://discord.com/channels/1113924620781883405/1124022445054111926/1128747641853972590) by Tom in the Noir Discord. + +Let's look at how we can optimize a function to turn a `u72` into an array of `u8`s. + +```rust +fn main(num: u72) -> pub [u8; 8] { + let mut out: [u8; 8] = [0; 8]; + for i in 0..8 { + out[i] = (num >> (56 - (i * 8)) as u72 & 0xff) as u8; + } + + out +} +``` + +``` +Total ACIR opcodes generated for language PLONKCSat { width: 3 }: 91 +Backend circuit size: 3619 +``` + +A lot of the operations in this function are optimized away by the compiler (all the bit-shifts turn into divisions by constants). However we can save a bunch of gates by casting to u8 a bit earlier. This automatically truncates the bit-shifted value to fit in a u8 which allows us to remove the XOR against 0xff. This saves us ~480 gates in total. + +```rust +fn main(num: u72) -> pub [u8; 8] { + let mut out: [u8; 8] = [0; 8]; + for i in 0..8 { + out[i] = (num >> (56 - (i * 8)) as u8; + } + + out +} +``` + +``` +Total ACIR opcodes generated for language PLONKCSat { width: 3 }: 75 +Backend circuit size: 3143 +``` + +Those are some nice savings already but we can do better. This code is all constrained so we're proving every step of calculating out using num, but we don't actually care about how we calculate this, just that it's correct. This is where brillig comes in. + +It turns out that truncating a u72 into a u8 is hard to do inside a snark, each time we do as u8 we lay down 4 ACIR opcodes which get converted into multiple gates. It's actually much easier to calculate num from out than the other way around. All we need to do is multiply each element of out by a constant and add them all together, both relatively easy operations inside a snark. + +We can then run u72_to_u8 as unconstrained brillig code in order to calculate out, then use that result in our constrained function and assert that if we were to do the reverse calculation we'd get back num. This looks a little like the below: + +```rust +fn main(num: u72) -> pub [u8; 8] { + let out = u72_to_u8(num); + + let mut reconstructed_num: u72 = 0; + for i in 0..8 { + reconstructed_num += (out[i] as u72 << (56 - (8 * i))); + } + assert(num == reconstructed_num); + out +} + +unconstrained fn u72_to_u8(num: u72) -> [u8; 8] { + let mut out: [u8; 8] = [0; 8]; + for i in 0..8 { + out[i] = (num >> (56 - (i * 8))) as u8; + } + out +} +``` + +``` +Total ACIR opcodes generated for language PLONKCSat { width: 3 }: 78 +Backend circuit size: 2902 +``` + +This ends up taking off another ~250 gates from our circuit! We've ended up with more ACIR opcodes than before but they're easier for the backend to prove (resulting in fewer gates). + +Generally we want to use brillig whenever there's something that's easy to verify but hard to compute within the circuit. For example, if you wanted to calculate a square root of a number it'll be a much better idea to calculate this in brillig and then assert that if you square the result you get back your number. diff --git a/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/_category_.json b/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/_category_.json new file mode 100644 index 00000000000..1debcfe7675 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Modules, Packages and Crates", + "position": 2, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/crates_and_packages.md b/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/crates_and_packages.md new file mode 100644 index 00000000000..760a463094c --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/crates_and_packages.md @@ -0,0 +1,43 @@ +--- +title: Crates and Packages +description: Learn how to use Crates and Packages in your Noir project +keywords: [Nargo, dependencies, package management, crates, package] +sidebar_position: 0 +--- + +## Crates + +A crate is the smallest amount of code that the Noir compiler considers at a time. +Crates can contain modules, and the modules may be defined in other files that get compiled with the crate, as we’ll see in the coming sections. + +### Crate Types + +A Noir crate can come in several forms: binaries, libraries or contracts. + +#### Binaries + +_Binary crates_ are programs which you can compile to an ACIR circuit which you can then create proofs against. Each must have a function called `main` that defines the ACIR circuit which is to be proved. + +#### Libraries + +_Library crates_ don't have a `main` function and they don't compile down to ACIR. Instead they define functionality intended to be shared with multiple projects, and eventually included in a binary crate. + +#### Contracts + +Contract crates are similar to binary crates in that they compile to ACIR which you can create proofs against. They are different in that they do not have a single `main` function, but are a collection of functions to be deployed to the [Aztec network](https://aztec.network). You can learn more about the technical details of Aztec in the [monorepo](https://github.com/AztecProtocol/aztec-packages) or contract [examples](https://github.com/AztecProtocol/aztec-packages/tree/master/yarn-project/noir-contracts/contracts). + +### Crate Root + +Every crate has a root, which is the source file that the compiler starts, this is also known as the root module. The Noir compiler does not enforce any conditions on the name of the file which is the crate root, however if you are compiling via Nargo the crate root must be called `lib.nr` or `main.nr` for library or binary crates respectively. + +## Packages + +A Nargo _package_ is a collection of one of more crates that provides a set of functionality. A package must include a Nargo.toml file. + +A package _must_ contain either a library or a binary crate, but not both. + +### Differences from Cargo Packages + +One notable difference between Rust's Cargo and Noir's Nargo is that while Cargo allows a package to contain an unlimited number of binary crates and a single library crate, Nargo currently only allows a package to contain a single crate. + +In future this restriction may be lifted to allow a Nargo package to contain both a binary and library crate or multiple binary crates. diff --git a/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/dependencies.md b/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/dependencies.md new file mode 100644 index 00000000000..a37dc401b7d --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/dependencies.md @@ -0,0 +1,124 @@ +--- +title: Dependencies +description: + Learn how to specify and manage dependencies in Nargo, allowing you to upload packages to GitHub + and use them easily in your project. +keywords: [Nargo, dependencies, GitHub, package management, versioning] +sidebar_position: 1 +--- + +Nargo allows you to upload packages to GitHub and use them as dependencies. + +## Specifying a dependency + +Specifying a dependency requires a tag to a specific commit and the git url to the url containing +the package. + +Currently, there are no requirements on the tag contents. If requirements are added, it would follow +semver 2.0 guidelines. + +> Note: Without a `tag` , there would be no versioning and dependencies would change each time you +> compile your project. + +For example, to add the [ecrecover-noir library](https://github.com/colinnielsen/ecrecover-noir) to your project, add it to `Nargo.toml`: + +```toml +# Nargo.toml + +[dependencies] +ecrecover = {tag = "v0.8.0", git = "https://github.com/colinnielsen/ecrecover-noir"} +``` + +If the module is in a subdirectory, you can define a subdirectory in your git repository, for example: + +```toml +# Nargo.toml + +[dependencies] +easy_private_token_contract = {tag ="v0.1.0-alpha62", git = "https://github.com/AztecProtocol/aztec-packages", directory = "yarn-project/noir-contracts/contracts/easy_private_token_contract"} +``` + +## Specifying a local dependency + +You can also specify dependencies that are local to your machine. + +For example, this file structure has a library and binary crate + +```tree +├── binary_crate +│   ├── Nargo.toml +│   └── src +│   └── main.nr +└── lib_a + ├── Nargo.toml + └── src + └── lib.nr +``` + +Inside of the binary crate, you can specify: + +```toml +# Nargo.toml + +[dependencies] +lib_a = { path = "../lib_a" } +``` + +## Importing dependencies + +You can import a dependency to a Noir file using the following syntax. For example, to import the +ecrecover-noir library and local lib_a referenced above: + +```rust +use dep::ecrecover; +use dep::lib_a; +``` + +You can also import only the specific parts of dependency that you want to use, like so: + +```rust +use dep::std::hash::sha256; +use dep::std::scalar_mul::fixed_base_embedded_curve; +``` + +Lastly, as demonstrated in the +[elliptic curve example](../standard_library/cryptographic_primitives/ec_primitives#examples), you +can import multiple items in the same line by enclosing them in curly braces: + +```rust +use dep::std::ec::tecurve::affine::{Curve, Point}; +``` + +We don't have a way to consume libraries from inside a [workspace](./workspaces) as external dependencies right now. + +Inside a workspace, these are consumed as `{ path = "../to_lib" }` dependencies in Nargo.toml. + +## Dependencies of Dependencies + +Note that when you import a dependency, you also get access to all of the dependencies of that package. + +For example, the [phy_vector](https://github.com/resurgencelabs/phy_vector) library imports an [fraction](https://github.com/resurgencelabs/fraction) library. If you're importing the phy_vector library, then you can access the functions in fractions library like so: + +```rust +use dep::phy_vector; + +fn main(x : Field, y : pub Field) { + //... + let f = phy_vector::fraction::toFraction(true, 2, 1); + //... +} +``` + +## Available Libraries + +Noir does not currently have an official package manager. You can find a list of available Noir libraries in the [awesome-noir repo here](https://github.com/noir-lang/awesome-noir#libraries). + +Some libraries that are available today include: + +- [Standard Library](https://github.com/noir-lang/noir/tree/master/noir_stdlib) - the Noir Standard Library +- [Ethereum Storage Proof Verification](https://github.com/aragonzkresearch/noir-trie-proofs) - a library that contains the primitives necessary for RLP decoding (in the form of look-up table construction) and Ethereum state and storage proof verification (or verification of any trie proof involving 32-byte long keys) +- [BigInt](https://github.com/shuklaayush/noir-bigint) - a library that provides a custom BigUint56 data type, allowing for computations on large unsigned integers +- [ECrecover](https://github.com/colinnielsen/ecrecover-noir/tree/main) - a library to verify an ECDSA signature and return the source Ethereum address +- [Sparse Merkle Tree Verifier](https://github.com/vocdoni/smtverifier-noir/tree/main) - a library for verification of sparse Merkle trees +- [Signed Int](https://github.com/resurgencelabs/signed_int) - a library for accessing a custom Signed Integer data type, allowing access to negative numbers on Noir +- [Fraction](https://github.com/resurgencelabs/fraction) - a library for accessing fractional number data type in Noir, allowing results that aren't whole numbers diff --git a/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/modules.md b/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/modules.md new file mode 100644 index 00000000000..ae822a1cff4 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/modules.md @@ -0,0 +1,105 @@ +--- +title: Modules +description: + Learn how to organize your files using modules in Noir, following the same convention as Rust's + module system. Examples included. +keywords: [Noir, Rust, modules, organizing files, sub-modules] +sidebar_position: 2 +--- + +Noir's module system follows the same convention as the _newer_ version of Rust's module system. + +## Purpose of Modules + +Modules are used to organize files. Without modules all of your code would need to live in a single +file. In Noir, the compiler does not automatically scan all of your files to detect modules. This +must be done explicitly by the developer. + +## Examples + +### Importing a module in the crate root + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::hello_world(); +} +``` + +Filename : `src/foo.nr` + +```rust +fn from_foo() {} +``` + +In the above snippet, the crate root is the `src/main.nr` file. The compiler sees the module +declaration `mod foo` which prompts it to look for a foo.nr file. + +Visually this module hierarchy looks like the following : + +``` +crate + ├── main + │ + └── foo + └── from_foo + +``` + +### Importing a module throughout the tree + +All modules are accessible from the `crate::` namespace. + +``` +crate + ├── bar + ├── foo + └── main + +``` + +In the above snippet, if `bar` would like to use functions in `foo`, it can do so by `use crate::foo::function_name`. + +### Sub-modules + +Filename : `src/main.nr` + +```rust +mod foo; + +fn main() { + foo::from_foo(); +} +``` + +Filename : `src/foo.nr` + +```rust +mod bar; +fn from_foo() {} +``` + +Filename : `src/foo/bar.nr` + +```rust +fn from_bar() {} +``` + +In the above snippet, we have added an extra module to the module tree; `bar`. `bar` is a submodule +of `foo` hence we declare bar in `foo.nr` with `mod bar`. Since `foo` is not the crate root, the +compiler looks for the file associated with the `bar` module in `src/foo/bar.nr` + +Visually the module hierarchy looks as follows: + +``` +crate + ├── main + │ + └── foo + ├── from_foo + └── bar + └── from_bar +``` diff --git a/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/workspaces.md b/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/workspaces.md new file mode 100644 index 00000000000..67a1dafa372 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/modules_packages_crates/workspaces.md @@ -0,0 +1,40 @@ +--- +title: Workspaces +sidebar_position: 3 +--- + +Workspaces are a feature of nargo that allow you to manage multiple related Noir packages in a single repository. A workspace is essentially a group of related projects that share common build output directories and configurations. + +Each Noir project (with it's own Nargo.toml file) can be thought of as a package. Each package is expected to contain exactly one "named circuit", being the "name" defined in Nargo.toml with the program logic defined in `./src/main.nr`. + +For a project with the following structure: + +```tree +├── crates +│   ├── a +│   │   ├── Nargo.toml +│   │   └── src +│   │   └── main.nr +│   └── b +│   ├── Nargo.toml +│   └── src +│   └── main.nr +├── Nargo.toml +└── Prover.toml +``` + +You can define a workspace in Nargo.toml like so: + +```toml +[workspace] +members = ["crates/a", "crates/b"] +default-member = "crates/a" +``` + +`members` indicates which packages are included in the workspace. As such, all member packages of a workspace will be processed when the `--workspace` flag is used with various commands or if a `default-member` is not specified. + +`default-member` indicates which package various commands process by default. + +Libraries can be defined in a workspace. Inside a workspace, these are consumed as `{ path = "../to_lib" }` dependencies in Nargo.toml. + +Inside a workspace, these are consumed as `{ path = "../to_lib" }` dependencies in Nargo.toml. diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/_category_.json b/docs/versioned_docs/version-v0.23.0/noir/standard_library/_category_.json new file mode 100644 index 00000000000..af04c0933fd --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Standard Library", + "position": 1, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/black_box_fns.md b/docs/versioned_docs/version-v0.23.0/noir/standard_library/black_box_fns.md new file mode 100644 index 00000000000..4b1efbd17de --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/black_box_fns.md @@ -0,0 +1,45 @@ +--- +title: Black Box Functions +description: Black box functions are functions in Noir that rely on backends implementing support for specialized constraints. +keywords: [noir, black box functions] +--- + +Black box functions are functions in Noir that rely on backends implementing support for specialized constraints. This makes certain zk-snark unfriendly computations cheaper than if they were implemented in Noir. + +:::warning + +It is likely that not all backends will support a particular black box function. + +::: + +Because it is not guaranteed that all backends will support black box functions, it is possible that certain Noir programs won't compile against a particular backend if they use an unsupported black box function. It is possible to fallback to less efficient implementations written in Noir/ACIR in some cases. + +Black box functions are specified with the `#[foreign(black_box_fn)]` attribute. For example, the SHA256 function in the Noir [source code](https://github.com/noir-lang/noir/blob/v0.5.1/noir_stdlib/src/hash.nr) looks like: + +```rust +#[foreign(sha256)] +fn sha256(_input : [u8; N]) -> [u8; 32] {} +``` + +## Function list + +Here is a list of the current black box functions that are supported by UltraPlonk: + +- AES +- [SHA256](./cryptographic_primitives/hashes#sha256) +- [Schnorr signature verification](./cryptographic_primitives/schnorr) +- [Blake2s](./cryptographic_primitives/hashes#blake2s) +- [Pedersen Hash](./cryptographic_primitives/hashes#pedersen_hash) +- [Pedersen Commitment](./cryptographic_primitives/hashes#pedersen_commitment) +- [ECDSA signature verification](./cryptographic_primitives/ecdsa_sig_verification) +- [Fixed base scalar multiplication](./cryptographic_primitives/scalar) +- [Compute merkle root](./merkle_trees#compute_merkle_root) +- AND +- XOR +- RANGE +- [Keccak256](./cryptographic_primitives/hashes#keccak256) +- [Recursive proof verification](./recursion) + +Most black box functions are included as part of the Noir standard library, however `AND`, `XOR` and `RANGE` are used as part of the Noir language syntax. For instance, using the bitwise operator `&` will invoke the `AND` black box function. To ensure compatibility across backends, the ACVM has fallback implementations of `AND`, `XOR` and `RANGE` defined in its standard library which it can seamlessly fallback to if the backend doesn't support them. + +You can view the black box functions defined in the ACVM code [here](https://github.com/noir-lang/noir/blob/master/acvm-repo/acir/src/circuit/black_box_functions.rs). diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/_category_.json b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/_category_.json new file mode 100644 index 00000000000..5d694210bbf --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 0, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/ec_primitives.md b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/ec_primitives.md new file mode 100644 index 00000000000..d2b42d67b7c --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/ec_primitives.md @@ -0,0 +1,102 @@ +--- +title: Elliptic Curve Primitives +keywords: [cryptographic primitives, Noir project] +sidebar_position: 4 +--- + +Data structures and methods on them that allow you to carry out computations involving elliptic +curves over the (mathematical) field corresponding to `Field`. For the field currently at our +disposal, applications would involve a curve embedded in BN254, e.g. the +[Baby Jubjub curve](https://eips.ethereum.org/EIPS/eip-2494). + +## Data structures + +### Elliptic curve configurations + +(`std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::Curve`), i.e. the specific elliptic +curve you want to use, which would be specified using any one of the methods +`std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::new` which take the coefficients in the +defining equation together with a generator point as parameters. You can find more detail in the +comments in +[`noir_stdlib/src/ec.nr`](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr), but +the gist of it is that the elliptic curves of interest are usually expressed in one of the standard +forms implemented here (Twisted Edwards, Montgomery and Short Weierstraß), and in addition to that, +you could choose to use `affine` coordinates (Cartesian coordinates - the usual (x,y) - possibly +together with a point at infinity) or `curvegroup` coordinates (some form of projective coordinates +requiring more coordinates but allowing for more efficient implementations of elliptic curve +operations). Conversions between all of these forms are provided, and under the hood these +conversions are done whenever an operation is more efficient in a different representation (or a +mixed coordinate representation is employed). + +### Points + +(`std::ec::{tecurve,montcurve,swcurve}::{affine,curvegroup}::Point`), i.e. points lying on the +elliptic curve. For a curve configuration `c` and a point `p`, it may be checked that `p` +does indeed lie on `c` by calling `c.contains(p1)`. + +## Methods + +(given a choice of curve representation, e.g. use `std::ec::tecurve::affine::Curve` and use +`std::ec::tecurve::affine::Point`) + +- The **zero element** is given by `Point::zero()`, and we can verify whether a point `p: Point` is + zero by calling `p.is_zero()`. +- **Equality**: Points `p1: Point` and `p2: Point` may be checked for equality by calling + `p1.eq(p2)`. +- **Addition**: For `c: Curve` and points `p1: Point` and `p2: Point` on the curve, adding these two + points is accomplished by calling `c.add(p1,p2)`. +- **Negation**: For a point `p: Point`, `p.negate()` is its negation. +- **Subtraction**: For `c` and `p1`, `p2` as above, subtracting `p2` from `p1` is accomplished by + calling `c.subtract(p1,p2)`. +- **Scalar multiplication**: For `c` as above, `p: Point` a point on the curve and `n: Field`, + scalar multiplication is given by `c.mul(n,p)`. If instead `n :: [u1; N]`, i.e. `n` is a bit + array, the `bit_mul` method may be used instead: `c.bit_mul(n,p)` +- **Multi-scalar multiplication**: For `c` as above and arrays `n: [Field; N]` and `p: [Point; N]`, + multi-scalar multiplication is given by `c.msm(n,p)`. +- **Coordinate representation conversions**: The `into_group` method converts a point or curve + configuration in the affine representation to one in the CurveGroup representation, and + `into_affine` goes in the other direction. +- **Curve representation conversions**: `tecurve` and `montcurve` curves and points are equivalent + and may be converted between one another by calling `into_montcurve` or `into_tecurve` on their + configurations or points. `swcurve` is more general and a curve c of one of the other two types + may be converted to this representation by calling `c.into_swcurve()`, whereas a point `p` lying + on the curve given by `c` may be mapped to its corresponding `swcurve` point by calling + `c.map_into_swcurve(p)`. +- **Map-to-curve methods**: The Elligator 2 method of mapping a field element `n: Field` into a + `tecurve` or `montcurve` with configuration `c` may be called as `c.elligator2_map(n)`. For all of + the curve configurations, the SWU map-to-curve method may be called as `c.swu_map(z,n)`, where + `z: Field` depends on `Field` and `c` and must be chosen by the user (the conditions it needs to + satisfy are specified in the comments + [here](https://github.com/noir-lang/noir/blob/master/noir_stdlib/src/ec.nr)). + +## Examples + +The +[ec_baby_jubjub test](https://github.com/noir-lang/noir/blob/master/test_programs/compile_success_empty/ec_baby_jubjub/src/main.nr) +illustrates all of the above primitives on various forms of the Baby Jubjub curve. A couple of more +interesting examples in Noir would be: + +Public-key cryptography: Given an elliptic curve and a 'base point' on it, determine the public key +from the private key. This is a matter of using scalar multiplication. In the case of Baby Jubjub, +for example, this code would do: + +```rust +use dep::std::ec::tecurve::affine::{Curve, Point}; + +fn bjj_pub_key(priv_key: Field) -> Point +{ + + let bjj = Curve::new(168700, 168696, G::new(995203441582195749578291179787384436505546430278305826713579947235728471134,5472060717959818805561601436314318772137091100104008585924551046643952123905)); + + let base_pt = Point::new(5299619240641551281634865583518297030282874472190772894086521144482721001553, 16950150798460657717958625567821834550301663161624707787222815936182638968203); + + bjj.mul(priv_key,base_pt) +} +``` + +This would come in handy in a Merkle proof. + +- EdDSA signature verification: This is a matter of combining these primitives with a suitable hash + function. See + [feat(stdlib): EdDSA sig verification noir#1136](https://github.com/noir-lang/noir/pull/1136) for + the case of Baby Jubjub and the Poseidon hash function. diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx new file mode 100644 index 00000000000..1376c51dfde --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/ecdsa_sig_verification.mdx @@ -0,0 +1,46 @@ +--- +title: ECDSA Signature Verification +description: Learn about the cryptographic primitives regarding ECDSA over the secp256k1 and secp256r1 curves +keywords: [cryptographic primitives, Noir project, ecdsa, secp256k1, secp256r1, signatures] +sidebar_position: 3 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; + +Noir supports ECDSA signatures verification over the secp256k1 and secp256r1 curves. + +## ecdsa_secp256k1::verify_signature + +Verifier for ECDSA Secp256k1 signatures + +```rust +fn verify_signature(_public_key_x : [u8; 32], _public_key_y : [u8; 32], _signature: [u8; 64], _message: [u8]) -> bool +``` + +example: + +```rust +fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], signature : [u8;64]) { + let valid_signature = std::ecdsa_secp256k1::verify_signature(pub_key_x, pub_key_y, signature, hashed_message); + assert(valid_signature); +} +``` + +## ecdsa_secp256r1::verify_signature + +Verifier for ECDSA Secp256r1 signatures + +```rust +fn verify_signature(_public_key_x : [u8; 32], _public_key_y : [u8; 32], _signature: [u8; 64], _message: [u8]) -> bool +``` + +example: + +```rust +fn main(hashed_message : [u8;32], pub_key_x : [u8;32], pub_key_y : [u8;32], signature : [u8;64]) { + let valid_signature = std::ecdsa_secp256r1::verify_signature(pub_key_x, pub_key_y, signature, hashed_message); + assert(valid_signature); +} +``` + + diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/eddsa.mdx b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/eddsa.mdx new file mode 100644 index 00000000000..a9c10da6c06 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/eddsa.mdx @@ -0,0 +1,18 @@ +--- +title: EdDSA Verification +description: Learn about the cryptographic primitives regarding EdDSA +keywords: [cryptographic primitives, Noir project, eddsa, signatures] +sidebar_position: 5 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; + +## eddsa::eddsa_poseidon_verify + +Verifier for EdDSA signatures + +```rust +fn eddsa_poseidon_verify(public_key_x : Field, public_key_y : Field, signature_s: Field, signature_r8_x: Field, signature_r8_y: Field, message: Field) -> bool +``` + + diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/hashes.mdx b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/hashes.mdx new file mode 100644 index 00000000000..3c5f7f79603 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -0,0 +1,167 @@ +--- +title: Hash methods +description: + Learn about the cryptographic primitives ready to use for any Noir project, including sha256, + blake2s, pedersen, mimc_bn254 and mimc +keywords: + [cryptographic primitives, Noir project, sha256, blake2s, pedersen, mimc_bn254, mimc, hash] +sidebar_position: 0 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; + +## sha256 + +Given an array of bytes, returns the resulting sha256 hash. + +```rust +fn sha256(_input : [u8]) -> [u8; 32] +``` + +example: + +```rust +fn main() { + let x = [163, 117, 178, 149]; // some random bytes + let hash = std::hash::sha256(x); +} +``` + + + +## blake2s + +Given an array of bytes, returns an array with the Blake2 hash + +```rust +fn blake2s(_input : [u8]) -> [u8; 32] +``` + +example: + +```rust +fn main() { + let x = [163, 117, 178, 149]; // some random bytes + let hash = std::hash::blake2s(x); +} +``` + + + +## pedersen_hash + +Given an array of Fields, returns the Pedersen hash. + +```rust +fn pedersen_hash(_input : [Field]) -> Field +``` + +example: + +```rust +fn main() { + let x = [163, 117, 178, 149]; // some random bytes + let hash = std::hash::pedersen_hash(x); +} +``` + + + + + +## pedersen_commitment + +Given an array of Fields, returns the Pedersen commitment. + +```rust +fn pedersen_commitment(_input : [Field]) -> [Field; 2] +``` + +example: + +```rust +fn main() { + let x = [163, 117, 178, 149]; // some random bytes + let commitment = std::hash::pedersen_commitment(x); +} +``` + + + +## keccak256 + +Given an array of bytes (`u8`), returns the resulting keccak hash as an array of 32 bytes +(`[u8; 32]`). Specify a message_size to hash only the first `message_size` bytes +of the input. + +```rust +fn keccak256(_input : [u8; N], _message_size: u32) -> [u8; 32] +``` + +example: + +```rust +fn main() { + let x = [163, 117, 178, 149]; // some random bytes + let message_size = 4; + let hash = std::hash::keccak256(x, message_size); +} +``` + + + +## poseidon + +Given an array of Fields, returns a new Field with the Poseidon Hash. Mind that you need to specify +how many inputs are there to your Poseidon function. + +```rust +// example for hash_1, hash_2 accepts an array of length 2, etc +fn hash_1(input: [Field; 1]) -> Field +``` + +example: + +```rust +fn main() +{ + let hash_2 = std::hash::poseidon::bn254::hash_2([1, 2]); + assert(hash2 == 0x115cc0f5e7d690413df64c6b9662e9cf2a3617f2743245519e19607a4417189a); +} +``` + +## mimc_bn254 and mimc + +`mimc_bn254` is `mimc`, but with hardcoded parameters for the BN254 curve. You can use it by +providing an array of Fields, and it returns a Field with the hash. You can use the `mimc` method if +you're willing to input your own constants: + +```rust +fn mimc(x: Field, k: Field, constants: [Field; N], exp : Field) -> Field +``` + +otherwise, use the `mimc_bn254` method: + +```rust +fn mimc_bn254(array: [Field; N]) -> Field +``` + +example: + +```rust + +fn main() { + let x = [163, 117, 178, 149]; // some random bytes + let hash = std::hash::mimc::mimc_bn254(x); +} +``` + +## hash_to_field + +```rust +fn hash_to_field(_input : [Field; N]) -> Field {} +``` + +Calculates the `blake2s` hash of the inputs and returns the hash modulo the field modulus to return +a value which can be represented as a `Field`. + diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/index.md b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/index.md new file mode 100644 index 00000000000..650f30165d5 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/index.md @@ -0,0 +1,14 @@ +--- +title: Cryptographic Primitives +description: + Learn about the cryptographic primitives ready to use for any Noir project +keywords: + [ + cryptographic primitives, + Noir project, + ] +--- + +The Noir team is progressively adding new cryptographic primitives to the standard library. Reach out for news or if you would be interested in adding more of these calculations in Noir. + +Some methods are available thanks to the Aztec backend, not being performed using Noir. When using other backends, these methods may or may not be supplied. diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/scalar.mdx b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/scalar.mdx new file mode 100644 index 00000000000..aa4fb8cbaed --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/scalar.mdx @@ -0,0 +1,28 @@ +--- +title: Scalar multiplication +description: See how you can perform scalar multiplications over a fixed base in Noir +keywords: [cryptographic primitives, Noir project, scalar multiplication] +sidebar_position: 1 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; + +## scalar_mul::fixed_base_embedded_curve + +Performs scalar multiplication over the embedded curve whose coordinates are defined by the +configured noir field. For the BN254 scalar field, this is BabyJubJub or Grumpkin. + +```rust +fn fixed_base_embedded_curve(_input : Field) -> [Field; 2] +``` + +example + +```rust +fn main(x : Field) { + let scal = std::scalar_mul::fixed_base_embedded_curve(x); + println(scal); +} +``` + + diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/schnorr.mdx b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/schnorr.mdx new file mode 100644 index 00000000000..7a2c9c20226 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/cryptographic_primitives/schnorr.mdx @@ -0,0 +1,38 @@ +--- +title: Schnorr Signatures +description: Learn how you can verify Schnorr signatures using Noir +keywords: [cryptographic primitives, Noir project, schnorr, signatures] +sidebar_position: 2 +--- + +import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; + +## schnorr::verify_signature + +Verifier for Schnorr signatures over the embedded curve (for BN254 it is Grumpkin). + +```rust +fn verify_signature(_public_key_x: Field, _public_key_y: Field, _signature: [u8; 64], _message: [u8]) -> bool +``` + +where `_signature` can be generated like so using the npm package +[@noir-lang/barretenberg](https://www.npmjs.com/package/@noir-lang/barretenberg) + +```js +const { BarretenbergWasm } = require('@noir-lang/barretenberg/dest/wasm'); +const { Schnorr } = require('@noir-lang/barretenberg/dest/crypto/schnorr'); + +... + +const barretenberg = await BarretenbergWasm.new(); +const schnorr = new Schnorr(barretenberg); +const pubKey = schnorr.computePublicKey(privateKey); +const message = ... +const signature = Array.from( + schnorr.constructSignature(hash, privateKey).toBuffer() +); + +... +``` + + diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/logging.md b/docs/versioned_docs/version-v0.23.0/noir/standard_library/logging.md new file mode 100644 index 00000000000..db75ef9f86f --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/logging.md @@ -0,0 +1,78 @@ +--- +title: Logging +description: + Learn how to use the println statement for debugging in Noir with this tutorial. Understand the + basics of logging in Noir and how to implement it in your code. +keywords: + [ + noir logging, + println statement, + print statement, + debugging in noir, + noir std library, + logging tutorial, + basic logging in noir, + noir logging implementation, + noir debugging techniques, + rust, + ] +--- + +The standard library provides two familiar statements you can use: `println` and `print`. Despite being a limited implementation of rust's `println!` and `print!` macros, these constructs can be useful for debugging. + +You can print the output of both statements in your Noir code by using the `nargo execute` command or the `--show-output` flag when using `nargo test` (provided there are print statements in your tests). + +It is recommended to use `nargo execute` if you want to debug failing constraints with `println` or `print` statements. This is due to every input in a test being a constant rather than a witness, so we issue an error during compilation while we only print during execution (which comes after compilation). Neither `println`, nor `print` are callable for failed constraints caught at compile time. + +Both `print` and `println` are generic functions which can work on integers, fields, strings, and even structs or expressions. Note however, that slices are currently unsupported. For example: + +```rust +struct Person { + age: Field, + height: Field, +} + +fn main(age: Field, height: Field) { + let person = Person { + age: age, + height: height, + }; + println(person); + println(age + height); + println("Hello world!"); +} +``` + +You can print different types in the same statement (including strings) with a type called `fmtstr`. It can be specified in the same way as a normal string, just prepended with an "f" character: + +```rust + let fmt_str = f"i: {i}, j: {j}"; + println(fmt_str); + + let s = myStruct { y: x, x: y }; + println(s); + + println(f"i: {i}, s: {s}"); + + println(x); + println([x, y]); + + let foo = fooStruct { my_struct: s, foo: 15 }; + println(f"s: {s}, foo: {foo}"); + + println(15); // prints 0x0f, implicit Field + println(-1 as u8); // prints 255 + println(-1 as i8); // prints -1 +``` + +Examples shown above are interchangeable between the two `print` statements: + +```rust +let person = Person { age : age, height : height }; + +println(person); +print(person); + +println("Hello world!"); // Prints with a newline at the end of the input +print("Hello world!"); // Prints the input and keeps cursor on the same line +``` diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/merkle_trees.md b/docs/versioned_docs/version-v0.23.0/noir/standard_library/merkle_trees.md new file mode 100644 index 00000000000..fa488677884 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/merkle_trees.md @@ -0,0 +1,58 @@ +--- +title: Merkle Trees +description: Learn about Merkle Trees in Noir with this tutorial. Explore the basics of computing a merkle root using a proof, with examples. +keywords: + [ + Merkle trees in Noir, + Noir programming language, + check membership, + computing root from leaf, + Noir Merkle tree implementation, + Merkle tree tutorial, + Merkle tree code examples, + Noir libraries, + pedersen hash., + ] +--- + +## compute_merkle_root + +Returns the root of the tree from the provided leaf and its hash path, using a [Pedersen hash](./cryptographic_primitives/hashes.mdx#pedersen_hash). + +```rust +fn compute_merkle_root(leaf : Field, index : Field, hash_path: [Field]) -> Field +``` + +example: + +```rust +/** + // these values are for this example only + index = "0" + priv_key = "0x000000000000000000000000000000000000000000000000000000616c696365" + secret = "0x1929ea3ab8d9106a899386883d9428f8256cfedb3c4f6b66bf4aa4d28a79988f" + note_hash_path = [ + "0x1e61bdae0f027b1b2159e1f9d3f8d00fa668a952dddd822fda80dc745d6f65cc", + "0x0e4223f3925f98934393c74975142bd73079ab0621f4ee133cee050a3c194f1a", + "0x2fd7bb412155bf8693a3bd2a3e7581a679c95c68a052f835dddca85fa1569a40" + ] + */ +fn main(index: Field, priv_key: Field, secret: Field, note_hash_path: [Field; 3]) { + + let pubkey = std::scalar_mul::fixed_base_embedded_curve(priv_key); + let pubkey_x = pubkey[0]; + let pubkey_y = pubkey[1]; + let note_commitment = std::hash::pedersen([pubkey_x, pubkey_y, secret]); + + let root = std::merkle::compute_merkle_root(note_commitment[0], index, note_hash_path); + println(root); +} +``` + +To check merkle tree membership: + +1. Include a merkle root as a program input. +2. Compute the merkle root of a given leaf, index and hash path. +3. Assert the merkle roots are equal. + +For more info about merkle trees, see the Wikipedia [page](https://en.wikipedia.org/wiki/Merkle_tree). diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/options.md b/docs/versioned_docs/version-v0.23.0/noir/standard_library/options.md new file mode 100644 index 00000000000..970c9cfbf11 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/options.md @@ -0,0 +1,97 @@ +--- +title: Option Type +--- + +The `Option` type is a way to express that a value might be present (`Some(T))` or absent (`None`). It's a safer way to handle potential absence of values, compared to using nulls in many other languages. + +```rust +struct Option { + None, + Some(T), +} +``` + +The `Option` type, already imported into your Noir program, can be used directly: + +```rust +fn main() { + let none = Option::none(); + let some = Option::some(3); +} +``` + +See [this test](https://github.com/noir-lang/noir/blob/5cbfb9c4a06c8865c98ff2b594464b037d821a5c/crates/nargo_cli/tests/test_data/option/src/main.nr) for a more comprehensive set of examples of each of the methods described below. + +## Methods + +### none + +Constructs a none value. + +### some + +Constructs a some wrapper around a given value. + +### is_none + +Returns true if the Option is None. + +### is_some + +Returns true of the Option is Some. + +### unwrap + +Asserts `self.is_some()` and returns the wrapped value. + +### unwrap_unchecked + +Returns the inner value without asserting `self.is_some()`. This method can be useful within an if condition when we already know that `option.is_some()`. If the option is None, there is no guarantee what value will be returned, only that it will be of type T for an `Option`. + +### unwrap_or + +Returns the wrapped value if `self.is_some()`. Otherwise, returns the given default value. + +### unwrap_or_else + +Returns the wrapped value if `self.is_some()`. Otherwise, calls the given function to return a default value. + +### map + +If self is `Some(x)`, this returns `Some(f(x))`. Otherwise, this returns `None`. + +### map_or + +If self is `Some(x)`, this returns `f(x)`. Otherwise, this returns the given default value. + +### map_or_else + +If self is `Some(x)`, this returns `f(x)`. Otherwise, this returns `default()`. + +### and + +Returns None if self is None. Otherwise, this returns `other`. + +### and_then + +If self is None, this returns None. Otherwise, this calls the given function with the Some value contained within self, and returns the result of that call. In some languages this function is called `flat_map` or `bind`. + +### or + +If self is Some, return self. Otherwise, return `other`. + +### or_else + +If self is Some, return self. Otherwise, return `default()`. + +### xor + +If only one of the two Options is Some, return that option. Otherwise, if both options are Some or both are None, None is returned. + +### filter + +Returns `Some(x)` if self is `Some(x)` and `predicate(x)` is true. Otherwise, this returns `None`. + +### flatten + +Flattens an `Option>` into a `Option`. This returns `None` if the outer Option is None. Otherwise, this returns the inner Option. diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/recursion.md b/docs/versioned_docs/version-v0.23.0/noir/standard_library/recursion.md new file mode 100644 index 00000000000..f252150c8b5 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/recursion.md @@ -0,0 +1,68 @@ +--- +title: Recursive Proofs +description: Learn about how to write recursive proofs in Noir. +keywords: [recursion, recursive proofs, verification_key, verify_proof] +--- + +Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. + +Read [the explainer on recursion](../../explainers/explainer-recursion.md) to know more about this function and the [guide on how to use it.](../../how_to/how-to-recursion.md) + +```rust +#[foreign(verify_proof)] +fn verify_proof(_verification_key : [Field], _proof : [Field], _public_input : Field, _key_hash : Field) {} +``` + +:::info + +This is a black box function. Read [this section](./black_box_fns) to learn more about black box functions in Noir. + +::: + +## Example usage + +```rust +use dep::std; + +fn main( + verification_key : [Field; 114], + proof : [Field; 93], + public_inputs : [Field; 1], + key_hash : Field, + proof_b : [Field; 93], +) { + std::verify_proof( + verification_key.as_slice(), + proof.as_slice(), + public_inputs.as_slice(), + key_hash + ); + + std::verify_proof( + verification_key.as_slice(), + proof_b.as_slice(), + public_inputs.as_slice(), + key_hash + ); +} +``` + +You can see a full example of recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). + +## Parameters + +### `verification_key` + +The verification key for the zk program that is being verified. + +### `proof` + +The proof for the zk program that is being verified. + +### `public_inputs` + +These represent the public inputs of the proof we are verifying. + +### `key_hash` + +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. diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/traits.md b/docs/versioned_docs/version-v0.23.0/noir/standard_library/traits.md new file mode 100644 index 00000000000..f2960ca5080 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/traits.md @@ -0,0 +1,284 @@ +--- +title: Traits +description: Noir's stdlib provides a few commonly used traits. +keywords: [traits, trait, interface, protocol, default, add, eq] +--- + +## `std::default` + +### `std::default::Default` + +```rust +trait Default { + fn default() -> Self; +} +``` + +Constructs a default value of a type. + +Implementations: +```rust +impl Default for Field { .. } + +impl Default for i8 { .. } +impl Default for i16 { .. } +impl Default for i32 { .. } +impl Default for i64 { .. } + +impl Default for u8 { .. } +impl Default for u16 { .. } +impl Default for u32 { .. } +impl Default for u64 { .. } + +impl Default for () { .. } +impl Default for bool { .. } + +impl Default for [T; N] + where T: Default { .. } + +impl Default for (A, B) + where A: Default, B: Default { .. } + +impl Default for (A, B, C) + where A: Default, B: Default, C: Default { .. } + +impl Default for (A, B, C, D) + where A: Default, B: Default, C: Default, D: Default { .. } + +impl Default for (A, B, C, D, E) + where A: Default, B: Default, C: Default, D: Default, E: Default { .. } +``` + +For primitive integer types, the return value of `default` is `0`. Container +types such as arrays are filled with default values of their element type. + +## `std::cmp` + +### `std::cmp::Eq` + +```rust +trait Eq { + fn eq(self, other: Self) -> bool; +} +``` +Returns `true` if `self` is equal to `other`. Implementing this trait on a type +allows the type to be used with `==` and `!=`. + +Implementations: +```rust +impl Eq for Field { .. } + +impl Eq for i8 { .. } +impl Eq for i16 { .. } +impl Eq for i32 { .. } +impl Eq for i64 { .. } + +impl Eq for u8 { .. } +impl Eq for u16 { .. } +impl Eq for u32 { .. } +impl Eq for u64 { .. } + +impl Eq for () { .. } +impl Eq for bool { .. } + +impl Eq for [T; N] + where T: Eq { .. } + +impl Eq for (A, B) + where A: Eq, B: Eq { .. } + +impl Eq for (A, B, C) + where A: Eq, B: Eq, C: Eq { .. } + +impl Eq for (A, B, C, D) + where A: Eq, B: Eq, C: Eq, D: Eq { .. } + +impl Eq for (A, B, C, D, E) + where A: Eq, B: Eq, C: Eq, D: Eq, E: Eq { .. } +``` + +### `std::cmp::Cmp` + +```rust +trait Cmp { + fn cmp(self, other: Self) -> Ordering; +} +``` + +`a.cmp(b)` compares two values returning `Ordering::less()` if `a < b`, +`Ordering::equal()` if `a == b`, or `Ordering::greater()` if `a > b`. +Implementing this trait on a type allows `<`, `<=`, `>`, and `>=` to be +used on values of the type. + +Implementations: + +```rust +impl Ord for u8 { .. } +impl Ord for u16 { .. } +impl Ord for u32 { .. } +impl Ord for u64 { .. } + +impl Ord for i8 { .. } +impl Ord for i16 { .. } +impl Ord for i32 { .. } + +impl Ord for i64 { .. } + +impl Ord for () { .. } +impl Ord for bool { .. } + +impl Ord for [T; N] + where T: Ord { .. } + +impl Ord for (A, B) + where A: Ord, B: Ord { .. } + +impl Ord for (A, B, C) + where A: Ord, B: Ord, C: Ord { .. } + +impl Ord for (A, B, C, D) + where A: Ord, B: Ord, C: Ord, D: Ord { .. } + +impl Ord for (A, B, C, D, E) + where A: Ord, B: Ord, C: Ord, D: Ord, E: Ord { .. } +``` + +## `std::ops` + +### `std::ops::Add`, `std::ops::Sub`, `std::ops::Mul`, and `std::ops::Div` + +These traits abstract over addition, subtraction, multiplication, and division respectively. +Implementing these traits for a given type will also allow that type to be used with the corresponding operator +for that trait (`+` for Add, etc) in addition to the normal method names. + +```rust +trait Add { + fn add(self, other: Self) -> Self; +} + +trait Sub { + fn sub(self, other: Self) -> Self; +} + +trait Mul { + fn mul(self, other: Self) -> Self; +} + +trait Div { + fn div(self, other: Self) -> Self; +} +``` + +The implementations block below is given for the `Add` trait, but the same types that implement +`Add` also implement `Sub`, `Mul`, and `Div`. + +Implementations: +```rust +impl Add for Field { .. } + +impl Add for i8 { .. } +impl Add for i16 { .. } +impl Add for i32 { .. } +impl Add for i64 { .. } + +impl Add for u8 { .. } +impl Add for u16 { .. } +impl Add for u32 { .. } +impl Add for u64 { .. } +``` + +### `std::ops::Rem` + +```rust +trait Rem { + fn rem(self, other: Self) -> Self; +} +``` + +`Rem::rem(a, b)` is the remainder function returning the result of what is +left after dividing `a` and `b`. Implementing `Rem` allows the `%` operator +to be used with the implementation type. + +Unlike other numeric traits, `Rem` is not implemented for `Field`. + +Implementations: +```rust +impl Rem for u8 { fn rem(self, other: u8) -> u8 { self % other } } +impl Rem for u16 { fn rem(self, other: u16) -> u16 { self % other } } +impl Rem for u32 { fn rem(self, other: u32) -> u32 { self % other } } +impl Rem for u64 { fn rem(self, other: u64) -> u64 { self % other } } + +impl Rem for i8 { fn rem(self, other: i8) -> i8 { self % other } } +impl Rem for i16 { fn rem(self, other: i16) -> i16 { self % other } } +impl Rem for i32 { fn rem(self, other: i32) -> i32 { self % other } } +impl Rem for i64 { fn rem(self, other: i64) -> i64 { self % other } } +``` + +### `std::ops::{ BitOr, BitAnd, BitXor }` + +```rust +trait BitOr { + fn bitor(self, other: Self) -> Self; +} + +trait BitAnd { + fn bitand(self, other: Self) -> Self; +} + +trait BitXor { + fn bitxor(self, other: Self) -> Self; +} +``` + +Traits for the bitwise operations `|`, `&`, and `^`. + +Implementing `BitOr`, `BitAnd` or `BitXor` for a type allows the `|`, `&`, or `^` operator respectively +to be used with the type. + +The implementations block below is given for the `BitOr` trait, but the same types that implement +`BitOr` also implement `BitAnd` and `BitXor`. + +Implementations: +```rust +impl BitOr for bool { fn bitor(self, other: bool) -> bool { self | other } } + +impl BitOr for u8 { fn bitor(self, other: u8) -> u8 { self | other } } +impl BitOr for u16 { fn bitor(self, other: u16) -> u16 { self | other } } +impl BitOr for u32 { fn bitor(self, other: u32) -> u32 { self | other } } +impl BitOr for u64 { fn bitor(self, other: u64) -> u64 { self | other } } + +impl BitOr for i8 { fn bitor(self, other: i8) -> i8 { self | other } } +impl BitOr for i16 { fn bitor(self, other: i16) -> i16 { self | other } } +impl BitOr for i32 { fn bitor(self, other: i32) -> i32 { self | other } } +impl BitOr for i64 { fn bitor(self, other: i64) -> i64 { self | other } } +``` + +### `std::ops::{ Shl, Shr }` + +```rust +trait Shl { + fn shl(self, other: Self) -> Self; +} + +trait Shr { + fn shr(self, other: Self) -> Self; +} +``` + +Traits for a bit shift left and bit shift right. + +Implementing `Shl` for a type allows the left shift operator (`<<`) to be used with the implementation type. +Similarly, implementing `Shr` allows the right shift operator (`>>`) to be used with the type. + +Note that bit shifting is not currently implemented for signed types. + +The implementations block below is given for the `Shl` trait, but the same types that implement +`Shl` also implement `Shr`. + +Implementations: +```rust +impl Shl for u8 { fn shl(self, other: u8) -> u8 { self << other } } +impl Shl for u16 { fn shl(self, other: u16) -> u16 { self << other } } +impl Shl for u32 { fn shl(self, other: u32) -> u32 { self << other } } +impl Shl for u64 { fn shl(self, other: u64) -> u64 { self << other } } +``` diff --git a/docs/versioned_docs/version-v0.23.0/noir/standard_library/zeroed.md b/docs/versioned_docs/version-v0.23.0/noir/standard_library/zeroed.md new file mode 100644 index 00000000000..97dab02dac2 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir/standard_library/zeroed.md @@ -0,0 +1,25 @@ +--- +title: Zeroed Function +description: + The zeroed function returns a zeroed value of any type. +keywords: + [ + zeroed + ] +--- + +Implements `fn zeroed() -> T` to return a zeroed value of any type. This function is generally unsafe to use as the zeroed bit pattern is not guaranteed to be valid for all types. It can however, be useful in cases when the value is guaranteed not to be used such as in a BoundedVec library implementing a growable vector, up to a certain length, backed by an array. The array can be initialized with zeroed values which are guaranteed to be inaccessible until the vector is pushed to. Similarly, enumerations in noir can be implemented using this method by providing zeroed values for the unused variants. + +You can access the function at `std::unsafe::zeroed`. + +This function currently supports the following types: + +- Field +- Bool +- Uint +- Array +- String +- Tuple +- Function + +Using it on other types could result in unexpected behavior. diff --git a/docs/docs/reference/NoirJS/backend_barretenberg/.nojekyll b/docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/.nojekyll similarity index 100% rename from docs/docs/reference/NoirJS/backend_barretenberg/.nojekyll rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/.nojekyll diff --git a/docs/docs/reference/NoirJS/backend_barretenberg/classes/BarretenbergBackend.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/classes/BarretenbergBackend.md similarity index 100% rename from docs/docs/reference/NoirJS/backend_barretenberg/classes/BarretenbergBackend.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/classes/BarretenbergBackend.md diff --git a/docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/index.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/index.md new file mode 100644 index 00000000000..93b248b0f65 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/index.md @@ -0,0 +1,45 @@ +# Backend Barretenberg + +## Exports + +### Classes + +| Class | Description | +| :------ | :------ | +| [BarretenbergBackend](classes/BarretenbergBackend.md) | - | + +### Interfaces + +| Interface | Description | +| :------ | :------ | +| [Backend](interfaces/Backend.md) | - | + +### Type Aliases + +| Type alias | Description | +| :------ | :------ | +| [BackendOptions](type-aliases/BackendOptions.md) | - | +| [CompiledCircuit](type-aliases/CompiledCircuit.md) | - | +| [ProofData](type-aliases/ProofData.md) | - | + +## Functions + +### flattenPublicInputs() + +```ts +flattenPublicInputs(publicInputs): string[] +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `publicInputs` | `WitnessMap` | + +#### Returns + +`string`[] + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/docs/reference/NoirJS/backend_barretenberg/interfaces/Backend.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/interfaces/Backend.md similarity index 100% rename from docs/docs/reference/NoirJS/backend_barretenberg/interfaces/Backend.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/interfaces/Backend.md diff --git a/docs/docs/reference/NoirJS/backend_barretenberg/type-aliases/BackendOptions.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/type-aliases/BackendOptions.md similarity index 100% rename from docs/docs/reference/NoirJS/backend_barretenberg/type-aliases/BackendOptions.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/type-aliases/BackendOptions.md diff --git a/docs/docs/reference/NoirJS/backend_barretenberg/type-aliases/CompiledCircuit.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/type-aliases/CompiledCircuit.md similarity index 100% rename from docs/docs/reference/NoirJS/backend_barretenberg/type-aliases/CompiledCircuit.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/type-aliases/CompiledCircuit.md diff --git a/docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/type-aliases/ProofData.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/type-aliases/ProofData.md new file mode 100644 index 00000000000..3eb360a78f1 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/type-aliases/ProofData.md @@ -0,0 +1,20 @@ +# ProofData + +```ts +type ProofData: object; +``` + +## Description + +The representation of a proof + +## Type declaration + +| Member | Type | Description | +| :------ | :------ | :------ | +| `proof` | `Uint8Array` | **Description**

An byte array representing the proof | +| `publicInputs` | `WitnessMap` | **Description**

Public inputs of a proof | + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/typedoc-sidebar.cjs b/docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/typedoc-sidebar.cjs new file mode 100644 index 00000000000..04e662c845f --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir_js/reference/backend_barretenberg/typedoc-sidebar.cjs @@ -0,0 +1,4 @@ +// @ts-check +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const typedocSidebar = { items: [{"type":"category","label":"Classes","items":[{"type":"doc","id":"noir_js/reference/backend_barretenberg/classes/BarretenbergBackend","label":"BarretenbergBackend"}]},{"type":"category","label":"Interfaces","items":[{"type":"doc","id":"noir_js/reference/backend_barretenberg/interfaces/Backend","label":"Backend"}]},{"type":"category","label":"Type Aliases","items":[{"type":"doc","id":"noir_js/reference/backend_barretenberg/type-aliases/BackendOptions","label":"BackendOptions"},{"type":"doc","id":"noir_js/reference/backend_barretenberg/type-aliases/CompiledCircuit","label":"CompiledCircuit"},{"type":"doc","id":"noir_js/reference/backend_barretenberg/type-aliases/ProofData","label":"ProofData"}]}]}; +module.exports = typedocSidebar.items; \ No newline at end of file diff --git a/docs/docs/reference/NoirJS/noir_js/.nojekyll b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/.nojekyll similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/.nojekyll rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/.nojekyll diff --git a/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/classes/Noir.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/classes/Noir.md new file mode 100644 index 00000000000..c54468891af --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/classes/Noir.md @@ -0,0 +1,131 @@ +# Noir + +## Constructors + +### new Noir(circuit, backend) + +```ts +new Noir(circuit, backend?): Noir +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `circuit` | [`CompiledCircuit`](../type-aliases/CompiledCircuit.md) | +| `backend`? | `Backend` | + +#### Returns + +[`Noir`](Noir.md) + +## Methods + +### destroy() + +```ts +destroy(): Promise +``` + +#### Returns + +`Promise`\<`void`\> + +#### Description + +Destroys the underlying backend instance. + +#### Example + +```typescript +await noir.destroy(); +``` + +*** + +### execute() + +```ts +execute(inputs, foreignCallHandler?): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `inputs` | [`InputMap`](../type-aliases/InputMap.md) | +| `foreignCallHandler`? | [`ForeignCallHandler`](../type-aliases/ForeignCallHandler.md) | + +#### Returns + +`Promise`\<`object`\> + +#### Description + +Allows to execute a circuit to get its witness and return value. + +#### Example + +```typescript +async execute(inputs) +``` + +*** + +### generateFinalProof() + +```ts +generateFinalProof(inputs): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `inputs` | [`InputMap`](../type-aliases/InputMap.md) | + +#### Returns + +`Promise`\<[`ProofData`](../type-aliases/ProofData.md)\> + +#### Description + +Generates a witness and a proof given an object as input. + +#### Example + +```typescript +async generateFinalProof(input) +``` + +*** + +### verifyFinalProof() + +```ts +verifyFinalProof(proofData): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | [`ProofData`](../type-aliases/ProofData.md) | + +#### Returns + +`Promise`\<`boolean`\> + +#### Description + +Instantiates the verification key and verifies a proof. + +#### Example + +```typescript +async verifyFinalProof(proof) +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/docs/reference/NoirJS/noir_js/functions/and.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/and.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/functions/and.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/and.md diff --git a/docs/docs/reference/NoirJS/noir_js/functions/blake2s256.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/blake2s256.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/functions/blake2s256.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/blake2s256.md diff --git a/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/ecdsa_secp256k1_verify.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/ecdsa_secp256k1_verify.md new file mode 100644 index 00000000000..0ba5783f0d5 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/ecdsa_secp256k1_verify.md @@ -0,0 +1,29 @@ +# ecdsa\_secp256k1\_verify() + +```ts +ecdsa_secp256k1_verify( + hashed_msg, + public_key_x_bytes, + public_key_y_bytes, + signature): boolean +``` + +Calculates the Blake2s256 hash of the input bytes and represents these as a single field element. +Verifies a ECDSA signature over the secp256k1 curve. + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `hashed_msg` | `Uint8Array` | | +| `public_key_x_bytes` | `Uint8Array` | | +| `public_key_y_bytes` | `Uint8Array` | | +| `signature` | `Uint8Array` | | + +## Returns + +`boolean` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/docs/reference/NoirJS/noir_js/functions/ecdsa_secp256r1_verify.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/ecdsa_secp256r1_verify.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/functions/ecdsa_secp256r1_verify.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/ecdsa_secp256r1_verify.md diff --git a/docs/docs/reference/NoirJS/noir_js/functions/keccak256.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/keccak256.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/functions/keccak256.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/keccak256.md diff --git a/docs/docs/reference/NoirJS/noir_js/functions/sha256.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/sha256.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/functions/sha256.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/sha256.md diff --git a/docs/docs/reference/NoirJS/noir_js/functions/xor.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/xor.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/functions/xor.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/functions/xor.md diff --git a/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/index.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/index.md new file mode 100644 index 00000000000..348453c0059 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/index.md @@ -0,0 +1,37 @@ +# Noir JS + +## Exports + +### Classes + +| Class | Description | +| :------ | :------ | +| [Noir](classes/Noir.md) | - | + +### Type Aliases + +| Type alias | Description | +| :------ | :------ | +| [CompiledCircuit](type-aliases/CompiledCircuit.md) | - | +| [ForeignCallHandler](type-aliases/ForeignCallHandler.md) | A callback which performs an foreign call and returns the response. | +| [ForeignCallInput](type-aliases/ForeignCallInput.md) | - | +| [ForeignCallOutput](type-aliases/ForeignCallOutput.md) | - | +| [InputMap](type-aliases/InputMap.md) | - | +| [ProofData](type-aliases/ProofData.md) | - | +| [WitnessMap](type-aliases/WitnessMap.md) | - | + +### Functions + +| Function | Description | +| :------ | :------ | +| [and](functions/and.md) | Performs a bitwise AND operation between `lhs` and `rhs` | +| [blake2s256](functions/blake2s256.md) | Calculates the Blake2s256 hash of the input bytes | +| [ecdsa\_secp256k1\_verify](functions/ecdsa_secp256k1_verify.md) | Calculates the Blake2s256 hash of the input bytes and represents these as a single field element. | +| [ecdsa\_secp256r1\_verify](functions/ecdsa_secp256r1_verify.md) | Verifies a ECDSA signature over the secp256r1 curve. | +| [keccak256](functions/keccak256.md) | Calculates the Keccak256 hash of the input bytes | +| [sha256](functions/sha256.md) | Calculates the SHA256 hash of the input bytes | +| [xor](functions/xor.md) | Performs a bitwise XOR operation between `lhs` and `rhs` | + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/docs/reference/NoirJS/noir_js/type-aliases/CompiledCircuit.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/CompiledCircuit.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/type-aliases/CompiledCircuit.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/CompiledCircuit.md diff --git a/docs/docs/reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/ForeignCallHandler.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/ForeignCallHandler.md diff --git a/docs/docs/reference/NoirJS/noir_js/type-aliases/ForeignCallInput.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/ForeignCallInput.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/type-aliases/ForeignCallInput.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/ForeignCallInput.md diff --git a/docs/docs/reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/ForeignCallOutput.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/ForeignCallOutput.md diff --git a/docs/docs/reference/NoirJS/noir_js/type-aliases/InputMap.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/InputMap.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/type-aliases/InputMap.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/InputMap.md diff --git a/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/ProofData.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/ProofData.md new file mode 100644 index 00000000000..3eb360a78f1 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/ProofData.md @@ -0,0 +1,20 @@ +# ProofData + +```ts +type ProofData: object; +``` + +## Description + +The representation of a proof + +## Type declaration + +| Member | Type | Description | +| :------ | :------ | :------ | +| `proof` | `Uint8Array` | **Description**

An byte array representing the proof | +| `publicInputs` | `WitnessMap` | **Description**

Public inputs of a proof | + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/docs/reference/NoirJS/noir_js/type-aliases/WitnessMap.md b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/WitnessMap.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/type-aliases/WitnessMap.md rename to docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/type-aliases/WitnessMap.md diff --git a/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/typedoc-sidebar.cjs b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/typedoc-sidebar.cjs new file mode 100644 index 00000000000..077ebeb133e --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/noir_js/reference/noir_js/typedoc-sidebar.cjs @@ -0,0 +1,4 @@ +// @ts-check +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const typedocSidebar = { items: [{"type":"category","label":"Classes","items":[{"type":"doc","id":"noir_js/reference/noir_js/classes/Noir","label":"Noir"}]},{"type":"category","label":"Type Aliases","items":[{"type":"doc","id":"noir_js/reference/noir_js/type-aliases/CompiledCircuit","label":"CompiledCircuit"},{"type":"doc","id":"noir_js/reference/noir_js/type-aliases/ForeignCallHandler","label":"ForeignCallHandler"},{"type":"doc","id":"noir_js/reference/noir_js/type-aliases/ForeignCallInput","label":"ForeignCallInput"},{"type":"doc","id":"noir_js/reference/noir_js/type-aliases/ForeignCallOutput","label":"ForeignCallOutput"},{"type":"doc","id":"noir_js/reference/noir_js/type-aliases/InputMap","label":"InputMap"},{"type":"doc","id":"noir_js/reference/noir_js/type-aliases/ProofData","label":"ProofData"},{"type":"doc","id":"noir_js/reference/noir_js/type-aliases/WitnessMap","label":"WitnessMap"}]},{"type":"category","label":"Functions","items":[{"type":"doc","id":"noir_js/reference/noir_js/functions/and","label":"and"},{"type":"doc","id":"noir_js/reference/noir_js/functions/blake2s256","label":"blake2s256"},{"type":"doc","id":"noir_js/reference/noir_js/functions/ecdsa_secp256k1_verify","label":"ecdsa_secp256k1_verify"},{"type":"doc","id":"noir_js/reference/noir_js/functions/ecdsa_secp256r1_verify","label":"ecdsa_secp256r1_verify"},{"type":"doc","id":"noir_js/reference/noir_js/functions/keccak256","label":"keccak256"},{"type":"doc","id":"noir_js/reference/noir_js/functions/sha256","label":"sha256"},{"type":"doc","id":"noir_js/reference/noir_js/functions/xor","label":"xor"}]}]}; +module.exports = typedocSidebar.items; \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/.nojekyll b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/.nojekyll new file mode 100644 index 00000000000..e2ac6616add --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergBackend.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergBackend.md new file mode 100644 index 00000000000..5cbe9421b92 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/classes/BarretenbergBackend.md @@ -0,0 +1,185 @@ +# BarretenbergBackend + +## Implements + +- [`Backend`](../interfaces/Backend.md) + +## Constructors + +### new BarretenbergBackend(acirCircuit, options) + +```ts +new BarretenbergBackend(acirCircuit, options): BarretenbergBackend +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `acirCircuit` | [`CompiledCircuit`](../type-aliases/CompiledCircuit.md) | +| `options` | [`BackendOptions`](../type-aliases/BackendOptions.md) | + +#### Returns + +[`BarretenbergBackend`](BarretenbergBackend.md) + +## Methods + +### destroy() + +```ts +destroy(): Promise +``` + +#### Returns + +`Promise`\<`void`\> + +#### Implementation of + +[`Backend`](../interfaces/Backend.md).[`destroy`](../interfaces/Backend.md#destroy) + +#### Description + +Destroys the backend + +*** + +### generateFinalProof() + +```ts +generateFinalProof(decompressedWitness): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `decompressedWitness` | `Uint8Array` | + +#### Returns + +`Promise`\<[`ProofData`](../type-aliases/ProofData.md)\> + +#### Implementation of + +[`Backend`](../interfaces/Backend.md).[`generateFinalProof`](../interfaces/Backend.md#generatefinalproof) + +#### Description + +Generates a final proof (not meant to be verified in another circuit) + +*** + +### generateIntermediateProof() + +```ts +generateIntermediateProof(witness): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `witness` | `Uint8Array` | + +#### Returns + +`Promise`\<[`ProofData`](../type-aliases/ProofData.md)\> + +#### Implementation of + +[`Backend`](../interfaces/Backend.md).[`generateIntermediateProof`](../interfaces/Backend.md#generateintermediateproof) + +#### Example + +```typescript +const intermediateProof = await backend.generateIntermediateProof(witness); +``` + +*** + +### generateIntermediateProofArtifacts() + +```ts +generateIntermediateProofArtifacts(proofData, numOfPublicInputs): Promise +``` + +#### Parameters + +| Parameter | Type | Default value | +| :------ | :------ | :------ | +| `proofData` | [`ProofData`](../type-aliases/ProofData.md) | `undefined` | +| `numOfPublicInputs` | `number` | `0` | + +#### Returns + +`Promise`\<`object`\> + +#### Implementation of + +[`Backend`](../interfaces/Backend.md).[`generateIntermediateProofArtifacts`](../interfaces/Backend.md#generateintermediateproofartifacts) + +#### Example + +```typescript +const artifacts = await backend.generateIntermediateProofArtifacts(proof, numOfPublicInputs); +``` + +*** + +### verifyFinalProof() + +```ts +verifyFinalProof(proofData): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | [`ProofData`](../type-aliases/ProofData.md) | + +#### Returns + +`Promise`\<`boolean`\> + +#### Implementation of + +[`Backend`](../interfaces/Backend.md).[`verifyFinalProof`](../interfaces/Backend.md#verifyfinalproof) + +#### Description + +Verifies a final proof + +*** + +### verifyIntermediateProof() + +```ts +verifyIntermediateProof(proofData): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | [`ProofData`](../type-aliases/ProofData.md) | + +#### Returns + +`Promise`\<`boolean`\> + +#### Implementation of + +[`Backend`](../interfaces/Backend.md).[`verifyIntermediateProof`](../interfaces/Backend.md#verifyintermediateproof) + +#### Example + +```typescript +const isValidIntermediate = await backend.verifyIntermediateProof(proof); +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/docs/reference/NoirJS/backend_barretenberg/index.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/index.md similarity index 100% rename from docs/docs/reference/NoirJS/backend_barretenberg/index.md rename to docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/index.md diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/interfaces/Backend.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/interfaces/Backend.md new file mode 100644 index 00000000000..3eb9645c8d2 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/interfaces/Backend.md @@ -0,0 +1,132 @@ +# Backend + +## Methods + +### destroy() + +```ts +destroy(): Promise +``` + +#### Returns + +`Promise`\<`void`\> + +#### Description + +Destroys the backend + +*** + +### generateFinalProof() + +```ts +generateFinalProof(decompressedWitness): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `decompressedWitness` | `Uint8Array` | + +#### Returns + +`Promise`\<[`ProofData`](../type-aliases/ProofData.md)\> + +#### Description + +Generates a final proof (not meant to be verified in another circuit) + +*** + +### generateIntermediateProof() + +```ts +generateIntermediateProof(decompressedWitness): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `decompressedWitness` | `Uint8Array` | + +#### Returns + +`Promise`\<[`ProofData`](../type-aliases/ProofData.md)\> + +#### Description + +Generates an intermediate proof (meant to be verified in another circuit) + +*** + +### generateIntermediateProofArtifacts() + +```ts +generateIntermediateProofArtifacts(proofData, numOfPublicInputs): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | [`ProofData`](../type-aliases/ProofData.md) | +| `numOfPublicInputs` | `number` | + +#### Returns + +`Promise`\<`object`\> + +#### Description + +Retrieves the artifacts from a proof in the Field format + +*** + +### verifyFinalProof() + +```ts +verifyFinalProof(proofData): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | [`ProofData`](../type-aliases/ProofData.md) | + +#### Returns + +`Promise`\<`boolean`\> + +#### Description + +Verifies a final proof + +*** + +### verifyIntermediateProof() + +```ts +verifyIntermediateProof(proofData): Promise +``` + +#### Parameters + +| Parameter | Type | +| :------ | :------ | +| `proofData` | [`ProofData`](../type-aliases/ProofData.md) | + +#### Returns + +`Promise`\<`boolean`\> + +#### Description + +Verifies an intermediate proof + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/type-aliases/BackendOptions.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/type-aliases/BackendOptions.md new file mode 100644 index 00000000000..266ade75d17 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/type-aliases/BackendOptions.md @@ -0,0 +1,19 @@ +# BackendOptions + +```ts +type BackendOptions: object; +``` + +## Description + +An options object, currently only used to specify the number of threads to use. + +## Type declaration + +| Member | Type | Description | +| :------ | :------ | :------ | +| `threads` | `number` | **Description**

Number of threads | + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/type-aliases/CompiledCircuit.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/type-aliases/CompiledCircuit.md new file mode 100644 index 00000000000..34e0dd04205 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/type-aliases/CompiledCircuit.md @@ -0,0 +1,20 @@ +# CompiledCircuit + +```ts +type CompiledCircuit: object; +``` + +## Description + +The representation of a compiled circuit + +## Type declaration + +| Member | Type | Description | +| :------ | :------ | :------ | +| `abi` | `Abi` | **Description**

ABI representation of the circuit | +| `bytecode` | `string` | **Description**

The bytecode of the circuit | + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/docs/reference/NoirJS/backend_barretenberg/type-aliases/ProofData.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/type-aliases/ProofData.md similarity index 100% rename from docs/docs/reference/NoirJS/backend_barretenberg/type-aliases/ProofData.md rename to docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/type-aliases/ProofData.md diff --git a/docs/docs/reference/NoirJS/backend_barretenberg/typedoc-sidebar.cjs b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/typedoc-sidebar.cjs similarity index 100% rename from docs/docs/reference/NoirJS/backend_barretenberg/typedoc-sidebar.cjs rename to docs/versioned_docs/version-v0.23.0/reference/NoirJS/backend_barretenberg/typedoc-sidebar.cjs diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/.nojekyll b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/.nojekyll new file mode 100644 index 00000000000..e2ac6616add --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/docs/docs/reference/NoirJS/noir_js/classes/Noir.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/classes/Noir.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/classes/Noir.md rename to docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/classes/Noir.md diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/and.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/and.md new file mode 100644 index 00000000000..c783283e396 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/and.md @@ -0,0 +1,22 @@ +# and() + +```ts +and(lhs, rhs): string +``` + +Performs a bitwise AND operation between `lhs` and `rhs` + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `lhs` | `string` | | +| `rhs` | `string` | | + +## Returns + +`string` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/blake2s256.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/blake2s256.md new file mode 100644 index 00000000000..7882d0da8d5 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/blake2s256.md @@ -0,0 +1,21 @@ +# blake2s256() + +```ts +blake2s256(inputs): Uint8Array +``` + +Calculates the Blake2s256 hash of the input bytes + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `inputs` | `Uint8Array` | | + +## Returns + +`Uint8Array` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/docs/reference/NoirJS/noir_js/functions/ecdsa_secp256k1_verify.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/ecdsa_secp256k1_verify.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/functions/ecdsa_secp256k1_verify.md rename to docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/ecdsa_secp256k1_verify.md diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/ecdsa_secp256r1_verify.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/ecdsa_secp256r1_verify.md new file mode 100644 index 00000000000..0b20ff68957 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/ecdsa_secp256r1_verify.md @@ -0,0 +1,28 @@ +# ecdsa\_secp256r1\_verify() + +```ts +ecdsa_secp256r1_verify( + hashed_msg, + public_key_x_bytes, + public_key_y_bytes, + signature): boolean +``` + +Verifies a ECDSA signature over the secp256r1 curve. + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `hashed_msg` | `Uint8Array` | | +| `public_key_x_bytes` | `Uint8Array` | | +| `public_key_y_bytes` | `Uint8Array` | | +| `signature` | `Uint8Array` | | + +## Returns + +`boolean` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/keccak256.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/keccak256.md new file mode 100644 index 00000000000..d10f155ce86 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/keccak256.md @@ -0,0 +1,21 @@ +# keccak256() + +```ts +keccak256(inputs): Uint8Array +``` + +Calculates the Keccak256 hash of the input bytes + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `inputs` | `Uint8Array` | | + +## Returns + +`Uint8Array` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/sha256.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/sha256.md new file mode 100644 index 00000000000..6ba4ecac022 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/sha256.md @@ -0,0 +1,21 @@ +# sha256() + +```ts +sha256(inputs): Uint8Array +``` + +Calculates the SHA256 hash of the input bytes + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `inputs` | `Uint8Array` | | + +## Returns + +`Uint8Array` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/xor.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/xor.md new file mode 100644 index 00000000000..8d762b895d3 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/functions/xor.md @@ -0,0 +1,22 @@ +# xor() + +```ts +xor(lhs, rhs): string +``` + +Performs a bitwise XOR operation between `lhs` and `rhs` + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `lhs` | `string` | | +| `rhs` | `string` | | + +## Returns + +`string` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/docs/reference/NoirJS/noir_js/index.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/index.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/index.md rename to docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/index.md diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/CompiledCircuit.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/CompiledCircuit.md new file mode 100644 index 00000000000..34e0dd04205 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/CompiledCircuit.md @@ -0,0 +1,20 @@ +# CompiledCircuit + +```ts +type CompiledCircuit: object; +``` + +## Description + +The representation of a compiled circuit + +## Type declaration + +| Member | Type | Description | +| :------ | :------ | :------ | +| `abi` | `Abi` | **Description**

ABI representation of the circuit | +| `bytecode` | `string` | **Description**

The bytecode of the circuit | + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md new file mode 100644 index 00000000000..812b8b16481 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/ForeignCallHandler.md @@ -0,0 +1,24 @@ +# ForeignCallHandler + +```ts +type ForeignCallHandler: (name, inputs) => Promise; +``` + +A callback which performs an foreign call and returns the response. + +## Parameters + +| Parameter | Type | Description | +| :------ | :------ | :------ | +| `name` | `string` | The identifier for the type of foreign call being performed. | +| `inputs` | [`ForeignCallInput`](ForeignCallInput.md)[] | An array of hex encoded inputs to the foreign call. | + +## Returns + +`Promise`\<[`ForeignCallOutput`](ForeignCallOutput.md)[]\> + +outputs - An array of hex encoded outputs containing the results of the foreign call. + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/ForeignCallInput.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/ForeignCallInput.md new file mode 100644 index 00000000000..dd95809186a --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/ForeignCallInput.md @@ -0,0 +1,9 @@ +# ForeignCallInput + +```ts +type ForeignCallInput: string[]; +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md new file mode 100644 index 00000000000..b71fb78a946 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/ForeignCallOutput.md @@ -0,0 +1,9 @@ +# ForeignCallOutput + +```ts +type ForeignCallOutput: string | string[]; +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/InputMap.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/InputMap.md new file mode 100644 index 00000000000..c714e999d93 --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/InputMap.md @@ -0,0 +1,13 @@ +# InputMap + +```ts +type InputMap: object; +``` + +## Index signature + + \[`key`: `string`\]: `InputValue` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/docs/reference/NoirJS/noir_js/type-aliases/ProofData.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/ProofData.md similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/type-aliases/ProofData.md rename to docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/ProofData.md diff --git a/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/WitnessMap.md b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/WitnessMap.md new file mode 100644 index 00000000000..258c46f9d0c --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/type-aliases/WitnessMap.md @@ -0,0 +1,9 @@ +# WitnessMap + +```ts +type WitnessMap: Map; +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/docs/docs/reference/NoirJS/noir_js/typedoc-sidebar.cjs b/docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/typedoc-sidebar.cjs similarity index 100% rename from docs/docs/reference/NoirJS/noir_js/typedoc-sidebar.cjs rename to docs/versioned_docs/version-v0.23.0/reference/NoirJS/noir_js/typedoc-sidebar.cjs diff --git a/docs/versioned_docs/version-v0.23.0/reference/_category_.json b/docs/versioned_docs/version-v0.23.0/reference/_category_.json new file mode 100644 index 00000000000..5b6a20a609a --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/_category_.json @@ -0,0 +1,5 @@ +{ + "position": 4, + "collapsible": true, + "collapsed": true +} diff --git a/docs/versioned_docs/version-v0.23.0/reference/nargo_commands.md b/docs/versioned_docs/version-v0.23.0/reference/nargo_commands.md new file mode 100644 index 00000000000..fc2671b2bfc --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/reference/nargo_commands.md @@ -0,0 +1,253 @@ +--- +title: Nargo +description: + Noir CLI Commands for Noir Prover and Verifier to create, execute, prove and verify programs, + generate Solidity verifier smart contract and compile into JSON file containing ACIR + representation and ABI of circuit. +keywords: + [ + Nargo, + Noir CLI, + Noir Prover, + Noir Verifier, + generate Solidity verifier, + compile JSON file, + ACIR representation, + ABI of circuit, + TypeScript, + ] +sidebar_position: 0 +--- + +## General options + +| Option | Description | +| -------------------- | -------------------------------------------------- | +| `--show-ssa` | Emit debug information for the intermediate SSA IR | +| `--deny-warnings` | Quit execution when warnings are emitted | +| `--silence-warnings` | Suppress warnings | +| `-h, --help` | Print help | + +## `nargo help [subcommand]` + +Prints the list of available commands or specific information of a subcommand. + +_Arguments_ + +| Argument | Description | +| -------------- | -------------------------------------------- | +| `` | The subcommand whose help message to display | + +## `nargo backend` + +Installs and selects custom backends used to generate and verify proofs. + +### Commands + +| Command | Description | +| ----------- | --------------------------------------------------------- | +| `current` | Prints the name of the currently active backend | +| `ls` | Prints the list of currently installed backends | +| `use` | Select the backend to use | +| `install` | Install a new backend from a URL | +| `uninstall` | Uninstalls a backend | +| `help` | Print this message or the help of the given subcommand(s) | + +### Options + +| Option | Description | +| ------------ | ----------- | +| `-h, --help` | Print help | + +## `nargo check` + +Generate the `Prover.toml` and `Verifier.toml` files for specifying prover and verifier in/output +values of the Noir program respectively. + +### Options + +| Option | Description | +| --------------------- | ------------------------------------- | +| `--package ` | The name of the package to check | +| `--workspace` | Check all packages in the workspace | +| `--print-acir` | Display the ACIR for compiled circuit | +| `--deny-warnings` | Treat all warnings as errors | +| `--silence-warnings` | Suppress warnings | +| `-h, --help` | Print help | + +### `nargo codegen-verifier` + +Generate a Solidity verifier smart contract for the program. + +### Options + +| Option | Description | +| --------------------- | ------------------------------------- | +| `--package ` | The name of the package to codegen | +| `--workspace` | Codegen all packages in the workspace | +| `--print-acir` | Display the ACIR for compiled circuit | +| `--deny-warnings` | Treat all warnings as errors | +| `--silence-warnings` | Suppress warnings | +| `-h, --help` | Print help | + +## `nargo compile` + +Compile the program into a JSON build artifact file containing the ACIR representation and the ABI +of the circuit. This build artifact can then be used to generate and verify proofs. + +You can also use "build" as an alias for compile (e.g. `nargo build`). + +### Options + +| Option | Description | +| --------------------- | ------------------------------------------------------------ | +| `--package ` | The name of the package to compile | +| `--workspace` | Compile all packages in the workspace | +| `--print-acir` | Display the ACIR for compiled circuit | +| `--deny-warnings` | Treat all warnings as errors | +| `--silence-warnings` | Suppress warnings | +| `-h, --help` | Print help | + +## `nargo new ` + +Creates a new Noir project in a new folder. + +**Arguments** + +| Argument | Description | +| -------- | -------------------------------- | +| `` | The path to save the new project | + +### Options + +| Option | Description | +| --------------- | ----------------------------------------------------- | +| `--name ` | Name of the package [default: package directory name] | +| `--lib` | Use a library template | +| `--bin` | Use a binary template [default] | +| `--contract` | Use a contract template | +| `-h, --help` | Print help | + +## `nargo init` + +Creates a new Noir project in the current directory. + +### Options + +| Option | Description | +| --------------- | ----------------------------------------------------- | +| `--name ` | Name of the package [default: current directory name] | +| `--lib` | Use a library template | +| `--bin` | Use a binary template [default] | +| `--contract` | Use a contract template | +| `-h, --help` | Print help | + +## `nargo execute [WITNESS_NAME]` + +Runs the Noir program and prints its return value. + +**Arguments** + +| Argument | Description | +| ---------------- | ----------------------------------------- | +| `[WITNESS_NAME]` | Write the execution witness to named file | + +### Options + +| Option | Description | +| --------------------------------- | ------------------------------------------------------------------------------------ | +| `-p, --prover-name ` | The name of the toml file which contains the inputs for the prover [default: Prover] | +| `--package ` | The name of the package to execute | +| `--workspace` | Execute all packages in the workspace | +| `--print-acir` | Display the ACIR for compiled circuit | +| `--deny-warnings` | Treat all warnings as errors | +| `--silence-warnings` | Suppress warnings | +| `--oracle-resolver` | JSON RPC url to solve oracle calls | +| `-h, --help` | Print help | + +_Usage_ + +The inputs to the circuit are read from the `Prover.toml` file generated by `nargo check`, which +must be filled in. + +To save the witness to file, run the command with a value for the `WITNESS_NAME` argument. A +`.tr` file will then be saved in the `./target` folder. + +## `nargo prove` + +Creates a proof for the program. + +### Options + +| Option | Description | +| ------------------------------------- | ---------------------------------------------------------------------------------------- | +| `-p, --prover-name ` | The name of the toml file which contains the inputs for the prover [default: Prover] | +| `-v, --verifier-name ` | The name of the toml file which contains the inputs for the verifier [default: Verifier] | +| `--verify` | Verify proof after proving | +| `--package ` | The name of the package to prove | +| `--workspace` | Prove all packages in the workspace | +| `--print-acir` | Display the ACIR for compiled circuit | +| `--deny-warnings` | Treat all warnings as errors | +| `--silence-warnings` | Suppress warnings | +| `--oracle-resolver` | JSON RPC url to solve oracle calls | +| `-h, --help` | Print help | + +## `nargo verify` + +Given a proof and a program, verify whether the proof is valid. + +### Options + +| Option | Description | +| ------------------------------------- | ---------------------------------------------------------------------------------------- | +| `-v, --verifier-name ` | The name of the toml file which contains the inputs for the verifier [default: Verifier] | +| `--package ` | The name of the package to verify | +| `--workspace` | Verify all packages in the workspace | +| `--print-acir` | Display the ACIR for compiled circuit | +| `--deny-warnings` | Treat all warnings as errors | +| `--silence-warnings` | Suppress warnings | +| `-h, --help` | Print help | + +## `nargo test [TEST_NAME]` + +Nargo will automatically compile and run any functions which have the decorator `#[test]` on them if +you run `nargo test`. To print `println` statements in tests, use the `--show-output` flag. + +Takes an optional `--exact` flag which allows you to select tests based on an exact name. + +See an example on the [testing page](../getting_started/tooling/testing.md). + +### Options + +| Option | Description | +| --------------------- | -------------------------------------- | +| `--show-output` | Display output of `println` statements | +| `--exact` | Only run tests that match exactly | +| `--package ` | The name of the package to test | +| `--workspace` | Test all packages in the workspace | +| `--print-acir` | Display the ACIR for compiled circuit | +| `--deny-warnings` | Treat all warnings as errors | +| `--silence-warnings` | Suppress warnings | +| `--oracle-resolver` | JSON RPC url to solve oracle calls | +| `-h, --help` | Print help | + +## `nargo info` + +Prints a table containing the information of the package. + +Currently the table provide + +1. The number of ACIR opcodes +2. The final number gates in the circuit used by a backend + +If the file contains a contract the table will provide the +above information about each function of the contract. + +## `nargo lsp` + +Start a long-running Language Server process that communicates over stdin/stdout. +Usually this command is not run by a user, but instead will be run by a Language Client, such as [vscode-noir](https://github.com/noir-lang/vscode-noir). + +## `nargo fmt` + +Automatically formats your Noir source code based on the default formatting settings. diff --git a/docs/versioned_docs/version-v0.23.0/tutorials/noirjs_app.md b/docs/versioned_docs/version-v0.23.0/tutorials/noirjs_app.md new file mode 100644 index 00000000000..23534795dde --- /dev/null +++ b/docs/versioned_docs/version-v0.23.0/tutorials/noirjs_app.md @@ -0,0 +1,279 @@ +--- +title: Building a web app with NoirJS +description: Learn how to setup a new app that uses Noir to generate and verify zero-knowledge SNARK proofs in a typescript or javascript environment. +keywords: [how to, guide, javascript, typescript, noir, barretenberg, zero-knowledge, proofs, app] +sidebar_position: 0 +pagination_next: noir/concepts/data_types/index +--- + +NoirJS is a set of packages meant to work both in a browser and a server environment. In this tutorial, we will build a simple web app using them. From here, you should get an idea on how to proceed with your own Noir projects! + +You can find the complete app code for this guide [here](https://github.com/noir-lang/tiny-noirjs-app). + +## Setup + +:::note + +Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.19.x matches `noir_js@0.19.x`, etc. + +In this guide, we will be pinned to 0.19.4. + +::: + +Before we start, we want to make sure we have Node and Nargo installed. + +We start by opening a terminal and executing `node --version`. If we don't get an output like `v20.10.0`, that means node is not installed. Let's do that by following the handy [nvm guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script). + +As for `Nargo`, we can follow the the [Nargo guide](../getting_started/installation/index.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: + +```sh +curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash +``` + +Easy enough. Onwards! + +## Our project + +ZK is a powerful technology. An app that doesn't reveal one of the inputs to *anyone* is almost unbelievable, yet Noir makes it as easy as a single line of code. + +In fact, it's so simple that it comes nicely packaged in `nargo`. Let's do that! + +### Nargo + +Run: + +```nargo new circuit``` + +And... That's about it. Your program is ready to be compiled and run. + +To compile, let's `cd` into the `circuit` folder to enter our project, and call: + +```nargo compile``` + +This compiles our circuit into `json` format and add it to a new `target` folder. + +:::info + +At this point in the tutorial, your folder structure should look like this: + +```tree +. +└── circuit <---- our working directory + ├── Nargo.toml + ├── src + │ └── main.nr + └── target + └── circuit.json +``` + +::: + +### Node and Vite + +If you want to explore Nargo, feel free to go on a side-quest now and follow the steps in the +[getting started](../getting_started/hello_noir/index.md) guide. However, we want our app to run on the browser, so we need Vite. + +Vite is a powerful tool to generate static websites. While it provides all kinds of features, let's just go barebones with some good old vanilla JS. + +To do this this, go back to the previous folder (`cd ..`) and create a new vite project by running `npm create vite` and choosing "Vanilla" and "Javascript". + +You should see `vite-project` appear in your root folder. This seems like a good time to `cd` into it and install our NoirJS packages: + +```bash +npm i @noir-lang/backend_barretenberg@0.19.4 @noir-lang/noir_js@0.19.4 +``` + +:::info + +At this point in the tutorial, your folder structure should look like this: + +```tree +. +└── circuit + └── ...etc... +└── vite-project <---- our working directory + └── ...etc... +``` + +::: + +#### Some cleanup + +`npx create vite` is amazing but it creates a bunch of files we don't really need for our simple example. Actually, let's just delete everything except for `index.html`, `main.js` and `package.json`. I feel lighter already. + +![my heart is ready for you, noir.js](../../static/img/memes/titanic.jpeg) + +## HTML + +Our app won't run like this, of course. We need some working HTML, at least. Let's open our broken-hearted `index.html` and replace everything with this code snippet: + +```html + + + + + + +

Noir app

+
+ + +
+
+

Logs

+

Proof

+
+ + +``` + +It *could* be a beautiful UI... Depending on which universe you live in. + +## Some good old vanilla Javascript + +Our love for Noir needs undivided attention, so let's just open `main.js` and delete everything (this is where the romantic scenery becomes a bit creepy). + +Start by pasting in this boilerplate code: + +```js +const setup = async () => { + await Promise.all([ + import("@noir-lang/noirc_abi").then(module => + module.default(new URL("@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm", import.meta.url).toString()) + ), + import("@noir-lang/acvm_js").then(module => + module.default(new URL("@noir-lang/acvm_js/web/acvm_js_bg.wasm", import.meta.url).toString()) + ) + ]); +} + +function display(container, msg) { + const c = document.getElementById(container); + const p = document.createElement('p'); + p.textContent = msg; + c.appendChild(p); +} + +document.getElementById('submitGuess').addEventListener('click', async () => { + try { + // here's where love happens + } catch(err) { + display("logs", "Oh 💔 Wrong guess") + } +}); + +``` + +The display function doesn't do much. We're simply manipulating our website to see stuff happening. For example, if the proof fails, it will simply log a broken heart 😢 + +As for the `setup` function, it's just a sad reminder that dealing with `wasm` on the browser is not as easy as it should. Just copy, paste, and forget. + +:::info + +At this point in the tutorial, your folder structure should look like this: + +```tree +. +└── circuit + └── ...same as above +└── vite-project + ├── main.js + ├── package.json + └── index.html +``` + +You'll see other files and folders showing up (like `package-lock.json`, `node_modules`) but you shouldn't have to care about those. + +::: + +## Some NoirJS + +We're starting with the good stuff now. If you've compiled the circuit as described above, you should have a `json` file we want to import at the very top of our `main.js` file: + +```ts +import circuit from '../circuit/target/circuit.json'; +``` + +[Noir is backend-agnostic](../index.mdx#whats-new-about-noir). We write Noir, but we also need a proving backend. That's why we need to import and instantiate the two dependencies we installed above: `BarretenbergBackend` and `Noir`. Let's import them right below: + +```js +import { BarretenbergBackend } from '@noir-lang/backend_barretenberg'; +import { Noir } from '@noir-lang/noir_js'; +``` + +And instantiate them inside our try-catch block: + +```ts +// try { +const backend = new BarretenbergBackend(circuit); +const noir = new Noir(circuit, backend); +// } +``` + +:::note + +For the remainder of the tutorial, everything will be happening inside the `try` block + +::: + +## Our app + +Now for the app itself. We're capturing whatever is in the input when people press the submit button. Just add this: + +```js +const x = parseInt(document.getElementById('guessInput').value); +const input = { x, y: 2 }; +``` + +Now we're ready to prove stuff! Let's feed some inputs to our circuit and calculate the proof: + +```js +await setup(); // let's squeeze our wasm inits here + +display('logs', 'Generating proof... ⌛'); +const proof = await noir.generateFinalProof(input); +display('logs', 'Generating proof... ✅'); +display('results', proof.proof); +``` + +You're probably eager to see stuff happening, so go and run your app now! + +From your terminal, run `npm run dev`. If it doesn't open a browser for you, just visit `localhost:5173`. You should now see the worst UI ever, with an ugly input. + +![Getting Started 0](@site/static/img/noir_getting_started_1.png) + +Now, our circuit says `fn main(x: Field, y: pub Field)`. This means only the `y` value is public, and it's hardcoded above: `input = { x, y: 2 }`. In other words, you won't need to send your secret`x` to the verifier! + +By inputting any number other than 2 in the input box and clicking "submit", you should get a valid proof. Otherwise the proof won't even generate correctly. By the way, if you're human, you shouldn't be able to understand anything on the "proof" box. That's OK. We like you, human ❤️. + +## Verifying + +Time to celebrate, yes! But we shouldn't trust machines so blindly. Let's add these lines to see our proof being verified: + +```js +display('logs', 'Verifying proof... ⌛'); +const verification = await noir.verifyFinalProof(proof); +if (verification) display('logs', 'Verifying proof... ✅'); +``` + +You have successfully generated a client-side Noir web app! + +![coded app without math knowledge](../../static/img/memes/flextape.jpeg) + +## Further Reading + +You can see how noirjs is used in a full stack Next.js hardhat application in the [noir-starter repo here](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat). The example shows how to calculate a proof in the browser and verify it with a deployed Solidity verifier contract from noirjs. + +You should also check out the more advanced examples in the [noir-examples repo](https://github.com/noir-lang/noir-examples), where you'll find reference usage for some cool apps. diff --git a/docs/versioned_sidebars/version-v0.23.0-sidebars.json b/docs/versioned_sidebars/version-v0.23.0-sidebars.json new file mode 100644 index 00000000000..b16f79cc176 --- /dev/null +++ b/docs/versioned_sidebars/version-v0.23.0-sidebars.json @@ -0,0 +1,83 @@ +{ + "sidebar": [ + { + "type": "doc", + "id": "index" + }, + { + "type": "category", + "label": "Getting Started", + "items": [ + { + "type": "autogenerated", + "dirName": "getting_started" + } + ] + }, + { + "type": "category", + "label": "The Noir Language", + "items": [ + { + "type": "autogenerated", + "dirName": "noir" + } + ] + }, + { + "type": "html", + "value": "
", + "defaultStyle": true + }, + { + "type": "category", + "label": "How To Guides", + "items": [ + { + "type": "autogenerated", + "dirName": "how_to" + } + ] + }, + { + "type": "category", + "label": "Explainers", + "items": [ + { + "type": "autogenerated", + "dirName": "explainers" + } + ] + }, + { + "type": "category", + "label": "Tutorials", + "items": [ + { + "type": "autogenerated", + "dirName": "tutorials" + } + ] + }, + { + "type": "category", + "label": "Reference", + "items": [ + { + "type": "autogenerated", + "dirName": "reference" + } + ] + }, + { + "type": "html", + "value": "
", + "defaultStyle": true + }, + { + "type": "doc", + "id": "migration_notes", + "label": "Migration notes" + } + ] +} diff --git a/flake.nix b/flake.nix index 2300d009114..6849dc0a0ad 100644 --- a/flake.nix +++ b/flake.nix @@ -73,7 +73,7 @@ # Configuration shared between builds config = { # x-release-please-start-version - version = "0.22.0"; + version = "0.23.0"; # x-release-please-end src = pkgs.lib.cleanSourceWith { diff --git a/noir_stdlib/src/array.nr b/noir_stdlib/src/array.nr index bcdf56dd7aa..87cf4167dac 100644 --- a/noir_stdlib/src/array.nr +++ b/noir_stdlib/src/array.nr @@ -3,10 +3,10 @@ // by the methods in the `slice` module impl [T; N] { #[builtin(array_len)] - pub fn len(_self: Self) -> Field {} + pub fn len(self) -> Field {} #[builtin(arraysort)] - pub fn sort(_self: Self) -> Self {} + pub fn sort(self) -> Self {} // Sort with a custom sorting function. pub fn sort_via(mut a: Self, ordering: fn[Env](T, T) -> bool) -> Self { diff --git a/noir_stdlib/src/bigint.nr b/noir_stdlib/src/bigint.nr new file mode 100644 index 00000000000..14790f69241 --- /dev/null +++ b/noir_stdlib/src/bigint.nr @@ -0,0 +1,53 @@ +use crate::ops::{Add, Sub, Mul, Div, Rem,}; + +struct BigInt { + pointer: u32, + modulus: u32, +} + +impl BigInt { + #[builtin(bigint_add)] + pub fn bigint_add(_self: Self, _other: BigInt) -> BigInt { + } + #[builtin(bigint_neg)] + pub fn bigint_neg(_self: Self, _other: BigInt) -> BigInt { + } + #[builtin(bigint_mul)] + pub fn bigint_mul(_self: Self, _other: BigInt) -> BigInt { + } + #[builtin(bigint_div)] + pub fn bigint_div(_self: Self, _other: BigInt) -> BigInt { + } + #[builtin(bigint_from_le_bytes)] + pub fn from_le_bytes(_bytes: [u8], _modulus: [u8]) -> BigInt {} + #[builtin(bigint_to_le_bytes)] + pub fn to_le_bytes(_self: Self) -> [u8] {} +} + +impl Add for BigInt { + fn add(self: Self, other: BigInt) -> BigInt { + self.bigint_add(other) + } +} +impl Sub for BigInt { + fn sub(self: Self, other: BigInt) -> BigInt { + self.bigint_neg(other) + } +} +impl Mul for BigInt { + fn mul(self: Self, other: BigInt) -> BigInt { + self.bigint_mul(other) + } +} +impl Div for BigInt { + fn div(self: Self, other: BigInt) -> BigInt { + self.bigint_div(other) + } +} +impl Rem for BigInt { + fn rem(self: Self, other: BigInt) -> BigInt { + let quotient = self.bigint_div(other); + self.bigint_neg(quotient.bigint_mul(other)) + } +} + diff --git a/noir_stdlib/src/cmp.nr b/noir_stdlib/src/cmp.nr index 11127494c18..38316e5d6a8 100644 --- a/noir_stdlib/src/cmp.nr +++ b/noir_stdlib/src/cmp.nr @@ -1,17 +1,17 @@ +// docs:start:eq-trait trait Eq { fn eq(self, other: Self) -> bool; } +// docs:end:eq-trait impl Eq for Field { fn eq(self, other: Field) -> bool { self == other } } impl Eq for u1 { fn eq(self, other: u1) -> bool { self == other } } impl Eq for u8 { fn eq(self, other: u8) -> bool { self == other } } -impl Eq for u16 { fn eq(self, other: u16) -> bool { self == other } } impl Eq for u32 { fn eq(self, other: u32) -> bool { self == other } } impl Eq for u64 { fn eq(self, other: u64) -> bool { self == other } } impl Eq for i8 { fn eq(self, other: i8) -> bool { self == other } } -impl Eq for i16 { fn eq(self, other: i16) -> bool { self == other } } impl Eq for i32 { fn eq(self, other: i32) -> bool { self == other } } impl Eq for i64 { fn eq(self, other: i64) -> bool { self == other } } @@ -66,7 +66,6 @@ impl Eq for Ordering { } } - // Noir doesn't have enums yet so we emulate (Lt | Eq | Gt) with a struct // that has 3 public functions for constructing the struct. struct Ordering { @@ -90,10 +89,11 @@ impl Ordering { } } - +// docs:start:ord-trait trait Ord { fn cmp(self, other: Self) -> Ordering; } +// docs:end:ord-trait // Note: Field deliberately does not implement Ord @@ -109,18 +109,6 @@ impl Ord for u8 { } } -impl Ord for u16 { - fn cmp(self, other: u16) -> Ordering { - if self < other { - Ordering::less() - } else if self > other { - Ordering::greater() - } else { - Ordering::equal() - } - } -} - impl Ord for u32 { fn cmp(self, other: u32) -> Ordering { if self < other { @@ -157,18 +145,6 @@ impl Ord for i8 { } } -impl Ord for i16 { - fn cmp(self, other: i16) -> Ordering { - if self < other { - Ordering::less() - } else if self > other { - Ordering::greater() - } else { - Ordering::equal() - } - } -} - impl Ord for i32 { fn cmp(self, other: i32) -> Ordering { if self < other { diff --git a/noir_stdlib/src/collections.nr b/noir_stdlib/src/collections.nr index e06c662e658..177ca96816f 100644 --- a/noir_stdlib/src/collections.nr +++ b/noir_stdlib/src/collections.nr @@ -1 +1,2 @@ mod vec; +mod bounded_vec; diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr new file mode 100644 index 00000000000..332fefa63f9 --- /dev/null +++ b/noir_stdlib/src/collections/bounded_vec.nr @@ -0,0 +1,88 @@ +struct BoundedVec { + storage: [T; MaxLen], + // TODO: change this to return a u64 as Noir now + // uses u64 for indexing + len: Field, + empty_value: T, +} + +impl BoundedVec { + pub fn new(initial_value: T) -> Self { + BoundedVec { storage: [initial_value; MaxLen], len: 0, empty_value: initial_value } + } + + pub fn get(mut self: Self, index: Field) -> T { + assert(index as u64 < self.len as u64); + self.storage[index] + } + + pub fn get_unchecked(mut self: Self, index: Field) -> T { + self.storage[index] + } + + pub fn push(&mut self, elem: T) { + assert(self.len as u64 < MaxLen as u64, "push out of bounds"); + + self.storage[self.len] = elem; + self.len += 1; + } + + pub fn len(self) -> Field { + self.len + } + + pub fn max_len(_self: BoundedVec) -> Field { + MaxLen + } + + // This is a intermediate method, while we don't have an + // .extend method + pub fn storage(self) -> [T; MaxLen] { + self.storage + } + + pub fn extend_from_array(&mut self, array: [T; Len]) { + let new_len = self.len + array.len(); + assert(new_len as u64 <= MaxLen as u64, "extend_from_array out of bounds"); + for i in 0..array.len() { + self.storage[self.len + i] = array[i]; + } + self.len = new_len; + } + + pub fn extend_from_bounded_vec(&mut self, vec: BoundedVec) { + let append_len = vec.len(); + let new_len = self.len + append_len; + assert(new_len as u64 <= MaxLen as u64, "extend_from_bounded_vec out of bounds"); + + let mut exceeded_len = false; + for i in 0..Len { + exceeded_len |= i == append_len; + if !exceeded_len { + self.storage[self.len + (i as Field)] = vec.get_unchecked(i as Field); + } + } + self.len = new_len; + } + + pub fn pop(&mut self) -> T { + assert(self.len as u64 > 0); + self.len -= 1; + + let elem = self.storage[self.len]; + self.storage[self.len] = self.empty_value; + elem + } + + pub fn any(self, predicate: fn[Env](T) -> bool) -> bool { + let mut ret = false; + let mut exceeded_len = false; + for i in 0..MaxLen { + exceeded_len |= i == self.len; + if (!exceeded_len) { + ret |= predicate(self.storage[i]); + } + } + ret + } +} \ No newline at end of file diff --git a/noir_stdlib/src/convert.nr b/noir_stdlib/src/convert.nr new file mode 100644 index 00000000000..00ac0a0fd8c --- /dev/null +++ b/noir_stdlib/src/convert.nr @@ -0,0 +1,52 @@ +// docs:start:from-trait +trait From { + fn from(input: T) -> Self; +} +// docs:end:from-trait + +impl From for T { + fn from(input: T) -> T { + input + } +} + +// docs:start:into-trait +trait Into { + fn into(input: Self) -> T; +} + +impl Into for U where T: From { + fn into(input: U) -> T { + T::from(input) + } +} +// docs:end:into-trait + +// docs:start:from-impls +// Unsigned integers + +impl From for u32 { fn from(value: u8) -> u32 { value as u32 } } + +impl From for u64 { fn from(value: u8) -> u64 { value as u64 } } +impl From for u64 { fn from(value: u32) -> u64 { value as u64 } } + +impl From for Field { fn from(value: u8) -> Field { value as Field } } +impl From for Field { fn from(value: u32) -> Field { value as Field } } +impl From for Field { fn from(value: u64) -> Field { value as Field } } + +// Signed integers + +impl From for i32 { fn from(value: i8) -> i32 { value as i32 } } + +impl From for i64 { fn from(value: i8) -> i64 { value as i64 } } +impl From for i64 { fn from(value: i32) -> i64 { value as i64 } } + +// Booleans +impl From for u8 { fn from(value: bool) -> u8 { value as u8 } } +impl From for u32 { fn from(value: bool) -> u32 { value as u32 } } +impl From for u64 { fn from(value: bool) -> u64 { value as u64 } } +impl From for i8 { fn from(value: bool) -> i8 { value as i8 } } +impl From for i32 { fn from(value: bool) -> i32 { value as i32 } } +impl From for i64 { fn from(value: bool) -> i64 { value as i64 } } +impl From for Field { fn from(value: bool) -> Field { value as Field } } +// docs:end:from-impls diff --git a/noir_stdlib/src/default.nr b/noir_stdlib/src/default.nr index 232be74489c..32c4f3f3b48 100644 --- a/noir_stdlib/src/default.nr +++ b/noir_stdlib/src/default.nr @@ -1,16 +1,16 @@ +// docs:start:default-trait trait Default { fn default() -> Self; } +// docs:end:default-trait impl Default for Field { fn default() -> Field { 0 } } impl Default for u8 { fn default() -> u8 { 0 } } -impl Default for u16 { fn default() -> u16 { 0 } } impl Default for u32 { fn default() -> u32 { 0 } } impl Default for u64 { fn default() -> u64 { 0 } } impl Default for i8 { fn default() -> i8 { 0 } } -impl Default for i16 { fn default() -> i16 { 0 } } impl Default for i32 { fn default() -> i32 { 0 } } impl Default for i64 { fn default() -> i64 { 0 } } diff --git a/noir_stdlib/src/ecdsa_secp256k1.nr b/noir_stdlib/src/ecdsa_secp256k1.nr index b1f2b12c76b..e8d9af2230f 100644 --- a/noir_stdlib/src/ecdsa_secp256k1.nr +++ b/noir_stdlib/src/ecdsa_secp256k1.nr @@ -1,7 +1,10 @@ #[foreign(ecdsa_secp256k1)] +// docs:start:ecdsa_secp256k1 pub fn verify_signature( - _public_key_x: [u8; 32], - _public_key_y: [u8; 32], - _signature: [u8; 64], - _message_hash: [u8; N] -) -> bool {} + public_key_x: [u8; 32], + public_key_y: [u8; 32], + signature: [u8; 64], + message_hash: [u8; N] +) -> bool +// docs:end:ecdsa_secp256k1 +{} \ No newline at end of file diff --git a/noir_stdlib/src/ecdsa_secp256r1.nr b/noir_stdlib/src/ecdsa_secp256r1.nr index 6c3cf4d7945..9fe932a2f3d 100644 --- a/noir_stdlib/src/ecdsa_secp256r1.nr +++ b/noir_stdlib/src/ecdsa_secp256r1.nr @@ -1,7 +1,10 @@ #[foreign(ecdsa_secp256r1)] +// docs:start:ecdsa_secp256r1 pub fn verify_signature( - _public_key_x: [u8; 32], - _public_key_y: [u8; 32], - _signature: [u8; 64], - _message_hash: [u8; N] -) -> bool {} + public_key_x: [u8; 32], + public_key_y: [u8; 32], + signature: [u8; 64], + message_hash: [u8; N] +) -> bool +// docs:end:ecdsa_secp256r1 +{} \ No newline at end of file diff --git a/noir_stdlib/src/field.nr b/noir_stdlib/src/field.nr index fbd76a1e8a2..66fb50119f9 100644 --- a/noir_stdlib/src/field.nr +++ b/noir_stdlib/src/field.nr @@ -13,13 +13,13 @@ impl Field { } #[builtin(to_le_bits)] - fn __to_le_bits(_self: Self, _bit_size: u32) -> [u1] {} + fn __to_le_bits(self, _bit_size: u32) -> [u1] {} #[builtin(to_be_bits)] - fn __to_be_bits(_self: Self, _bit_size: u32) -> [u1] {} + fn __to_be_bits(self, bit_size: u32) -> [u1] {} #[builtin(apply_range_constraint)] - fn __assert_max_bit_size(_self: Self, _bit_size: u32) {} + fn __assert_max_bit_size(self, bit_size: u32) {} pub fn assert_max_bit_size(self: Self, bit_size: u32) { crate::assert_constant(bit_size); @@ -53,10 +53,10 @@ impl Field { // decompose `_self` into a `_result_len` vector over the `_radix` basis // `_radix` must be less than 256 #[builtin(to_le_radix)] - fn __to_le_radix(_self: Self, _radix: u32, _result_len: u32) -> [u8] {} + fn __to_le_radix(self, radix: u32, result_len: u32) -> [u8] {} #[builtin(to_be_radix)] - fn __to_be_radix(_self: Self, _radix: u32, _result_len: u32) -> [u8] {} + fn __to_be_radix(self, radix: u32, result_len: u32) -> [u8] {} // Returns self to the power of the given exponent value. diff --git a/noir_stdlib/src/hash.nr b/noir_stdlib/src/hash.nr index 5933209d9bc..cc864039a90 100644 --- a/noir_stdlib/src/hash.nr +++ b/noir_stdlib/src/hash.nr @@ -2,43 +2,58 @@ mod poseidon; mod mimc; #[foreign(sha256)] -pub fn sha256(_input: [u8; N]) -> [u8; 32] {} +// docs:start:sha256 +pub fn sha256(input: [u8; N]) -> [u8; 32] +// docs:end:sha256 +{} #[foreign(blake2s)] -pub fn blake2s(_input: [u8; N]) -> [u8; 32] {} +// docs:start:blake2s +pub fn blake2s(input: [u8; N]) -> [u8; 32] +// docs:end:blake2s +{} #[foreign(blake3)] -pub fn blake3(_input: [u8; N]) -> [u8; 32] {} +// docs:start:blake3 +pub fn blake3(input: [u8; N]) -> [u8; 32] +// docs:end:blake3 +{} +// docs:start:pedersen_commitment struct PedersenPoint { x : Field, y : Field, } -pub fn pedersen_commitment(input: [Field; N]) -> PedersenPoint { +pub fn pedersen_commitment(input: [Field; N]) -> PedersenPoint +// docs:end:pedersen_commitment +{ pedersen_commitment_with_separator(input, 0) } #[foreign(pedersen_commitment)] -pub fn __pedersen_commitment_with_separator(_input: [Field; N], _separator: u32) -> [Field; 2] {} +pub fn __pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> [Field; 2] {} pub fn pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> PedersenPoint { let values = __pedersen_commitment_with_separator(input, separator); PedersenPoint { x: values[0], y: values[1] } } -pub fn pedersen_hash(input: [Field; N]) -> Field { +// docs:start:pedersen_hash +pub fn pedersen_hash(input: [Field; N]) -> Field +// docs:end:pedersen_hash +{ pedersen_hash_with_separator(input, 0) } #[foreign(pedersen_hash)] -pub fn pedersen_hash_with_separator(_input: [Field; N], _separator: u32) -> Field {} +pub fn pedersen_hash_with_separator(input: [Field; N], separator: u32) -> Field {} -pub fn hash_to_field(_input: [Field; N]) -> Field { +pub fn hash_to_field(input: [Field; N]) -> Field { let mut inputs_as_bytes = []; for i in 0..N { - let input_bytes = _input[i].to_le_bytes(32); + let input_bytes = input[i].to_le_bytes(32); for i in 0..32 { inputs_as_bytes = inputs_as_bytes.push_back(input_bytes[i]); } @@ -49,5 +64,13 @@ pub fn hash_to_field(_input: [Field; N]) -> Field { } #[foreign(keccak256)] -pub fn keccak256(_input: [u8; N], _message_size: u32) -> [u8; 32] {} +// docs:start:keccak256 +pub fn keccak256(input: [u8; N], message_size: u32) -> [u8; 32] +// docs:end:keccak256 +{} +#[foreign(poseidon2_permutation)] +pub fn poseidon2_permutation(_input: [u8; N], _state_length: u32) -> [u8; N] {} + +#[foreign(sha256_compression)] +pub fn sha256_compression(_input: [u32; 16], _state: [u32; 8]) -> [u32; 8] {} diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 23a7c71ff45..5165d1ee07b 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -16,6 +16,7 @@ mod ec; mod unsafe; mod collections; mod compat; +mod convert; mod option; mod string; mod test; @@ -24,11 +25,12 @@ mod ops; mod default; mod prelude; mod uint128; +// mod bigint; // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident #[oracle(print)] -unconstrained fn print_oracle(_with_newline: bool, _input: T) {} +unconstrained fn print_oracle(with_newline: bool, input: T) {} unconstrained pub fn print(input: T) { print_oracle(false, input); @@ -39,20 +41,20 @@ unconstrained pub fn println(input: T) { } #[foreign(recursive_aggregation)] -pub fn verify_proof(_verification_key: [Field], _proof: [Field], _public_inputs: [Field], _key_hash: Field) {} +pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} // Asserts that the given value is known at compile-time. // Useful for debugging for-loop bounds. #[builtin(assert_constant)] -pub fn assert_constant(_x: T) {} +pub fn assert_constant(x: T) {} // from_field and as_field are private since they are not valid for every type. // `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 { crate::from_field(crate::as_field(x) + crate::as_field(y)) diff --git a/noir_stdlib/src/ops.nr b/noir_stdlib/src/ops.nr index 3078ac11296..e561265629e 100644 --- a/noir_stdlib/src/ops.nr +++ b/noir_stdlib/src/ops.nr @@ -1,156 +1,155 @@ - +// docs:start:add-trait trait Add { fn add(self, other: Self) -> Self; } +// docs:end:add-trait impl Add for Field { fn add(self, other: Field) -> Field { self + other } } impl Add for u8 { fn add(self, other: u8) -> u8 { self + other } } -impl Add for u16 { fn add(self, other: u16) -> u16 { self + other } } impl Add for u32 { fn add(self, other: u32) -> u32 { self + other } } impl Add for u64 { fn add(self, other: u64) -> u64 { self + other } } impl Add for i8 { fn add(self, other: i8) -> i8 { self + other } } -impl Add for i16 { fn add(self, other: i16) -> i16 { self + other } } impl Add for i32 { fn add(self, other: i32) -> i32 { self + other } } impl Add for i64 { fn add(self, other: i64) -> i64 { self + other } } +// docs:start:sub-trait trait Sub { fn sub(self, other: Self) -> Self; } +// docs:end:sub-trait impl Sub for Field { fn sub(self, other: Field) -> Field { self - other } } impl Sub for u8 { fn sub(self, other: u8) -> u8 { self - other } } -impl Sub for u16 { fn sub(self, other: u16) -> u16 { self - other } } impl Sub for u32 { fn sub(self, other: u32) -> u32 { self - other } } impl Sub for u64 { fn sub(self, other: u64) -> u64 { self - other } } impl Sub for i8 { fn sub(self, other: i8) -> i8 { self - other } } -impl Sub for i16 { fn sub(self, other: i16) -> i16 { self - other } } impl Sub for i32 { fn sub(self, other: i32) -> i32 { self - other } } impl Sub for i64 { fn sub(self, other: i64) -> i64 { self - other } } +// docs:start:mul-trait trait Mul { fn mul(self, other: Self) -> Self; } +// docs:end:mul-trait impl Mul for Field { fn mul(self, other: Field) -> Field { self * other } } impl Mul for u8 { fn mul(self, other: u8) -> u8 { self * other } } -impl Mul for u16 { fn mul(self, other: u16) -> u16 { self * other } } impl Mul for u32 { fn mul(self, other: u32) -> u32 { self * other } } impl Mul for u64 { fn mul(self, other: u64) -> u64 { self * other } } impl Mul for i8 { fn mul(self, other: i8) -> i8 { self * other } } -impl Mul for i16 { fn mul(self, other: i16) -> i16 { self * other } } impl Mul for i32 { fn mul(self, other: i32) -> i32 { self * other } } impl Mul for i64 { fn mul(self, other: i64) -> i64 { self * other } } +// docs:start:div-trait trait Div { fn div(self, other: Self) -> Self; } +// docs:end:div-trait impl Div for Field { fn div(self, other: Field) -> Field { self / other } } impl Div for u8 { fn div(self, other: u8) -> u8 { self / other } } -impl Div for u16 { fn div(self, other: u16) -> u16 { self / other } } impl Div for u32 { fn div(self, other: u32) -> u32 { self / other } } impl Div for u64 { fn div(self, other: u64) -> u64 { self / other } } impl Div for i8 { fn div(self, other: i8) -> i8 { self / other } } -impl Div for i16 { fn div(self, other: i16) -> i16 { self / other } } impl Div for i32 { fn div(self, other: i32) -> i32 { self / other } } impl Div for i64 { fn div(self, other: i64) -> i64 { self / other } } -trait Rem { +// docs:start:rem-trait +trait Rem{ fn rem(self, other: Self) -> Self; } +// docs:end:rem-trait impl Rem for u8 { fn rem(self, other: u8) -> u8 { self % other } } -impl Rem for u16 { fn rem(self, other: u16) -> u16 { self % other } } impl Rem for u32 { fn rem(self, other: u32) -> u32 { self % other } } impl Rem for u64 { fn rem(self, other: u64) -> u64 { self % other } } impl Rem for i8 { fn rem(self, other: i8) -> i8 { self % other } } -impl Rem for i16 { fn rem(self, other: i16) -> i16 { self % other } } impl Rem for i32 { fn rem(self, other: i32) -> i32 { self % other } } impl Rem for i64 { fn rem(self, other: i64) -> i64 { self % other } } +// docs:start:bitor-trait trait BitOr { fn bitor(self, other: Self) -> Self; } +// docs:end:bitor-trait impl BitOr for bool { fn bitor(self, other: bool) -> bool { self | other } } impl BitOr for u8 { fn bitor(self, other: u8) -> u8 { self | other } } -impl BitOr for u16 { fn bitor(self, other: u16) -> u16 { self | other } } impl BitOr for u32 { fn bitor(self, other: u32) -> u32 { self | other } } impl BitOr for u64 { fn bitor(self, other: u64) -> u64 { self | other } } impl BitOr for i8 { fn bitor(self, other: i8) -> i8 { self | other } } -impl BitOr for i16 { fn bitor(self, other: i16) -> i16 { self | other } } impl BitOr for i32 { fn bitor(self, other: i32) -> i32 { self | other } } impl BitOr for i64 { fn bitor(self, other: i64) -> i64 { self | other } } +// docs:start:bitand-trait trait BitAnd { fn bitand(self, other: Self) -> Self; } +// docs:end:bitand-trait impl BitAnd for bool { fn bitand(self, other: bool) -> bool { self & other } } impl BitAnd for u8 { fn bitand(self, other: u8) -> u8 { self & other } } -impl BitAnd for u16 { fn bitand(self, other: u16) -> u16 { self & other } } impl BitAnd for u32 { fn bitand(self, other: u32) -> u32 { self & other } } impl BitAnd for u64 { fn bitand(self, other: u64) -> u64 { self & other } } impl BitAnd for i8 { fn bitand(self, other: i8) -> i8 { self & other } } -impl BitAnd for i16 { fn bitand(self, other: i16) -> i16 { self & other } } impl BitAnd for i32 { fn bitand(self, other: i32) -> i32 { self & other } } impl BitAnd for i64 { fn bitand(self, other: i64) -> i64 { self & other } } +// docs:start:bitxor-trait trait BitXor { fn bitxor(self, other: Self) -> Self; } +// docs:end:bitxor-trait impl BitXor for bool { fn bitxor(self, other: bool) -> bool { self ^ other } } impl BitXor for u8 { fn bitxor(self, other: u8) -> u8 { self ^ other } } -impl BitXor for u16 { fn bitxor(self, other: u16) -> u16 { self ^ other } } impl BitXor for u32 { fn bitxor(self, other: u32) -> u32 { self ^ other } } impl BitXor for u64 { fn bitxor(self, other: u64) -> u64 { self ^ other } } impl BitXor for i8 { fn bitxor(self, other: i8) -> i8 { self ^ other } } -impl BitXor for i16 { fn bitxor(self, other: i16) -> i16 { self ^ other } } impl BitXor for i32 { fn bitxor(self, other: i32) -> i32 { self ^ other } } impl BitXor for i64 { fn bitxor(self, other: i64) -> i64 { self ^ other } } +// docs:start:shl-trait trait Shl { fn shl(self, other: Self) -> Self; } +// docs:end:shl-trait impl Shl for u8 { fn shl(self, other: u8) -> u8 { self << other } } -impl Shl for u16 { fn shl(self, other: u16) -> u16 { self << other } } impl Shl for u32 { fn shl(self, other: u32) -> u32 { self << other } } impl Shl for u64 { fn shl(self, other: u64) -> u64 { self << other } } // Bit shifting is not currently supported for signed integer types // impl Shl for i8 { fn shl(self, other: i8) -> i8 { self << other } } -// impl Shl for i16 { fn shl(self, other: i16) -> i16 { self << other } } // impl Shl for i32 { fn shl(self, other: i32) -> i32 { self << other } } // impl Shl for i64 { fn shl(self, other: i64) -> i64 { self << other } } +// docs:start:shr-trait trait Shr { fn shr(self, other: Self) -> Self; } +// docs:end:shr-trait impl Shr for u8 { fn shr(self, other: u8) -> u8 { self >> other } } -impl Shr for u16 { fn shr(self, other: u16) -> u16 { self >> other } } impl Shr for u32 { fn shr(self, other: u32) -> u32 { self >> other } } impl Shr for u64 { fn shr(self, other: u64) -> u64 { self >> other } } // Bit shifting is not currently supported for signed integer types // impl Shr for i8 { fn shr(self, other: i8) -> i8 { self >> other } } -// impl Shr for i16 { fn shr(self, other: i16) -> i16 { self >> other } } // impl Shr for i32 { fn shr(self, other: i32) -> i32 { self >> other } } // impl Shr for i64 { fn shr(self, other: i64) -> i64 { self >> other } } diff --git a/noir_stdlib/src/option.nr b/noir_stdlib/src/option.nr index 137d57f33db..cab95731d05 100644 --- a/noir_stdlib/src/option.nr +++ b/noir_stdlib/src/option.nr @@ -56,6 +56,12 @@ impl Option { } } + /// Asserts `self.is_some()` with a provided custom message and returns the contained `Some` value + fn expect(self, message: fmtstr) -> T { + assert(self.is_some(), message); + self._value + } + /// If self is `Some(x)`, this returns `Some(f(x))`. Otherwise, this returns `None`. pub fn map(self, f: fn[Env](T) -> U) -> Option { if self._is_some { diff --git a/noir_stdlib/src/prelude.nr b/noir_stdlib/src/prelude.nr index b57ff460371..3244329aa4b 100644 --- a/noir_stdlib/src/prelude.nr +++ b/noir_stdlib/src/prelude.nr @@ -1,6 +1,8 @@ use crate::collections::vec::Vec; +use crate::collections::bounded_vec::BoundedVec; use crate::option::Option; use crate::{print, println, assert_constant}; use crate::uint128::U128; use crate::cmp::{Eq, Ord}; use crate::default::Default; +use crate::convert::{From, Into}; diff --git a/noir_stdlib/src/scalar_mul.nr b/noir_stdlib/src/scalar_mul.nr index 37cd935cdb9..26378e4839a 100644 --- a/noir_stdlib/src/scalar_mul.nr +++ b/noir_stdlib/src/scalar_mul.nr @@ -1,3 +1,22 @@ +use crate::ops::Add; + +struct EmbeddedCurvePoint { + x: Field, + y: Field, +} + +impl EmbeddedCurvePoint { + fn double(self) -> EmbeddedCurvePoint { + embedded_curve_add(self, self) + } +} + +impl Add for EmbeddedCurvePoint { + fn add(self, other: EmbeddedCurvePoint) -> EmbeddedCurvePoint { + embedded_curve_add(self, other) + } +} + // Computes a fixed base scalar multiplication over the embedded curve. // For bn254, We have Grumpkin and Baby JubJub. // For bls12-381, we have JubJub and Bandersnatch. @@ -5,4 +24,13 @@ // The embedded curve being used is decided by the // underlying proof system. #[foreign(fixed_base_scalar_mul)] -pub fn fixed_base_embedded_curve(_low: Field, _high: Field) -> [Field; 2] {} +// docs:start:fixed_base_embedded_curve +pub fn fixed_base_embedded_curve( + low: Field, + high: Field +) -> [Field; 2] +// docs:end:fixed_base_embedded_curve +{} + +#[foreign(embedded_curve_add)] +fn embedded_curve_add(_point1: EmbeddedCurvePoint, _point2: EmbeddedCurvePoint) -> EmbeddedCurvePoint {} diff --git a/noir_stdlib/src/schnorr.nr b/noir_stdlib/src/schnorr.nr index 5ed95096f97..33656254550 100644 --- a/noir_stdlib/src/schnorr.nr +++ b/noir_stdlib/src/schnorr.nr @@ -1,7 +1,10 @@ #[foreign(schnorr_verify)] +// docs:start:schnorr_verify pub fn verify_signature( - _public_key_x: Field, - _public_key_y: Field, - _signature: [u8; 64], - _message: [u8; N] -) -> bool {} + public_key_x: Field, + public_key_y: Field, + signature: [u8; 64], + message: [u8; N] +) -> bool +// docs:end:schnorr_verify +{} \ No newline at end of file diff --git a/noir_stdlib/src/slice.nr b/noir_stdlib/src/slice.nr index a5a9a38ed53..aa4b73edc1a 100644 --- a/noir_stdlib/src/slice.nr +++ b/noir_stdlib/src/slice.nr @@ -3,34 +3,34 @@ impl [T] { /// new slice with a length one greater than the /// original unmodified slice. #[builtin(slice_push_back)] - pub fn push_back(_self: Self, _elem: T) -> Self { } + pub fn push_back(self, elem: T) -> Self { } /// Push a new element to the front of the slice, returning a /// new slice with a length one greater than the /// original unmodified slice. #[builtin(slice_push_front)] - pub fn push_front(_self: Self, _elem: T) -> Self { } + pub fn push_front(self, elem: T) -> Self { } /// Remove the last element of the slice, returning the /// popped slice and the element in a tuple #[builtin(slice_pop_back)] - pub fn pop_back(_self: Self) -> (Self, T) { } + pub fn pop_back(self) -> (Self, T) { } /// Remove the first element of the slice, returning the /// element and the popped slice in a tuple #[builtin(slice_pop_front)] - pub fn pop_front(_self: Self) -> (T, Self) { } + pub fn pop_front(self) -> (T, Self) { } /// Insert an element at a specified index, shifting all elements /// after it to the right #[builtin(slice_insert)] - pub fn insert(_self: Self, _index: Field, _elem: T) -> Self { } + pub fn insert(self, index: Field, elem: T) -> Self { } /// Remove an element at a specified index, shifting all elements /// after it to the left, returning the altered slice and /// the removed element #[builtin(slice_remove)] - pub fn remove(_self: Self, _index: Field) -> (Self, T) { } + pub fn remove(self, index: Field) -> (Self, T) { } // Append each element of the `other` slice to the end of `self`. // This returns a new slice and leaves both input slices unchanged. diff --git a/noir_stdlib/src/string.nr b/noir_stdlib/src/string.nr index e402abf9ab6..ad6fd19e2de 100644 --- a/noir_stdlib/src/string.nr +++ b/noir_stdlib/src/string.nr @@ -2,7 +2,7 @@ use crate::collections::vec::Vec; impl str { /// Converts the given string into a byte array #[builtin(str_as_bytes)] - pub fn as_bytes(_self: Self) -> [u8; N] { } + pub fn as_bytes(self) -> [u8; N] { } /// return a byte vector of the str content pub fn as_bytes_vec(self: Self) -> Vec { diff --git a/noir_stdlib/src/test.nr b/noir_stdlib/src/test.nr index 47b31f4acea..560cfde741c 100644 --- a/noir_stdlib/src/test.nr +++ b/noir_stdlib/src/test.nr @@ -1,17 +1,17 @@ #[oracle(create_mock)] -unconstrained fn create_mock_oracle(_name: str) -> Field {} +unconstrained fn create_mock_oracle(name: str) -> Field {} #[oracle(set_mock_params)] -unconstrained fn set_mock_params_oracle

(_id: Field, _params: P) {} +unconstrained fn set_mock_params_oracle

(id: Field, params: P) {} #[oracle(set_mock_returns)] -unconstrained fn set_mock_returns_oracle(_id: Field, _returns: R) {} +unconstrained fn set_mock_returns_oracle(id: Field, returns: R) {} #[oracle(set_mock_times)] -unconstrained fn set_mock_times_oracle(_id: Field, _times: u64) {} +unconstrained fn set_mock_times_oracle(id: Field, times: u64) {} #[oracle(clear_mock)] -unconstrained fn clear_mock_oracle(_id: Field) {} +unconstrained fn clear_mock_oracle(id: Field) {} struct OracleMock { id: Field, diff --git a/noirc_macros/Cargo.toml b/noirc_macros/Cargo.toml new file mode 100644 index 00000000000..699e6b01cae --- /dev/null +++ b/noirc_macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "noirc_macros" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +noirc_frontend.workspace = true +iter-extended.workspace = true \ No newline at end of file diff --git a/noirc_macros/src/lib.rs b/noirc_macros/src/lib.rs new file mode 100644 index 00000000000..4337214d69f --- /dev/null +++ b/noirc_macros/src/lib.rs @@ -0,0 +1,61 @@ +use noirc_frontend::macros_api::parse_program; +use noirc_frontend::macros_api::HirContext; +use noirc_frontend::macros_api::SortedModule; +use noirc_frontend::macros_api::{CrateId, FileId}; +use noirc_frontend::macros_api::{MacroError, MacroProcessor}; + +pub struct AssertMessageMacro; + +impl MacroProcessor for AssertMessageMacro { + fn process_untyped_ast( + &self, + ast: SortedModule, + crate_id: &CrateId, + _context: &HirContext, + ) -> Result { + transform(ast, crate_id) + } + + // This macro does not need to process any information after name resolution + fn process_typed_ast( + &self, + _crate_id: &CrateId, + _context: &mut HirContext, + ) -> Result<(), (MacroError, FileId)> { + Ok(()) + } +} + +fn transform(ast: SortedModule, crate_id: &CrateId) -> Result { + let ast = add_resolve_assert_message_funcs(ast, crate_id)?; + + Ok(ast) +} + +fn add_resolve_assert_message_funcs( + mut ast: SortedModule, + crate_id: &CrateId, +) -> Result { + if !crate_id.is_stdlib() { + return Ok(ast); + } + let assert_message_oracles = " + #[oracle(assert_message)] + unconstrained fn assert_message_oracle(_input: T) {} + unconstrained pub fn resolve_assert_message(input: T, condition: bool) { + if !condition { + assert_message_oracle(input); + } + }"; + + let (assert_msg_funcs_ast, errors) = parse_program(assert_message_oracles); + assert_eq!(errors.len(), 0, "Failed to parse Noir macro code. This is either a bug in the compiler or the Noir macro code"); + + let assert_msg_funcs_ast = assert_msg_funcs_ast.into_sorted(); + + for func in assert_msg_funcs_ast.functions { + ast.functions.push(func) + } + + Ok(ast) +} diff --git a/scripts/bootstrap_native.sh b/scripts/bootstrap_native.sh index 3e0e2ed853a..974f0edcfec 100755 --- a/scripts/bootstrap_native.sh +++ b/scripts/bootstrap_native.sh @@ -12,6 +12,12 @@ else export GIT_COMMIT=$(git rev-parse --verify HEAD) fi +# Check if the 'cargo' command is available in the system +if ! command -v cargo > /dev/null; then + echo "Cargo is not installed. Please install Cargo and the Rust toolchain." + exit 1 +fi + # Build native. if [ -n "${DEBUG:-}" ]; then cargo build diff --git a/scripts/install_wasm-bindgen.sh b/scripts/install_wasm-bindgen.sh index c6e85bac50b..f34ed4c0ad0 100755 --- a/scripts/install_wasm-bindgen.sh +++ b/scripts/install_wasm-bindgen.sh @@ -3,8 +3,12 @@ set -eu cd $(dirname "$0")/.. +# Install binstall +curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + # Install wasm-bindgen-cli. if [ "$(wasm-bindgen --version | cut -d' ' -f2)" != "0.2.86" ]; then echo "Building wasm-bindgen..." - RUSTFLAGS="-Ctarget-feature=-crt-static" cargo install -f wasm-bindgen-cli --version 0.2.86 + cargo binstall wasm-bindgen-cli@0.2.86 --force --no-confirm fi + diff --git a/scripts/nargo_compile_noir_js_assert_lt.sh b/scripts/nargo_compile_noir_js_assert_lt.sh deleted file mode 100755 index 636ae59b996..00000000000 --- a/scripts/nargo_compile_noir_js_assert_lt.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -cd ./tooling/noir_js/test/noir_compiled_examples/assert_lt -nargo compile \ No newline at end of file diff --git a/test_programs/compile_failure/assert_msg_runtime/Nargo.toml b/test_programs/compile_failure/assert_msg_runtime/Nargo.toml new file mode 100644 index 00000000000..765f632ff74 --- /dev/null +++ b/test_programs/compile_failure/assert_msg_runtime/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "assert_msg_runtime" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/assert_msg_runtime/Prover.toml b/test_programs/compile_failure/assert_msg_runtime/Prover.toml new file mode 100644 index 00000000000..f28f2f8cc48 --- /dev/null +++ b/test_programs/compile_failure/assert_msg_runtime/Prover.toml @@ -0,0 +1,2 @@ +x = "5" +y = "10" diff --git a/test_programs/compile_failure/assert_msg_runtime/src/main.nr b/test_programs/compile_failure/assert_msg_runtime/src/main.nr new file mode 100644 index 00000000000..bec3082550a --- /dev/null +++ b/test_programs/compile_failure/assert_msg_runtime/src/main.nr @@ -0,0 +1,7 @@ +fn main(x: Field, y: pub Field) { + assert(x != y, f"Expected x != y, but got both equal {x}"); + assert(x != y); + let z = x + y; + assert(z != y, f"Expected z != y, but got both equal {z}"); + assert_eq(x, y, f"Expected x == y, but x is {x} and y is {y}"); +} \ No newline at end of file diff --git a/test_programs/compile_failure/brillig_assert_msg_runtime/Nargo.toml b/test_programs/compile_failure/brillig_assert_msg_runtime/Nargo.toml new file mode 100644 index 00000000000..00f97b7273a --- /dev/null +++ b/test_programs/compile_failure/brillig_assert_msg_runtime/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "brillig_assert_msg_runtime" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/brillig_assert_msg_runtime/Prover.toml b/test_programs/compile_failure/brillig_assert_msg_runtime/Prover.toml new file mode 100644 index 00000000000..0e5dfd5638d --- /dev/null +++ b/test_programs/compile_failure/brillig_assert_msg_runtime/Prover.toml @@ -0,0 +1 @@ +x = "5" diff --git a/test_programs/compile_failure/brillig_assert_msg_runtime/src/main.nr b/test_programs/compile_failure/brillig_assert_msg_runtime/src/main.nr new file mode 100644 index 00000000000..428b2006363 --- /dev/null +++ b/test_programs/compile_failure/brillig_assert_msg_runtime/src/main.nr @@ -0,0 +1,10 @@ +fn main(x: Field) { + assert(1 == conditional(x)); +} + +unconstrained fn conditional(x: Field) -> Field { + let z = x as u8 + 20; + assert_eq(z, 25, f"Expected 25 but got {z}"); + assert(x == 10, f"Expected x to equal 10, but got {x}"); + 1 +} \ No newline at end of file diff --git a/test_programs/compile_failure/builtin_function_declaration/Nargo.toml b/test_programs/compile_failure/builtin_function_declaration/Nargo.toml new file mode 100644 index 00000000000..3835292a6ba --- /dev/null +++ b/test_programs/compile_failure/builtin_function_declaration/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "builtin_function_declaration" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] diff --git a/test_programs/compile_failure/builtin_function_declaration/src/main.nr b/test_programs/compile_failure/builtin_function_declaration/src/main.nr new file mode 100644 index 00000000000..ed376557371 --- /dev/null +++ b/test_programs/compile_failure/builtin_function_declaration/src/main.nr @@ -0,0 +1,10 @@ +// This test prevents users from trying to create their own builtin functions as these should only exist in the stdlib. + +// This would otherwise be a perfectly valid declaration of the `to_le_bits` builtin function +#[builtin(to_le_bits)] +fn to_le_bits(_x: Field, _bit_size: u32) -> [u1] {} + +fn main(x: Field) -> pub u1 { + let bits = to_le_bits(x, 100); + bits[0] +} diff --git a/test_programs/compile_failure/foreign_function_declaration/Nargo.toml b/test_programs/compile_failure/foreign_function_declaration/Nargo.toml new file mode 100644 index 00000000000..951658d7fb8 --- /dev/null +++ b/test_programs/compile_failure/foreign_function_declaration/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "foreign_function_declaration" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] diff --git a/test_programs/compile_failure/foreign_function_declaration/src/main.nr b/test_programs/compile_failure/foreign_function_declaration/src/main.nr new file mode 100644 index 00000000000..6273067f6a7 --- /dev/null +++ b/test_programs/compile_failure/foreign_function_declaration/src/main.nr @@ -0,0 +1,10 @@ +// This test prevents users from trying to create their own blackbox functions as these should only exist in the stdlib. + +// This would otherwise be a perfectly valid definition of the `pedersen_hash` black box function, +// however executing the circuit results in an unhelpful ICE. +#[foreign(pedersen_hash)] +fn my_pedersen_hash(_input: [Field; N]) -> Field {} + +fn main() -> pub Field { + my_pedersen_hash([1]) +} diff --git a/test_programs/compile_failure/multiple_contracts/Nargo.toml b/test_programs/compile_failure/multiple_contracts/Nargo.toml deleted file mode 100644 index d6e4e632f95..00000000000 --- a/test_programs/compile_failure/multiple_contracts/Nargo.toml +++ /dev/null @@ -1,5 +0,0 @@ -[package] -name = "multiple_contracts" -type = "contract" -authors = [""] -[dependencies] diff --git a/test_programs/compile_failure/multiple_contracts/src/main.nr b/test_programs/compile_failure/multiple_contracts/src/main.nr deleted file mode 100644 index a6c49d75378..00000000000 --- a/test_programs/compile_failure/multiple_contracts/src/main.nr +++ /dev/null @@ -1,3 +0,0 @@ -contract Foo {} - -contract Bar {} diff --git a/test_programs/compile_failure/option_expect/Nargo.toml b/test_programs/compile_failure/option_expect/Nargo.toml new file mode 100644 index 00000000000..1ee1215ff71 --- /dev/null +++ b/test_programs/compile_failure/option_expect/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "option_expect" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/option_expect/src/main.nr b/test_programs/compile_failure/option_expect/src/main.nr new file mode 100644 index 00000000000..439ce4f386e --- /dev/null +++ b/test_programs/compile_failure/option_expect/src/main.nr @@ -0,0 +1,8 @@ +fn main() { + let inner_value = 3; + let none = Option::none(); + let some = Option::some(inner_value); + + assert(some.expect(f"Should have the value {inner_value}") == 3); + assert(none.expect(f"Should have the value {inner_value}") == 3); +} diff --git a/test_programs/compile_failure/option_expect_bad_input/Nargo.toml b/test_programs/compile_failure/option_expect_bad_input/Nargo.toml new file mode 100644 index 00000000000..0555681e188 --- /dev/null +++ b/test_programs/compile_failure/option_expect_bad_input/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "option_expect_bad_input" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/option_expect_bad_input/src/main.nr b/test_programs/compile_failure/option_expect_bad_input/src/main.nr new file mode 100644 index 00000000000..cc93e767975 --- /dev/null +++ b/test_programs/compile_failure/option_expect_bad_input/src/main.nr @@ -0,0 +1,6 @@ +fn main() { + let inner_value = 3; + let some = Option::some(inner_value); + + assert(some.expect("Should have the value {inner_value}") == 3); +} diff --git a/test_programs/compile_success_empty/brillig_cast/src/main.nr b/test_programs/compile_success_empty/brillig_cast/src/main.nr index 3ba29b52982..ecb832468ba 100644 --- a/test_programs/compile_success_empty/brillig_cast/src/main.nr +++ b/test_programs/compile_success_empty/brillig_cast/src/main.nr @@ -17,33 +17,25 @@ unconstrained fn bool_casts() { unconstrained fn field_casts() { assert(5 as u8 as Field == 5); - assert(16 as u4 as Field == 0); + assert(256 as u8 as Field == 0); } unconstrained fn uint_casts() { - let x: u32 = 100; - assert(x as u2 == 0); - assert(x as u4 == 4); - assert(x as u6 == 36); - assert(x as u8 == 100); - assert(x as u64 == 100); - assert(x as u126 == 100); + let x: u32 = 300; + assert(x as u8 == 44); + assert(x as u32 == 300); + assert(x as u64 == 300); } unconstrained fn int_casts() { - let x: i32 = 100; - assert(x as i2 == 0); - assert(x as i4 == 4); - assert(x as i6 == -28 as i6); - assert(x as i8 == 100); - assert(x as i8 == 100); - assert(x as i8 == 100); + let x: i32 = 456; + assert(x as i8 == -56 as i8); + assert(x as i64 == 456); } unconstrained fn mixed_casts() { assert(100 as u32 as i32 as u32 == 100); - assert(13 as u4 as i2 as u32 == 1); - assert(15 as u4 as i2 as u32 == 3); + assert(257 as u8 as u32 == 1); assert(1 as u8 as bool == true); assert(true as i8 == 1); } diff --git a/test_programs/compile_success_empty/brillig_modulo/src/main.nr b/test_programs/compile_success_empty/brillig_modulo/src/main.nr index ed0353b101a..195ed31fb08 100644 --- a/test_programs/compile_success_empty/brillig_modulo/src/main.nr +++ b/test_programs/compile_success_empty/brillig_modulo/src/main.nr @@ -7,9 +7,9 @@ fn main() { assert(signed_modulo(5, 3) == 2); assert(signed_modulo(2, 3) == 2); - let minus_two: i4 = -2; // 14 - let minus_three: i4 = -3; // 13 - let minus_five: i4 = -5; // 11 + let minus_two: i8 = -2; // 254 + let minus_three: i8 = -3; // 253 + let minus_five: i8 = -5; // 251 // (5 / -3) * -3 + 2 = -1 * -3 + 2 = 3 + 2 = 5 assert(signed_modulo(5, minus_three) == 2); // (-5 / 3) * 3 - 2 = -1 * 3 - 2 = -3 - 2 = -5 @@ -22,6 +22,6 @@ unconstrained fn modulo(x: u32, y: u32) -> u32 { x % y } -unconstrained fn signed_modulo(x: i4, y: i4) -> i4 { +unconstrained fn signed_modulo(x: i8, y: i8) -> i8 { x % y } diff --git a/test_programs/compile_success_empty/intrinsic_die/src/main.nr b/test_programs/compile_success_empty/intrinsic_die/src/main.nr index 88f7a3634c1..8cac707dfea 100644 --- a/test_programs/compile_success_empty/intrinsic_die/src/main.nr +++ b/test_programs/compile_success_empty/intrinsic_die/src/main.nr @@ -1,8 +1,6 @@ use dep::std; // This test checks that we perform dead-instruction-elimination on intrinsic functions. fn main(x: Field) { - let bytes = x.to_be_bytes(32); - let hash = std::hash::pedersen_commitment([x]); let _p1 = std::scalar_mul::fixed_base_embedded_curve(x, 0); } diff --git a/test_programs/compile_success_empty/method_call_regression/Nargo.toml b/test_programs/compile_success_empty/method_call_regression/Nargo.toml index 92c9b942008..09f95590aad 100644 --- a/test_programs/compile_success_empty/method_call_regression/Nargo.toml +++ b/test_programs/compile_success_empty/method_call_regression/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "short" +name = "method_call_regression" type = "bin" authors = [""] compiler_version = ">=0.19.4" diff --git a/test_programs/compile_success_empty/option/src/main.nr b/test_programs/compile_success_empty/option/src/main.nr index 1f879bd375f..989c8f65bf4 100644 --- a/test_programs/compile_success_empty/option/src/main.nr +++ b/test_programs/compile_success_empty/option/src/main.nr @@ -1,5 +1,3 @@ -use dep::std::option::Option; - fn main() { let ten = 10; // giving this a name, to ensure that the Option functions work with closures let none = Option::none(); @@ -22,6 +20,8 @@ fn main() { assert(some.map(|x| x * 2).unwrap() == 6); assert(some.map(|x| x * ten).unwrap() == 30); + assert(some.expect(f"Should have a value") == 3); + assert(none.map_or(0, |x| x * 2) == 0); assert(some.map_or(0, |x| x * 2) == 6); assert(none.map_or(0, |x| x * ten) == 0); diff --git a/test_programs/compile_success_empty/trait_generics/src/main.nr b/test_programs/compile_success_empty/trait_generics/src/main.nr index 9a3c54c3fa1..30b2e79d579 100644 --- a/test_programs/compile_success_empty/trait_generics/src/main.nr +++ b/test_programs/compile_success_empty/trait_generics/src/main.nr @@ -1,4 +1,3 @@ - fn main() { let xs: [Field; 1] = [3]; let ys: [u32; 1] = [3]; @@ -8,21 +7,21 @@ fn main() { assert_eq(15, sum_static(Data { a: 5, b: 10 })); } -fn foo(x: T, u: U) where T: Into, U: Eq { +fn foo(x: T, u: U) where T: MyInto, U: Eq { assert(x.into() == u); } -trait Into { +trait MyInto { fn into(self) -> T; } -impl Into<[U; N]> for [T; N] where T: Into { +impl MyInto<[U; N]> for [T; N] where T: MyInto { fn into(self) -> [U; N] { self.map(|x: T| x.into()) } } -impl Into for Field { +impl MyInto for Field { fn into(self) -> u32 { self as u32 } diff --git a/test_programs/compile_success_empty/trait_static_methods/Nargo.toml b/test_programs/compile_success_empty/trait_static_methods/Nargo.toml index 71c541ccd4f..ea30031b9a5 100644 --- a/test_programs/compile_success_empty/trait_static_methods/Nargo.toml +++ b/test_programs/compile_success_empty/trait_static_methods/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "trait_self" +name = "trait_static_methods" type = "bin" authors = [""] diff --git a/test_programs/execution_success/5_over/src/main.nr b/test_programs/execution_success/5_over/src/main.nr index f24ff06cb2a..313d580a8d1 100644 --- a/test_programs/execution_success/5_over/src/main.nr +++ b/test_programs/execution_success/5_over/src/main.nr @@ -5,6 +5,6 @@ fn main(mut x: u32, y: u32) { x = std::wrapping_mul(x,x); assert(y == x); - let c: u3 = 2; - assert(c > x as u3); + let c: u1 = 0; + assert(x as u1 > c); } diff --git a/test_programs/execution_success/bit_not/Nargo.toml b/test_programs/execution_success/bit_not/Nargo.toml new file mode 100644 index 00000000000..e89a338595b --- /dev/null +++ b/test_programs/execution_success/bit_not/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bit_not" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] diff --git a/test_programs/execution_success/bit_not/Prover.toml b/test_programs/execution_success/bit_not/Prover.toml new file mode 100644 index 00000000000..b4bcbcec177 --- /dev/null +++ b/test_programs/execution_success/bit_not/Prover.toml @@ -0,0 +1 @@ +four_as_u32 = 4 diff --git a/test_programs/execution_success/bit_not/src/main.nr b/test_programs/execution_success/bit_not/src/main.nr new file mode 100644 index 00000000000..30b78d330ce --- /dev/null +++ b/test_programs/execution_success/bit_not/src/main.nr @@ -0,0 +1,8 @@ +fn main(four_as_u32: u32) { + let four_as_u8: u8 = 4; + let not_four_as_u8: u8 = !four_as_u8; + assert_eq(not_four_as_u8, 251); + + let not_four_as_u32: u32 = !four_as_u32; + assert_eq(not_four_as_u32, 4294967291); +} diff --git a/test_programs/execution_success/bit_shifts_comptime/src/main.nr b/test_programs/execution_success/bit_shifts_comptime/src/main.nr index 9bb1028173d..9184b5bd5e6 100644 --- a/test_programs/execution_success/bit_shifts_comptime/src/main.nr +++ b/test_programs/execution_success/bit_shifts_comptime/src/main.nr @@ -14,7 +14,7 @@ fn main(x: u64) { //regression for 3481 assert(x << 63 == 0); - assert_eq((1 as u56) << (32 as u56), 0x0100000000); + assert_eq((1 as u64) << (32 as u64), 0x0100000000); } fn regression_2250() { diff --git a/test_programs/execution_success/brillig_assert/src/main.nr b/test_programs/execution_success/brillig_assert/src/main.nr index 91e4cebd9d3..16fe7b29061 100644 --- a/test_programs/execution_success/brillig_assert/src/main.nr +++ b/test_programs/execution_success/brillig_assert/src/main.nr @@ -6,7 +6,7 @@ fn main(x: Field) { } unconstrained fn conditional(x: bool) -> Field { - assert(x, "x is false"); - assert_eq(x, true, "x is false"); + assert(x, f"Expected x to be false but got {x}"); + assert_eq(x, true, f"Expected x to be false but got {x}"); 1 } diff --git a/test_programs/execution_success/conditional_regression_underflow/src/main.nr b/test_programs/execution_success/conditional_regression_underflow/src/main.nr index a101af32505..aaf3754a20f 100644 --- a/test_programs/execution_success/conditional_regression_underflow/src/main.nr +++ b/test_programs/execution_success/conditional_regression_underflow/src/main.nr @@ -1,12 +1,12 @@ // Regression test for https://github.com/noir-lang/noir/issues/3493 -fn main(x: u4) { +fn main(x: u8) { if x == 10 { - x + 15; + x + 255; } if x == 9 { - x << 3; + x << 7; } - if x == 8 { + if x == 128 { x * 3; } if x == 7 { diff --git a/test_programs/execution_success/databus/src/main.nr b/test_programs/execution_success/databus/src/main.nr index 631331ef2d7..61a9637f5fe 100644 --- a/test_programs/execution_success/databus/src/main.nr +++ b/test_programs/execution_success/databus/src/main.nr @@ -1,10 +1,12 @@ -// Test unsafe integer multiplication with overflow: 12^8 = 429 981 696 -// The circuit should handle properly the growth of the bit size use dep::std; -fn main(mut x: u32, y: call_data u32, z: call_data [u32;4] ) -> return_data u32 { - -let a = z[x]; - a+y +fn main(mut x: u32, y: call_data u32, z: call_data [u32;4]) -> return_data u32 { + let a = z[x]; + a+foo(y) +} +// Use an unconstrained function to force the compiler to avoid inlining +unconstrained fn foo(x: u32) -> u32 { + x+1 } + diff --git a/test_programs/execution_success/debug_logs/src/main.nr b/test_programs/execution_success/debug_logs/src/main.nr index 52c910065c1..b640e4f5f0c 100644 --- a/test_programs/execution_success/debug_logs/src/main.nr +++ b/test_programs/execution_success/debug_logs/src/main.nr @@ -3,8 +3,15 @@ use dep::std; fn main(x: Field, y: pub Field) { let string = "i: {i}, j: {j}"; std::println(string); + + // TODO: fmtstr cannot be printed + // let fmt_str: fmtstr<14, (Field, Field)> = f"i: {x}, j: {y}"; + // let fmt_fmt_str = f"fmtstr: {fmt_str}, i: {x}"; + // std::println(fmt_fmt_str); + // A `fmtstr` lets you easily perform string interpolation. let fmt_str: fmtstr<14, (Field, Field)> = f"i: {x}, j: {y}"; + let fmt_str = string_identity(fmt_str); std::println(fmt_str); diff --git a/test_programs/execution_success/keccak256/src/main.nr b/test_programs/execution_success/keccak256/src/main.nr index ff2167694d6..eb401fe614c 100644 --- a/test_programs/execution_success/keccak256/src/main.nr +++ b/test_programs/execution_success/keccak256/src/main.nr @@ -1,5 +1,4 @@ -// Keccak256 example -// +// docs:start:keccak256 use dep::std; fn main(x: Field, result: [u8; 32]) { @@ -7,7 +6,8 @@ fn main(x: Field, result: [u8; 32]) { // The padding is taken care of by the program let digest = std::hash::keccak256([x as u8], 1); assert(digest == result); - //#1399: variable meesage size + + //#1399: variable message size let message_size = 4; let hash_a = std::hash::keccak256([1, 2, 3, 4], message_size); let hash_b = std::hash::keccak256([1, 2, 3, 4, 0, 0, 0, 0], message_size); @@ -19,3 +19,4 @@ fn main(x: Field, result: [u8; 32]) { assert(hash_a != hash_c); } +// docs:end:keccak256 diff --git a/test_programs/execution_success/missing_closure_env/Nargo.toml b/test_programs/execution_success/missing_closure_env/Nargo.toml new file mode 100644 index 00000000000..284e61b1144 --- /dev/null +++ b/test_programs/execution_success/missing_closure_env/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "missing_closure_env" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/missing_closure_env/Prover.toml b/test_programs/execution_success/missing_closure_env/Prover.toml new file mode 100644 index 00000000000..2d76abaa89f --- /dev/null +++ b/test_programs/execution_success/missing_closure_env/Prover.toml @@ -0,0 +1 @@ +x = 42 diff --git a/test_programs/execution_success/missing_closure_env/src/main.nr b/test_programs/execution_success/missing_closure_env/src/main.nr new file mode 100644 index 00000000000..0bc99b0671c --- /dev/null +++ b/test_programs/execution_success/missing_closure_env/src/main.nr @@ -0,0 +1,16 @@ +fn main(x: Field) { + let x1 = &mut 42; + let set_x1 = |y| { *x1 = y; }; + + assert(*x1 == 42); + set_x1(44); + assert(*x1 == 44); + set_x1(*x1); + assert(*x1 == 44); + assert(x == 42); +} + +#[test] +fn test_main() { + main(42); +} diff --git a/test_programs/execution_success/pedersen_commitment/Nargo.toml b/test_programs/execution_success/pedersen_commitment/Nargo.toml new file mode 100644 index 00000000000..e257ce252d8 --- /dev/null +++ b/test_programs/execution_success/pedersen_commitment/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "pedersen_commitment" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/execution_success/pedersen_commitment/Prover.toml b/test_programs/execution_success/pedersen_commitment/Prover.toml new file mode 100644 index 00000000000..6ad8c6bca57 --- /dev/null +++ b/test_programs/execution_success/pedersen_commitment/Prover.toml @@ -0,0 +1,6 @@ +x = "0" +y = "1" + +[expected_commitment] +x = "0x054aa86a73cb8a34525e5bbed6e43ba1198e860f5f3950268f71df4591bde402" +y = "0x209dcfbf2cfb57f9f6046f44d71ac6faf87254afc7407c04eb621a6287cac126" diff --git a/test_programs/execution_success/pedersen_commitment/src/main.nr b/test_programs/execution_success/pedersen_commitment/src/main.nr new file mode 100644 index 00000000000..83cbe20851d --- /dev/null +++ b/test_programs/execution_success/pedersen_commitment/src/main.nr @@ -0,0 +1,10 @@ +// docs:start:pedersen-commitment +use dep::std; + +fn main(x: Field, y: Field, expected_commitment: std::hash::PedersenPoint) { + let commitment = std::hash::pedersen_commitment([x, y]); + assert_eq(commitment.x, expected_commitment.x); + assert_eq(commitment.y, expected_commitment.y); +} +// docs:end:pedersen-commitment + diff --git a/test_programs/execution_success/pedersen_hash/Nargo.toml b/test_programs/execution_success/pedersen_hash/Nargo.toml new file mode 100644 index 00000000000..6248a96b3c9 --- /dev/null +++ b/test_programs/execution_success/pedersen_hash/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "pedersen_hash" +type = "bin" +authors = [""] + +[dependencies] diff --git a/test_programs/execution_success/pedersen_hash/Prover.toml b/test_programs/execution_success/pedersen_hash/Prover.toml new file mode 100644 index 00000000000..931b121fa6a --- /dev/null +++ b/test_programs/execution_success/pedersen_hash/Prover.toml @@ -0,0 +1,4 @@ +x = "0" +y = "1" + +expected_hash = "0x0d98561fb02ca04d00801dfdc118b2a24cea0351963587712a28d368041370e1" diff --git a/test_programs/execution_success/pedersen_hash/src/main.nr b/test_programs/execution_success/pedersen_hash/src/main.nr new file mode 100644 index 00000000000..20c7de12d6c --- /dev/null +++ b/test_programs/execution_success/pedersen_hash/src/main.nr @@ -0,0 +1,9 @@ +// docs:start:pedersen-hash +use dep::std; + +fn main(x: Field, y: Field, expected_hash: Field) { + let hash = std::hash::pedersen_hash([x, y]); + assert_eq(hash, expected_hash); +} +// docs:end:pedersen-hash + diff --git a/test_programs/execution_success/poseidon_bn254_hash/src/main.nr b/test_programs/execution_success/poseidon_bn254_hash/src/main.nr index 3d30ebad279..e742a440d1c 100644 --- a/test_programs/execution_success/poseidon_bn254_hash/src/main.nr +++ b/test_programs/execution_success/poseidon_bn254_hash/src/main.nr @@ -1,3 +1,4 @@ +// docs:start:poseidon use dep::std::hash::poseidon; fn main(x1: [Field; 2], y1: pub Field, x2: [Field; 4], y2: pub Field) { @@ -7,3 +8,4 @@ fn main(x1: [Field; 2], y1: pub Field, x2: [Field; 4], y2: pub Field) { let hash2 = poseidon::bn254::hash_4(x2); assert(hash2 == y2); } +// docs:end:poseidon diff --git a/test_programs/execution_success/regression/src/main.nr b/test_programs/execution_success/regression/src/main.nr index 08112d4c616..c70e2e75fa8 100644 --- a/test_programs/execution_success/regression/src/main.nr +++ b/test_programs/execution_success/regression/src/main.nr @@ -1,29 +1,49 @@ global NIBBLE_LENGTH: Field = 16; -fn compact_decode(input: [u8; N], length: Field) -> ([u4; NIBBLE_LENGTH], Field) { +struct U4 { + inner: u8, +} + +impl U4 { + fn zero() -> U4 { + U4 { inner: 0 } + } + + fn from_u8(x: u8) -> U4 { + U4 { inner: x % 16 } + } +} + +impl Eq for U4 { + fn eq(self, other: Self) -> bool { + self.inner == other.inner + } +} + +fn compact_decode(input: [u8; N], length: Field) -> ([U4; NIBBLE_LENGTH], Field) { assert(2 * input.len() as u64 <= NIBBLE_LENGTH as u64); assert(length as u64 <= input.len() as u64); - let mut nibble = [0 as u4; NIBBLE_LENGTH]; + let mut nibble = [U4::zero(); NIBBLE_LENGTH]; - let first_nibble = (input[0] >> 4) as u4; - let parity = first_nibble as u1; + let first_nibble = U4::from_u8(input[0] >> 4); + let parity = first_nibble.inner as u1; if parity == 1 { - nibble[0] = (input[0] & 0x0f) as u4; + nibble[0] = U4::from_u8(input[0] & 0x0f); for i in 1..input.len() { if i as u64 < length as u64 { let x = input[i]; - nibble[2*i - 1] = (x >> 4) as u4; - nibble[2*i] = (x & 0x0f) as u4; + nibble[2*i - 1] = U4::from_u8(x >> 4); + nibble[2*i] = U4::from_u8(x & 0x0f); } } } else { for i in 0..2 { if (i as u64) < length as u64 - 1 { let x = input[i + 1]; - nibble[2*i] = (x >> 4) as u4; - nibble[2*i + 1] = (x & 0x0f) as u4; + nibble[2*i] = U4::from_u8(x >> 4); + nibble[2*i + 1] = U4::from_u8(x & 0x0f); } } } @@ -78,7 +98,10 @@ fn main(x: [u8; 5], z: Field) { //Issue 1144 let (nib, len) = compact_decode(x, z); assert(len == 5); - assert([nib[0], nib[1], nib[2], nib[3], nib[4]] == [15, 1, 12, 11, 8]); + assert( + [nib[0], nib[1], nib[2], nib[3], nib[4]] + == [U4::from_u8(15), U4::from_u8(1), U4::from_u8(12), U4::from_u8(11), U4::from_u8(8)] + ); // Issue 1169 let val1 = [ 0xb8, 0x8f, 0x61, 0xe6, 0xfb, 0xda, 0x83, 0xfb, 0xff, 0xfa, 0xbe, 0x36, 0x41, 0x12, 0x13, diff --git a/test_programs/execution_success/regression_4088/Nargo.toml b/test_programs/execution_success/regression_4088/Nargo.toml new file mode 100644 index 00000000000..a5e7832b734 --- /dev/null +++ b/test_programs/execution_success/regression_4088/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "regression_4088" +type = "bin" +authors = [""] +[dependencies] diff --git a/test_programs/execution_success/regression_4088/Prover.toml b/test_programs/execution_success/regression_4088/Prover.toml new file mode 100644 index 00000000000..839e31e7e40 --- /dev/null +++ b/test_programs/execution_success/regression_4088/Prover.toml @@ -0,0 +1,2 @@ +[note] +value = 0 diff --git a/test_programs/execution_success/regression_4088/src/main.nr b/test_programs/execution_success/regression_4088/src/main.nr new file mode 100644 index 00000000000..9e4d7892fc3 --- /dev/null +++ b/test_programs/execution_success/regression_4088/src/main.nr @@ -0,0 +1,27 @@ +trait Serialize { + fn serialize(self) -> [Field; N]; +} + +struct ValueNote { + value: Field, +} + +impl Serialize<1> for ValueNote { + fn serialize(self) -> [Field; 1] { + [self.value] + } +} + +fn check(serialized_note: [Field; N]) { + assert(serialized_note[0] == 0); +} + +fn oopsie(note: Note) where Note: Serialize { + let serialized_note = Note::serialize(note); + + check(serialized_note) +} + +fn main(mut note: ValueNote) { + oopsie(note); +} diff --git a/test_programs/execution_success/regression_4124/Nargo.toml b/test_programs/execution_success/regression_4124/Nargo.toml new file mode 100644 index 00000000000..9b97d1ce087 --- /dev/null +++ b/test_programs/execution_success/regression_4124/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "regression_4124" +type = "bin" +authors = [""] +compiler_version = ">=0.22.0" + +[dependencies] diff --git a/test_programs/execution_success/regression_4124/Prover.toml b/test_programs/execution_success/regression_4124/Prover.toml new file mode 100644 index 00000000000..533d1af92b9 --- /dev/null +++ b/test_programs/execution_success/regression_4124/Prover.toml @@ -0,0 +1 @@ +value = 0 diff --git a/test_programs/execution_success/regression_4124/src/main.nr b/test_programs/execution_success/regression_4124/src/main.nr new file mode 100644 index 00000000000..b47bf28d461 --- /dev/null +++ b/test_programs/execution_success/regression_4124/src/main.nr @@ -0,0 +1,39 @@ +use dep::std::option::Option; + +trait MyDeserialize { + fn deserialize(fields: [Field; N]) -> Self; +} + +impl MyDeserialize<1> for Field { + fn deserialize(fields: [Field; 1]) -> Self { + fields[0] + } +} + +pub fn storage_read() -> [Field; N] { + dep::std::unsafe::zeroed() +} + +struct PublicState { + storage_slot: Field, +} + +impl PublicState { + pub fn new(storage_slot: Field) -> Self { + assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1."); + PublicState { storage_slot } + } + + pub fn read(_self: Self) -> T where T: MyDeserialize { + // storage_read returns slice here + let fields: [Field; T_SERIALIZED_LEN] = storage_read(); + T::deserialize(fields) + } +} + +fn main(value: Field) { + let ps: PublicState = PublicState::new(27); + + // error here + assert(ps.read() == value); +} diff --git a/test_programs/execution_success/scalar_mul/src/main.nr b/test_programs/execution_success/scalar_mul/src/main.nr index 2ddf22cf71e..e20f47907db 100644 --- a/test_programs/execution_success/scalar_mul/src/main.nr +++ b/test_programs/execution_success/scalar_mul/src/main.nr @@ -20,4 +20,12 @@ fn main( let res = std::scalar_mul::fixed_base_embedded_curve(priv_key, 0); assert(res[0] == pub_x); assert(res[1] == pub_y); + let pub_point= std::scalar_mul::EmbeddedCurvePoint { x: pub_x, y: pub_y }; + let g1_y = 17631683881184975370165255887551781615748388533673675138860; + let g1= std::scalar_mul::EmbeddedCurvePoint { x: 1, y: g1_y }; + + let res = pub_point.double(); + let double = g1.add(g1); + + assert(double.x == res.x); } diff --git a/test_programs/execution_success/to_le_bytes/Prover.toml b/test_programs/execution_success/to_le_bytes/Prover.toml index 07fe857ac7c..bf58776d557 100644 --- a/test_programs/execution_success/to_le_bytes/Prover.toml +++ b/test_programs/execution_success/to_le_bytes/Prover.toml @@ -1 +1,2 @@ x = "2040124" +cond = false \ No newline at end of file diff --git a/test_programs/execution_success/to_le_bytes/src/main.nr b/test_programs/execution_success/to_le_bytes/src/main.nr index 05eefc0f143..a0b48efe528 100644 --- a/test_programs/execution_success/to_le_bytes/src/main.nr +++ b/test_programs/execution_success/to_le_bytes/src/main.nr @@ -1,4 +1,4 @@ -fn main(x: Field) -> pub [u8; 31] { +fn main(x: Field, cond: bool) -> pub [u8; 31] { // The result of this byte array will be little-endian let byte_array = x.to_le_bytes(31); assert(byte_array.len() == 31); @@ -7,5 +7,12 @@ fn main(x: Field) -> pub [u8; 31] { for i in 0..31 { bytes[i] = byte_array[i]; } + + if cond { + // We've set x = "2040124" so we shouldn't be able to represent this as a single byte. + let bad_byte_array = x.to_le_bytes(1); + assert_eq(bad_byte_array.len(), 1); + } + bytes } diff --git a/test_programs/execution_success/u128/src/main.nr b/test_programs/execution_success/u128/src/main.nr index 4c734f3a8f9..dc586408795 100644 --- a/test_programs/execution_success/u128/src/main.nr +++ b/test_programs/execution_success/u128/src/main.nr @@ -39,6 +39,6 @@ fn main(mut x: u32, y: u32, z: u32, big_int: U128, hexa: str<7>) { assert(shift >> small_int == small_int); assert(shift >> U128::from_integer(127) == U128::from_integer(0)); assert(shift << U128::from_integer(127) == U128::from_integer(0)); - + assert(U128::from_integer(3).to_integer() == 3); } diff --git a/test_programs/noir_test_success/bounded_vec/Nargo.toml b/test_programs/noir_test_success/bounded_vec/Nargo.toml new file mode 100644 index 00000000000..0d58f5872ef --- /dev/null +++ b/test_programs/noir_test_success/bounded_vec/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bounded_vec" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/noir_test_success/bounded_vec/Prover.toml b/test_programs/noir_test_success/bounded_vec/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test_programs/noir_test_success/bounded_vec/src/main.nr b/test_programs/noir_test_success/bounded_vec/src/main.nr new file mode 100644 index 00000000000..d51d2cc3685 --- /dev/null +++ b/test_programs/noir_test_success/bounded_vec/src/main.nr @@ -0,0 +1,105 @@ +#[test] +fn test_vec_push_pop() { + let mut vec: BoundedVec = BoundedVec::new(0); + assert(vec.len == 0); + vec.push(2); + assert(vec.len == 1); + vec.push(4); + assert(vec.len == 2); + vec.push(6); + assert(vec.len == 3); + let x = vec.pop(); + assert(x == 6); + assert(vec.len == 2); + assert(vec.get(0) == 2); + assert(vec.get(1) == 4); +} + +#[test] +fn test_vec_extend_from_array() { + let mut vec: BoundedVec = BoundedVec::new(0); + vec.extend_from_array([2, 4]); + assert(vec.len == 2); + assert(vec.get(0) == 2); + assert(vec.get(1) == 4); +} + +#[test(should_fail_with="extend_from_array out of bounds")] +fn test_vec_extend_from_array_out_of_bound() { + let mut vec: BoundedVec = BoundedVec::new(0); + vec.extend_from_array([2, 4, 6]); +} + +#[test(should_fail_with="extend_from_array out of bounds")] +fn test_vec_extend_from_array_twice_out_of_bound() { + let mut vec: BoundedVec = BoundedVec::new(0); + vec.extend_from_array([2]); + assert(vec.len == 1); + vec.extend_from_array([4, 6]); +} + +#[test(should_fail)] +fn test_vec_get_out_of_bound() { + let mut vec: BoundedVec = BoundedVec::new(0); + vec.extend_from_array([2, 4]); + let _x = vec.get(2); +} + +#[test(should_fail)] +fn test_vec_get_not_declared() { + let mut vec: BoundedVec = BoundedVec::new(0); + vec.extend_from_array([2]); + let _x = vec.get(1); +} + +#[test(should_fail)] +fn test_vec_get_uninitialized() { + let mut vec: BoundedVec = BoundedVec::new(0); + let _x = vec.get(0); +} + +#[test(should_fail_with="push out of bounds")] +fn test_vec_push_out_of_bound() { + let mut vec: BoundedVec = BoundedVec::new(0); + vec.push(1); + vec.push(2); +} + +#[test(should_fail_with="extend_from_bounded_vec out of bounds")] +fn test_vec_extend_from_bounded_vec_out_of_bound() { + let mut vec: BoundedVec = BoundedVec::new(0); + + let mut another_vec: BoundedVec = BoundedVec::new(0); + another_vec.extend_from_array([1, 2, 3]); + + vec.extend_from_bounded_vec(another_vec); +} + +#[test(should_fail_with="extend_from_bounded_vec out of bounds")] +fn test_vec_extend_from_bounded_vec_twice_out_of_bound() { + let mut vec: BoundedVec = BoundedVec::new(0); + vec.extend_from_array([1, 2]); + + let mut another_vec: BoundedVec = BoundedVec::new(0); + another_vec.push(3); + + vec.extend_from_bounded_vec(another_vec); +} + +#[test] +fn test_vec_any() { + let mut vec: BoundedVec = BoundedVec::new(0); + vec.extend_from_array([2, 4, 6]); + assert(vec.any(|v| v == 2) == true); + assert(vec.any(|v| v == 4) == true); + assert(vec.any(|v| v == 6) == true); + assert(vec.any(|v| v == 3) == false); +} + +#[test] +fn test_vec_any_not_default() { + let default_value = 1; + let mut vec: BoundedVec = BoundedVec::new(default_value); + vec.extend_from_array([2, 4]); + assert(vec.any(|v| v == default_value) == false); +} \ No newline at end of file diff --git a/test_programs/noir_test_success/out_of_bounds_alignment/Nargo.toml b/test_programs/noir_test_success/out_of_bounds_alignment/Nargo.toml new file mode 100644 index 00000000000..e535c113f20 --- /dev/null +++ b/test_programs/noir_test_success/out_of_bounds_alignment/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "out_of_bounds_alignment" +type = "bin" +authors = [""] +[dependencies] diff --git a/test_programs/noir_test_success/out_of_bounds_alignment/Prover.toml b/test_programs/noir_test_success/out_of_bounds_alignment/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test_programs/noir_test_success/out_of_bounds_alignment/src/main.nr b/test_programs/noir_test_success/out_of_bounds_alignment/src/main.nr new file mode 100644 index 00000000000..a47ab37eb31 --- /dev/null +++ b/test_programs/noir_test_success/out_of_bounds_alignment/src/main.nr @@ -0,0 +1,17 @@ +fn out_of_bounds(arr_1: [Field; 50]) -> Field { + arr_1[50 + 1] +} + +unconstrained fn out_of_bounds_unconstrained_wrapper(arr_1: [Field; 50], arr_2: [Field; 50]) -> Field { + out_of_bounds(arr_1) +} + +#[test(should_fail)] +fn test_acir() { + assert_eq(out_of_bounds([0; 50]), 0); +} + +#[test(should_fail)] +fn test_brillig() { + assert_eq(out_of_bounds_unconstrained_wrapper([0; 50], [0; 50]), 0); +} diff --git a/test_programs/noir_test_success/regression_4080/Nargo.toml b/test_programs/noir_test_success/regression_4080/Nargo.toml new file mode 100644 index 00000000000..a38baf389d6 --- /dev/null +++ b/test_programs/noir_test_success/regression_4080/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "regression_4080" +type = "bin" +authors = [""] +[dependencies] diff --git a/test_programs/noir_test_success/regression_4080/Prover.toml b/test_programs/noir_test_success/regression_4080/Prover.toml new file mode 100644 index 00000000000..0e5dfd5638d --- /dev/null +++ b/test_programs/noir_test_success/regression_4080/Prover.toml @@ -0,0 +1 @@ +x = "5" diff --git a/test_programs/noir_test_success/regression_4080/src/main.nr b/test_programs/noir_test_success/regression_4080/src/main.nr new file mode 100644 index 00000000000..781d3e33ea3 --- /dev/null +++ b/test_programs/noir_test_success/regression_4080/src/main.nr @@ -0,0 +1,8 @@ +// This test checks that `var^var` is assigned the correct type. +// https://github.com/noir-lang/noir/issues/4080 + +#[test(should_fail_with = "attempt to add with overflow")] +fn main() { + let var1: u8 = ((255 + 1) ^ (255 + 1)) - ((255 + 1) - (255 + 1)); + assert_eq(var1, 0); +} diff --git a/tooling/backend_interface/src/cli/mod.rs b/tooling/backend_interface/src/cli/mod.rs index 3ea65f28103..b4dec859839 100644 --- a/tooling/backend_interface/src/cli/mod.rs +++ b/tooling/backend_interface/src/cli/mod.rs @@ -1,4 +1,4 @@ -// Reference: https://github.com/AztecProtocol/aztec-packages/blob/master/circuits/cpp/barretenberg/cpp/src/barretenberg/bb/main.cpp +// Reference: https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/cpp/src/barretenberg/bb/main.cpp mod contract; mod gates; diff --git a/tooling/bb_abstraction_leaks/build.rs b/tooling/bb_abstraction_leaks/build.rs index 18413f87793..6197f52cb4b 100644 --- a/tooling/bb_abstraction_leaks/build.rs +++ b/tooling/bb_abstraction_leaks/build.rs @@ -10,7 +10,7 @@ use const_format::formatcp; const USERNAME: &str = "AztecProtocol"; const REPO: &str = "aztec-packages"; -const VERSION: &str = "0.19.0"; +const VERSION: &str = "0.21.0"; const TAG: &str = formatcp!("aztec-packages-v{}", VERSION); const API_URL: &str = diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index 12b55708b15..ab807c65a46 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -1,5 +1,6 @@ use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation}; use acvm::acir::native_types::{Witness, WitnessMap}; +use acvm::brillig_vm::brillig::ForeignCallResult; use acvm::brillig_vm::{brillig::Value, Registers}; use acvm::pwg::{ ACVMStatus, BrilligSolver, BrilligSolverStatus, ForeignCallWaitInfo, StepResult, ACVM, @@ -224,6 +225,9 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { let foreign_call_result = self.foreign_call_executor.execute(&foreign_call); match foreign_call_result { Ok(foreign_call_result) => { + let foreign_call_result = foreign_call_result + .get_brillig_output() + .unwrap_or(ForeignCallResult::default()); if let Some(mut solver) = self.brillig_solver.take() { solver.resolve_pending_foreign_call(foreign_call_result); self.brillig_solver = Some(solver); diff --git a/tooling/debugger/src/repl.rs b/tooling/debugger/src/repl.rs index b1af2bc2686..40ee6efdb86 100644 --- a/tooling/debugger/src/repl.rs +++ b/tooling/debugger/src/repl.rs @@ -53,7 +53,15 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { Some(location) => { match location { OpcodeLocation::Acir(ip) => { - println!("At opcode {}: {}", ip, opcodes[ip]) + // Default Brillig display is too bloated for this context, + // so we limit it to denoting it's the start of a Brillig + // block. The user can still use the `opcodes` command to + // take a look at the whole block. + let opcode_summary = match opcodes[ip] { + Opcode::Brillig(..) => "BRILLIG: ...".into(), + _ => format!("{}", opcodes[ip]), + }; + println!("At opcode {}: {}", ip, opcode_summary); } OpcodeLocation::Brillig { acir_index, brillig_index } => { let Opcode::Brillig(ref brillig) = opcodes[acir_index] else { diff --git a/tooling/lsp/src/lib.rs b/tooling/lsp/src/lib.rs index b64fc474b0b..a0e024c70fd 100644 --- a/tooling/lsp/src/lib.rs +++ b/tooling/lsp/src/lib.rs @@ -4,11 +4,12 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))] use std::{ - collections::HashMap, + collections::{BTreeMap, HashMap}, future::Future, ops::{self, ControlFlow}, path::{Path, PathBuf}, pin::Pin, + str::FromStr, task::{self, Poll}, }; @@ -20,7 +21,11 @@ use async_lsp::{ use fm::{codespan_files as files, FileManager}; use fxhash::FxHashSet; use lsp_types::CodeLens; -use nargo::{parse_all, workspace::Workspace}; +use nargo::{ + package::{Package, PackageType}, + parse_all, + workspace::Workspace, +}; use nargo_toml::{find_file_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{file_manager_with_stdlib, prepare_crate, NOIR_ARTIFACT_VERSION_STRING}; use noirc_frontend::{ @@ -209,23 +214,38 @@ fn byte_span_to_range<'a, F: files::Files<'a> + ?Sized>( } pub(crate) fn resolve_workspace_for_source_path(file_path: &Path) -> Result { - let package_root = find_file_manifest(file_path); - - let toml_path = package_root.ok_or_else(|| { - LspError::WorkspaceResolutionError(format!( - "Nargo.toml not found for file: {:?}", - file_path - )) - })?; - - let workspace = resolve_workspace_from_toml( - &toml_path, - PackageSelection::All, - Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), - ) - .map_err(|err| LspError::WorkspaceResolutionError(err.to_string()))?; - - Ok(workspace) + if let Some(toml_path) = find_file_manifest(file_path) { + resolve_workspace_from_toml( + &toml_path, + PackageSelection::All, + Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), + ) + .map_err(|err| LspError::WorkspaceResolutionError(err.to_string())) + } else { + let Some(parent_folder) = file_path.parent().and_then(|f| f.file_name()).and_then(|file_name_os_str| file_name_os_str.to_str()) else { + return Err(LspError::WorkspaceResolutionError(format!( + "Could not resolve parent folder for file: {:?}", + file_path + ))) + }; + let assumed_package = Package { + version: None, + compiler_required_version: Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), + root_dir: PathBuf::from(parent_folder), + package_type: PackageType::Binary, + entry_path: PathBuf::from(file_path), + name: CrateName::from_str(parent_folder) + .map_err(|err| LspError::WorkspaceResolutionError(err.to_string()))?, + dependencies: BTreeMap::new(), + }; + let workspace = Workspace { + root_dir: PathBuf::from(parent_folder), + members: vec![assumed_package], + selected_package_index: Some(0), + is_assumed: true, + }; + Ok(workspace) + } } /// Prepares a package from a source string diff --git a/tooling/lsp/src/requests/profile_run.rs b/tooling/lsp/src/requests/profile_run.rs index 8ba91338f55..d866be8988b 100644 --- a/tooling/lsp/src/requests/profile_run.rs +++ b/tooling/lsp/src/requests/profile_run.rs @@ -64,26 +64,32 @@ fn on_profile_run_request_inner( &workspace_file_manager, &parsed_files, &workspace, - expression_width, &CompileOptions::default(), ) .map_err(|err| ResponseError::new(ErrorCode::REQUEST_FAILED, err))?; let mut opcodes_counts: HashMap = HashMap::new(); let mut file_map: BTreeMap = BTreeMap::new(); - for compiled_program in &compiled_programs { + for compiled_program in compiled_programs { + let compiled_program = + nargo::ops::transform_program(compiled_program, expression_width); + let span_opcodes = compiled_program.debug.count_span_opcodes(); let debug_artifact: DebugArtifact = compiled_program.clone().into(); opcodes_counts.extend(span_opcodes); file_map.extend(debug_artifact.file_map); } - for compiled_contract in &compiled_contracts { - let functions = &compiled_contract.functions; - let debug_artifact: DebugArtifact = compiled_contract.clone().into(); + for compiled_contract in compiled_contracts { + let compiled_contract = + nargo::ops::transform_contract(compiled_contract, expression_width); + + let function_debug_info: Vec<_> = + compiled_contract.functions.iter().map(|func| &func.debug).cloned().collect(); + let debug_artifact: DebugArtifact = compiled_contract.into(); file_map.extend(debug_artifact.file_map); - for contract_function in functions { - let span_opcodes = contract_function.debug.count_span_opcodes(); + for contract_function_debug in function_debug_info { + let span_opcodes = contract_function_debug.count_span_opcodes(); opcodes_counts.extend(span_opcodes); } } diff --git a/tooling/lsp/src/solver.rs b/tooling/lsp/src/solver.rs index 6217b7ad71f..f001cebaa4d 100644 --- a/tooling/lsp/src/solver.rs +++ b/tooling/lsp/src/solver.rs @@ -49,12 +49,4 @@ impl BlackBoxFunctionSolver for WrapperSolver { ) -> Result<(acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError> { self.0.ec_add(input1_x, input1_y, input2_x, input2_y) } - - fn ec_double( - &self, - input_x: &acvm::FieldElement, - input_y: &acvm::FieldElement, - ) -> Result<(acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError> { - self.0.ec_double(input_x, input_y) - } } diff --git a/tooling/nargo/src/artifacts/debug.rs b/tooling/nargo/src/artifacts/debug.rs index 3f5df801b66..2e2d98f279e 100644 --- a/tooling/nargo/src/artifacts/debug.rs +++ b/tooling/nargo/src/artifacts/debug.rs @@ -126,18 +126,18 @@ impl From for DebugArtifact { } } -impl From<&CompiledContract> for DebugArtifact { - fn from(compiled_artifact: &CompiledContract) -> Self { +impl From for DebugArtifact { + fn from(compiled_artifact: CompiledContract) -> Self { let all_functions_debug: Vec = compiled_artifact .functions - .iter() - .map(|contract_function| contract_function.debug.clone()) + .into_iter() + .map(|contract_function| contract_function.debug) .collect(); DebugArtifact { debug_symbols: all_functions_debug, - file_map: compiled_artifact.file_map.clone(), - warnings: compiled_artifact.warnings.clone(), + file_map: compiled_artifact.file_map, + warnings: compiled_artifact.warnings, } } } diff --git a/tooling/nargo/src/ops/compile.rs b/tooling/nargo/src/ops/compile.rs index 866bfe39d7b..dccd2cedbf5 100644 --- a/tooling/nargo/src/ops/compile.rs +++ b/tooling/nargo/src/ops/compile.rs @@ -1,4 +1,3 @@ -use acvm::ExpressionWidth; use fm::FileManager; use noirc_driver::{CompilationResult, CompileOptions, CompiledContract, CompiledProgram}; use noirc_frontend::hir::ParsedFiles; @@ -18,7 +17,6 @@ pub fn compile_workspace( file_manager: &FileManager, parsed_files: &ParsedFiles, workspace: &Workspace, - expression_width: ExpressionWidth, compile_options: &CompileOptions, ) -> Result<(Vec, Vec), CompileError> { let (binary_packages, contract_packages): (Vec<_>, Vec<_>) = workspace @@ -30,22 +28,11 @@ pub fn compile_workspace( // Compile all of the packages in parallel. let program_results: Vec> = binary_packages .par_iter() - .map(|package| { - compile_program( - file_manager, - parsed_files, - package, - compile_options, - expression_width, - None, - ) - }) + .map(|package| compile_program(file_manager, parsed_files, package, compile_options, None)) .collect(); let contract_results: Vec> = contract_packages .par_iter() - .map(|package| { - compile_contract(file_manager, parsed_files, package, compile_options, expression_width) - }) + .map(|package| compile_contract(file_manager, parsed_files, package, compile_options)) .collect(); // Report any warnings/errors which were encountered during compilation. @@ -80,18 +67,10 @@ pub fn compile_program( parsed_files: &ParsedFiles, package: &Package, compile_options: &CompileOptions, - expression_width: ExpressionWidth, cached_program: Option, ) -> CompilationResult { let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); - - let (program, warnings) = - noirc_driver::compile_main(&mut context, crate_id, compile_options, cached_program)?; - - // Apply backend specific optimizations. - let optimized_program = crate::ops::optimize_program(program, expression_width); - - Ok((optimized_program, warnings)) + noirc_driver::compile_main(&mut context, crate_id, compile_options, cached_program) } pub fn compile_contract( @@ -99,15 +78,9 @@ pub fn compile_contract( parsed_files: &ParsedFiles, package: &Package, compile_options: &CompileOptions, - expression_width: ExpressionWidth, ) -> CompilationResult { let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); - let (contract, warnings) = - noirc_driver::compile_contract(&mut context, crate_id, compile_options)?; - - let optimized_contract = crate::ops::optimize_contract(contract, expression_width); - - Ok((optimized_contract, warnings)) + noirc_driver::compile_contract(&mut context, crate_id, compile_options) } pub(crate) fn report_errors( diff --git a/tooling/nargo/src/ops/execute.rs b/tooling/nargo/src/ops/execute.rs index 4fc7f7b599f..370393fea09 100644 --- a/tooling/nargo/src/ops/execute.rs +++ b/tooling/nargo/src/ops/execute.rs @@ -1,3 +1,4 @@ +use acvm::brillig_vm::brillig::ForeignCallResult; use acvm::pwg::{ACVMStatus, ErrorLocation, OpcodeResolutionError, ACVM}; use acvm::BlackBoxFunctionSolver; use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; @@ -5,7 +6,7 @@ use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; use crate::errors::ExecutionError; use crate::NargoError; -use super::foreign_calls::ForeignCallExecutor; +use super::foreign_calls::{ForeignCallExecutor, NargoForeignCallResult}; #[tracing::instrument(level = "trace", skip_all)] pub fn execute_circuit( @@ -16,6 +17,8 @@ pub fn execute_circuit( ) -> Result { let mut acvm = ACVM::new(blackbox_solver, &circuit.opcodes, initial_witness); + // This message should be resolved by a nargo foreign call only when we have an unsatisfied assertion. + let mut assert_message: Option = None; loop { let solver_status = acvm.solve(); @@ -37,7 +40,13 @@ pub fn execute_circuit( return Err(NargoError::ExecutionError(match call_stack { Some(call_stack) => { - if let Some(assert_message) = circuit.get_assert_message( + // First check whether we have a runtime assertion message that should be resolved on an ACVM failure + // If we do not have a runtime assertion message, we should check whether the circuit has any hardcoded + // messages associated with a specific `OpcodeLocation`. + // Otherwise return the provided opcode resolution error. + if let Some(assert_message) = assert_message { + ExecutionError::AssertionFailed(assert_message.to_owned(), call_stack) + } else if let Some(assert_message) = circuit.get_assert_message( *call_stack.last().expect("Call stacks should not be empty"), ) { ExecutionError::AssertionFailed(assert_message.to_owned(), call_stack) @@ -50,7 +59,19 @@ pub fn execute_circuit( } ACVMStatus::RequiresForeignCall(foreign_call) => { let foreign_call_result = foreign_call_executor.execute(&foreign_call)?; - acvm.resolve_pending_foreign_call(foreign_call_result); + match foreign_call_result { + NargoForeignCallResult::BrilligOutput(foreign_call_result) => { + acvm.resolve_pending_foreign_call(foreign_call_result); + } + NargoForeignCallResult::ResolvedAssertMessage(message) => { + if assert_message.is_some() { + unreachable!("Resolving an assert message should happen only once as the VM should have failed"); + } + assert_message = Some(message); + + acvm.resolve_pending_foreign_call(ForeignCallResult::default()); + } + } } } } diff --git a/tooling/nargo/src/ops/foreign_calls.rs b/tooling/nargo/src/ops/foreign_calls.rs index cbe40c92b4e..f600b3047fc 100644 --- a/tooling/nargo/src/ops/foreign_calls.rs +++ b/tooling/nargo/src/ops/foreign_calls.rs @@ -9,13 +9,69 @@ pub trait ForeignCallExecutor { fn execute( &mut self, foreign_call: &ForeignCallWaitInfo, - ) -> Result; + ) -> Result; +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum NargoForeignCallResult { + BrilligOutput(ForeignCallResult), + ResolvedAssertMessage(String), +} + +impl NargoForeignCallResult { + pub fn get_assert_message(self) -> Option { + match self { + Self::ResolvedAssertMessage(msg) => Some(msg), + _ => None, + } + } + + pub fn get_brillig_output(self) -> Option { + match self { + Self::BrilligOutput(foreign_call_result) => Some(foreign_call_result), + _ => None, + } + } +} + +impl From for NargoForeignCallResult { + fn from(value: ForeignCallResult) -> Self { + Self::BrilligOutput(value) + } +} + +impl From for NargoForeignCallResult { + fn from(value: String) -> Self { + Self::ResolvedAssertMessage(value) + } +} + +impl From for NargoForeignCallResult { + fn from(value: Value) -> Self { + let foreign_call_result: ForeignCallResult = value.into(); + foreign_call_result.into() + } +} + +impl From> for NargoForeignCallResult { + fn from(values: Vec) -> Self { + let foreign_call_result: ForeignCallResult = values.into(); + foreign_call_result.into() + } +} + +impl From> for NargoForeignCallResult { + fn from(values: Vec) -> Self { + let foreign_call_result: ForeignCallResult = values.into(); + foreign_call_result.into() + } } /// This enumeration represents the Brillig foreign calls that are natively supported by nargo. /// After resolution of a foreign call, nargo will restart execution of the ACVM pub(crate) enum ForeignCall { Print, + AssertMessage, CreateMock, SetMockParams, SetMockReturns, @@ -33,6 +89,7 @@ impl ForeignCall { pub(crate) fn name(&self) -> &'static str { match self { ForeignCall::Print => "print", + ForeignCall::AssertMessage => "assert_message", ForeignCall::CreateMock => "create_mock", ForeignCall::SetMockParams => "set_mock_params", ForeignCall::SetMockReturns => "set_mock_returns", @@ -44,6 +101,7 @@ impl ForeignCall { pub(crate) fn lookup(op_name: &str) -> Option { match op_name { "print" => Some(ForeignCall::Print), + "assert_message" => Some(ForeignCall::AssertMessage), "create_mock" => Some(ForeignCall::CreateMock), "set_mock_params" => Some(ForeignCall::SetMockParams), "set_mock_returns" => Some(ForeignCall::SetMockReturns), @@ -82,8 +140,8 @@ impl MockedCall { } impl MockedCall { - fn matches(&self, name: &str, params: &Vec) -> bool { - self.name == name && (self.params.is_none() || self.params.as_ref() == Some(params)) + fn matches(&self, name: &str, params: &[ForeignCallParam]) -> bool { + self.name == name && (self.params.is_none() || self.params.as_deref() == Some(params)) } } @@ -134,29 +192,49 @@ impl DefaultForeignCallExecutor { fn execute_print(foreign_call_inputs: &[ForeignCallParam]) -> Result<(), ForeignCallError> { let skip_newline = foreign_call_inputs[0].unwrap_value().is_zero(); - let display_values: PrintableValueDisplay = foreign_call_inputs - .split_first() - .ok_or(ForeignCallError::MissingForeignCallInputs)? - .1 - .try_into()?; - print!("{display_values}{}", if skip_newline { "" } else { "\n" }); + + let foreign_call_inputs = + foreign_call_inputs.split_first().ok_or(ForeignCallError::MissingForeignCallInputs)?.1; + let display_string = Self::format_printable_value(foreign_call_inputs, skip_newline)?; + + print!("{display_string}"); + Ok(()) } + + fn execute_assert_message( + foreign_call_inputs: &[ForeignCallParam], + ) -> Result { + let display_string = Self::format_printable_value(foreign_call_inputs, true)?; + Ok(display_string.into()) + } + + fn format_printable_value( + foreign_call_inputs: &[ForeignCallParam], + skip_newline: bool, + ) -> Result { + let display_values: PrintableValueDisplay = foreign_call_inputs.try_into()?; + + let result = format!("{display_values}{}", if skip_newline { "" } else { "\n" }); + + Ok(result) + } } impl ForeignCallExecutor for DefaultForeignCallExecutor { fn execute( &mut self, foreign_call: &ForeignCallWaitInfo, - ) -> Result { + ) -> Result { let foreign_call_name = foreign_call.function.as_str(); match ForeignCall::lookup(foreign_call_name) { Some(ForeignCall::Print) => { if self.show_output { Self::execute_print(&foreign_call.inputs)?; } - Ok(ForeignCallResult { values: vec![] }) + Ok(ForeignCallResult::default().into()) } + Some(ForeignCall::AssertMessage) => Self::execute_assert_message(&foreign_call.inputs), Some(ForeignCall::CreateMock) => { let mock_oracle_name = Self::parse_string(&foreign_call.inputs[0]); assert!(ForeignCall::lookup(&mock_oracle_name).is_none()); @@ -164,7 +242,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { self.mocked_responses.push(MockedCall::new(id, mock_oracle_name)); self.last_mock_id += 1; - Ok(ForeignCallResult { values: vec![Value::from(id).into()] }) + Ok(Value::from(id).into()) } Some(ForeignCall::SetMockParams) => { let (id, params) = Self::extract_mock_id(&foreign_call.inputs)?; @@ -172,7 +250,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { .unwrap_or_else(|| panic!("Unknown mock id {}", id)) .params = Some(params.to_vec()); - Ok(ForeignCallResult { values: vec![] }) + Ok(ForeignCallResult::default().into()) } Some(ForeignCall::SetMockReturns) => { let (id, params) = Self::extract_mock_id(&foreign_call.inputs)?; @@ -180,7 +258,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { .unwrap_or_else(|| panic!("Unknown mock id {}", id)) .result = ForeignCallResult { values: params.to_vec() }; - Ok(ForeignCallResult { values: vec![] }) + Ok(ForeignCallResult::default().into()) } Some(ForeignCall::SetMockTimes) => { let (id, params) = Self::extract_mock_id(&foreign_call.inputs)?; @@ -194,12 +272,12 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { .unwrap_or_else(|| panic!("Unknown mock id {}", id)) .times_left = Some(times); - Ok(ForeignCallResult { values: vec![] }) + Ok(ForeignCallResult::default().into()) } Some(ForeignCall::ClearMock) => { let (id, _) = Self::extract_mock_id(&foreign_call.inputs)?; self.mocked_responses.retain(|response| response.id != id); - Ok(ForeignCallResult { values: vec![] }) + Ok(ForeignCallResult::default().into()) } None => { let mock_response_position = self @@ -222,7 +300,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { } } - Ok(ForeignCallResult { values: result }) + Ok(result.into()) } (None, Some(external_resolver)) => { let encoded_params: Vec<_> = @@ -235,7 +313,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { let parsed_response: ForeignCallResult = response.result()?; - Ok(parsed_response) + Ok(parsed_response.into()) } (None, None) => panic!("Unknown foreign call {}", foreign_call_name), } @@ -312,7 +390,7 @@ mod tests { }; let result = executor.execute(&foreign_call); - assert_eq!(result.unwrap(), ForeignCallResult { values: foreign_call.inputs }); + assert_eq!(result.unwrap(), ForeignCallResult { values: foreign_call.inputs }.into()); server.close(); } diff --git a/tooling/nargo/src/ops/mod.rs b/tooling/nargo/src/ops/mod.rs index 4912c84839e..4f92faa73a4 100644 --- a/tooling/nargo/src/ops/mod.rs +++ b/tooling/nargo/src/ops/mod.rs @@ -2,6 +2,8 @@ pub use self::compile::{compile_contract, compile_program, compile_workspace}; pub use self::execute::execute_circuit; pub use self::foreign_calls::{DefaultForeignCallExecutor, ForeignCallExecutor}; pub use self::optimize::{optimize_contract, optimize_program}; +pub use self::transform::{transform_contract, transform_program}; + pub use self::test::{run_test, TestStatus}; mod compile; @@ -9,3 +11,4 @@ mod execute; mod foreign_calls; mod optimize; mod test; +mod transform; diff --git a/tooling/nargo/src/ops/optimize.rs b/tooling/nargo/src/ops/optimize.rs index d3a36dd65ac..2d0c4c43d25 100644 --- a/tooling/nargo/src/ops/optimize.rs +++ b/tooling/nargo/src/ops/optimize.rs @@ -1,26 +1,16 @@ -use acvm::ExpressionWidth; use iter_extended::vecmap; use noirc_driver::{CompiledContract, CompiledProgram}; -pub fn optimize_program( - mut program: CompiledProgram, - expression_width: ExpressionWidth, -) -> CompiledProgram { - let (optimized_circuit, location_map) = - acvm::compiler::compile(program.circuit, expression_width); - +pub fn optimize_program(mut program: CompiledProgram) -> CompiledProgram { + let (optimized_circuit, location_map) = acvm::compiler::optimize(program.circuit); program.circuit = optimized_circuit; program.debug.update_acir(location_map); program } -pub fn optimize_contract( - contract: CompiledContract, - expression_width: ExpressionWidth, -) -> CompiledContract { +pub fn optimize_contract(contract: CompiledContract) -> CompiledContract { let functions = vecmap(contract.functions, |mut func| { - let (optimized_bytecode, location_map) = - acvm::compiler::compile(func.bytecode, expression_width); + let (optimized_bytecode, location_map) = acvm::compiler::optimize(func.bytecode); func.bytecode = optimized_bytecode; func.debug.update_acir(location_map); func diff --git a/tooling/nargo/src/ops/transform.rs b/tooling/nargo/src/ops/transform.rs new file mode 100644 index 00000000000..f3efd82333e --- /dev/null +++ b/tooling/nargo/src/ops/transform.rs @@ -0,0 +1,30 @@ +use acvm::ExpressionWidth; +use iter_extended::vecmap; +use noirc_driver::{CompiledContract, CompiledProgram}; + +pub fn transform_program( + mut program: CompiledProgram, + expression_width: ExpressionWidth, +) -> CompiledProgram { + let (optimized_circuit, location_map) = + acvm::compiler::compile(program.circuit, expression_width); + + program.circuit = optimized_circuit; + program.debug.update_acir(location_map); + program +} + +pub fn transform_contract( + contract: CompiledContract, + expression_width: ExpressionWidth, +) -> CompiledContract { + let functions = vecmap(contract.functions, |mut func| { + let (optimized_bytecode, location_map) = + acvm::compiler::compile(func.bytecode, expression_width); + func.bytecode = optimized_bytecode; + func.debug.update_acir(location_map); + func + }); + + CompiledContract { functions, ..contract } +} diff --git a/tooling/nargo/src/workspace.rs b/tooling/nargo/src/workspace.rs index 5696a758531..0795ffd9304 100644 --- a/tooling/nargo/src/workspace.rs +++ b/tooling/nargo/src/workspace.rs @@ -20,6 +20,8 @@ pub struct Workspace { pub members: Vec, // If `Some()`, the `selected_package_index` is used to select the only `Package` when iterating a Workspace pub selected_package_index: Option, + /// If we could not resolve the workspace we would inform the user we have assumed it (ie. from lsp file path given) + pub is_assumed: bool, } impl Workspace { diff --git a/tooling/nargo_cli/src/cli/codegen_verifier_cmd.rs b/tooling/nargo_cli/src/cli/codegen_verifier_cmd.rs index 8bf12ee4100..63d27e30836 100644 --- a/tooling/nargo_cli/src/cli/codegen_verifier_cmd.rs +++ b/tooling/nargo_cli/src/cli/codegen_verifier_cmd.rs @@ -1,12 +1,11 @@ +use super::fs::{create_named_dir, write_to_file}; use super::NargoConfig; -use super::{ - compile_cmd::compile_bin_package, - fs::{create_named_dir, write_to_file}, -}; use crate::backends::Backend; +use crate::cli::compile_cmd::report_errors; use crate::errors::CliError; use clap::Args; +use nargo::ops::compile_program; use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_driver::{file_manager_with_stdlib, CompileOptions, NOIR_ARTIFACT_VERSION_STRING}; @@ -47,15 +46,25 @@ pub(crate) fn run( let parsed_files = parse_all(&workspace_file_manager); let expression_width = backend.get_backend_info()?; - for package in &workspace { - let program = compile_bin_package( + let binary_packages = workspace.into_iter().filter(|package| package.is_binary()); + for package in binary_packages { + let compilation_result = compile_program( &workspace_file_manager, &parsed_files, package, &args.compile_options, - expression_width, + None, + ); + + let program = report_errors( + compilation_result, + &workspace_file_manager, + args.compile_options.deny_warnings, + args.compile_options.silence_warnings, )?; + let program = nargo::ops::transform_program(program, expression_width); + let smart_contract_string = backend.eth_contract(&program.circuit)?; let contract_dir = workspace.contracts_directory_path(package); diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index aa9a46f39ef..34fb05249b5 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -1,7 +1,5 @@ use std::path::Path; -use acvm::ExpressionWidth; - use fm::FileManager; use nargo::artifacts::program::ProgramArtifact; use nargo::errors::CompileError; @@ -63,12 +61,14 @@ pub(crate) fn run( insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); let parsed_files = parse_all(&workspace_file_manager); - let expression_width = backend.get_backend_info_or_default(); + let expression_width = args + .compile_options + .expression_width + .unwrap_or_else(|| backend.get_backend_info_or_default()); let (compiled_program, compiled_contracts) = compile_workspace( &workspace_file_manager, &parsed_files, &workspace, - expression_width, &args.compile_options, )?; @@ -81,9 +81,11 @@ pub(crate) fn run( // Save build artifacts to disk. let only_acir = args.compile_options.only_acir; for (package, program) in binary_packages.into_iter().zip(compiled_program) { + let program = nargo::ops::transform_program(program, expression_width); save_program(program.clone(), &package, &workspace.target_directory_path(), only_acir); } for (package, contract) in contract_packages.into_iter().zip(compiled_contracts) { + let contract = nargo::ops::transform_contract(contract, expression_width); save_contract(contract, &package, &circuit_dir); } @@ -94,7 +96,6 @@ pub(super) fn compile_workspace( file_manager: &FileManager, parsed_files: &ParsedFiles, workspace: &Workspace, - expression_width: ExpressionWidth, compile_options: &CompileOptions, ) -> Result<(Vec, Vec), CliError> { let (binary_packages, contract_packages): (Vec<_>, Vec<_>) = workspace @@ -114,21 +115,12 @@ pub(super) fn compile_workspace( .filter(|p| p.noir_version == NOIR_ARTIFACT_VERSION_STRING) .map(|p| p.into()); - compile_program( - file_manager, - parsed_files, - package, - compile_options, - expression_width, - cached_program, - ) + compile_program(file_manager, parsed_files, package, compile_options, cached_program) }) .collect(); let contract_results: Vec> = contract_packages .par_iter() - .map(|package| { - compile_contract(file_manager, parsed_files, package, compile_options, expression_width) - }) + .map(|package| compile_contract(file_manager, parsed_files, package, compile_options)) .collect(); // Report any warnings/errors which were encountered during compilation. @@ -158,36 +150,6 @@ pub(super) fn compile_workspace( Ok((compiled_programs, compiled_contracts)) } -pub(crate) fn compile_bin_package( - file_manager: &FileManager, - parsed_files: &ParsedFiles, - package: &Package, - compile_options: &CompileOptions, - expression_width: ExpressionWidth, -) -> Result { - if package.is_library() { - return Err(CompileError::LibraryCrate(package.name.clone()).into()); - } - - let compilation_result = compile_program( - file_manager, - parsed_files, - package, - compile_options, - expression_width, - None, - ); - - let program = report_errors( - compilation_result, - file_manager, - compile_options.deny_warnings, - compile_options.silence_warnings, - )?; - - Ok(program) -} - pub(super) fn save_program( program: CompiledProgram, package: &Package, diff --git a/tooling/nargo_cli/src/cli/dap_cmd.rs b/tooling/nargo_cli/src/cli/dap_cmd.rs index 9798cbedfeb..67322b1873e 100644 --- a/tooling/nargo_cli/src/cli/dap_cmd.rs +++ b/tooling/nargo_cli/src/cli/dap_cmd.rs @@ -1,7 +1,9 @@ use acvm::acir::native_types::WitnessMap; +use acvm::ExpressionWidth; use backend_interface::Backend; use clap::Args; use nargo::constants::PROVER_INPUT_FILE; +use nargo::ops::compile_program; use nargo::workspace::Workspace; use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; @@ -21,14 +23,28 @@ use dap::server::Server; use dap::types::Capabilities; use serde_json::Value; -use super::compile_cmd::compile_bin_package; +use super::compile_cmd::report_errors; use super::fs::inputs::read_inputs_from_file; use crate::errors::CliError; use super::NargoConfig; #[derive(Debug, Clone, Args)] -pub(crate) struct DapCommand; +pub(crate) struct DapCommand { + /// Override the expression width requested by the backend. + #[arg(long, value_parser = parse_expression_width)] + expression_width: Option, +} + +fn parse_expression_width(input: &str) -> Result { + use std::io::{Error, ErrorKind}; + + let width = input + .parse::() + .map_err(|err| Error::new(ErrorKind::InvalidInput, err.to_string()))?; + + Ok(ExpressionWidth::from(width)) +} struct LoadError(&'static str); @@ -53,16 +69,14 @@ fn find_workspace(project_folder: &str, package: Option<&str>) -> Option, prover_name: &str, + expression_width: ExpressionWidth, ) -> Result<(CompiledProgram, WitnessMap), LoadError> { let workspace = find_workspace(project_folder, package).ok_or(LoadError("Cannot open workspace"))?; - let expression_width = - backend.get_backend_info().map_err(|_| LoadError("Failed to get backend info"))?; let package = workspace .into_iter() .find(|p| p.is_binary()) @@ -72,15 +86,20 @@ fn load_and_compile_project( insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); let parsed_files = parse_all(&workspace_file_manager); - let compiled_program = compile_bin_package( + let compile_options = CompileOptions::default(); + let compilation_result = + compile_program(&workspace_file_manager, &parsed_files, package, &compile_options, None); + + let compiled_program = report_errors( + compilation_result, &workspace_file_manager, - &parsed_files, - package, - &CompileOptions::default(), - expression_width, + compile_options.deny_warnings, + compile_options.silence_warnings, ) .map_err(|_| LoadError("Failed to compile project"))?; + let compiled_program = nargo::ops::transform_program(compiled_program, expression_width); + let (inputs_map, _) = read_inputs_from_file(&package.root_dir, prover_name, Format::Toml, &compiled_program.abi) .map_err(|_| LoadError("Failed to read program inputs"))?; @@ -94,7 +113,7 @@ fn load_and_compile_project( fn loop_uninitialized_dap( mut server: Server, - backend: &Backend, + expression_width: ExpressionWidth, ) -> Result<(), ServerError> { loop { let req = match server.poll_request()? { @@ -134,7 +153,12 @@ fn loop_uninitialized_dap( eprintln!("Package: {}", package.unwrap_or("(default)")); eprintln!("Prover name: {}", prover_name); - match load_and_compile_project(backend, project_folder, package, prover_name) { + match load_and_compile_project( + project_folder, + package, + prover_name, + expression_width, + ) { Ok((compiled_program, initial_witness)) => { server.respond(req.ack()?)?; @@ -170,12 +194,15 @@ fn loop_uninitialized_dap( pub(crate) fn run( backend: &Backend, - _args: DapCommand, + args: DapCommand, _config: NargoConfig, ) -> Result<(), CliError> { let output = BufWriter::new(std::io::stdout()); let input = BufReader::new(std::io::stdin()); let server = Server::new(input, output); - loop_uninitialized_dap(server, backend).map_err(CliError::DapError) + let expression_width = + args.expression_width.unwrap_or_else(|| backend.get_backend_info_or_default()); + + loop_uninitialized_dap(server, expression_width).map_err(CliError::DapError) } diff --git a/tooling/nargo_cli/src/cli/debug_cmd.rs b/tooling/nargo_cli/src/cli/debug_cmd.rs index e62cbc11ec8..a0bac3bdac1 100644 --- a/tooling/nargo_cli/src/cli/debug_cmd.rs +++ b/tooling/nargo_cli/src/cli/debug_cmd.rs @@ -6,6 +6,7 @@ use clap::Args; use nargo::artifacts::debug::DebugArtifact; use nargo::constants::PROVER_INPUT_FILE; +use nargo::ops::compile_program; use nargo::package::Package; use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; @@ -16,7 +17,7 @@ use noirc_driver::{ }; use noirc_frontend::graph::CrateName; -use super::compile_cmd::compile_bin_package; +use super::compile_cmd::report_errors; use super::fs::{inputs::read_inputs_from_file, witness::save_witness_to_dir}; use super::NargoConfig; use crate::backends::Backend; @@ -53,7 +54,10 @@ pub(crate) fn run( Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), )?; let target_dir = &workspace.target_directory_path(); - let expression_width = backend.get_backend_info()?; + let expression_width = args + .compile_options + .expression_width + .unwrap_or_else(|| backend.get_backend_info_or_default()); let mut workspace_file_manager = file_manager_with_stdlib(std::path::Path::new("")); insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); @@ -66,14 +70,23 @@ pub(crate) fn run( return Ok(()); }; - let compiled_program = compile_bin_package( + let compilation_result = compile_program( &workspace_file_manager, &parsed_files, package, &args.compile_options, - expression_width, + None, + ); + + let compiled_program = report_errors( + compilation_result, + &workspace_file_manager, + args.compile_options.deny_warnings, + args.compile_options.silence_warnings, )?; + let compiled_program = nargo::ops::transform_program(compiled_program, expression_width); + run_async(package, compiled_program, &args.prover_name, &args.witness_name, target_dir) } diff --git a/tooling/nargo_cli/src/cli/execute_cmd.rs b/tooling/nargo_cli/src/cli/execute_cmd.rs index cf0d46a0718..a3fcebab94f 100644 --- a/tooling/nargo_cli/src/cli/execute_cmd.rs +++ b/tooling/nargo_cli/src/cli/execute_cmd.rs @@ -5,7 +5,7 @@ use clap::Args; use nargo::artifacts::debug::DebugArtifact; use nargo::constants::PROVER_INPUT_FILE; use nargo::errors::try_to_diagnose_runtime_error; -use nargo::ops::DefaultForeignCallExecutor; +use nargo::ops::{compile_program, DefaultForeignCallExecutor}; use nargo::package::Package; use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; @@ -16,10 +16,10 @@ use noirc_driver::{ }; use noirc_frontend::graph::CrateName; -use super::compile_cmd::compile_bin_package; use super::fs::{inputs::read_inputs_from_file, witness::save_witness_to_dir}; use super::NargoConfig; use crate::backends::Backend; +use crate::cli::compile_cmd::report_errors; use crate::errors::CliError; /// Executes a circuit to calculate its return value @@ -68,16 +68,29 @@ pub(crate) fn run( insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); let parsed_files = parse_all(&workspace_file_manager); - let expression_width = backend.get_backend_info_or_default(); - for package in &workspace { - let compiled_program = compile_bin_package( + let expression_width = args + .compile_options + .expression_width + .unwrap_or_else(|| backend.get_backend_info_or_default()); + let binary_packages = workspace.into_iter().filter(|package| package.is_binary()); + for package in binary_packages { + let compilation_result = compile_program( &workspace_file_manager, &parsed_files, package, &args.compile_options, - expression_width, + None, + ); + + let compiled_program = report_errors( + compilation_result, + &workspace_file_manager, + args.compile_options.deny_warnings, + args.compile_options.silence_warnings, )?; + let compiled_program = nargo::ops::transform_program(compiled_program, expression_width); + let (return_value, solved_witness) = execute_program_and_decode( compiled_program, package, diff --git a/tooling/nargo_cli/src/cli/info_cmd.rs b/tooling/nargo_cli/src/cli/info_cmd.rs index 8dfff67b47f..131fd6ad214 100644 --- a/tooling/nargo_cli/src/cli/info_cmd.rs +++ b/tooling/nargo_cli/src/cli/info_cmd.rs @@ -69,24 +69,33 @@ pub(crate) fn run( insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); let parsed_files = parse_all(&workspace_file_manager); - let expression_width = backend.get_backend_info_or_default(); + let expression_width = args + .compile_options + .expression_width + .unwrap_or_else(|| backend.get_backend_info_or_default()); let (compiled_programs, compiled_contracts) = compile_workspace( &workspace_file_manager, &parsed_files, &workspace, - expression_width, &args.compile_options, )?; + let compiled_programs = vecmap(compiled_programs, |program| { + nargo::ops::transform_program(program, expression_width) + }); + let compiled_contracts = vecmap(compiled_contracts, |contract| { + nargo::ops::transform_contract(contract, expression_width) + }); + if args.profile_info { for compiled_program in &compiled_programs { let span_opcodes = compiled_program.debug.count_span_opcodes(); - let debug_artifact: DebugArtifact = compiled_program.clone().into(); + let debug_artifact = DebugArtifact::from(compiled_program.clone()); print_span_opcodes(span_opcodes, &debug_artifact); } for compiled_contract in &compiled_contracts { - let debug_artifact: DebugArtifact = compiled_contract.clone().into(); + let debug_artifact = DebugArtifact::from(compiled_contract.clone()); let functions = &compiled_contract.functions; for contract_function in functions { let span_opcodes = contract_function.debug.count_span_opcodes(); diff --git a/tooling/nargo_cli/src/cli/prove_cmd.rs b/tooling/nargo_cli/src/cli/prove_cmd.rs index d02464fd6df..1d20e97af85 100644 --- a/tooling/nargo_cli/src/cli/prove_cmd.rs +++ b/tooling/nargo_cli/src/cli/prove_cmd.rs @@ -1,5 +1,6 @@ use clap::Args; use nargo::constants::{PROVER_INPUT_FILE, VERIFIER_INPUT_FILE}; +use nargo::ops::compile_program; use nargo::package::Package; use nargo::workspace::Workspace; use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; @@ -10,7 +11,7 @@ use noirc_driver::{ }; use noirc_frontend::graph::CrateName; -use super::compile_cmd::compile_bin_package; +use super::compile_cmd::report_errors; use super::fs::{ inputs::{read_inputs_from_file, write_inputs_to_file}, proof::save_proof_to_dir, @@ -68,21 +69,34 @@ pub(crate) fn run( insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); let parsed_files = parse_all(&workspace_file_manager); - let expression_width = backend.get_backend_info()?; - for package in &workspace { - let program = compile_bin_package( + let expression_width = args + .compile_options + .expression_width + .unwrap_or_else(|| backend.get_backend_info_or_default()); + let binary_packages = workspace.into_iter().filter(|package| package.is_binary()); + for package in binary_packages { + let compilation_result = compile_program( &workspace_file_manager, &parsed_files, package, &args.compile_options, - expression_width, + None, + ); + + let compiled_program = report_errors( + compilation_result, + &workspace_file_manager, + args.compile_options.deny_warnings, + args.compile_options.silence_warnings, )?; + let compiled_program = nargo::ops::transform_program(compiled_program, expression_width); + prove_package( backend, &workspace, package, - program, + compiled_program, &args.prover_name, &args.verifier_name, args.verify, diff --git a/tooling/nargo_cli/src/cli/test_cmd.rs b/tooling/nargo_cli/src/cli/test_cmd.rs index 5db842609e5..9fee27b9172 100644 --- a/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/tooling/nargo_cli/src/cli/test_cmd.rs @@ -83,22 +83,44 @@ pub(crate) fn run( }; let blackbox_solver = Bn254BlackBoxSolver::new(); - for package in &workspace { - // By unwrapping here with `?`, we stop the test runner upon a package failing - // TODO: We should run the whole suite even if there are failures in a package - run_tests( - &workspace_file_manager, - &parsed_files, - &blackbox_solver, - package, - pattern, - args.show_output, - args.oracle_resolver.as_deref(), - &args.compile_options, - )?; + + let test_reports: Vec> = workspace + .into_iter() + .map(|package| { + run_tests( + &workspace_file_manager, + &parsed_files, + &blackbox_solver, + package, + pattern, + args.show_output, + args.oracle_resolver.as_deref(), + &args.compile_options, + ) + }) + .collect::>()?; + let test_report: Vec<(String, TestStatus)> = test_reports.into_iter().flatten().collect(); + + if test_report.is_empty() { + match &pattern { + FunctionNameMatch::Exact(pattern) => { + return Err(CliError::Generic( + format!("Found 0 tests matching input '{pattern}'.",), + )) + } + FunctionNameMatch::Contains(pattern) => { + return Err(CliError::Generic(format!("Found 0 tests containing '{pattern}'.",))) + } + // If we are running all tests in a crate, having none is not an error + FunctionNameMatch::Anything => {} + }; } - Ok(()) + if test_report.iter().any(|(_, status)| !matches!(status, TestStatus::Fail { .. })) { + Ok(()) + } else { + Err(CliError::Generic(String::new())) + } } #[allow(clippy::too_many_arguments)] @@ -111,7 +133,7 @@ fn run_tests( show_output: bool, foreign_call_resolver_url: Option<&str>, compile_options: &CompileOptions, -) -> Result<(), CliError> { +) -> Result, CliError> { let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); check_crate_and_report_errors( &mut context, @@ -123,45 +145,29 @@ fn run_tests( let test_functions = context.get_all_test_functions_in_crate_matching(&crate_id, fn_name); let count_all = test_functions.len(); - if count_all == 0 { - match &fn_name { - FunctionNameMatch::Exact(pattern) => { - return Err(CliError::Generic(format!( - "[{}] Found 0 tests matching input '{pattern}'.", - package.name - ))) - } - FunctionNameMatch::Contains(pattern) => { - return Err(CliError::Generic(format!( - "[{}] Found 0 tests containing '{pattern}'.", - package.name - ))) - } - // If we are running all tests in a crate, having none is not an error - FunctionNameMatch::Anything => {} - }; - } let plural = if count_all == 1 { "" } else { "s" }; println!("[{}] Running {count_all} test function{plural}", package.name); - let mut count_failed = 0; let writer = StandardStream::stderr(ColorChoice::Always); let mut writer = writer.lock(); + let mut test_report: Vec<(String, TestStatus)> = Vec::new(); for (test_name, test_function) in test_functions { write!(writer, "[{}] Testing {test_name}... ", package.name) .expect("Failed to write to stderr"); writer.flush().expect("Failed to flush writer"); - match run_test( + let test_status = run_test( blackbox_solver, &context, test_function, show_output, foreign_call_resolver_url, compile_options, - ) { + ); + + match &test_status { TestStatus::Pass { .. } => { writer .set_color(ColorSpec::new().set_fg(Some(Color::Green))) @@ -176,35 +182,36 @@ fn run_tests( if let Some(diag) = error_diagnostic { noirc_errors::reporter::report_all( context.file_manager.as_file_map(), - &[diag], + &[diag.clone()], compile_options.deny_warnings, compile_options.silence_warnings, ); } - count_failed += 1; } TestStatus::CompileError(err) => { noirc_errors::reporter::report_all( context.file_manager.as_file_map(), - &[err], + &[err.clone()], compile_options.deny_warnings, compile_options.silence_warnings, ); - count_failed += 1; } } + + test_report.push((test_name, test_status)); + writer.reset().expect("Failed to reset writer"); } write!(writer, "[{}] ", package.name).expect("Failed to write to stderr"); + let count_failed = + test_report.iter().filter(|(_, status)| !matches!(status, TestStatus::Pass)).count(); if count_failed == 0 { writer.set_color(ColorSpec::new().set_fg(Some(Color::Green))).expect("Failed to set color"); write!(writer, "{count_all} test{plural} passed").expect("Failed to write to stderr"); writer.reset().expect("Failed to reset writer"); writeln!(writer).expect("Failed to write to stderr"); - - Ok(()) } else { let count_passed = count_all - count_failed; let plural_failed = if count_failed == 1 { "" } else { "s" }; @@ -219,11 +226,10 @@ fn run_tests( } writer.set_color(ColorSpec::new().set_fg(Some(Color::Red))).expect("Failed to set color"); - write!(writer, "{count_failed} test{plural_failed} failed") + writeln!(writer, "{count_failed} test{plural_failed} failed") .expect("Failed to write to stderr"); writer.reset().expect("Failed to reset writer"); - - // Writes final newline. - Err(CliError::Generic(String::new())) } + + Ok(test_report) } diff --git a/tooling/nargo_cli/src/cli/verify_cmd.rs b/tooling/nargo_cli/src/cli/verify_cmd.rs index 1701b9e063c..ea4aaa051bb 100644 --- a/tooling/nargo_cli/src/cli/verify_cmd.rs +++ b/tooling/nargo_cli/src/cli/verify_cmd.rs @@ -1,12 +1,11 @@ +use super::compile_cmd::report_errors; +use super::fs::{inputs::read_inputs_from_file, load_hex_data}; use super::NargoConfig; -use super::{ - compile_cmd::compile_bin_package, - fs::{inputs::read_inputs_from_file, load_hex_data}, -}; use crate::{backends::Backend, errors::CliError}; use clap::Args; use nargo::constants::{PROOF_EXT, VERIFIER_INPUT_FILE}; +use nargo::ops::compile_program; use nargo::package::Package; use nargo::workspace::Workspace; use nargo::{insert_all_files_for_workspace_into_file_manager, parse_all}; @@ -55,17 +54,30 @@ pub(crate) fn run( insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); let parsed_files = parse_all(&workspace_file_manager); - let expression_width = backend.get_backend_info()?; - for package in &workspace { - let program = compile_bin_package( + let expression_width = args + .compile_options + .expression_width + .unwrap_or_else(|| backend.get_backend_info_or_default()); + let binary_packages = workspace.into_iter().filter(|package| package.is_binary()); + for package in binary_packages { + let compilation_result = compile_program( &workspace_file_manager, &parsed_files, package, &args.compile_options, - expression_width, + None, + ); + + let compiled_program = report_errors( + compilation_result, + &workspace_file_manager, + args.compile_options.deny_warnings, + args.compile_options.silence_warnings, )?; - verify_package(backend, &workspace, package, program, &args.verifier_name)?; + let compiled_program = nargo::ops::transform_program(compiled_program, expression_width); + + verify_package(backend, &workspace, package, compiled_program, &args.verifier_name)?; } Ok(()) diff --git a/tooling/nargo_fmt/src/visitor/stmt.rs b/tooling/nargo_fmt/src/visitor/stmt.rs index 800a8656ef3..cfe5acb21a7 100644 --- a/tooling/nargo_fmt/src/visitor/stmt.rs +++ b/tooling/nargo_fmt/src/visitor/stmt.rs @@ -38,8 +38,7 @@ impl super::FmtVisitor<'_> { nested_shape.indent.block_indent(self.config); - let message = - message.map_or(String::new(), |message| format!(", \"{message}\"")); + let message = message.map_or(String::new(), |message| format!(", {message}")); let (callee, args) = match kind { ConstrainKind::Assert => { diff --git a/tooling/nargo_toml/src/lib.rs b/tooling/nargo_toml/src/lib.rs index cecc3f7e26a..985cb30dc24 100644 --- a/tooling/nargo_toml/src/lib.rs +++ b/tooling/nargo_toml/src/lib.rs @@ -345,6 +345,7 @@ fn toml_to_workspace( root_dir: nargo_toml.root_dir, selected_package_index: Some(0), members: vec![member], + is_assumed: false, }, } } @@ -392,7 +393,12 @@ fn toml_to_workspace( PackageSelection::All => (), } - Workspace { root_dir: nargo_toml.root_dir, members, selected_package_index } + Workspace { + root_dir: nargo_toml.root_dir, + members, + selected_package_index, + is_assumed: false, + } } }; diff --git a/tooling/noir_codegen/package.json b/tooling/noir_codegen/package.json index 7d76b1a9138..60ccf5ec2a5 100644 --- a/tooling/noir_codegen/package.json +++ b/tooling/noir_codegen/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.22.0", + "version": "0.23.0", "packageManager": "yarn@3.5.1", "license": "(MIT OR Apache-2.0)", "type": "module", diff --git a/tooling/noir_js/.gitignore b/tooling/noir_js/.gitignore index 5b57ba1708d..a55d1794141 100644 --- a/tooling/noir_js/.gitignore +++ b/tooling/noir_js/.gitignore @@ -1,3 +1 @@ crs - -!test/noir_compiled_examples/*/target diff --git a/tooling/noir_js/package.json b/tooling/noir_js/package.json index ed2fd225810..5f83a03019b 100644 --- a/tooling/noir_js/package.json +++ b/tooling/noir_js/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.22.0", + "version": "0.23.0", "packageManager": "yarn@3.5.1", "license": "(MIT OR Apache-2.0)", "type": "module", @@ -37,7 +37,8 @@ "scripts": { "dev": "tsc-multi --watch", "build": "tsc-multi", - "test": "yarn test:node:esm && yarn test:node:cjs", + "test": "yarn test:compile_program && yarn test:node:esm && yarn test:node:cjs", + "test:compile_program": "./scripts/compile_test_programs.sh", "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'", diff --git a/tooling/noir_js/scripts/compile_test_programs.sh b/tooling/noir_js/scripts/compile_test_programs.sh new file mode 100755 index 00000000000..5257aaae696 --- /dev/null +++ b/tooling/noir_js/scripts/compile_test_programs.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +rm -rf ./test/noir_compiled_examples/**/target +nargo --program-dir ./test/noir_compiled_examples/assert_lt compile --force +nargo --program-dir ./test/noir_compiled_examples/assert_msg_runtime compile --force diff --git a/tooling/noir_js/src/witness_generation.ts b/tooling/noir_js/src/witness_generation.ts index 1f233422061..cef1d817d9b 100644 --- a/tooling/noir_js/src/witness_generation.ts +++ b/tooling/noir_js/src/witness_generation.ts @@ -26,6 +26,12 @@ const defaultForeignCallHandler: ForeignCallHandler = async (name: string, args: // // If a user needs to print values then they should provide a custom foreign call handler. return []; + } else if (name == 'assert_message') { + // By default we do not do anything for `assert_message` foreign calls due to a need for formatting, + // however we provide an empty response in order to not halt execution. + // + // If a user needs to use dynamic assertion messages then they should provide a custom foreign call handler. + return []; } throw Error(`Unexpected oracle during execution: ${name}(${args.join(', ')})`); }; diff --git a/tooling/noir_js/test/node/execute.test.ts b/tooling/noir_js/test/node/execute.test.ts index bfaf80882ab..491bcb0dfc4 100644 --- a/tooling/noir_js/test/node/execute.test.ts +++ b/tooling/noir_js/test/node/execute.test.ts @@ -1,9 +1,11 @@ import assert_lt_json from '../noir_compiled_examples/assert_lt/target/assert_lt.json' assert { type: 'json' }; +import assert_msg_json from '../noir_compiled_examples/assert_msg_runtime/target/assert_msg_runtime.json' assert { type: 'json' }; import { Noir } from '@noir-lang/noir_js'; import { CompiledCircuit } from '@noir-lang/types'; import { expect } from 'chai'; const assert_lt_program = assert_lt_json as CompiledCircuit; +const assert_msg_runtime = assert_msg_json as CompiledCircuit; it('returns the return value of the circuit', async () => { const inputs = { @@ -14,3 +16,16 @@ it('returns the return value of the circuit', async () => { expect(returnValue).to.be.eq('0x05'); }); + +it('circuit with a dynamic assert message should fail on an assert failure not the foreign call handler', async () => { + const inputs = { + x: '10', + y: '5', + }; + try { + await new Noir(assert_msg_runtime).execute(inputs); + } catch (error) { + const knownError = error as Error; + expect(knownError.message).to.equal('Circuit execution failed: Error: Cannot satisfy constraint'); + } +}); diff --git a/tooling/noir_js/test/noir_compiled_examples/assert_lt/src/main.nr b/tooling/noir_js/test/noir_compiled_examples/assert_lt/src/main.nr index 693e7285736..a9aaae5f2f7 100644 --- a/tooling/noir_js/test/noir_compiled_examples/assert_lt/src/main.nr +++ b/tooling/noir_js/test/noir_compiled_examples/assert_lt/src/main.nr @@ -4,6 +4,10 @@ fn main(x: u64, y: pub u64) -> pub u64 { // We include a println statement to show that noirJS will ignore this and continue execution std::println("foo"); + // A dynamic assertion message is used to show that noirJS will ignore the call and continue execution + // The assertion passes and thus the foreign call for resolving an assertion message should not be called. + assert(x < y, f"Expected x < y but got {x} < {y}"); + assert(x < y); x + y } diff --git a/tooling/noir_js/test/noir_compiled_examples/assert_lt/target/assert_lt.json b/tooling/noir_js/test/noir_compiled_examples/assert_lt/target/assert_lt.json deleted file mode 100644 index 5b511cdc140..00000000000 --- a/tooling/noir_js/test/noir_compiled_examples/assert_lt/target/assert_lt.json +++ /dev/null @@ -1 +0,0 @@ -{"noir_version":"0.20.0+010fdb69616f47fc0a9f252a65a903316d3cbe80","hash":17538653710107541030,"backend":"acvm-backend-barretenberg","abi":{"parameters":[{"name":"x","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"private"},{"name":"y","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"public"}],"param_witnesses":{"x":[{"start":1,"end":2}],"y":[{"start":2,"end":3}]},"return_type":{"abi_type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"public"},"return_witnesses":[5]},"bytecode":"H4sIAAAAAAAA/9Wa627aMBiGHcKhBBLOR+0Hl+Ak5PQP9U6gBK3SNqoqWm9kFzzMYu2DujhbPjvFEkrsJu/7vJ+TFDAtQkiH/GnG6VXLtxvQr+V9Mx/jx5pE3Db5lpZqYahI96Ba91e+bee1g60J6objS90mehbqN8nfuUbSpLAeNVAjXg++bZxeD/m+k9esjlyz6+t3A/p1MFfYvkyzharpPrXzmsFmXPU3YL8F8jUV5HvA1aRMs42qGe2YhgVqwuvH2Tvg721QLwu5Xgbw5Lq8bynz9Tye8Vb+joCjozF/R5lveJ7/riR/V8DR1Zi/q8w3TJiGLclvCzhsjfltZb5hyjQcSX5HwOFozO8o8w3XTKMnyd8TcPQ05od8RVmtilnxff0t0+hL8vcFHH2N+SFfUVarYlZ838hnGgNJ/oGAY6Ax/0CZb3R+rgwl+YcCjqHG/ENlvtH5fdVIkn8k4BhpzA/5irJ274jVrpgV3zeMmMZYkn8s4BhrzA/5irJaFbPi+3pPTGMiyT8RcEw05od8RVmtilnxfcPzXE0l+acCjqnG/FNlvmHANGaS/DMBx0xjfshXlNW+I9bRHbEOKmbF9w1jpjGX5J8LOOYa80O+oqzWHbH2KmbF9/XPnwUXkvwLAcdCY/6FMt9ozzSWkvxLAcdSY/4l8MVet3gAmV9en39kHKAOYPg+X2xlzQRjXOALOIeDtsj7hR60qpnk/eolAWNYPgbQ8k/fTK7TyEtd391SL9nFAV0HuzB2YzeIg70X+34ar+Mo2SURTdy1n7qHIPEPuVjt/7nc6wFBdDRtWFe46tgAE18D44/geLgCb4A5eQTniI4xPtBpgzF+vkMUXljQHEvTJJc/T+C6ZS8oE4+R8kmtk8vlWELwfxKg6qYqq9VErOet+v0jJ73idE3EzHXEeS1Rv5sPuM9839yaZ1quXdwntFxzMe+TBsF/7nDNj/6B8P0GuXz4848R/B3INsvS7y/ZKjuutvv96u05+7o6/kxfD9+Ob78Bhjydn08mAAA="} \ No newline at end of file diff --git a/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/Nargo.toml b/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/Nargo.toml new file mode 100644 index 00000000000..765f632ff74 --- /dev/null +++ b/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "assert_msg_runtime" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] \ No newline at end of file diff --git a/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/src/main.nr b/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/src/main.nr new file mode 100644 index 00000000000..40e447cad02 --- /dev/null +++ b/tooling/noir_js/test/noir_compiled_examples/assert_msg_runtime/src/main.nr @@ -0,0 +1,6 @@ +fn main(x: u64, y: pub u64) { + // A dynamic assertion message is used to show that noirJS will ignore the call and continue execution + // We need this assertion to fail as the `assert_message` oracle in Noir is only called + // upon a failing condition in an assert. + assert(x < y, f"Expected x < y but got {x} < {y}"); +} diff --git a/tooling/noir_js_backend_barretenberg/package.json b/tooling/noir_js_backend_barretenberg/package.json index e22ea2ff49d..a0123883efd 100644 --- a/tooling/noir_js_backend_barretenberg/package.json +++ b/tooling/noir_js_backend_barretenberg/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.22.0", + "version": "0.23.0", "packageManager": "yarn@3.5.1", "license": "(MIT OR Apache-2.0)", "type": "module", @@ -42,7 +42,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "0.19.0", + "@aztec/bb.js": "0.21.0", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, diff --git a/tooling/noir_js_backend_barretenberg/src/index.ts b/tooling/noir_js_backend_barretenberg/src/index.ts index 6e619fd59cf..61094a3451f 100644 --- a/tooling/noir_js_backend_barretenberg/src/index.ts +++ b/tooling/noir_js_backend_barretenberg/src/index.ts @@ -1,11 +1,12 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { decompressSync as gunzip } from 'fflate'; import { acirToUint8Array } from './serialize.js'; import { Backend, CompiledCircuit, ProofData } from '@noir-lang/types'; import { BackendOptions } from './types.js'; import { deflattenPublicInputs, flattenPublicInputsAsArray } from './public_inputs.js'; +import { type Barretenberg } from '@aztec/bb.js'; export { publicInputsToWitnessMap } from './public_inputs.js'; + // This is the number of bytes in a UltraPlonk proof // minus the public inputs. const numBytesInProofWithoutPublicInputs: number = 2144; @@ -15,12 +16,14 @@ export class BarretenbergBackend implements Backend { // 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. - private api: any; + + private api!: Barretenberg; + // eslint-disable-next-line @typescript-eslint/no-explicit-any private acirComposer: any; private acirUncompressedBytecode: Uint8Array; constructor( - private acirCircuit: CompiledCircuit, + acirCircuit: CompiledCircuit, private options: BackendOptions = { threads: 1 }, ) { const acirBytecodeBase64 = acirCircuit.bytecode; @@ -30,8 +33,6 @@ export class BarretenbergBackend implements Backend { /** @ignore */ async instantiate(): Promise { if (!this.api) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore const { Barretenberg, RawBuffer, Crs } = await import('@aztec/bb.js'); const api = await Barretenberg.new({ threads: this.options.threads }); @@ -46,29 +47,25 @@ export class BarretenbergBackend implements 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. - // - // The settings for this proof are the same as the settings for a "normal" proof - // ie one that is not in the recursive setting. + /** + * Generate a final proof. This is the proof for the circuit which will verify + * intermediate proofs and or can be seen as the proof created for regular circuits. + */ async generateFinalProof(decompressedWitness: Uint8Array): Promise { + // The settings for this proof are the same as the settings for a "normal" proof + // i.e. one that is not in the recursive setting. const makeEasyToVerifyInCircuit = false; return this.generateProof(decompressedWitness, makeEasyToVerifyInCircuit); } - // Generates an inner proof. This is the proof that will be verified - // in another circuit. - // - // This is sometimes referred to as a recursive proof. - // We avoid this terminology as the only property of this proof - // that matters, is the fact that it is easy to verify in another - // circuit. We _could_ choose to verify this proof in the CLI. - // - // 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. - /** + * Generates an intermediate proof. This is the proof that can be verified + * in another circuit. + * + * This is sometimes referred to as a recursive proof. + * We avoid this terminology as the only property of this proof + * that matters is the fact that it is easy to verify in another circuit. + * We _could_ choose to verify this proof outside of a circuit just as easily. * * @example * ```typescript @@ -76,6 +73,9 @@ export class BarretenbergBackend implements Backend { * ``` */ async generateIntermediateProof(witness: Uint8Array): Promise { + // 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. const makeEasyToVerifyInCircuit = true; return this.generateProof(witness, makeEasyToVerifyInCircuit); } @@ -99,17 +99,16 @@ export class BarretenbergBackend implements Backend { return { proof, publicInputs }; } - // Generates artifacts that will be passed to a circuit that will verify this proof. - // - // Instead of passing the proof and verification key as a byte array, we pass them - // as fields which makes it cheaper to verify in a circuit. - // - // The proof that is passed here will have been created using the `generateInnerProof` - // method. - // - // The number of public inputs denotes how many public inputs are in the inner proof. - /** + * Generates artifacts that will be passed to a circuit that will verify this proof. + * + * Instead of passing the proof and verification key as a byte array, we pass them + * as fields which makes it cheaper to verify in a circuit. + * + * The proof that is passed here will have been created using the `generateIntermediateProof` + * method. + * + * The number of public inputs denotes how many public inputs are in the inner proof. * * @example * ```typescript diff --git a/tooling/noir_js_types/package.json b/tooling/noir_js_types/package.json index 0276b8d087c..ef75f3d2fb3 100644 --- a/tooling/noir_js_types/package.json +++ b/tooling/noir_js_types/package.json @@ -4,7 +4,7 @@ "The Noir Team " ], "packageManager": "yarn@3.5.1", - "version": "0.22.0", + "version": "0.23.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://noir-lang.org/", "repository": { diff --git a/tooling/noirc_abi/src/lib.rs b/tooling/noirc_abi/src/lib.rs index 066b1635ced..1fc257c1676 100644 --- a/tooling/noirc_abi/src/lib.rs +++ b/tooling/noirc_abi/src/lib.rs @@ -512,7 +512,7 @@ fn decode_string_value(field_elements: &[FieldElement]) -> String { final_string.to_owned() } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ContractEvent { /// Event name name: String, diff --git a/tooling/noirc_abi_wasm/package.json b/tooling/noirc_abi_wasm/package.json index d023e1e4391..db0f6c29153 100644 --- a/tooling/noirc_abi_wasm/package.json +++ b/tooling/noirc_abi_wasm/package.json @@ -3,7 +3,7 @@ "contributors": [ "The Noir Team " ], - "version": "0.22.0", + "version": "0.23.0", "license": "(MIT OR Apache-2.0)", "homepage": "https://noir-lang.org/", "repository": { diff --git a/yarn.lock b/yarn.lock index e7822f59bdc..743068f1907 100644 --- a/yarn.lock +++ b/yarn.lock @@ -221,9 +221,9 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@npm:0.19.0": - version: 0.19.0 - resolution: "@aztec/bb.js@npm:0.19.0" +"@aztec/bb.js@npm:0.16.0": + version: 0.16.0 + resolution: "@aztec/bb.js@npm:0.16.0" dependencies: comlink: ^4.4.1 commander: ^10.0.1 @@ -231,7 +231,21 @@ __metadata: tslib: ^2.4.0 bin: bb.js: dest/node/main.js - checksum: c78c22c3b8c43e0010a43145f973489aa7da9fcf0b8527884107f1f34ac3ca5f5d4ab087d74ce50cb75d6dbaef88bfc693e23745282faa30b81dc78481c65874 + checksum: 5f68b4ad16284a3a871e0ad21fea05aed670383bc639c9d07ab3bf9b7a9d15cc8a4e5cda404a9290775ad5023924739543a8aac37d602892dd1fb5087521970b + languageName: node + linkType: hard + +"@aztec/bb.js@npm:0.21.0": + version: 0.21.0 + resolution: "@aztec/bb.js@npm:0.21.0" + dependencies: + comlink: ^4.4.1 + commander: ^10.0.1 + debug: ^4.3.4 + tslib: ^2.4.0 + bin: + bb.js: dest/node/main.js + checksum: a0fb97476f52025f3c31b7a5e890966ac375ed47c5cfd3434f5c3e4265af3c7566a162f37d6c56f394f44bfe4ba67e5002b7c5998ecc4f6abe70e04f5b8abe34 languageName: node linkType: hard @@ -4381,6 +4395,13 @@ __metadata: languageName: node linkType: hard +"@noir-lang/acvm_js@npm:0.38.0": + version: 0.38.0 + resolution: "@noir-lang/acvm_js@npm:0.38.0" + checksum: 42a5bba45135d1df0d0eb3f7b65439733e016580bad610e859e140638d42200d6b856ff11c4b30417b74ce011da7c39861aafb1c5b8c7211de2172aea449c635 + 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" @@ -4399,11 +4420,22 @@ __metadata: languageName: unknown linkType: soft -"@noir-lang/backend_barretenberg@^0.22.0, @noir-lang/backend_barretenberg@workspace:*, @noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg": +"@noir-lang/backend_barretenberg@npm:^0.22.0": + version: 0.22.0 + resolution: "@noir-lang/backend_barretenberg@npm:0.22.0" + dependencies: + "@aztec/bb.js": 0.16.0 + "@noir-lang/types": 0.22.0 + fflate: ^0.8.0 + checksum: ead456218ba61d925e0fc5b47d1b94272e980b44a220f1262fb6cdc73cff7cd4232ddc69dd67bb21e50f0b43e7696d4a96fde15e3eadc0bf223ec6d59e014e23 + languageName: node + linkType: hard + +"@noir-lang/backend_barretenberg@workspace:*, @noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg": version: 0.0.0-use.local resolution: "@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg" dependencies: - "@aztec/bb.js": 0.19.0 + "@aztec/bb.js": 0.21.0 "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3 @@ -4443,7 +4475,18 @@ __metadata: languageName: unknown linkType: soft -"@noir-lang/noir_js@^0.22.0, @noir-lang/noir_js@workspace:*, @noir-lang/noir_js@workspace:tooling/noir_js": +"@noir-lang/noir_js@npm:^0.22.0": + version: 0.22.0 + resolution: "@noir-lang/noir_js@npm:0.22.0" + dependencies: + "@noir-lang/acvm_js": 0.38.0 + "@noir-lang/noirc_abi": 0.22.0 + "@noir-lang/types": 0.22.0 + checksum: 3b0873ad87521415af11208bebe5690191d03fa06dcd515789f0a63f7641146cdcb01d292b208452856ea3967e196c8332cb2618e013f9e7e5ce7d6e09de043d + languageName: node + linkType: hard + +"@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: @@ -4466,7 +4509,14 @@ __metadata: languageName: unknown linkType: soft -"@noir-lang/noir_wasm@^0.22.0, @noir-lang/noir_wasm@workspace:*, @noir-lang/noir_wasm@workspace:compiler/wasm": +"@noir-lang/noir_wasm@npm:^0.22.0": + version: 0.22.0 + resolution: "@noir-lang/noir_wasm@npm:0.22.0" + checksum: 7ac0ca170bf312df761d7ccfd32a67a27f88f15ad4eed1807864295d761d3b2176ffb82f4c6931e1bc06b225d6f738519962c79ffbce9a33d5ef8a6a2bdea82c + languageName: node + linkType: hard + +"@noir-lang/noir_wasm@workspace:*, @noir-lang/noir_wasm@workspace:compiler/wasm": version: 0.0.0-use.local resolution: "@noir-lang/noir_wasm@workspace:compiler/wasm" dependencies: @@ -4510,6 +4560,13 @@ __metadata: languageName: unknown linkType: soft +"@noir-lang/noirc_abi@npm:0.22.0": + version: 0.22.0 + resolution: "@noir-lang/noirc_abi@npm:0.22.0" + checksum: a250c6cc5ca37fcf02663f8d6b027776f0e58920fb8f8a84efcf74f079f235bb11bbad682ba332211d9b9a79b6a3eb7faede7701cd88582b682971a41ca6212d + languageName: node + linkType: hard + "@noir-lang/noirc_abi@workspace:*, @noir-lang/noirc_abi@workspace:tooling/noirc_abi_wasm": version: 0.0.0-use.local resolution: "@noir-lang/noirc_abi@workspace:tooling/noirc_abi_wasm" @@ -4540,7 +4597,16 @@ __metadata: languageName: unknown linkType: soft -"@noir-lang/types@^0.22.0, @noir-lang/types@workspace:*, @noir-lang/types@workspace:tooling/noir_js_types": +"@noir-lang/types@npm:0.22.0, @noir-lang/types@npm:^0.22.0": + version: 0.22.0 + resolution: "@noir-lang/types@npm:0.22.0" + dependencies: + "@noir-lang/noirc_abi": 0.22.0 + checksum: 5dd1badf0449c518e755172de1d2f2c1b95bfaf7b7328b7de00b8ce9ba68bd447ca65e827185da7d737e7e88dcaf296b29687ffe2e1f5b4d5cc31ce3e3b4f208 + languageName: node + linkType: hard + +"@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" dependencies: