mirror of
https://github.com/arnaucube/hash-chain-sonobe.git
synced 2026-01-19 20:21:32 +01:00
add naive_approach_sha_chain.rs to compare folding to naive approach
This commit is contained in:
@@ -15,6 +15,7 @@ ark-ec = "0.4.1"
|
||||
ark-ff = "0.4.1"
|
||||
ark-r1cs-std = { version = "0.4.0", default-features = false }
|
||||
ark-relations = { version = "0.4.0", default-features = false }
|
||||
ark-snark = { version = "^0.4.0", default-features = false }
|
||||
ark-poly-commit = "^0.4.0"
|
||||
ark-crypto-primitives = { version = "^0.4.0", default-features = false, features = [
|
||||
"r1cs",
|
||||
|
||||
@@ -30,3 +30,10 @@ Note: the Circom variant currently has a bit of extra overhead since at each fol
|
||||
### Repo structure
|
||||
- the Circom circuit (that defines the keccak-chain) to be folded is defined at [./circuit/keccak-chain.circom](https://github.com/arnaucube/hash-chain-sonobe/blob/main/circuit/keccak-chain.circom)
|
||||
- the logic to fold the circuit using Sonobe is defined at [src/{sha_chain, keccak_chain}.rs](https://github.com/arnaucube/hash-chain-sonobe/blob/main/src)
|
||||
|
||||
|
||||
|
||||
## Other
|
||||
Additionally there is the `src/naive_approach_sha_chain.rs` file, which mimics the amount of hashes computed by the `src/sha_chain.rs` file, but instead of folding it does it by building a big circuit that does all the hashes at once, as we would do before folding existed.
|
||||
|
||||
To run it: `cargo test --release naive_approach_sha_chain -- --nocapture`
|
||||
|
||||
@@ -136,7 +136,11 @@ mod tests {
|
||||
start.elapsed()
|
||||
);
|
||||
}
|
||||
println!("Nova's all steps time: {:?}", start_full.elapsed());
|
||||
println!(
|
||||
"Nova's all {} steps time: {:?}",
|
||||
n_steps,
|
||||
start_full.elapsed()
|
||||
);
|
||||
|
||||
// perform the hash chain natively in rust (which uses a rust Keccak256 library)
|
||||
let mut z_i_native = z_0.clone();
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
mod keccak_chain;
|
||||
mod naive_approach_sha_chain;
|
||||
mod sha_chain;
|
||||
mod utils;
|
||||
|
||||
132
src/naive_approach_sha_chain.rs
Normal file
132
src/naive_approach_sha_chain.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
/// This example does the hash chain but in the naive approach: instead of using folding, it does a
|
||||
/// big circuit containing n instantiations of the sha256 constraints.
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ark_bn254::{Bn254, Fr};
|
||||
|
||||
use ark_groth16::Groth16;
|
||||
use ark_snark::SNARK;
|
||||
|
||||
use ark_ff::PrimeField;
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
use ark_crypto_primitives::crh::sha256::{constraints::Sha256Gadget, digest::Digest, Sha256};
|
||||
use ark_r1cs_std::fields::fp::FpVar;
|
||||
use ark_r1cs_std::{alloc::AllocVar, eq::EqGadget};
|
||||
use ark_r1cs_std::{bits::uint8::UInt8, boolean::Boolean, ToBitsGadget, ToBytesGadget};
|
||||
use ark_relations::r1cs::{
|
||||
ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError,
|
||||
};
|
||||
|
||||
use crate::utils::tests::*;
|
||||
|
||||
/// Test circuit to be folded
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SHA256ChainCircuit<F: PrimeField, const N: usize, const HASHES_PER_STEP: usize> {
|
||||
z_0: Option<Vec<F>>,
|
||||
z_n: Option<Vec<F>>,
|
||||
}
|
||||
impl<F: PrimeField, const N: usize, const HASHES_PER_STEP: usize> ConstraintSynthesizer<F>
|
||||
for SHA256ChainCircuit<F, N, HASHES_PER_STEP>
|
||||
{
|
||||
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
|
||||
let z_0 = Vec::<FpVar<F>>::new_witness(cs.clone(), || {
|
||||
Ok(self.z_0.unwrap_or(vec![F::zero()]))
|
||||
})?;
|
||||
let z_n =
|
||||
Vec::<FpVar<F>>::new_input(cs.clone(), || Ok(self.z_n.unwrap_or(vec![F::zero()])))?;
|
||||
let mut z_i: Vec<FpVar<F>> = z_0.clone();
|
||||
for _ in 0..N {
|
||||
let mut b: Vec<UInt8<F>> = z_i
|
||||
.iter()
|
||||
.map(|f| UInt8::<F>::from_bits_le(&f.to_bits_le().unwrap()[..8]))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for _ in 0..HASHES_PER_STEP {
|
||||
let mut sha256_var = Sha256Gadget::default();
|
||||
sha256_var.update(&b).unwrap();
|
||||
b = sha256_var.finalize()?.to_bytes()?;
|
||||
}
|
||||
|
||||
// update z_i = z_{i+1}
|
||||
z_i = b
|
||||
.iter()
|
||||
.map(|e| {
|
||||
let bits = e.to_bits_le().unwrap();
|
||||
Boolean::<F>::le_bits_to_fp_var(&bits).unwrap()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
z_i.enforce_equal(&z_n)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
// compute natively in rust the expected result
|
||||
fn rust_native_result(z_0: Vec<Fr>, n_steps: usize, hashes_per_step: usize) -> Vec<Fr> {
|
||||
let mut z_i: Vec<Fr> = z_0.clone();
|
||||
for _ in 0..n_steps {
|
||||
let mut b = f_vec_to_bytes(z_i.to_vec());
|
||||
|
||||
for _ in 0..hashes_per_step {
|
||||
let mut sha256 = Sha256::default();
|
||||
sha256.update(b);
|
||||
b = sha256.finalize().to_vec();
|
||||
}
|
||||
|
||||
z_i = bytes_to_f_vec(b.to_vec()).unwrap();
|
||||
}
|
||||
z_i.clone()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_flow() {
|
||||
// set how many iterations of the SHA256ChainCircuit circuit internal loop we want to
|
||||
// compute
|
||||
const N_STEPS: usize = 50;
|
||||
const HASHES_PER_STEP: usize = 10;
|
||||
println!("running the 'naive' SHA256ChainCircuit, with N_STEPS={}, HASHES_PER_STEP={}. Total hashes = {}", N_STEPS, HASHES_PER_STEP, N_STEPS* HASHES_PER_STEP);
|
||||
|
||||
// set the initial state
|
||||
// let z_0_aux: Vec<u32> = vec![0_u32; 32 * 8];
|
||||
let z_0_aux: Vec<u8> = vec![0_u8; 32];
|
||||
let z_0: Vec<Fr> = z_0_aux.iter().map(|v| Fr::from(*v)).collect::<Vec<Fr>>();
|
||||
|
||||
// run the N iterations 'natively' in rust to compute the expected `z_n`
|
||||
let z_n = rust_native_result(z_0.clone(), N_STEPS, HASHES_PER_STEP);
|
||||
|
||||
let circuit = SHA256ChainCircuit::<Fr, N_STEPS, HASHES_PER_STEP> {
|
||||
z_0: Some(z_0),
|
||||
z_n: Some(z_n.clone()),
|
||||
};
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
circuit.clone().generate_constraints(cs.clone()).unwrap();
|
||||
println!(
|
||||
"number of constraints of the (naive) SHA256ChainCircuit with N={} hash iterations: {}",
|
||||
N_STEPS,
|
||||
cs.num_constraints()
|
||||
);
|
||||
|
||||
// now let's generate an actual Groth16 proof
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
let (g16_pk, g16_vk) =
|
||||
Groth16::<Bn254>::circuit_specific_setup(circuit.clone(), &mut rng).unwrap();
|
||||
|
||||
let start = Instant::now();
|
||||
let proof = Groth16::<Bn254>::prove(&g16_pk, circuit.clone(), &mut rng).unwrap();
|
||||
println!(
|
||||
"Groth16 proof generation (for the naive SHA256ChainCircuit): {:?}",
|
||||
start.elapsed()
|
||||
);
|
||||
|
||||
let public_inputs = z_n;
|
||||
let valid_proof = Groth16::<Bn254>::verify(&g16_vk, &public_inputs, &proof).unwrap();
|
||||
|
||||
assert!(valid_proof);
|
||||
|
||||
println!("finished running the 'naive' SHA256ChainCircuit, with N_STEPS={}, HASHES_PER_STEP={}. Total hashes = {}", N_STEPS, HASHES_PER_STEP, N_STEPS* HASHES_PER_STEP);
|
||||
}
|
||||
}
|
||||
@@ -44,10 +44,12 @@ mod tests {
|
||||
|
||||
/// Test circuit to be folded
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SHA256FoldStepCircuit<F: PrimeField> {
|
||||
pub struct SHA256FoldStepCircuit<F: PrimeField, const HASHES_PER_STEP: usize> {
|
||||
_f: PhantomData<F>,
|
||||
}
|
||||
impl<F: PrimeField> FCircuit<F> for SHA256FoldStepCircuit<F> {
|
||||
impl<F: PrimeField, const HASHES_PER_STEP: usize> FCircuit<F>
|
||||
for SHA256FoldStepCircuit<F, HASHES_PER_STEP>
|
||||
{
|
||||
type Params = ();
|
||||
fn new(_params: Self::Params) -> Result<Self, Error> {
|
||||
Ok(Self { _f: PhantomData })
|
||||
@@ -66,12 +68,15 @@ mod tests {
|
||||
z_i: Vec<F>,
|
||||
_external_inputs: Vec<F>,
|
||||
) -> Result<Vec<F>, Error> {
|
||||
let b = f_vec_to_bytes(z_i.to_vec());
|
||||
let mut sha256 = Sha256::default();
|
||||
sha256.update(b);
|
||||
let z_i1 = sha256.finalize().to_vec();
|
||||
let mut b = f_vec_to_bytes(z_i.to_vec());
|
||||
|
||||
bytes_to_f_vec(z_i1.to_vec())
|
||||
for _ in 0..HASHES_PER_STEP {
|
||||
let mut sha256 = Sha256::default();
|
||||
sha256.update(b);
|
||||
b = sha256.finalize().to_vec();
|
||||
}
|
||||
|
||||
bytes_to_f_vec(b.to_vec()) // z_{i+1}
|
||||
}
|
||||
fn generate_step_constraints(
|
||||
&self,
|
||||
@@ -80,14 +85,18 @@ mod tests {
|
||||
z_i: Vec<FpVar<F>>,
|
||||
_external_inputs: Vec<FpVar<F>>,
|
||||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||
let mut sha256_var = Sha256Gadget::default();
|
||||
let z_i_u8: Vec<UInt8<F>> = z_i
|
||||
let mut b: Vec<UInt8<F>> = z_i
|
||||
.iter()
|
||||
.map(|f| UInt8::<F>::from_bits_le(&f.to_bits_le().unwrap()[..8]))
|
||||
.collect::<Vec<_>>();
|
||||
sha256_var.update(&z_i_u8).unwrap();
|
||||
let z_i1_u8 = sha256_var.finalize()?.to_bytes()?;
|
||||
let z_i1: Vec<FpVar<F>> = z_i1_u8
|
||||
|
||||
for _ in 0..HASHES_PER_STEP {
|
||||
let mut sha256_var = Sha256Gadget::default();
|
||||
sha256_var.update(&b).unwrap();
|
||||
b = sha256_var.finalize()?.to_bytes()?;
|
||||
}
|
||||
|
||||
let z_i1: Vec<FpVar<F>> = b
|
||||
.iter()
|
||||
.map(|e| {
|
||||
let bits = e.to_bits_le().unwrap();
|
||||
@@ -102,14 +111,16 @@ mod tests {
|
||||
#[test]
|
||||
fn full_flow() {
|
||||
// set how many steps of folding we want to compute
|
||||
let n_steps = 100;
|
||||
const N_STEPS: usize = 100;
|
||||
const HASHES_PER_STEP: usize = 10;
|
||||
println!("running Nova folding scheme on SHA256FoldStepCircuit, with N_STEPS={}, HASHES_PER_STEP={}. Total hashes = {}", N_STEPS, HASHES_PER_STEP, N_STEPS* HASHES_PER_STEP);
|
||||
|
||||
// set the initial state
|
||||
// let z_0_aux: Vec<u32> = vec![0_u32; 32 * 8];
|
||||
let z_0_aux: Vec<u8> = vec![0_u8; 32];
|
||||
let z_0: Vec<Fr> = z_0_aux.iter().map(|v| Fr::from(*v)).collect::<Vec<Fr>>();
|
||||
|
||||
let f_circuit = SHA256FoldStepCircuit::<Fr>::new(()).unwrap();
|
||||
let f_circuit = SHA256FoldStepCircuit::<Fr, HASHES_PER_STEP>::new(()).unwrap();
|
||||
|
||||
// ----------------
|
||||
// Sanity check
|
||||
@@ -128,6 +139,10 @@ mod tests {
|
||||
assert_eq!(z_1_var.value().unwrap(), z_1_native);
|
||||
// check that the constraint system is satisfied
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
println!(
|
||||
"number of constraints of a single instantiation of the SHA256FoldStepCircuit: {}",
|
||||
cs.num_constraints()
|
||||
);
|
||||
// ----------------
|
||||
|
||||
// define type aliases to avoid writting the whole type each time
|
||||
@@ -136,7 +151,7 @@ mod tests {
|
||||
GVar,
|
||||
G2,
|
||||
GVar2,
|
||||
SHA256FoldStepCircuit<Fr>,
|
||||
SHA256FoldStepCircuit<Fr, HASHES_PER_STEP>,
|
||||
KZG<'static, Bn254>,
|
||||
Pedersen<G2>,
|
||||
false,
|
||||
@@ -146,7 +161,7 @@ mod tests {
|
||||
GVar,
|
||||
G2,
|
||||
GVar2,
|
||||
SHA256FoldStepCircuit<Fr>,
|
||||
SHA256FoldStepCircuit<Fr, HASHES_PER_STEP>,
|
||||
KZG<'static, Bn254>,
|
||||
Pedersen<G2>,
|
||||
Groth16<Bn254>,
|
||||
@@ -172,7 +187,7 @@ mod tests {
|
||||
|
||||
// run n steps of the folding iteration
|
||||
let start_full = Instant::now();
|
||||
for _ in 0..n_steps {
|
||||
for _ in 0..N_STEPS {
|
||||
let start = Instant::now();
|
||||
nova.prove_step(rng, vec![], None).unwrap();
|
||||
println!(
|
||||
@@ -181,7 +196,11 @@ mod tests {
|
||||
start.elapsed()
|
||||
);
|
||||
}
|
||||
println!("Nova's all steps time: {:?}", start_full.elapsed());
|
||||
println!(
|
||||
"Nova's all {} steps time: {:?}",
|
||||
N_STEPS,
|
||||
start_full.elapsed()
|
||||
);
|
||||
|
||||
// ----------------
|
||||
// Sanity check
|
||||
|
||||
Reference in New Issue
Block a user