Skip to content

Commit

Permalink
circuit telemetry and benches
Browse files Browse the repository at this point in the history
  • Loading branch information
Dustin-Ray committed Aug 23, 2024
1 parent 2c7f3d5 commit 0cc05ff
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 11 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ opt-level = 3
[[bench]]
name = "hash_chain_bench"
path = "benches/hash_chain_benches.rs"
harness = false
harness = false
46 changes: 39 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,30 @@ error[E0554]: `#![feature]` may not be used on the stable release channel

Then please double check your toolchain. Otherwise, this repo should work out of the box.

You can also run:

```bash
RUSTFLAGS="-Ctarget-cpu=native" cargo run --release --example circuit_telemetry -- -vv --steps 20
```

To quickly benchmark prover and verifer performance, as well as examine details about the chain over a given number of steps:

```text
[2024-08-23T08:01:38Z INFO hash_chain] Number of gates in circuit: 112960
[2024-08-23T08:01:46Z INFO hash_chain] Total Proof length: 133440 bytes
[2024-08-23T08:01:46Z INFO circuit_telemetry] Proof time: 9.733192095s
[2024-08-23T08:01:46Z INFO circuit_telemetry] Verification time: 4.142599ms
[2024-08-23T08:01:46Z INFO circuit_telemetry] Circuit depth: 20
```
## Supported Hashes:

The following hashes are integrated into the recursive chain:
The following hashes are supported in the recursive chain:

