Kothapalli, Abhiram, Srinath Setty, and Ioanna Tzialla. “Nova: Recursive zero-knowledge arguments from folding schemes.” Annual International Cryptology Conference. Cham: Springer Nature Switzerland, 2022.
Nova: Paper Code
type E1 = Bn256EngineKZG;
type E2 = GrumpkinEngine;
type EE1 = nova_snark::provider::mlkzg::EvaluationEngine<E1>;
type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine<E2>;
type S1 = nova_snark::spartan::snark::RelaxedR1CSSNARK<E1, EE1>; // non-preprocessing SNARK
type S2 = nova_snark::spartan::snark::RelaxedR1CSSNARK<E2, EE2>; // non-preprocessing SNARK
struct MinRootIteration<G: Group> {
x_i: G::Scalar,
y_i: G::Scalar,
x_i_plus_1: G::Scalar,
y_i_plus_1: G::Scalar,
}
这段代码定义了一个名为 MinRootIteration 的结构体,它是用于表示最小根迭代的数据结构。这个结构体需要一个泛型参数 G,这个参数必须实现了 Group trait。
在这个结构体中,我们定义了四个字段,它们都是 G::Scalar 类型。这个类型是由 Group trait 定义的关联类型,通常用于表示一个群的标量值。
具体来说,这四个字段分别是:
impl<G: Group> MinRootIteration<G> {
// produces a sample non-deterministic advice, executing one invocation of MinRoot per step
fn new(num_iters: usize, x_0: &G::Scalar, y_0: &G::Scalar) -> (Vec<G::Scalar>, Vec<Self>) {
// exp = (p - 3 / 5), where p is the order of the group
// x^{exp} mod p provides the fifth root of x
let exp = {
let p = G::group_params().2.to_biguint().unwrap();
let two = BigUint::parse_bytes(b"2", 10).unwrap();
let three = BigUint::parse_bytes(b"3", 10).unwrap();
let five = BigUint::parse_bytes(b"5", 10).unwrap();
let five_inv = five.modpow(&(&p - &two), &p);
(&five_inv * (&p - &three)) % &p
};
为什么这段代码得到 exp = (p - 3) / 5 mod p?
let mut res = Vec::new();
let mut x_i = *x_0;
let mut y_i = *y_0;
for _i in 0..num_iters {
let x_i_plus_1 = (x_i + y_i).pow_vartime(&exp.to_u64_digits()); // computes the fifth root of x_i + y_i
// sanity check
if cfg!(debug_assertions) {
let sq = x_i_plus_1 * x_i_plus_1;
let quad = sq * sq;
let fifth = quad * x_i_plus_1;
assert_eq!(fifth, x_i + y_i);
}
let y_i_plus_1 = x_i;
res.push(Self {
x_i,
y_i,
x_i_plus_1,
y_i_plus_1,
});
x_i = x_i_plus_1;
y_i = y_i_plus_1;
}
let z0 = vec![*x_0, *y_0];
(z0, res)
}
let mut res = Vec::new();
let mut x_i = *x_0;
let mut y_i = *y_0;
for _i in 0..num_iters {
let x_i_plus_1 = (x_i + y_i).pow_vartime(&exp.to_u64_digits());
if cfg!(debug_assertions) { ... }
let y_i_plus_1 = x_i;
res.push(Self { ... });
x_i = x_i_plus_1;
y_i = y_i_plus_1;
let z0 = vec![*x_0, *y_0];(z0, res);
#[derive(Clone, Debug)]
struct MinRootCircuit<G: Group> {
seq: Vec<MinRootIteration<G>>,
}
impl<G: Group> StepCircuit<G::Scalar> for MinRootCircuit<G> {
fn arity(&self) -> usize {
2
}
fn synthesize<CS: ConstraintSystem<G::Scalar>>(
&self,
cs: &mut CS,
z: &[AllocatedNum<G::Scalar>],
) -> Result<Vec<AllocatedNum<G::Scalar>>, SynthesisError> {
let mut z_out: Result<Vec<AllocatedNum<G::Scalar>>, SynthesisError> =
Err(SynthesisError::AssignmentMissing);
// use the provided inputs
let x_0 = z[0].clone();
let y_0 = z[1].clone();
// variables to hold running x_i and y_i
let mut x_i = x_0;
let mut y_i = y_0;
for i in 0..self.seq.len() {
// non deterministic advice
let x_i_plus_1 =
AllocatedNum::alloc(cs.namespace(|| format!("x_i_plus_1_iter_{i}")), || {
Ok(self.seq[i].x_i_plus_1)
})?;
// check the following conditions hold:
// (i) x_i_plus_1 = (x_i + y_i)^{1/5}, which can be more easily checked with x_i_plus_1^5 = x_i + y_i
// (ii) y_i_plus_1 = x_i
// (1) constraints for condition (i) are below
// (2) constraints for condition (ii) is avoided because we just used x_i wherever y_i_plus_1 is used
let x_i_plus_1_sq = x_i_plus_1.square(cs.namespace(|| format!("x_i_plus_1_sq_iter_{i}")))?;
let x_i_plus_1_quad =
x_i_plus_1_sq.square(cs.namespace(|| format!("x_i_plus_1_quad_{i}")))?;
cs.enforce(
|| format!("x_i_plus_1_quad * x_i_plus_1 = x_i + y_i_iter_{i}"),
|lc| lc + x_i_plus_1_quad.get_variable(),
|lc| lc + x_i_plus_1.get_variable(),
|lc| lc + x_i.get_variable() + y_i.get_variable(),
);
if i == self.seq.len() - 1 {
z_out = Ok(vec![x_i_plus_1.clone(), x_i.clone()]);
}
// update x_i and y_i for the next iteration
y_i = x_i;
x_i = x_i_plus_1;
}
z_out
}
}
fn main() {
println!("Nova-based VDF with MinRoot delay function");
println!("=========================================================");
let num_steps = 10;
for num_iters_per_step in [1024, 2048, 4096, 8192, 16384, 32768, 65536] {
// number of iterations of MinRoot per Nova's recursive step
...
}
let circuit_primary = MinRootCircuit {
seq: vec![
MinRootIteration {
x_i: <E1 as Engine>::Scalar::zero(),
y_i: <E1 as Engine>::Scalar::zero(),
x_i_plus_1: <E1 as Engine>::Scalar::zero(),
y_i_plus_1: <E1 as Engine>::Scalar::zero(),
};
num_iters_per_step
],
};
let circuit_secondary = TrivialCircuit::default();
println!("Proving {num_iters_per_step} iterations of MinRoot per step");
// produce public parameters
let start = Instant::now();
println!("Producing public parameters...");
let pp = PublicParams::<
E1,
E2,
MinRootCircuit<<E1 as Engine>::GE>,
TrivialCircuit<<E2 as Engine>::Scalar>,
>::setup(
&circuit_primary,
&circuit_secondary,
&*S1::ck_floor(),
&*S2::ck_floor(),
);
println!("PublicParams::setup, took {:?} ", start.elapsed());
// produce non-deterministic advice
let (z0_primary, minroot_iterations) = MinRootIteration::<<E1 as Engine>::GE>::new(
num_iters_per_step * num_steps,
&<E1 as Engine>::Scalar::zero(),
&<E1 as Engine>::Scalar::one(),
);
let minroot_circuits = (0..num_steps)
.map(|i| MinRootCircuit {
seq: (0..num_iters_per_step)
.map(|j| MinRootIteration {
x_i: minroot_iterations[i * num_iters_per_step + j].x_i,
y_i: minroot_iterations[i * num_iters_per_step + j].y_i,
x_i_plus_1: minroot_iterations[i * num_iters_per_step + j].x_i_plus_1,
y_i_plus_1: minroot_iterations[i * num_iters_per_step + j].y_i_plus_1,
})
.collect::<Vec<_>>(),
})
.collect::<Vec<_>>();
let z0_secondary = vec![<E2 as Engine>::Scalar::zero()];
type C1 = MinRootCircuit<<E1 as Engine>::GE>;
type C2 = TrivialCircuit<<E2 as Engine>::Scalar>;
// produce a recursive SNARK
println!("Generating a RecursiveSNARK...");
let mut recursive_snark: RecursiveSNARK<E1, E2, C1, C2> =
RecursiveSNARK::<E1, E2, C1, C2>::new(
&pp,
&minroot_circuits[0],
&circuit_secondary,
&z0_primary,
&z0_secondary,
)
.unwrap();
for (i, circuit_primary) in minroot_circuits.iter().enumerate() {
let start = Instant::now();
let res = recursive_snark.prove_step(&pp, circuit_primary, &circuit_secondary);
assert!(res.is_ok());
println!(
"RecursiveSNARK::prove_step {}: {:?}, took {:?} ",
i,
res.is_ok(),
start.elapsed()
);
}
// verify the recursive SNARK
println!("Verifying a RecursiveSNARK...");
let start = Instant::now();
let res = recursive_snark.verify(&pp, num_steps, &z0_primary, &z0_secondary);
println!(
"RecursiveSNARK::verify: {:?}, took {:?}",
res.is_ok(),
start.elapsed()
);
assert!(res.is_ok());
- 验证 RecursiveSNARK
// produce a compressed SNARK
println!("Generating a CompressedSNARK using Spartan with multilinear KZG...");
let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp).unwrap();
let start = Instant::now();
let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark);
println!(
"CompressedSNARK::prove: {:?}, took {:?}",
res.is_ok(),
start.elapsed()
);
assert!(res.is_ok());
let compressed_snark = res.unwrap();
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
bincode::serialize_into(&mut encoder, &compressed_snark).unwrap();
let compressed_snark_encoded = encoder.finish().unwrap();
println!(
"CompressedSNARK::len {:?} bytes",
compressed_snark_encoded.len()
);
// verify the compressed SNARK
println!("Verifying a CompressedSNARK...");
let start = Instant::now();
let res = compressed_snark.verify(&vk, num_steps, &z0_primary, &z0_secondary);
println!(
"CompressedSNARK::verify: {:?}, took {:?}",
res.is_ok(),
start.elapsed()
);
assert!(res.is_ok());