Skip to content

Commit

Permalink
Chore/update upstream (#18)
Browse files Browse the repository at this point in the history
Updates our main to be in sync with upstream. Branch was protected so
creating PR.

I notice there are a lot of changes in upstream and some seem relevant
for our purposes.

After this is merged, we can update the hypernova branch.

---------

Co-authored-by: François Garillot <4142+huitseeker@users.noreply.github.com>
Co-authored-by: Chiro Hiro <chiro8x@gmail.com>
Co-authored-by: François Garillot <francois@garillot.net>
Co-authored-by: Srinath Setty <srinath@microsoft.com>
Co-authored-by: Leo Alt <leo@ethereum.org>
Co-authored-by: JunheeLee <101318348+Jun-Hee-Lee@users.noreply.github.com>
Co-authored-by: 3for <zouyudi@gmail.com>
  • Loading branch information
8 people authored Jul 10, 2023
1 parent 7193483 commit 271f554
Show file tree
Hide file tree
Showing 30 changed files with 2,275 additions and 1,418 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ jobs:
run: cargo build --target wasm32-unknown-unknown
- name: Build examples
run: cargo build --examples --verbose
- name: Build benches
run: cargo build --benches --verbose
- name: Run tests
run: cargo +stable test --release --verbose
- name: Check Rustfmt Code Style
Expand Down
21 changes: 19 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nova-snark"
version = "0.21.0"
version = "0.22.0"
authors = ["Srinath Setty <srinath@microsoft.com>"]
edition = "2021"
description = "Recursive zkSNARKs without trusted setup"
Expand Down Expand Up @@ -32,14 +32,22 @@ flate2 = "1.0"
bitvec = "1.0"
byteorder = "1.4.3"
thiserror = "1.0"
halo2curves = { version="0.1.0", features = [ "derive_serde" ] }

[target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies]
pasta-msm = { version = "0.1.4" }

[target.wasm32-unknown-unknown.dependencies]
# see https://github.com/rust-random/rand/pull/948
getrandom = { version = "0.2.0", default-features = false, features = ["js"]}

[dev-dependencies]
criterion = "0.3.1"
criterion = { version = "0.4", features = ["html_reports"] }
rand = "0.8.4"
hex = "0.4.3"
pprof = { version = "0.11" }
cfg-if = "1.0.0"
sha2 = "0.10.7"

[[bench]]
name = "recursive-snark"
Expand All @@ -49,9 +57,18 @@ harness = false
name = "compressed-snark"
harness = false

[[bench]]
name = "compute-digest"
harness = false

[[bench]]
name = "sha256"
harness = false

[features]
default = []
# Compiles in portable mode, w/o ISA extensions => binary can be executed on all systems.
portable = ["pasta-msm/portable"]
cuda = ["neptune/cuda", "neptune/pasta", "neptune/arity24"]
opencl = ["neptune/opencl", "neptune/pasta", "neptune/arity24"]
flamegraph = ["pprof/flamegraph", "pprof/criterion"]
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@

Nova is a high-speed recursive SNARK (a SNARK is type cryptographic proof system that enables a prover to prove a mathematical statement to a verifier with a short proof and succinct verification, and a recursive SNARK enables producing proofs that prove statements about prior proofs).

Recursive SNARKs including Nova have a wide variety of applications such as Rollups, verifiable delay functions (VDFs), succinct blockchains, and incrementally verifiable versions of [verifiable state machines](https://eprint.iacr.org/2020/758.pdf). A distinctive aspect of Nova is that it is the simplest recursive proof system in the literature, yet it provides the fastest prover. Furthermore, it achieves the smallest verifier circuit (a key metric to minimize in this context): the circuit is constant-sized and its size is dominated by two group scalar multiplications. The details of Nova are described in our CRYPTO 2022 [paper](https://eprint.iacr.org/2021/370).
More precisely, Nova achieves [incrementally verifiable computation (IVC)](https://iacr.org/archive/tcc2008/49480001/49480001.pdf), a powerful cryptographic primitive that allows a prover to produce a proof of correct execution of a "long running" sequential computations in an incremental fashion. For example, IVC enables the following: The prover takes as input a proof $\pi_i$ proving the the first $i$ steps of its computation and then update it to produce a proof $\pi_{i+1}$ proving the correct execution of the first $i + 1$ steps. Crucially, the prover's work to update the proof does not depend on the number of steps executed thus far, and the verifier's work to verify a proof does not grow with the number of steps in the computation. IVC schemes including Nova have a wide variety of applications such as Rollups, verifiable delay functions (VDFs), succinct blockchains, incrementally verifiable versions of [verifiable state machines](https://eprint.iacr.org/2020/758.pdf), and, more generally, proofs of (virtual) machine executions (e.g., EVM, RISC-V).

This repository provides `nova-snark,` a Rust library implementation of Nova.
A distinctive aspect of Nova is that it is the simplest recursive proof system in the literature, yet it provides the fastest prover. Furthermore, it achieves the smallest verifier circuit (a key metric to minimize in this context): the circuit is constant-sized and its size is about 10,000 multiplication gates. Nova is constructed from a simple primitive called a *folding scheme*, a cryptographic primitive that reduces the task of checking two NP statements into the task of checking a single NP statement.

## Tests and examples
This repository provides `nova-snark,` a Rust library implementation of Nova on a cycle of elliptic curves. The code currently supports Pallas/Vesta (i.e., Pasta curves) and BN254/Grumpkin elliptic curve cycles. One can use Nova with other elliptic curve cycles (e.g., secp/secq) by providing an implementation of Nova's traits for those curves (e.g., see `src/provider/mod.rs`).

We also implement a SNARK, based on [Spartan](https://eprint.iacr.org/2019/550.pdf), to compress IVC proofs produced by Nova.

To run tests (we recommend the release mode to drastically shorten run times):
```text
Expand All @@ -17,10 +22,18 @@ cargo run --release --example minroot
```

## References
The following paper, which appeared at CRYPTO 2022, provides details of the Nova proof system and a proof of security:

[Nova: Recursive Zero-Knowledge Arguments from Folding Schemes](https://eprint.iacr.org/2021/370) \
Abhiram Kothapalli, Srinath Setty, and Ioanna Tzialla \
CRYPTO 2022

For efficiency, our implementation of the Nova proof system is instantiated over a cycle of elliptic curves. The following paper specifies that instantiation and provides a proof of security:

[Revisiting the Nova Proof System on a Cycle of Curves](https://eprint.iacr.org/2023/969) \
Wilson Nguyen, Dan Boneh, and Srinath Setty \
IACR ePrint 2023/969

## Acknowledgments
See the contributors list [here](https://github.com/microsoft/Nova/graphs/contributors)

Expand Down
60 changes: 37 additions & 23 deletions benches/compressed-snark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,29 @@ type G1 = pasta_curves::pallas::Point;
type G2 = pasta_curves::vesta::Point;
type EE1 = nova_snark::provider::ipa_pc::EvaluationEngine<G1>;
type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine<G2>;
type S1 = nova_snark::spartan::RelaxedR1CSSNARK<G1, EE1>;
type S2 = nova_snark::spartan::RelaxedR1CSSNARK<G2, EE2>;
type S1 = nova_snark::spartan::snark::RelaxedR1CSSNARK<G1, EE1>;
type S2 = nova_snark::spartan::snark::RelaxedR1CSSNARK<G2, EE2>;
type C1 = NonTrivialTestCircuit<<G1 as Group>::Scalar>;
type C2 = TrivialTestCircuit<<G2 as Group>::Scalar>;

criterion_group! {
name = compressed_snark;
config = Criterion::default().warm_up_time(Duration::from_millis(3000));
targets = bench_compressed_snark
// To run these benchmarks, first download `criterion` with `cargo install cargo install cargo-criterion`.
// Then `cargo criterion --bench compressed-snark`. The results are located in `target/criterion/data/<name-of-benchmark>`.
// For flamegraphs, run `cargo criterion --bench compressed-snark --features flamegraph -- --profile-time <secs>`.
// The results are located in `target/criterion/profile/<name-of-benchmark>`.
cfg_if::cfg_if! {
if #[cfg(feature = "flamegraph")] {
criterion_group! {
name = compressed_snark;
config = Criterion::default().warm_up_time(Duration::from_millis(3000)).with_profiler(pprof::criterion::PProfProfiler::new(100, pprof::criterion::Output::Flamegraph(None)));
targets = bench_compressed_snark
}
} else {
criterion_group! {
name = compressed_snark;
config = Criterion::default().warm_up_time(Duration::from_millis(3000));
targets = bench_compressed_snark
}
}
}

criterion_main!(compressed_snark);
Expand All @@ -43,46 +57,46 @@ fn bench_compressed_snark(c: &mut Criterion) {
let mut group = c.benchmark_group(format!("CompressedSNARK-StepCircuitSize-{num_cons}"));
group.sample_size(num_samples);

let c_primary = NonTrivialTestCircuit::new(num_cons);
let c_secondary = TrivialTestCircuit::default();

// Produce public parameters
let pp = PublicParams::<G1, G2, C1, C2>::setup(
NonTrivialTestCircuit::new(num_cons),
TrivialTestCircuit::default(),
);
let pp = PublicParams::<G1, G2, C1, C2>::setup(c_primary.clone(), c_secondary.clone());

// Produce prover and verifier keys for CompressedSNARK
let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp).unwrap();

// produce a recursive SNARK
let num_steps = 3;
let mut recursive_snark: Option<RecursiveSNARK<G1, G2, C1, C2>> = None;
let mut recursive_snark: RecursiveSNARK<G1, G2, C1, C2> = RecursiveSNARK::new(
&pp,
&c_primary,
&c_secondary,
vec![<G1 as Group>::Scalar::from(2u64)],
vec![<G2 as Group>::Scalar::from(2u64)],
);

for i in 0..num_steps {
let res = RecursiveSNARK::prove_step(
let res = recursive_snark.prove_step(
&pp,
recursive_snark,
NonTrivialTestCircuit::new(num_cons),
TrivialTestCircuit::default(),
&c_primary,
&c_secondary,
vec![<G1 as Group>::Scalar::from(2u64)],
vec![<G2 as Group>::Scalar::from(2u64)],
);
assert!(res.is_ok());
let recursive_snark_unwrapped = res.unwrap();

// verify the recursive snark at each step of recursion
let res = recursive_snark_unwrapped.verify(
let res = recursive_snark.verify(
&pp,
i + 1,
vec![<G1 as Group>::Scalar::from(2u64)],
vec![<G2 as Group>::Scalar::from(2u64)],
&[<G1 as Group>::Scalar::from(2u64)],
&[<G2 as Group>::Scalar::from(2u64)],
);
assert!(res.is_ok());

// set the running variable for the next iteration
recursive_snark = Some(recursive_snark_unwrapped);
}

// Bench time to produce a compressed SNARK
let recursive_snark = recursive_snark.unwrap();
group.bench_function("Prove", |b| {
b.iter(|| {
assert!(CompressedSNARK::<_, _, _, _, S1, S2>::prove(
Expand Down
84 changes: 84 additions & 0 deletions benches/compute-digest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use std::{marker::PhantomData, time::Duration};

use bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use ff::PrimeField;
use nova_snark::{
traits::{
circuit::{StepCircuit, TrivialTestCircuit},
Group,
},
PublicParams,
};

type G1 = pasta_curves::pallas::Point;
type G2 = pasta_curves::vesta::Point;
type C1 = NonTrivialTestCircuit<<G1 as Group>::Scalar>;
type C2 = TrivialTestCircuit<<G2 as Group>::Scalar>;

criterion_group! {
name = compute_digest;
config = Criterion::default().warm_up_time(Duration::from_millis(3000)).sample_size(10);
targets = bench_compute_digest
}

criterion_main!(compute_digest);

fn bench_compute_digest(c: &mut Criterion) {
c.bench_function("compute_digest", |b| {
b.iter(|| {
PublicParams::<G1, G2, C1, C2>::setup(black_box(C1::new(10)), black_box(C2::default()))
})
});
}

#[derive(Clone, Debug, Default)]
struct NonTrivialTestCircuit<F: PrimeField> {
num_cons: usize,
_p: PhantomData<F>,
}

impl<F> NonTrivialTestCircuit<F>
where
F: PrimeField,
{
pub fn new(num_cons: usize) -> Self {
Self {
num_cons,
_p: Default::default(),
}
}
}
impl<F> StepCircuit<F> for NonTrivialTestCircuit<F>
where
F: PrimeField,
{
fn arity(&self) -> usize {
1
}

fn synthesize<CS: ConstraintSystem<F>>(
&self,
cs: &mut CS,
z: &[AllocatedNum<F>],
) -> Result<Vec<AllocatedNum<F>>, SynthesisError> {
// Consider a an equation: `x^2 = y`, where `x` and `y` are respectively the input and output.
let mut x = z[0].clone();
let mut y = x.clone();
for i in 0..self.num_cons {
y = x.square(cs.namespace(|| format!("x_sq_{i}")))?;
x = y.clone();
}
Ok(vec![y])
}

fn output(&self, z: &[F]) -> Vec<F> {
let mut x = z[0];
let mut y = x;
for _i in 0..self.num_cons {
y = x * x;
x = y;
}
vec![y]
}
}
Loading

0 comments on commit 271f554

Please sign in to comment.