Skip to content

Commit

Permalink
port sha256 bench from nova
Browse files Browse the repository at this point in the history
  • Loading branch information
srinathsetty committed Aug 29, 2023
1 parent 50238d2 commit 8a1e812
Show file tree
Hide file tree
Showing 3 changed files with 278 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ cfg-if = "1.0.0"
sha2 = "0.10.7"
proptest = "1.2.0"

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

[features]
default = []
# Compiles in portable mode, w/o ISA extensions => binary can be executed on all systems.
Expand Down
154 changes: 154 additions & 0 deletions benches/sha256.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//! Benchmarks Nova's prover for proving SHA-256 with varying sized messages.
//! We run a single step with the step performing the entire computation.
//! This code invokes a hand-written SHA-256 gadget from bellman/bellperson.
//! It also uses code from bellman/bellperson to compare circuit-generated digest with sha2 crate's output
#![allow(non_snake_case)]
use bellperson::gadgets::{sha256::sha256, Assignment};
use bellperson::{gadgets::{
boolean::{AllocatedBit, Boolean},
num::{AllocatedNum, Num}},
Circuit,ConstraintSystem, SynthesisError,
};
use core::time::Duration;
use criterion::*;
use ff::{PrimeField, PrimeFieldBits};
use spartan2::{traits::Group,SNARK};
use sha2::{Digest, Sha256};
use std::marker::PhantomData;

type G = pasta_curves::pallas::Point;
type EE = spartan2::provider::hyrax_pc::HyraxEvaluationEngine<G>;
type S = spartan2::spartan::snark::RelaxedR1CSSNARK<G, EE>;

#[derive(Clone, Debug)]
struct Sha256Circuit<Scalar: PrimeField> {
preimage: Vec<u8>,
_p: PhantomData<Scalar>,
}

impl<Scalar: PrimeField + PrimeFieldBits> Sha256Circuit<Scalar> {
pub fn new(preimage: Vec<u8>) -> Self {
Self {
preimage,
_p: PhantomData,
}
}
}

impl<Scalar: PrimeField> Circuit<Scalar> for Sha256Circuit<Scalar> {
fn synthesize<CS: ConstraintSystem<Scalar>>(
self,
cs: &mut CS,
) -> Result<(), SynthesisError> {
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::<Result<Vec<_>, _>>()?;

let hash_bits = sha256(cs.namespace(|| "sha256"), &preimage_bits)?;

for (i, hash_bits) in hash_bits.chunks(256_usize).enumerate() {
let mut num = Num::<Scalar>::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(),
);
}

// 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")
}
}
}
Ok(())
}
}

criterion_group! {
name = snark;
config = Criterion::default().warm_up_time(Duration::from_millis(3000));
targets = bench_snark
}

criterion_main!(snark);

fn bench_snark(c: &mut Criterion) {
// Test vectors
let circuits = vec![
Sha256Circuit::new(vec![0u8; 1 << 6]),
Sha256Circuit::new(vec![0u8; 1 << 7]),
Sha256Circuit::new(vec![0u8; 1 << 8]),
Sha256Circuit::new(vec![0u8; 1 << 9]),
Sha256Circuit::new(vec![0u8; 1 << 10]),
Sha256Circuit::new(vec![0u8; 1 << 11]),
Sha256Circuit::new(vec![0u8; 1 << 12]),
Sha256Circuit::new(vec![0u8; 1 << 13]),
Sha256Circuit::new(vec![0u8; 1 << 14]),
Sha256Circuit::new(vec![0u8; 1 << 15]),
Sha256Circuit::new(vec![0u8; 1 << 16]),
];

for circuit in circuits {
let mut group = c.benchmark_group(format!(
"SpartanProve-Sha256-message-len-{}",
circuit.preimage.len()
));
group.sample_size(10);

// produce keys
let (pk, _vk) =
SNARK::<G, S, Sha256Circuit<<G as Group>::Scalar>>::setup(circuit.clone()).unwrap();

group.bench_function("Prove", |b| {
let res = b.iter(|| {
SNARK::<G, S, Sha256Circuit<<G as Group>::Scalar>>::prove(
black_box(&pk),
black_box(circuit.clone()),
);
});
});
group.finish();
}
}
120 changes: 120 additions & 0 deletions benches/snark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#![allow(non_snake_case)]

use bellperson::{Circuit, gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError};
use core::marker::PhantomData;
use criterion::*;
use ff::PrimeField;
use spartan2::{
traits::{
Group,
},
SNARK,
};
use std::time::Duration;

type G = pasta_curves::pallas::Point;
type EE = spartan2::provider::hyrax_pc::HyraxEvaluationEngine<G>;
type S = spartan2::spartan::snark::RelaxedR1CSSNARK<G, EE>;
type C = NonTrivialTestCircuit<<G as Group>::Scalar>;

// To run these benchmarks, first download `criterion` with `cargo install cargo install cargo-criterion`.
// Then `cargo criterion --bench 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 = 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_snark
}
} else {
criterion_group! {
name = snark;
config = Criterion::default().warm_up_time(Duration::from_millis(3000));
targets = bench_snark
}
}
}

criterion_main!(snark);

fn bench_snark(c: &mut Criterion) {
let num_samples = 10;
// we vary the number of constraints in the circuit
for &num_cons in
[8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576].iter()
{
let mut group = c.benchmark_group(format!("SNARK-CircuitSize-{num_cons}"));
group.sample_size(num_samples);

let circuit = NonTrivialTestCircuit::new(num_cons);

// produce keys
let (pk, vk) =
SNARK::<G, S, C>::setup(circuit.clone()).unwrap();

// Bench time to produce a compressed SNARK
group.bench_function("Prove", |b| {
b.iter(|| {
assert!(
// produce a SNARK
SNARK::prove(black_box(&pk), black_box(circuit.clone()))
.is_ok());
})
});
let res = SNARK::prove(black_box(&pk), black_box(circuit.clone()));
assert!(res.is_ok());
let snark = res.unwrap();

// Benchmark the verification time
group.bench_function("Verify", |b| {
b.iter(|| {
assert!(black_box(&snark)
.verify(
black_box(&vk),
)
.is_ok());
})
});

group.finish();
}
}

#[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> Circuit<F> for NonTrivialTestCircuit<F>
where
F: PrimeField,
{
fn synthesize<CS: ConstraintSystem<F>>(
self,
cs: &mut CS
) -> Result<(), SynthesisError> {
// Consider a an equation: `x^2 = y`, where `x` and `y` are respectively the input and output.
let mut x = AllocatedNum::alloc(cs.namespace(|| "x"), || Ok(F::ONE + F::ONE))?;
let mut y;
for i in 0..self.num_cons {
y = x.square(cs.namespace(|| format!("x_sq_{i}")))?;
x = y.clone();
}
Ok(())
}
}

0 comments on commit 8a1e812

Please sign in to comment.