A javascript library to generate and verify zkSNARK proofs.
Go to file
2020-07-13 23:46:51 +02:00
build fix solidity template dir 2020-07-13 08:54:48 +02:00
config fix template dir 2020-07-13 08:47:02 +02:00
src tutorial and many fixes 2020-07-13 07:21:03 +02:00
templates api and tutorial start 2020-07-11 10:31:52 +02:00
test tutorial and many fixes 2020-07-13 07:21:03 +02:00
.eslintrc.cjs api and tutorial start 2020-07-11 10:31:52 +02:00
.gitignore ignore keys 2019-02-15 09:36:45 +01:00
cli.js fix solidity template dir 2020-07-13 08:54:48 +02:00
COPYING Adapted to circom 0.5 2020-03-26 20:16:29 +01:00
main.js api and tutorial start 2020-07-11 10:31:52 +02:00
package-lock.json 0.3.4 2020-07-13 08:55:00 +02:00
package.json 0.3.4 2020-07-13 08:55:00 +02:00
README.md edits 2020-07-13 23:46:51 +02:00

snarkjs: JavaScript implementation of zkSNARKs.

This is a JavaScript and Pure Web Assembly implementation of zkSNARK schemes. It uses the Groth16 Protocol (3 point only and 3 pairings).

This library includes all the tools required to perform trusted setup multi-party ceremonies: including the universal "powers of tau" ceremony, and the second phase circuit specific ceremonies.

Any zk-snark project can pick a round from the common phase 1 to start their circuit-specific phase 2 ceremony.

The formats used in this library for the multi-party computation are compatible with the ones used in Semaphore's Perpetual Powers of Tau and other implementations.

This library uses the compiled circuits generated by the circom compiler.

It works in node.js as well as directly in the browser.

It's an ES module, so it can be directly imported into bigger projects using Rollup or Webpack.

The low-level cryptography is performed directly in wasm, and uses worker threads to parallelize the computations. The result is a high performance library with benchmarks comparable to host implementations.

Preliminaries

Install node v14

First off, make sure you have a recent version of Node.js installed. While any version after v12 should work fine, we recommend you install v14 or later.

If youre not sure which version of Node you have installed, you can run:

node -v

To download the latest version of Node, see here.

Install snarkjs and circom

To install circom and snarkjs, run:

npm install -g circom@latest
npm install -g snarkjs@latest

If you're seeing an error, try prefixing both commands with sudo.

Understand the Help command

To see a list of all snarkjs commands, as well as descriptions about their inputs and outputs, run:

snarkjs --help

You can also us the --help option with specific commands:

snarkjs groth16 prove --help

Most of the commands have an alternative shorter alias (which you can discover using --help).

For example, the previous command can also be invoked with:

snarkjs g16p --help

Debugging tip

If you a feel a command is taking longer than it should, re-run it with a -v or --verbose option to see more details about how it's progressing and where it's getting blocked. For example:

snarkjs g16p -v

Guide

0. Create and move into a new directory

mkdir snarkjs_example
cd snarkjs_example

1. Start a new powers of tau ceremony

snarkjs powersoftau new bn128 12 pot12_0000.ptau -v

The first parameter after new refers to the type of curve you wish to use. At the moment, we support both bn128 and bls12-381.

The second parameter, in this case 12, is the power of two of the maximum number of contraints that the ceremony can accept: in this case, the number of constraints is 2 ^ 12 = 4096. The maximum value supported here is 28, which means you can use snarkjs to securely generate zk-snark parameters for circuits with up to 2 ^ 28 (≈268 million) constraints.

Note that the creator of the ceremony is also the first contributor.

2. Contribute to the ceremony

snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau --name="First contribution" -v

You'll be prompted to enter a random text as an extra source of entropy.

contribute takes as input the transcript of the protocol so far, in this case pot12_0000.ptau, and outputs a new transcript, in this case pot12_0001.ptau, which includes the computation carried out by the new contributor.

