use core::marker::PhantomData;
use ff::{Field, PrimeField, PrimeFieldBits};
use flate2::{write::ZlibEncoder, Compression};
use nova_snark::{
frontend::{
num::AllocatedNum, AllocatedBit, ConstraintSystem, LinearCombination, SynthesisError,
},
nova::{CompressedSNARK, PublicParams, RecursiveSNARK},
provider::{Bn256EngineKZG, GrumpkinEngine},
traits::{circuit::StepCircuit, snark::RelaxedR1CSSNARKTrait, Engine, Group},
};
use rand::Rng;
use std::time::Instant;
type E1 = Bn256EngineKZG;
type E2 = GrumpkinEngine;
type EE1 = nova_snark::provider::hyperkzg::EvaluationEngine<E1>;
type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine<E2>;
type S1 = nova_snark::spartan::snark::RelaxedR1CSSNARK<E1, EE1>; type S2 = nova_snark::spartan::snark::RelaxedR1CSSNARK<E2, EE2>;
#[derive(Clone, Debug)]
struct AndInstance<G: Group> {
a: u64,
b: u64,
_p: PhantomData<G>,
}
impl<G: Group> AndInstance<G> {
fn new() -> Self {
let mut rng = rand::thread_rng();
let a: u64 = rng.gen();
let b: u64 = rng.gen();
Self {
a,
b,
_p: PhantomData,
}
}
}
#[derive(Clone, Debug)]
struct AndCircuit<G: Group> {
batch: Vec<AndInstance<G>>,
}
impl<G: Group> AndCircuit<G> {
fn new(num_ops_per_step: usize) -> Self {
let mut batch = Vec::new();
for _ in 0..num_ops_per_step {
batch.push(AndInstance::new());
}
Self { batch }
}
}
pub fn u64_into_bit_vec_le<Scalar: PrimeField, CS: ConstraintSystem<Scalar>>(
mut cs: CS,
value: Option<u64>,
) -> Result<Vec<AllocatedBit>, SynthesisError> {
let values = match value {
Some(ref value) => {
let mut tmp = Vec::with_capacity(64);
for i in 0..64 {
tmp.push(Some((*value >> i) & 1 == 1));
}
tmp
}
None => vec![None; 64],
};
let bits = values
.into_iter()
.enumerate()
.map(|(i, b)| AllocatedBit::alloc(cs.namespace(|| format!("bit {i}")), b))
.collect::<Result<Vec<_>, SynthesisError>>()?;
Ok(bits)
}
pub fn le_bits_to_num<Scalar, CS>(
mut cs: CS,
bits: &[AllocatedBit],
) -> Result<AllocatedNum<Scalar>, SynthesisError>
where
Scalar: PrimeField + PrimeFieldBits,
CS: ConstraintSystem<Scalar>,
{
let mut lc = LinearCombination::zero();
let mut coeff = Scalar::ONE;
let mut fe = Some(Scalar::ZERO);
for bit in bits.iter() {
lc = lc + (coeff, bit.get_variable());
fe = bit.get_value().map(|val| {
if val {
fe.unwrap() + coeff
} else {
fe.unwrap()
}
});
coeff = coeff.double();
}
let num = AllocatedNum::alloc(cs.namespace(|| "Field element"), || {
fe.ok_or(SynthesisError::AssignmentMissing)
})?;
lc = lc - num.get_variable();
cs.enforce(|| "compute number from bits", |lc| lc, |lc| lc, |_| lc);
Ok(num)
}
impl<G: Group> StepCircuit<G::Scalar> for AndCircuit<G> {
fn arity(&self) -> usize {
1
}
fn synthesize<CS: ConstraintSystem<G::Scalar>>(
&self,
cs: &mut CS,
z_in: &[AllocatedNum<G::Scalar>],
) -> Result<Vec<AllocatedNum<G::Scalar>>, SynthesisError> {
for i in 0..self.batch.len() {
let a = AllocatedNum::alloc(cs.namespace(|| format!("a_{i}")), || {
Ok(G::Scalar::from(self.batch[i].a))
})?;
let b = AllocatedNum::alloc(cs.namespace(|| format!("b_{i}")), || {
Ok(G::Scalar::from(self.batch[i].b))
})?;
let a_bits = u64_into_bit_vec_le(
cs.namespace(|| format!("a_bits_{i}")),
Some(self.batch[i].a),
)?; let b_bits = u64_into_bit_vec_le(
cs.namespace(|| format!("b_bits_{i}")),
Some(self.batch[i].b),
)?;
let a_from_bits = le_bits_to_num(cs.namespace(|| format!("a_{i}")), &a_bits)?;
let b_from_bits = le_bits_to_num(cs.namespace(|| format!("b_{i}")), &b_bits)?;
cs.enforce(
|| format!("a_{i} == a_from_bits"),
|lc| lc + a.get_variable(),
|lc| lc + CS::one(),
|lc| lc + a_from_bits.get_variable(),
);
cs.enforce(
|| format!("b_{i} == b_from_bits"),
|lc| lc + b.get_variable(),
|lc| lc + CS::one(),
|lc| lc + b_from_bits.get_variable(),
);
let mut c_bits = Vec::new();
for bit_idx in 0..64 {
let c_bit = AllocatedBit::and(
cs.namespace(|| format!("and_bit_{}_{}", i, bit_idx)),
&a_bits[bit_idx],
&b_bits[bit_idx],
)?;
c_bits.push(c_bit);
}
let c_from_bits = le_bits_to_num(cs.namespace(|| format!("c_{i}")), &c_bits)?;
let c = AllocatedNum::alloc(cs.namespace(|| format!("c_{i}")), || {
Ok(G::Scalar::from(self.batch[i].a & self.batch[i].b))
})?;
cs.enforce(
|| format!("c_{i} == c_from_bits"),
|lc| lc + c.get_variable(),
|lc| lc + CS::one(),
|lc| lc + c_from_bits.get_variable(),
);
}
Ok(z_in.to_vec())
}
}
fn main() {
println!("=========================================================");
println!("Nova-based 64-bit bitwise AND example");
println!("=========================================================");
let num_steps = 32;
for num_ops_per_step in [1024, 2048, 4096, 8192, 16384, 32768, 65536] {
let circuit = AndCircuit::new(num_ops_per_step);
println!(
"Proving {} AND ops ({num_ops_per_step} instances per step and {num_steps} steps)",
num_ops_per_step * num_steps
);
let start = Instant::now();
println!("Producing public parameters...");
let pp = PublicParams::<E1, E2, AndCircuit<<E1 as Engine>::GE>>::setup(
&circuit,
&*S1::ck_floor(),
&*S2::ck_floor(),
)
.unwrap();
println!("PublicParams::setup, took {:?} ", start.elapsed());
println!(
"Number of constraints per step (primary circuit): {}",
pp.num_constraints().0
);
println!(
"Number of constraints per step (secondary circuit): {}",
pp.num_constraints().1
);
println!(
"Number of variables per step (primary circuit): {}",
pp.num_variables().0
);
println!(
"Number of variables per step (secondary circuit): {}",
pp.num_variables().1
);
let circuits = (0..num_steps)
.map(|_| AndCircuit::new(num_ops_per_step))
.collect::<Vec<_>>();
type C = AndCircuit<<E1 as Engine>::GE>;
println!("Generating a RecursiveSNARK...");
let mut recursive_snark: RecursiveSNARK<E1, E2, C> =
RecursiveSNARK::<E1, E2, C>::new(&pp, &circuits[0], &[<E1 as Engine>::Scalar::zero()])
.unwrap();
let start = Instant::now();
for circuit in circuits.iter() {
let res = recursive_snark.prove_step(&pp, circuit);
assert!(res.is_ok());
}
println!(
"RecursiveSNARK::prove {} AND ops: took {:?} ",
num_ops_per_step * num_steps,
start.elapsed()
);
println!("Verifying a RecursiveSNARK...");
let res = recursive_snark.verify(&pp, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
println!("RecursiveSNARK::verify: {:?}", res.is_ok(),);
assert!(res.is_ok());
println!("Generating a CompressedSNARK using Spartan with HyperKZG...");
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::serde::encode_into_std_write(
&compressed_snark,
&mut encoder,
bincode::config::legacy(),
)
.expect("Failed to serialize compressed SNARK");
let compressed_snark_encoded = encoder.finish().unwrap();
println!(
"CompressedSNARK::len {:?} bytes",
compressed_snark_encoded.len()
);
println!("Verifying a CompressedSNARK...");
let start = Instant::now();
let res = compressed_snark.verify(&vk, num_steps, &[<E1 as Engine>::Scalar::ZERO]);
println!(
"CompressedSNARK::verify: {:?}, took {:?}",
res.is_ok(),
start.elapsed()
);
assert!(res.is_ok());
println!("=========================================================");
}
}