| Hasher | Validation |
|----------|----------|
| Poseidon Hash | [![Test Poseidon Hash](https://github.com/drcapybara/hash-chain/actions/workflows/test_poseidon_hash_chain.yml/badge.svg)](https://github.com/drcapybara/hash-chain/actions/workflows/test_poseidon_hash_chain.yml) |
| Keccak | [![Test Keccak Hash](https://github.com/drcapybara/hash-chain/actions/workflows/test_keccak_hash_chain.yml/badge.svg?branch=feat%2Fkeccak)](https://github.com/drcapybara/hash-chain/actions/workflows/test_keccak_hash_chain.yml) |

A failing test indicates that the circuit is failing to build, and thus work remains to introduce full support for the given hasher

# Strategy

Our approach is to insert the following gates into the circuit with the requisite connections. It is not enough to create a circuit that simply connects each hash output the next input, the prover must argue the hash computation _and_ verify the preceeding hash in a single step, taking into account the recursive structure of the chain:
Expand Down Expand Up @@ -134,7 +147,26 @@ let verification_result =
assert!(verification_result.is_ok());
```

We observe a total uncompressed proof size of 133440 bytes, regardless of number of steps in the chain. This is, in my humble opinion, totally awesome and cool, because this number stays the same no matter how many hashes we compute. In theory, recursively verifiable proofs of this nature can compress extremely large computations into a very small space. Think fully-succint blockchains, in which light clients can verify the entire state of the chain trustlessly by verifying a small and simple proof.
We observe a total uncompressed proof size of 133440 bytes, regardless of number of steps in the chain. This is, in my humble opinion, totally awesome and cool, because this number stays the same no matter how many hashes we compute. In theory, recursively verifiable proofs of this nature can compress extremely large computations into a very small space. Think fully-succinct blockchains, in which light clients can verify the entire state of the chain trustlessly by verifying a small and simple proof in a blisteringly fast amount of time.

## Benches

This crate uses criterion for formal benchmarks. Bench prover and verifier performance with:

```bash
cargo bench
```
Here are some prelimnary performance metrics observed thus far:

| Circuit depth (steps) | Prover Runtime (s) | Verifier Runtime (ms)|
|-----------------------|--------------------|----------------------|
| 2 | 3.3680 s | 3.1013 ms |
| 4 | 4.2126 s | 3.1220 ms |
| 8 | 5.7366 s | 3.0812 ms |
| 16 | 8.8146 s | 3.1098 ms |
| 32 | 14.957 s | 3.0865 ms |
| 64 | 27.294 s | 3.1625 ms |


## Acknowledgments

Expand All @@ -145,8 +177,8 @@ This project makes use of the following open-source libraries:
- **[plonky2_crypto](https://github.com/JumpCrypto/plonky2-crypto)** by Jump Crypto - This component extends the capabilities of plonky2 with additional cryptographic functionalities, sourced from the `patch-plonky2` branch. This crate contains keccak and sha256 hasher gadgets that we use in our recursive circuit.

TODO
- [ ] Compress the proof at the end
- [ ] support keccak
- [x] add benches
- [x] better error handling with thiserr
- [ ] better modularization and cleaner types
- [ ] Compress the proof at the end
- [ ] support keccak
- [ ] add richer circuit telemetry
4 changes: 2 additions & 2 deletions benches/hash_chain_benches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fn hash_chain_proving_benchmark(c: &mut Criterion) {
let config = CircuitConfig::standard_recursion_config();

// Power of two recursive step sizes
let step_sizes = [2 /*, 4, 8, 16, 32, 64 */];
let step_sizes = [2, 4, 8, 16, 32, 64];

let mut group = c.benchmark_group("HashChain Prover");

Expand Down Expand Up @@ -51,7 +51,7 @@ fn hash_chain_verification_benchmark(c: &mut Criterion) {
group.sample_size(10);

// Power of two recursive step sizes
let step_sizes = [2 /*, 4, 8, 16, 32, 64 */];
let step_sizes = [2, 4, 8, 16, 32, 64];

for &steps in &step_sizes {
group.bench_function(&format!("hash_chain_verify_{}_steps", steps), |b| {
Expand Down
63 changes: 63 additions & 0 deletions examples/circuit_telemetry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use hash_chain::HashChain;
use log::info;
use plonky2::{
field::goldilocks_field::GoldilocksField,
plonk::{
circuit_builder::CircuitBuilder,
circuit_data::CircuitConfig,
config::{GenericConfig, PoseidonGoldilocksConfig},
},
};
use std::{env, time::Instant};

fn main() {
let args: Vec<String> = env::args().collect();
let verbose = args.contains(&"-vv".to_string());
if env::args().any(|arg| arg == "-vv") {
std::env::set_var("RUST_LOG", "info");
}

env_logger::init();

// Default steps if not specified
let mut steps = 2;
if let Some(pos) = args.iter().position(|x| x == "--steps") {
if let Some(steps_arg) = args.get(pos + 1) {
steps = steps_arg.parse().expect("Invalid number for steps");
}
}

const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;

let config = CircuitConfig::standard_recursion_config();
let mut circuit = CircuitBuilder::<F, D>::new(config.clone());
// Timing verification
let start_time = Instant::now();
let (proof, circuit_map) = <CircuitBuilder<GoldilocksField, D> as HashChain<
GoldilocksField,
D,
C,
>>::build_hash_chain_circuit(&mut circuit, steps)
.expect("Failed to build hash chain circuit");
let proof_time = start_time.elapsed();

// Timing verification
let start_time = Instant::now();
let verification_result = <CircuitBuilder<GoldilocksField, D> as HashChain<
GoldilocksField,
D,
C,
>>::verify(proof, &circuit_map);
let verify_time = start_time.elapsed();

// Ensure the verification is successful before considering timing
assert!(verification_result.is_ok(), "Verification failed");

if verbose {
info!("Proof time: {:?}", proof_time);
info!("Verification time: {:?}", verify_time);
info!("Circuit depth: {}", steps);
}
}
9 changes: 8 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use env_logger::builder;

Check warning on line 1 in src/lib.rs

View workflow job for this annotation

GitHub Actions / build_and_test

unused import: `env_logger::builder`
use log::info;
use plonky2::{
field::extension::Extendable,
gates::noop::NoopGate,
Expand Down Expand Up @@ -231,6 +233,10 @@ where
counter,
)?;

info!(
"Number of gates in circuit: {}",
builder.num_gates() * steps
);
// We now have all of the appropriate layers setup, so
// now lets compile the circuit.
let cyclic_circuit_data = builder.build::<C>();
Expand Down Expand Up @@ -305,6 +311,7 @@ where
while builder.num_gates() < 1 << 12 {
builder.add_gate(NoopGate, vec![]);
}

builder.build::<C>().common
}

Expand Down Expand Up @@ -420,7 +427,7 @@ where
// the same regardless of the number of steps in the
// recursive circuit.
let proof_bytes = proof.to_bytes();
println!("Total Proof length: {} bytes", proof_bytes.len());
info!("Total Proof length: {} bytes", proof_bytes.len());
Ok(cyclic_circuit_data.verify(proof)?)
}
}
Expand Down

0 comments on commit 0cc05ff

Please sign in to comment.