Tutorial
This tutorial runs through a basic example of using the oxide command line tool. First we will create and populate a manifest, specifying which circuits we'll have available. Then we'll install those circuits and make use of them from a short JavaScript application we create.
Finding a Circuit
In an empty directory, we create a new manifest by running oxide init:
$ oxide init
This will create a manifest file circuits.toml which includes details about which circuits our project depends on.
For this example, we'll be creating a proof that we are old enough to vote. To do this, we want a circuit that checks if a number is greater than or equal to another number (gte).
First we'll search for a circuit which provides us with the comparison that we want:
$ oxide search gte
NAME | DESCRIPTION | AUTHOR | DATE | VERSION gte | Check whether the first input a (private) is greate... | ironmill | Fri, 18... | 1.0.0 dogteeth | Unrelated to your needs: the teeth of a dog. | dog | Fri, 18... | 0.3.0
The first is exactly what we want. We add it to our manifest with the command:
$ oxide add gte
Checking the manifest, you'll notice that it now says:
[dependencies] gte = { version = "1.0.0" }
Even though the manifest has updated, we won't have the circuits available locally until we run oxide install:
$ oxide install
Downloaded gte Succesfully installed 1 packages
The folder should now look like this:
$ tree .
. ├── circuits │ └── gte │ ├── gte.zkey │ ├── gte_js │ │ ├── Prover.js │ │ ├── Verifier.js │ │ └── gte.wasm │ └── index.js └── circuits.toml 4 directories, 6 files
You'll notice that a new directory has been added containing all of our circuits (Currently just gte). Within our circuit's folder, there's an index.js that exposes the prover/verifier behavior that we want, as well as some other files to make it all happen.
Integrating into our Javascript Project
With the zero-knowledge part taken care of, we can focus on developing the javascript part of our app. We'll be using npm in this example, but any js package manager works just as well.
The suggested way to create a new project in npm is with npm init.
$ npm init
For this example, we'll also make use of the snarkjs implementation for proving and verifying, so be sure to import that library (Note: We import a patched version here to resolve an issue with execution hanging):
$ npm add IronMill-xyz/snarkjs
Now finally it's time to do some coding. First we'll create an index.js file in the root of our project and import the prover and verifier:
const {Prover, Verifier } = require(`./circuits/gte`);
We'll put these to use in a main function:
async function main() { // Generate a proof that our age is more than 18 const ourAge = 20; const minimumAge = 18; const proof = await Prover.generateProof(ourAge, minimumAge); console.log(proof.publicSignals); } main();
When we generate the proof, we pass in the 2 inputs to the circuit as arguments to the `generateProof` method. For this circuit the first argument, `ourAge`, is a private input (witness) while the second, `minimumAge`, is publically known in the proof.
The documentation for the circuit and its inputs is visible if you hover over the `generateProof` method (In most IDEs that support JSDoc).
Also, generating the proof is an `async` operation, meaning that we must await the `Promise` that it returns, and the surrounding function must also be `async`.
$ node index.js
[ '1', '18' ]
We've generated a proof, but what exactly does that mean? Well, what we've created is an argument of knowledge that the gte function was run with a hidden input (Commonly called a witness) and public input of 18 and had the given result (In this case 1, or "true"). We can give this to any verifier, and as long as it has the right information about the circuit, it can verify that the computation was performed honestly.
The next step will be to do just that, although in practice we probably want to send this proof to a 3rd party to verify since we already trust ourself.
Adding the following lines to our function:
... // Verify the proof const result = await Verifier.verify(proof); console.log(`Verifier result is ${result}.`);
The verifier should accept the proof and print Verifier result is true.
Putting it all together, our index.js file looks like this:
const {Prover, Verifier } = require(`./circuits/gte`) async function main() { // Generate a proof const ourAge = 20; const minimumAge = 18; const proof = await Prover.generateProof(ourAge, minimumAge); console.log(proof.publicSignals); // Verify the proof const result = await Verifier.verify(proof); console.log(`Verifier result is ${result}.`); } main();
Next Steps
There are several circuits available for you to experiment with in projects you build. They provide some of the key functionality for zero-knowledge, verifiable execution, or both.
Additionally, you can publish your own circuits that you write or find online using the same command-line tool!
The circuit must be written in circom,and must be a single file. Publish it by providing its filepath to oxide, then provide the details about it.
$ oxide publish ./my_circuit.circom
This utility will walk you through specifying the details of the circuit you are publishing. package name: my_circuit description: Check whether the supplied witness a is odd. author: john version: (1.0.0) 1.0.0 Circuit my_circuit published to the registry
Your circuit will be bundled, and show up on the registry for you and others to use in your projects.
$ oxide search my_circuit
NAME | DESCRIPTION | AUTHOR | DATE | VERSION my_circuit | Check whether the supplied witness a is odd. | john | Mon, 21... | 1.0.0