0xalizk / Nova-Scotia

Middleware to compile Circom circuits to Nova prover

Home Page:https://crates.io/crates/nova-scotia

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Nova Scotia

Middleware to compile Circom circuits to Nova prover

Original from Tadashi Moriyama

This repository provides necessary middleware to take generated output of the Circom compiler (R1CS constraints and generated witnesses) and use them with Nova as a prover.

Why?

Nova is the state of the art for recursive SNARKs, Circom is the state of the art for ZK devtooling, so it makes a lot of sense to want to do this. Since Nova uses ~R1CS arithmetization, its mostly just a matter of parsing Circom output into something Nova can use.

As Justin Drake talks about it, I think the right way to think of Nova is as a preprocessor for zkSNARKs with lots of repeated structure -- Nova can shrink the cost (in number of R1CS constraints) of checking N instances of a problem to ~one instance of the same problem. This is clean and magical and lends itself well to a world where we take the output of Nova and then verify it in a "real" zkSNARK (like PLONK/groth16/Spartan) to obtain a actually fully minified proof (that is sublinear even in the size of one instance). Notably, this pattern is already used in settings like zkEVMs, but with STARK proofs instead of Nova proofs. IMO, Nova (and folding scheme-like things in particular) lend themselves better to the properties we want with the preprocessing layer vs. STARKs: fast compression, minimal cryptographic assumptions and low recursive overhead.1

How?

Nova Scotia

