In this tutorial, we will create a simple “Secret Public Contract”, using Noir and Foundry. The contract will be deployed with an execute function which can only be executed with a secret calldata, meaning that only if you know the secret function and prove it, you can execute the contract functionality.
You can see the complete code used in this tutorial in the following repository.
Noir is a Domain Specific Language for SNARK proving systems. Its design choices are influenced heavily by Rust and focus on a simple, familiar syntax. While Noir can be used for different purposes this tutorial focuses on Solidity development. We will use Noir to create the circuits and Foundry to test the Solidity contracts.
As Noir evolves constantly, the recommended way to install it is by using noirup
which installs Nargo, a command line tool for interacting with Noir programs (compiling, proving, verifying, creating contracts, etc). In OSX or Linux run the following command on the terminal:
$ curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash
You can check that the installation was successful by running nargo --version
. Other options for installation are available in the official documentation.
Just like Noir, the recommended way to install Foundry is by using foundryup
. Run the following command to install it:
curl -L https://foundry.paradigm.xyz | bash
As it’s outside the scope of this tutorial, you can read more information about Foundry on the official documentation site.
If you use VS Code I will suggest installing the Noir Language Support extension, It adds syntax highlighting, compilation errors and warnings, and useful snippets.
With Nargo and Foundry installed, we can make a project. While we can use the forge init projectName
and nargo new projectName
commands, we would need to do some file ordering and cleaning, so I will suggest you use the Noir starter with the Foundry repo.
You can fork it or use it as inspiration to order the files from scratch. Just keep in mind that you will need to edit the foundry.toml
file and add ffi
and fs_permissions
for Foundry tests to access the proofs generated by noir.
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
fs_permissions = [{ access = "read-write", path = "./"},{ access = "read-write", path = "/tmp/"}]
ffi = true
# See more config options https://github.com/foundry-rs/foundry/tree/master/config
We are ready to create our circuit, move to the circuits/src
folder and create a new Noir file main.nr
. This is a pretty simple circuit, only checks that the values sent from the contract are the ones we define in secret. We use the Pedersen hash function to create a unique hash based on the parameters then we compare them with the hash of our secret parameters to see if they match.
We can then build the output files by running:
$ nargo check
This will generate two files: Prover.toml
which holds input values and Verifier.toml
which has public values. We fill in the values on the Prover.toml if we already know which function we want to call and prove the valid execution by creating a proof running:
nargo prove nameOfProof
This is going to generate a new folder proofs
and it will contain a proof file with the name that you specify, depending on your computer and code this command might take a few seconds to execute. We can then go ahead and verify the correct execution of our program by running the following command:
$ nargo verify nameOfProof
If there are no errors, no message will be shown. We can finally generate the verifier contract by running.
$ nargo codegen-verifier
Which will create the contract
folder and the plonk_vk.sol
contract, which we can later use to verify our contracts.
Head back to the project folder and create two solidity files in the contract
folder. MyToken.sol
is going to be a simple ERC20 Token with a public function mint that anyone can call, add the solmate library running:
$ forge install transmissions11/solmate
And now let’s create SecretCaller.sol
where we will use our plonk_vk.sol
file to verify that the user making the call knows the secret.
We import the verifier contract and assign it to the constructor, we can call the verify function in the secretCall
function. We must pass the proof
as bytes and generate the publicInputs
as an array of bytes32 and we pass it to the verifier.
Once we verify that the proof is correct we can execute the contract call with the information we sent.
As we now have the contracts we want to test, let’s fill the Prover.toml
file with the correct values (you can use console.log on the test files to see the values) generate a proof, and test everything using Foundry. Start creating the SecretCaller.t.sol
file under the test
directory and let’s discuss what’s happening:
We import the contracts that we are going to test, deploy them and use a helper function to grab the proof we generated in the previous steps, then, we are going to create two test functions that send correct and bad data and see if we can execute the secretCall function.
We can then run the $ forge test
command in the console and the two tests should pass.
That’s all! remember that this code is for testing purposes and if someone makes a contract call, the data will be public on-chain meaning that anyone can replicate the call.
This tutorial was built using the Noir version 0.7.1
. As expected with an evolving language many changes are expected to come, I’ll suggest you follow the official Twitter account and join the official discord in case you have any doubts. Feel free to send me a DM on Twitter (X) with questions or comments @crisgarner.