name can be anything you want, and is just included for reference (it will be printed when you verify the file (step 4).

3. Provide a second contribution

snarkjs powersoftau contribute pot12_0001.ptau pot12_0002.ptau --name="Second contribution" -v -e="some random text"

By allowing you to write the random text as part of the command, the -e parameter allows contribute to be non-interactive.

4. Provide a third contribution using third party software

snarkjs powersoftau export challange pot12_0002.ptau challange_0003
snarkjs powersoftau challange contribute bn128 challange_0003 response_0003
snarkjs powersoftau import response pot12_0002.ptau response_0003 pot12_0003.ptau -n="Third contribution name"

The commands above use this software to help generate a challenge, response, and a new ptau file.

5. Verify the protocol so far

snarkjs powersoftau verify pot12_0003.ptau

The verify command verifies a ptau (powers of tau) file. Which means it checks all the contributions to the multi-party computation (MPC) up to that point. It also prints the hashes of all the intermediary results to the console.

If everything checks out, you should see the following at the top of the output:

[INFO]  snarkJS: Powers Of tau file OK!

In sum, whenever a new zk-snark project needs to perform a trusted setup, you can just pick the latest ptau file, and run the verify command to verify the entire chain of challenges and responses so far.

6. Apply a random beacon

snarkjs powersoftau beacon pot12_0003.ptau pot12_beacon.ptau 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 10 -n="Final Beacon"

The next step is to apply a random beacon to it (we need to apply a random beacon in order to finalise phase 1 of the trusted setup).

A random beacon is a source of public randomness that is not available before a fixed time. The beacon itself can be a delayed hash function (e.g. 2^40 iterations of SHA256) evaluated on some high entropy and publicly available data. Possible sources of data include: the closing value of the stock market on a certain date in the future, the output of a selected set of national lotteries, or the value of a block at a particular height in one or more blockchains. E.g. the hash of the 11 millionth Ethereum block (which as of this writing is some 3 months in the future). See here for more on the importance of a random beacon.

In the above case, the beacon is essentially a delayed hash function evaluated on 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f (in practice, this will be some form of high entropy and publicly available data). The 10 just means perform 2 ^ 10 iterations of this hash function.

Note that security holds even if an adversary has limited influence on the beacon.

7. Prepare phase 2

snarkjs powersoftau prepare phase2 pot12_beacon.ptau pot12_final.ptau -v

We're now ready to prepare phase 2 of the setup (the circuit-specific phase).

Under the hood, the prepare phase2 command calculates the evaluation of the Lagrange polynomials at tau for alpha*tau and beta*tau. It takes the beacon ptau file we generated in the previous step, and outputs a final ptau file which will be used to generate the circuit proving and verification keys.

8. Verify the final ptau file

snarkjs powersoftau verify pot12_final.ptau

Before we go ahead and create the circuit, we perform a final check and verify the final protocol transcript.

9. Create the circuit

cat <<EOT > circuit.circom
template Multiplier(n) {
    signal private input a;
    signal private input b;
    signal output c;

    signal int[n];

    int[0] <== a*a + b;
    for (var i=1; i<n; i++) {
    int[i] <== int[i-1]*int[i-1] + b;
    }

    c <== int[n-1];
}

component main = Multiplier(1000);
EOT

We create a circom file that allows us to easily test the system with a different number of contraints.

In this case, we've chosen 1000, but we can change this to anything we want (as long as it's below the number we defined in step 1).

10. Compile the circuit

circom circuit.circom --r1cs --wasm --sym -v

The circom command takes one input (the circuit to compile, in our case circuit.circom) and three options:

  • r1cs: generates circuit.r1cs (the r1cs constraint system of the circuit in binary format).

  • wasm: generates circuit.wasm (the wasm code to generate the witness more on that later).

  • sym: generates circuit.sym (a symbols file required for debugging and printing the constraint system in an annotated mode).

11. View information about the circuit

snarkjs r1cs info circuit.r1cs

You should see the following output:

[INFO]  snarkJS: Curve: bn-128
[INFO]  snarkJS: # of Wires: 1003
[INFO]  snarkJS: # of Constraints: 1000
[INFO]  snarkJS: # of Private Inputs: 2
[INFO]  snarkJS: # of Public Inputs: 0
[INFO]  snarkJS: # of Outputs: 1

This information fits with our mental map of the circuit we created: we had two private inputs a and b, one output c, and a thousand constraints of the form a * b = c.

12. Print the constraints

snarkjs r1cs print circuit.r1cs

To double check, we print the constraints of the circuit.

You should see a thousand constraints of the form:

[ -main.int[i] ] * [ main.int[i] ] - [ main.b -main.int[i+1] ] = 0

13. Export r1cs to json

snarkjs r1cs export json circuit.r1cs circuit.r1cs.json
cat circuit.r1cs.json

14. Generate the reference zKey without phase2 contributions.

snarkjs zkey new circuit.r1cs pot12_final.ptau circuit_0000.zkey

The zKey is a zero-knowledge key that includes both the pairing and verification keys as well as phase2 contributions.

Importantly, one can verify whether a zKey belongs to a specific circuit or not.

Note that circuit_0000.zkey (the output of the zkey command above) does not include any contributions yet, so it cannot be used in a final circuit.

The following steps are similar to the equivalent phase1 steps, except we use zkey instead of powersoftau as the main command, and we generate zkey rather that ptau files.

15. Contribute to the phase2 ceremony