To use it yourself, install this branch of Circom which adds support for the Pasta Curves to the C++ witness generator: nalinbhardwaj/pasta. To install this branch, clone the git repo (using git clone https://github.com/nalinbhardwaj/circom.git && git checkout pasta). Then build and install the circom binary by running cargo install --path circom. This will overwrite any existing circom binary. Refer to the Circom documentation for more information.

Note that if you are interested in generating and verifying proofs in browsers, you must use the WASM witness generator. We will describe in-browser proving and verification later in the README.

Writing Nova Step Circuits in Circom

To write Nova Scotia circuits in Circom, we operate on the abstraction of one step of recursion. We write a circuit that takes a list of public inputs (these must be named step_in for the Nova-Scotia interface) and outputs the same number of public outputs (named step_out). These public outputs will then be routed to the next step of recursion as step_in, and this will continue until we reach the end of the recursion iterations. Within a step circuit, besides the public inputs, Circom circuits can input additional private inputs (with any name/JSON structure Circom will accept). We will instrument the piping of these private inputs in our Rust shimming.

When you're ready, compile your circuit using circom [file].circom --r1cs --sym --c --prime vesta for the vesta curve. Compile the C++ witness generator in [file]_cpp by running make in that folder. Alternately, you can compile the WASM witness generator using circom [file].circom --r1cs --sym --wasm --prime vesta. We will later use the R1CS file and the witness generator binary (either C++ binary or WASM), so make note of their filepaths. You can independently test these step circuits by running witness generation as described in the Circom documentation.

Rust shimming for Nova Scotia

Now, start a new Rust project and add Nova Scotia to your dependencies. Then, you can start using your Circom step circuits with Nova. Start by defining the paths to the Circom output and loading the R1CS file:

let circuit_file = root.join("examples/bitcoin/circom/bitcoin_benchmark.r1cs");
let witness_generator_file =
    root.join("examples/bitcoin/circom/bitcoin_benchmark_cpp/bitcoin_benchmark");

let r1cs = load_r1cs(&circuit_file); // loads R1CS file into memory

Circom supports witness generation using both C++ and WASM, so you can choose which one to use by passing witness_generator_file either as the generated C++ binary or as the WASM output of Circom (the circuit.wasm file). If you use WASM, we assume you have a compatible version of node installed on your system.

Then, create the public parameters (CRS) using the create_public_params function:

let pp = create_public_params(r1cs.clone());

Now, construct the input to Circom witness generator at each step of recursion. This is a HashMap representation of the JSON input to your Circom input. For instance, in the case of the bitcoin example, private_inputs is a list of HashMaps, each containing block headers and block hashes for the blocks that step of recursion verifies, and the public input step_in is the previous block hash in the chain.

To instantiate this recursion, we use create_recursive_circuit from Nova Scotia:

let recursive_snark = create_recursive_circuit(
    witness_generator_file,
    r1cs,
    private_inputs,
    start_public_input.clone(),
    &pp,
).unwrap();

Verification is done using the verify function defined by Nova, which additionally takes secondary inputs that Nova Scotia will initialise to vec![<G2 as Group>::Scalar::zero()], so just pass that in:

println!("Verifying a RecursiveSNARK...");
let start = Instant::now();
let res = recursive_snark.verify(
    &pp,
    iteration_count,
    start_public_input.clone(),
    vec![<G2 as Group>::Scalar::zero()],
);
println!(
    "RecursiveSNARK::verify: {:?}, took {:?}",
    res,
    start.elapsed()
);
let verifier_time = start.elapsed();
assert!(res.is_ok());

For proper examples and more details, see the toy.rs and the bitcoin.rs examples documented below:

toy.rs is a very simple toy step circuit meant for testing purposes. It is helpful to start by looking at its Circom code and the Rust code that instantiates it in Nova. It is a simple variant of fibonacci that additionally takes a private input to add at each step.

bitcoin.rs is a more complex example that uses Nova to create a prover for bitcoin chain proof-of-work. For nearly the cost of just one block proof-of-work verification, Nova can compress the verification of the entire bitcoin chain. The Circom circuit is more complex for this construction (since it runs hashing and other bit-twiddling to verify each block in ~150k constraints). This is also helpful to look at for benchmarking purposes, since you can play around with the number of blocks verified in each step of recursion. Here are some simple benchmarks for different configurations of recursion for 120 blocks being proven and verified:

Number of recursion steps Blocks verified per step Prover time Verifier time (uncompressed)
120 1 57.33s 197.20ms
60 2 46.11s 307.08ms
40 3 43.60s 449.02ms
30 4 41.17s 560.53ms
24 5 39.73s 728.09ms

Note that the verification times are linear in the number of blocks per step of recursion, while the proving time reduces with fewer recursive steps. In practice, you would use the output of Nova as an input to another SNARK scheme like Plonk/groth16 (as previously mentioned) to obtain full succinctness.

Additionally, these are numbers on my (not great) laptop, so you should expect better performance on a beefier machine, especially because Nova supports GPU accelerated MSMs for proving under the hood.

In-browser proving and verification

Nova Scotia also supports proving and verification of proofs in browser, along with serde of proofs and public parameters. We provide an example of in-browser proving using Rust compiled to WASM in the browser-test folder of the repository. The test-client in the folder is a Create React App demonstrating in-browser proving and verification. If you are interested in similar usage, please look through the folders to understand how they work. It may also be useful to look at the halo2 guide to WASM compiling.

image

Notes for interested contributors

TODO list

  • Switch Nova to BN254/grumpkin cycle to make it work on Ethereum chain! This should be doable since Nova only needs DLOG hardness.
  • Write Relaxed R1CS verifiers in plonk/groth16 libraries (ex. Halo 2, Circom).
  • Make Nova work with secp/secq cycle for efficient ECDSA signature verification + aggregation

Seperately, since Nova's StepCircuit trait is pretty much the same as Bellperson's Circuit trait, we can probably also use the transpilation in this repo to use Bellperson with Circom circuits/proofs, along with its snarkpack aggregation features.

If you are interested in any of these tasks and want to work on them, please reach out! 0xPARC's PARC Squad may also be able to provide financial and technical support for related work.

Credits

Credits to the original Nova implementation and paper by Srinath Setty/Microsoft Research, and the Circom language from the iden3 team.

The parsing and generation strongly borrows from other similar repos like plonkit, ark-circom, zkutil etc.

I have never been to Nova Scotia. This repo is named Nova Scotia because crypto already has Tornado Cash Nova and Arbitrum Nova besides Microsoft Nova, so its time we start adding suffixes to the term to maximize confusion around it.

The art at the top of the page is by Tadashi Moriyama, all credits to him. I'm just a fan of it. :)

Footnotes

  1. But currently, Nova/R1CS lacks the customizability of STARKS (custom gates and lookup tables in particular), so there is a tradeoff here.

About

Middleware to compile Circom circuits to Nova prover

https://crates.io/crates/nova-scotia

License:MIT License


Languages

Language:JavaScript 64.1%Language:Rust 29.8%Language:TypeScript 4.1%Language:HTML 1.3%Language:CSS 0.7%