diff --git a/cspell.json b/cspell.json index 5c707e92e21..9a4bca1e339 100644 --- a/cspell.json +++ b/cspell.json @@ -32,6 +32,7 @@ "boilerplates", "bridgekeeper", "brillig", + "bunx", "bytecount", "cachix", "callsite", @@ -242,9 +243,11 @@ "walkdir", "wasi", "wasmer", + "webapps", "Weierstraß", "whitespace", "whitespaces", + "YOLO", "zkhash", "zshell" ], diff --git a/docs/docs/how_to/how-to-solidity-verifier.md b/docs/docs/how_to/how-to-solidity-verifier.mdx similarity index 64% rename from docs/docs/how_to/how-to-solidity-verifier.md rename to docs/docs/how_to/how-to-solidity-verifier.mdx index 2cc0f8e57ce..da36b60920d 100644 --- a/docs/docs/how_to/how-to-solidity-verifier.md +++ b/docs/docs/how_to/how-to-solidity-verifier.mdx @@ -20,24 +20,37 @@ 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. +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Noir is universal. The witness and the compiled program can be fed into a proving backend such as Aztec's [Barretenberg](https://github.com/AztecProtocol/aztec-packages/tree/master/barretenberg), which can then generate a verifier contract for deployment on blockchains. 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: +Although not strictly in the domain of Noir itself, this guide shows how to generate a Solidity Verifier with Barretenberg and deploy it on the [Remix IDE](https://remix.ethereum.org/). It is assumed that: +- You will be using Barretenberg as your proving backend +- You will be using an EVM blockchain to verify your proof - 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/quick_start.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: +Generating a Solidity Verifier with Barretenberg 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 +:::info[Which proving system to use?] + +Barretenberg currently provides two provers: `UltraPlonk` and `UltraHonk`. In a nutshell, `UltraHonk` is faster and uses less RAM, but its verifier contract is much more expensive. `UltraPlonk` is optimized for on-chain verification, but proving is more expensive. + +In any case, we provide instructions for both. Choose your poison ☠️ + +::: + ## Step 1 - Generate a contract This is by far the most straightforward step. Just run: @@ -46,25 +59,31 @@ This is by far the most straightforward step. Just run: nargo compile ``` -This will compile your source code into a Noir build artifact to be stored in the `./target` directory, you can then generate the smart contract using the commands: +This will compile your source code into a Noir build artifact to be stored in the `./target` directory. From here on, it's Barretenberg's work. You can generate the smart contract using the commands: + + + + +```sh +bb write_vk_ultra_keccak_honk -b ./target/.json +bb contract_ultra_honk +``` + + + ```sh -# Here we pass the path to the newly generated Noir artifact. bb write_vk -b ./target/.json bb contract ``` -replacing `` with the name of your Noir project. A new `contract` folder would then be generated in your project directory, containing the Solidity -file `contract.sol`. It can be deployed to any EVM blockchain acting as a verifier smart contract. + + -You can find more information about `bb` and the default Noir proving backend on [this page](../getting_started/quick_start.md#proving-backend). +replacing `` with the name of your Noir project. A `Verifier.sol` contract is now in the target folder and 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. +You can find more information about `bb` and the default Noir proving backend on [this page](../getting_started/quick_start.md#proving-backend). -Barretenberg, the default proving backend for Nargo, supports generation of verifier contracts, for the time being these are only in Solidity. -::: ## Step 2 - Compiling @@ -85,17 +104,12 @@ 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: +Remix should automatically match a suitable compiler version. However, hitting the "Compile" button will most likely tell you the contract is too big to deploy on mainnet, or complain about a stack too deep: -![Stack too deep](@site/static/img/how-tos/solidity_verifier_3.png) +![Contract code too big](@site/static/img/how-tos/solidity_verifier_6.png) +![Stack too deep](@site/static/img/how-tos/solidity_verifier_8.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. - -::: +To avoid this, you can just use some optimization. Open the "Advanced Configurations" tab and enable optimization. The default 200 runs will suffice. ![Compilation success](@site/static/img/how-tos/solidity_verifier_4.png) @@ -103,54 +117,81 @@ This time we will see a warning about an unused function parameter. This is expe At this point we should have a compiled contract ready 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": +Looking closely, we will notice that our "Solidity Verifier" is composed on multiple contracts working together. Remix will take care of the dependencies for us so we can simply deploy the Verifier 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 that the deployer contract is the correct one. +![Deploying HonkVerifier](@site/static/img/how-tos/solidity_verifier_7.png) -:::note + + -Why "UltraVerifier"? +![Deploying PlonkVerifier](@site/static/img/how-tos/solidity_verifier_9.png) -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. - -::: +A contract will show up in the "Deployed Contracts" section. ## Step 4 - Verifying -To verify a proof using the Solidity verifier contract, we call the `verify` function in this extended contract: +To verify a proof using the Solidity verifier contract, we call the `verify` function: ```solidity function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool) ``` -When using the default example in the [Hello Noir](../getting_started/quick_start.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. Note that the public inputs must be passed in separately to the rest of the proof so we must split the proof as returned from `bb`. +First generate a proof with `bb`. We need a `Prover.toml` file for our inputs. Run: -First generate a proof with `bb` at the location `./proof` using the steps in [get started](../getting_started/quick_start.md), this proof is in a binary format but we want to convert it into a hex string to pass into Remix, this can be done with the +```bash +nargo check +``` + +This will generate a `Prover.toml` you can fill with the values you want to prove. We can now execute the circuit with `nargo` and then use the proving backend to prove: + + + + +```bash +nargo execute +bb prove_ultra_keccak_honk -b ./target/.json -w ./target/ -o ./target/proof +``` + +:::tip[Public inputs] +Barretenberg attaches the public inputs to the proof, which in this case it's not very useful. If you're up for some JS, `bb.js` has [a method for it](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/ts/src/proof/index.ts), but in the CLI you can use this ugly snippet: + +```bash +cat ./target/proof | od -An -v -t x1 | tr -d $' \n' | sed 's/^.\{8\}//' | (read hex; echo "${hex:0:192}${hex:256}") +``` + +Beautiful. This assumes a circuit with one public input (32 bytes, for Barretenberg). For more inputs, you can just increment `hex:256` with 32 more bytes for each public input. + +::: + + + ```bash -# This value must be changed to match the number of public inputs (including return values!) in your program. -NUM_PUBLIC_INPUTS=1 -PUBLIC_INPUT_BYTES=32*NUM_PUBLIC_INPUTS -HEX_PUBLIC_INPUTS=$(head -c $PUBLIC_INPUT_BYTES ./proof | od -An -v -t x1 | tr -d $' \n') -HEX_PROOF=$(tail -c +$(($PUBLIC_INPUT_BYTES + 1)) ./proof | od -An -v -t x1 | tr -d $' \n') +nargo execute +bb prove -b ./target/.json -w ./target/ -o ./target/proof +``` + -echo "Public inputs:" -echo $HEX_PUBLIC_INPUTS +:::tip[Public inputs] +Barretenberg attaches the public inputs to the proof, which in this case it's not very useful. If you're up for some JS, `bb.js` has [a method for it](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/ts/src/proof/index.ts), but in the CLI you can use this ugly snippet: -echo "Proof:" -echo "0x$HEX_PROOF" +```bash +tail -c +33 ./target/proof | od -An -v -t x1 | tr -d $' \n' ``` +Beautiful. This assumes a circuit with one public input (32 bytes, for Barretenberg). For more inputs, you can just add 32 more bytes for each public input to the `tail` command. + +::: + + + + Remix expects that the public inputs will be split into an array of `bytes32` values so `HEX_PUBLIC_INPUTS` needs to be split up into 32 byte chunks which are prefixed with `0x` accordingly. 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): @@ -188,7 +229,7 @@ the `verify` function will expect the public inputs array (second function param 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`. +In this case, the inputs parameter to `verify` would be an array ordered as `[pubkey_x, pubkey_y, return]`. ::: diff --git a/docs/static/img/how-tos/solidity_verifier_6.png b/docs/static/img/how-tos/solidity_verifier_6.png new file mode 100644 index 00000000000..c92e874740a Binary files /dev/null and b/docs/static/img/how-tos/solidity_verifier_6.png differ diff --git a/docs/static/img/how-tos/solidity_verifier_7.png b/docs/static/img/how-tos/solidity_verifier_7.png new file mode 100644 index 00000000000..c8541fa544c Binary files /dev/null and b/docs/static/img/how-tos/solidity_verifier_7.png differ diff --git a/docs/static/img/how-tos/solidity_verifier_8.png b/docs/static/img/how-tos/solidity_verifier_8.png new file mode 100644 index 00000000000..125c140acec Binary files /dev/null and b/docs/static/img/how-tos/solidity_verifier_8.png differ diff --git a/docs/static/img/how-tos/solidity_verifier_9.png b/docs/static/img/how-tos/solidity_verifier_9.png new file mode 100644 index 00000000000..150812e3320 Binary files /dev/null and b/docs/static/img/how-tos/solidity_verifier_9.png differ