diff --git a/Cargo.toml b/Cargo.toml index 9030197..0bf1eab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,4 +39,4 @@ opt-level = 3 [[bench]] name = "hash_chain_bench" path = "benches/hash_chain_benches.rs" -harness = false \ No newline at end of file +harness = false diff --git a/README.md b/README.md index b666b57..bb5d1a5 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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 @@ -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 diff --git a/benches/hash_chain_benches.rs b/benches/hash_chain_benches.rs index 55b209a..508fbcb 100644 --- a/benches/hash_chain_benches.rs +++ b/benches/hash_chain_benches.rs @@ -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"); @@ -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| { diff --git a/examples/circuit_telemetry.rs b/examples/circuit_telemetry.rs new file mode 100644 index 0000000..891a1d0 --- /dev/null +++ b/examples/circuit_telemetry.rs @@ -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 = 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 = >::F; + + let config = CircuitConfig::standard_recursion_config(); + let mut circuit = CircuitBuilder::::new(config.clone()); + // Timing verification + let start_time = Instant::now(); + let (proof, circuit_map) = 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 = 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); + } +} diff --git a/src/lib.rs b/src/lib.rs index 9bdfe14..862e668 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +use env_logger::builder; +use log::info; use plonky2::{ field::extension::Extendable, gates::noop::NoopGate, @@ -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::(); @@ -305,6 +311,7 @@ where while builder.num_gates() < 1 << 12 { builder.add_gate(NoopGate, vec![]); } + builder.build::().common } @@ -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)?) } }