diff --git a/Cargo.toml b/Cargo.toml index addc7c21..72ce0741 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,9 @@ license-file = "LICENSE" keywords = ["zkSNARKs", "cryptography", "proofs"] [dependencies] -bellperson = { version = "0.24", default-features = false } +#bellperson = { version = "0.24", default-features = false } +#bellperson = { git = "https://github.com/vesselinux/bellperson", rev = "fe3d19a", default-features = false} +bellperson = { git = "https://github.com/vesselinux/bellperson", branch = "sha256modded", default-features = false} ff = { version = "0.12.0", features = ["derive"] } digest = "0.8.1" sha3 = "0.8.2" @@ -59,3 +61,7 @@ harness = false default = [] cuda = ["neptune/cuda", "neptune/pasta", "neptune/arity24"] opencl = ["neptune/opencl", "neptune/pasta", "neptune/arity24"] + +[patch.crates-io] +#bellperson = { git = "https://github.com/vesselinux/bellperson", rev = "fe3d19a", default-features = false} +bellperson = { git = "https://github.com/vesselinux/bellperson", branch = "sha256modded", default-features = false} diff --git a/benches/sha256.rs b/benches/sha256.rs index 73b612f1..26edad86 100644 --- a/benches/sha256.rs +++ b/benches/sha256.rs @@ -9,112 +9,219 @@ use ::bellperson::{ gadgets::{ boolean::{AllocatedBit, Boolean}, num::{AllocatedNum, Num}, - sha256::sha256, + //sha256::sha256, + sha256::sha256iterated, Assignment, }, ConstraintSystem, SynthesisError, }; use ff::{PrimeField, PrimeFieldBits}; use hex_literal::hex; +use flate2::{write::ZlibEncoder, Compression}; use nova_snark::{ traits::{ circuit::{StepCircuit, TrivialTestCircuit}, Group, }, - PublicParams, RecursiveSNARK, + CompressedSNARK, PublicParams, RecursiveSNARK, }; use sha2::{Digest, Sha256}; use criterion::*; use core::time::Duration; +use std::time::Instant; + +// Imports related to Poseidon hash function (unused for now) +/* +use nova_snark::traits::{ROCircuitTrait, ROConstantsTrait, ROTrait}; +use generic_array::typenum::U24; +use neptune::{ + circuit2::Elt, + poseidon::PoseidonConstants, + sponge::{ + api::{IOPattern, SpongeAPI, SpongeOp}, + circuit::SpongeCircuit, + vanilla::{Mode::Simplex, Sponge, SpongeTrait}, + }, + Strength, +}; +use nova_snark::provider::poseidon::PoseidonRO; +use nova_snark::provider::poseidon::PoseidonConstantsCircuit; + */ + +// let N = vec![13, 25, 50, 100]; + +// Number if iterations of the sha256 function implemented by the +// gadget +//const NITERATIONS: usize = 1; // 1KB +//const NITERATIONS: usize = 2; // 2KB +//const NITERATIONS: usize = 3; // 4KB +//const NITERATIONS: usize = 6; // 8KB +//const NITERATIONS: usize = 13; // 16KB +//const NITERATIONS: usize = 25; // 32KB +//const NITERATIONS: usize = 50; // 64KB +//const NITERATIONS: usize = 100; // 128KB +// Number of Nova steps (resp. foldings) over which we are producing +// the final Nova proof +//const NSTEPS: usize = 10; + +// 30KB experiments: SHA2 execution x Nova steps +// 2x120 +const NITERATIONS: usize = 2; +const NSTEPS: usize = 120; +// 3x80 +//const NITERATIONS: usize = 3; +//const NSTEPS: usize = 80; +// 4x60 +//const NITERATIONS: usize = 4; +//const NSTEPS: usize = 60; +// 6x40 +//const NITERATIONS: usize = 6; +//const NSTEPS: usize = 40; +// 8x30 +//const NITERATIONS: usize = 8; +//const NSTEPS: usize = 30; +// 10x24 +//const NITERATIONS: usize = 10; +//const NSTEPS: usize = 24; +// 12x20 +//const NITERATIONS: usize = 12; +//const NSTEPS: usize = 20; +// 15x16 +//const NITERATIONS: usize = 15; +//const NSTEPS: usize = 16; + +// 30KB experiments swaps +// 120x2 +//const NITERATIONS: usize = 120; +//const NSTEPS: usize = 2; +// 80x3 +//const NITERATIONS: usize = 80; +//const NSTEPS: usize = 3; +// 60x4 +//const NITERATIONS: usize = 60; +//const NSTEPS: usize = 4; +// 40x6 +//const NITERATIONS: usize = 40; +//const NSTEPS: usize = 6; +// 30x8 +//const NITERATIONS: usize = 30; +//const NSTEPS: usize = 8; +// 24x10 +//const NITERATIONS: usize = 24; +//const NSTEPS: usize = 10; +// 20x12 +//const NITERATIONS: usize = 20; +//const NSTEPS: usize = 12; +// 16x15 +//const NITERATIONS: usize = 16; +//const NSTEPS: usize = 15; + +// 32KB with N = 25 and k = 10 +//const NITERATIONS: usize = 10; +//const NSTEPS: usize = 25; + +/* +#[derive(Clone, Debug)] +struct Sha256CircuitOrig { + preimage: Vec, + digest: Scalar, +} +*/ #[derive(Clone, Debug)] struct Sha256Circuit { preimage: Vec, + // digest: Vec, digest: Scalar, } impl StepCircuit for Sha256Circuit { - fn arity(&self) -> usize { - 1 - } - - fn synthesize>( - &self, - cs: &mut CS, - _z: &[AllocatedNum], - ) -> Result>, SynthesisError> { - let mut z_out: Vec> = Vec::new(); - - let bit_values: Vec<_> = self - .preimage - .clone() - .into_iter() - .flat_map(|byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8)) - .map(Some) - .collect(); - assert_eq!(bit_values.len(), self.preimage.len() * 8); - - let preimage_bits = bit_values - .into_iter() - .enumerate() - .map(|(i, b)| AllocatedBit::alloc(cs.namespace(|| format!("preimage bit {i}")), b)) - .map(|b| b.map(Boolean::from)) - .collect::, _>>()?; - - let hash_bits = sha256(cs.namespace(|| "sha256"), &preimage_bits)?; - - for (i, hash_bits) in hash_bits.chunks(256_usize).enumerate() { - let mut num = Num::::zero(); - let mut coeff = Scalar::one(); - for bit in hash_bits { - num = num.add_bool_with_coeff(CS::one(), bit, coeff); - - coeff = coeff.double(); - } - - let hash = AllocatedNum::alloc(cs.namespace(|| format!("input {i}")), || { - Ok(*num.get_value().get()?) - })?; - - // num * 1 = hash - cs.enforce( - || format!("packing constraint {i}"), - |_| num.lc(Scalar::one()), - |lc| lc + CS::one(), - |lc| lc + hash.get_variable(), - ); - z_out.push(hash); + fn arity(&self) -> usize { + 1 } - // sanity check with the hasher - let mut hasher = Sha256::new(); - hasher.update(&self.preimage); - let hash_result = hasher.finalize(); - - let mut s = hash_result - .iter() - .flat_map(|&byte| (0..8).rev().map(move |i| (byte >> i) & 1u8 == 1u8)); - - for b in hash_bits { - match b { - Boolean::Is(b) => { - assert!(s.next().unwrap() == b.get_value().unwrap()); - } - Boolean::Not(b) => { - assert!(s.next().unwrap() != b.get_value().unwrap()); - } - Boolean::Constant(_b) => { - panic!("Can't reach here") - } - } - } + fn synthesize>( + &self, + cs: &mut CS, + _z: &[AllocatedNum], + ) -> Result>, SynthesisError> { + assert_eq!(self.preimage.len(), 64 * (NITERATIONS as usize)); + + let mut z_out: Vec> = Vec::new(); + + let bit_values: Vec<_> = + self.preimage.clone().into_iter().flat_map(|byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8)).map(Some).collect(); + assert_eq!(bit_values.len(), self.preimage.len() * 8); + + let preimage_bits = bit_values + .into_iter() + .enumerate() + .map(|(i, b)| AllocatedBit::alloc(cs.namespace(|| format!("preimage bit {i}")), b)) + .map(|b| b.map(Boolean::from)) + .collect::, _>>()?; + + let niterations: usize = NITERATIONS; + let hash_bits = sha256iterated(cs.namespace(|| "sha256"), &preimage_bits, niterations)?; + + for (i, hash_bits) in hash_bits.chunks(256_usize).enumerate() { + let mut num = Num::::zero(); + let mut coeff = Scalar::one(); + for bit in hash_bits { + num = num.add_bool_with_coeff(CS::one(), bit, coeff); - Ok(z_out) - } + coeff = coeff.double(); + } + + let hash = AllocatedNum::alloc(cs.namespace(|| format!("input {i}")), || { + Ok(*num.get_value().get()?) + })?; + + // num * 1 = hash + cs.enforce( + || format!("packing constraint {i}"), + |_| num.lc(Scalar::one()), + |lc| lc + CS::one(), + |lc| lc + hash.get_variable(), + ); + z_out.push(hash); + } + + // sanity check with the hasher Prepare a zero vector of 64 + // bytes. Note: we are not using &self.preimage member of the + // gadget as it represents a vector of zero 64-byte vectors + let preimage = vec![0u8; 64]; + + let mut hasher = Sha256::new(); + // hasher.update(&self.preimage); + hasher.update(preimage); + let hash_result = hasher.finalize(); + + let mut s = hash_result + .iter() + .flat_map(|&byte| (0..8).rev().map(move |i| (byte >> i) & 1u8 == 1u8)); + + for b in &hash_bits { + match b { + Boolean::Is(b) => { + assert!(s.next().unwrap() == b.get_value().unwrap()); + } + Boolean::Not(b) => { + assert!(s.next().unwrap() != b.get_value().unwrap()); + } + Boolean::Constant(_b) => { + panic!("Can't reach here") + } + } + } + + // println!("z_out length {:?}", z_out.len()); + Ok(z_out) + } - fn output(&self, _z: &[Scalar]) -> Vec { - vec![self.digest] - } + fn output(&self, _z: &[Scalar]) -> Vec { + vec![self.digest] + //self.digest.clone() + } } type C1 = Sha256Circuit<::Scalar>; @@ -128,110 +235,143 @@ targets = bench_recursive_snark criterion_main!(recursive_snark); -fn bench_recursive_snark(c: &mut Criterion) { - let bytes_to_scalar = |bytes: [u8; 32]| -> ::Scalar { - let mut bytes_le = bytes; - bytes_le.reverse(); - ::Scalar::from_repr(bytes_le).unwrap() - }; - - // Test vectors - let circuits = vec![ - /* - Sha256Circuit { - preimage: vec![0u8; 64], - digest: bytes_to_scalar(hex!( - "12df9ae4958c1957170f9b04c4bc00c27315c5d75a391f4b672f952842bfa5ac" - )), - }, - Sha256Circuit { - preimage: vec![0u8; 128], - digest: bytes_to_scalar(hex!( - "13abfac9782cb9c13c4508bde596f1914fe2f744f6a661c0c9a16659745c4e1b" - )), - }, - Sha256Circuit { - preimage: vec![0u8; 256], - digest: bytes_to_scalar(hex!( - "0f5a007b5aef126a58f9bbd937842967c44253e7f97d98b5cd10bfe44d6782c8" - )), - }, - Sha256Circuit { - preimage: vec![0u8; 512], - digest: bytes_to_scalar(hex!( - "06a6cfaad91d49366f18443cd4e11576ff27c174bb9fe2bc54735a79e3e456e0" - )), - }, - Sha256Circuit { - preimage: vec![0u8; 1024], - digest: bytes_to_scalar(hex!( - "3763c73508f5fbb36daae8257d6c5c07db08ec5df0549ccf692b9fa218fd0ef7" - )), - }, - Sha256Circuit { - preimage: vec![0u8; 2048], - digest: bytes_to_scalar(hex!( - "35c18d6c3cf49e42b3ffcb54ea04bdc16617efba0e673abc8c858257955005a5" - )), - }, - Sha256Circuit { - preimage: vec![0u8; 4096], - digest: bytes_to_scalar(hex!( - "25349112d1bd5ba15e3e2d3effa01af1da02c097ce6208cdf28f34b74d35feb2" - )), - }, - Sha256Circuit { - preimage: vec![0u8; 8192], - digest: bytes_to_scalar(hex!( - "22bc891155c7d423039a2206ed4a5342755948baeb13a54b61dbead7c3d3b8f6" - )), - }, - Sha256Circuit { - preimage: vec![0u8; 16384], - digest: bytes_to_scalar(hex!( - "3fda713dc72ddcd42ce625c75f7e41d526d30647278a3dfcda95904e59ade7f1" - )), - },*/ +fn bench_recursive_snark(_c: &mut Criterion) { + + let bytes_to_scalar = |bytes: [u8; 32]| -> ::Scalar { + let mut bytes_le = bytes; + bytes_le.reverse(); + ::Scalar::from_repr(bytes_le).unwrap() + }; + + // NITERATIONS + // let N = vec![13, 25, 50, 100]; - Sha256Circuit { - preimage: vec![0u8; 32768], - digest: bytes_to_scalar(hex!( - "1e2091bd3e3cedffebb7316b52414fff82511cbd232561874a4ae11ae2040ac1" - )), - }, - Sha256Circuit { - preimage: vec![0u8; 65536], - digest: bytes_to_scalar(hex!( - "0c33953975c438ce357912f27b0fbcf98bae6eb68a1a913386672ee406a4f479" - )), - }, - ]; - - - for circuit_primary in circuits { - let mut group = c.benchmark_group(format!("NovaProve-Sha256-message-len-{}", circuit_primary.preimage.len())); - group.sample_size(10); + println!("========================================================="); + println!("{NITERATIONS} non-recursive SHA256 iterations"); + println!("{NSTEPS} Nova steps"); + + // return single hash + let circuit_primary = + Sha256Circuit { + preimage: vec![0u8; 64 * (NITERATIONS as usize)], + digest: bytes_to_scalar(hex!( + "12df9ae4958c1957170f9b04c4bc00c27315c5d75a391f4b672f952842bfa5ac" + )), + }; // Produce public parameters let pp = PublicParams::::setup( - circuit_primary.clone(), - TrivialTestCircuit::default(), + circuit_primary.clone(), + TrivialTestCircuit::default(), + ); + println!( + "Number of constraints per step (primary circuit): {}", + pp.num_constraints().0 + ); + println!( + "Number of constraints per step (secondary circuit): {}", + pp.num_constraints().1 ); - group.bench_function("Prove", |b| { - b.iter(|| { - // produce a recursive SNARK for a step of the recursion - assert!(RecursiveSNARK::prove_step( - black_box(&pp), - black_box(None), - black_box(circuit_primary.clone()), - black_box(TrivialTestCircuit::default()), - black_box(vec![::Scalar::from(2u64)]), - black_box(vec![::Scalar::from(2u64)]), - ) - .is_ok()); - }) - }); - group.finish(); - } + println!( + "Number of variables per step (primary circuit): {}", + pp.num_variables().0 + ); + println!( + "Number of variables per step (secondary circuit): {}", + pp.num_variables().1 + ); + + // Produce SNARK for multiple steps + let num_steps = NSTEPS; + let sha256_circuits = (0..num_steps) + .map(|_| Sha256Circuit{ + preimage: vec![0u8; 64 * (NITERATIONS as usize)], + digest: bytes_to_scalar(hex!( + "12df9ae4958c1957170f9b04c4bc00c27315c5d75a391f4b672f952842bfa5ac" + )), + }).collect::>(); + + // Produce a recursive SNARK + println!("Generating a RecursiveSNARK..."); + let mut recursive_snark: Option> = None; + + let mut prove_step_time: u128 = 0; + for (i, circuit_primary) in sha256_circuits.iter().take(num_steps).enumerate() { + let start = Instant::now(); + let res = RecursiveSNARK::prove_step + ( + black_box(&pp), + //black_box(None), + recursive_snark, + black_box(circuit_primary.clone()), + black_box(TrivialTestCircuit::default()), + black_box(vec![::Scalar::from(2u64)]), + black_box(vec![::Scalar::from(2u64)]), + ); + assert!(res.is_ok()); + let step_time: u128 = start.elapsed().as_millis(); + println!( + "RecursiveSNARK::prove_step {}: {:?}, took {:?} ", + i, + res.is_ok(), + start.elapsed(), + ); + recursive_snark = Some(res.unwrap()); + prove_step_time += step_time; + } + println!("RecursiveSNARK all prove steps took ~{:?} ms", prove_step_time); + + assert!(recursive_snark.is_some()); + let recursive_snark = recursive_snark.unwrap(); + + // verify the recursive SNARK + println!("Verifying a RecursiveSNARK..."); + let start = Instant::now(); + let res = recursive_snark.verify(&pp, num_steps, black_box(vec![::Scalar::from(2u64)]), black_box(vec![::Scalar::from(2u64)])); + println!( + "RecursiveSNARK::verify: {:?}, took {:?}", + res.is_ok(), + start.elapsed() + ); + assert!(res.is_ok()); + + // produce a compressed SNARK + println!("Generating a CompressedSNARK using Spartan with IPA-PC..."); + let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp).unwrap(); + + let start = Instant::now(); + type EE1 = nova_snark::provider::ipa_pc::EvaluationEngine; + type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine; + type S1 = nova_snark::spartan::RelaxedR1CSSNARK; + type S2 = nova_snark::spartan::RelaxedR1CSSNARK; + + 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, black_box(vec![::Scalar::from(2u64)]), black_box(vec![::Scalar::from(2u64)])); + println!( + "CompressedSNARK::verify: {:?}, took {:?}", + res.is_ok(), + start.elapsed() + ); + assert!(res.is_ok()); + println!("========================================================="); }