snarkjs zkey contribute circuit_0000.zkey circuit_0001.zkey --name="1st Contributor Name" -v

16. Provide a second phase2 contribution

snarkjs zkey contribute circuit_0001.zkey circuit_0002.zkey --name="Second contribution Name" -v -e="Another random entropy"

17. Provide a third contribution using third party software

snarkjs zkey export bellman circuit_0002.zkey  challange_phase2_0003
snarkjs zkey bellman contribute bn128 challange_phase2_0003 response_phase2_0003
snarkjs zkey import bellman circuit_0002.zkey response_phase2_0003 circuit_0003.zkey -n="Third contribution name"

18. Verify the zkey file

snarkjs zkey verify circuit.r1cs pot12_final.ptau circuit_0003.zkey

19. Apply a random beacon

snarkjs zkey beacon circuit_0003.zkey circuit_final.zkey 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 10 -n="Final Beacon phase2"

20. Verify the final ptau file

snarkjs zkey verify circuit.r1cs pot12_final.ptau circuit_final.zkey

21. Export the verification key

snarkjs zkey export verificationkey circuit_final.zkey verification_key.json

22. Calculate the witness

cat <<EOT > input.json
{"a": 3, "b": 11}
EOT
snarkjs wtns calculate circuit.wasm input.json witness.wtns

23. Debug the final witness calculation

In general, when you are developing a new circuit you will want to check for some errors in the witness calculation process.

To do so, run:

snarkjs wtns debug circuit.wasm input.json witness.wtns circuit.sym --trigger --get --set

This will log every time a new component is started/ended (--trigger), when a signal is set (--set) and when it's get (--get)

24. Calculate the proof

snarkjs groth16 prove circuit_final.zkey witness.wtns proof.json public.json

Note that it's possible to calculate both the proof and the witness in the same command:

snarkjs groth16 fullprove input.json circuit.wasm circuit_final.zkey proof.json public.json

25. Verify the proof

snarkjs groth16 verify verification_key.json public.json proof.json

26. Turn the verifier into a smart contract

snarkjs zkey export solidityverifier circuit_final.zkey verifier.sol

You can then deploy the verifier smart-contract using remix for example.

In order to simulate a verification call, run:

snarkjs zkey export soliditycalldata public.json proof.json

And cut and paste the result directly in the verifyProof field in the deployed smart contract. For more details on how to do this, see section four of this tutorial.

This call will return true if both the proof and public data are valid.

Using Node

npm init
npm install snarkjs
const snarkjs = require("snarkjs");
const fs = require("fs");

async function run() {
    const { proof, publicSignals } = await snarkjs.groth16.fullProve({a: 10, b: 21}, "circuit.wasm", "circuit_final.zkey");

    console.log("Proof: ");
    console.log(JSON.stringify(proof, null, 1));

    const vKey = JSON.parse(fs.readFileSync("verification_key.json"));

    const res = await snarkjs.groth16.verify(vKey, publicSignals, proof);

    if (res === true) {
        console.log("Verification OK");
    } else {
        console.log("Invalid proof");
    }

}

run().then(() => {
    process.exit(0);
});

In the browser

Load snarkjs.min.js and start using it as usual.

cp node_modules/snarkjs/build/snarkjs.min.js .
<!doctype html>
<html>
<head>
  <title>Snarkjs client example</title>
</head>
<body>

  <h1>Snarkjs client example</h1>
  <button id="bGenProof"> Create proof </button>

  <!-- JS-generated output will be added here. -->
  <pre class="proof"> Proof: <code id="proof"></code></pre>

  <pre class="proof"> Result: <code id="result"></code></pre>


  <script src="snarkjs.min.js">   </script>


  <!-- This is the bundle generated by rollup.js -->
  <script>

const proofCompnent = document.getElementById('proof');
const resultComponent = document.getElementById('result');
const bGenProof = document.getElementById("bGenProof");

bGenProof.addEventListener("click", calculateProof);

async function calculateProof() {

    const { proof, publicSignals } =
      await snarkjs.groth16.fullProve( { a: 3, b: 11}, "circuit.wasm", "circuit_final.zkey");

    proofCompnent.innerHTML = JSON.stringify(proof, null, 1);


    const vkey = await fetch("verification_key.json").then( function(res) {
        return res.json();
    });

    const res = await snarkjs.groth16.verify(vkey, publicSignals, proof);

    resultComponent.innerHTML = res;
}

  </script>

</body>
</html>

P.S. Please address any questions you may have to our telegram group (its also a great way to join the community and stay up-to-date with the latest circom and snarkjs developments).

License

snarkjs is part of the iden3 project copyright 2018 0KIMS association and published with GPL-3 license. Please check the COPYING file for more details.