Skip to content
This repository was archived by the owner on Apr 18, 2025. It is now read-only.

Commit 65912f6

Browse files
committed
[refactor] remove pi agg circuit; integrated into proof agg circuit
1 parent b68e644 commit 65912f6

File tree

12 files changed

+181
-540
lines changed

12 files changed

+181
-540
lines changed

aggregator/src/batch.rs

+141-1
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,85 @@
1-
use eth_types::H256;
1+
//! This module implements related functions that aggregates public inputs of many chunks into a
2+
//! single one.
3+
//!
4+
//! # Spec
5+
//!
6+
//! A chunk is a list of continuous blocks. It consists of 4 hashes:
7+
//! - state root before this chunk
8+
//! - state root after this chunk
9+
//! - the withdraw root of this chunk
10+
//! - the data hash of this chunk
11+
//! Those 4 hashes are obtained from the caller.
12+
//!
13+
//! A chunk's public input hash is then derived from the above 4 attributes via
14+
//!
15+
//! - chunk_pi_hash := keccak(chain_id || prev_state_root || post_state_root || withdraw_root ||
16+
//! chunk_data_hash)
17+
//!
18+
//! A batch is a list of continuous chunks. It consists of 2 hashes
19+
//!
20+
//! - batch_data_hash := keccak(chunk_0.data_hash || ... || chunk_k-1.data_hash)
21+
//!
22+
//! - batch_pi_hash := keccak(chain_id || chunk_0.prev_state_root || chunk_k-1.post_state_root ||
23+
//! chunk_k-1.withdraw_root || batch_data_hash)
24+
//!
25+
//! Note that chain_id is used for all public input hashes. But not for any data hashes.
26+
//!
27+
//! # Circuit
28+
//!
29+
//! A BatchHashCircuit asserts that the batch is well-formed.
30+
//!
31+
//! ## Public Input
32+
//! The public inputs of the circuit (32 Field elements) is constructed as
33+
//! - batch_pi_hash: 32 Field elements
34+
//!
35+
//! ## Constraints
36+
//! The circuit attests the following statements:
37+
//!
38+
//! 1. all hashes are computed correctly
39+
//! 2. the relations between hash preimages and digests are satisfied
40+
//! - batch_data_hash is part of the input to compute batch_pi_hash
41+
//! - batch_pi_hash used same roots as chunk_pi_hash
42+
//! - same data_hash is used to compute batch_data_hash and chunk_pi_hash for all chunks
43+
//! - chunks are continuous: they are linked via the state roots
44+
//! - all hashes uses a same chain_id
45+
//! 3. the batch_pi_hash matches the circuit's public input (32 field elements) above
46+
47+
use eth_types::{Field, H256};
248
use ethers_core::utils::keccak256;
349

450
use super::chunk::ChunkHash;
551

652
#[derive(Default, Debug, Clone)]
753
/// A batch is a set of continuous chunks.
854
/// A BatchHash consists of 2 hashes.
55+
/// - batch_data_hash := keccak(chunk_0.data_hash || ... || chunk_k-1.data_hash)
56+
/// - batch_pi_hash := keccak(chain_id || chunk_0.prev_state_root || chunk_k-1.post_state_root ||
57+
/// chunk_k-1.withdraw_root || batch_data_hash)
958
pub struct BatchHash {
59+
pub(crate) chain_id: u64,
60+
pub(crate) chunks: Vec<ChunkHash>,
1061
pub(crate) data_hash: H256,
1162
pub(crate) public_input_hash: H256,
1263
}
1364

1465
impl BatchHash {
66+
/// Sample a batch hash circuit from random (for testing)
67+
#[cfg(test)]
68+
pub(crate) fn mock_batch_hash_circuit<R: rand::RngCore>(r: &mut R, size: usize) -> Self {
69+
let mut chunks = (0..size)
70+
.map(|_| ChunkHash::mock_chunk_hash(r))
71+
.collect::<Vec<_>>();
72+
for i in 0..size - 1 {
73+
chunks[i + 1].prev_state_root = chunks[i].post_state_root;
74+
}
75+
76+
Self::construct(&chunks)
77+
}
78+
1579
/// Build Batch hash from a list of chunks
1680
pub(crate) fn construct(chunk_hashes: &[ChunkHash]) -> Self {
81+
assert!(!chunk_hashes.is_empty(), "input chunk slice is empty");
82+
1783
// sanity: the chunks are continuous
1884
for i in 0..chunk_hashes.len() - 1 {
1985
assert_eq!(
@@ -50,8 +116,82 @@ impl BatchHash {
50116
let public_input_hash = keccak256(preimage);
51117

52118
Self {
119+
chain_id: chunk_hashes[0].chain_id,
120+
chunks: chunk_hashes.to_vec(),
53121
data_hash: data_hash.into(),
54122
public_input_hash: public_input_hash.into(),
55123
}
56124
}
125+
126+
/// Extract all the hash inputs that will ever be used
127+
/// orders:
128+
/// - batch_public_input_hash
129+
/// - batch_data_hash_preimage
130+
/// - chunk\[i\].piHash for i in \[0, k)
131+
pub(crate) fn extract_hash_preimages(&self) -> Vec<Vec<u8>> {
132+
let mut res = vec![];
133+
134+
// batchPiHash =
135+
// keccak(
136+
// chain_id ||
137+
// chunk[0].prev_state_root ||
138+
// chunk[k-1].post_state_root ||
139+
// chunk[k-1].withdraw_root ||
140+
// batch_data_hash )
141+
let batch_public_input_hash_preimage = [
142+
self.chain_id.to_le_bytes().as_ref(),
143+
self.chunks[0].prev_state_root.as_bytes(),
144+
self.chunks.last().unwrap().post_state_root.as_bytes(),
145+
self.chunks.last().unwrap().withdraw_root.as_bytes(),
146+
self.data_hash.as_bytes(),
147+
]
148+
.concat();
149+
res.push(batch_public_input_hash_preimage);
150+
151+
// batchDataHash = keccak(chunk[0].dataHash || ... || chunk[k-1].dataHash)
152+
let batch_data_hash_preimage = self
153+
.chunks
154+
.iter()
155+
.flat_map(|x| x.data_hash.as_bytes().iter())
156+
.cloned()
157+
.collect();
158+
res.push(batch_data_hash_preimage);
159+
160+
// compute piHash for each chunk for i in [0..k)
161+
// chunk[i].piHash =
162+
// keccak(
163+
// chain id ||
164+
// chunk[i].prevStateRoot || chunk[i].postStateRoot || chunk[i].withdrawRoot ||
165+
// chunk[i].datahash)
166+
for chunk in self.chunks.iter() {
167+
let chunk_pi_hash_preimage = [
168+
self.chain_id.to_le_bytes().as_ref(),
169+
chunk.prev_state_root.as_bytes(),
170+
chunk.post_state_root.as_bytes(),
171+
chunk.withdraw_root.as_bytes(),
172+
chunk.data_hash.as_bytes(),
173+
]
174+
.concat();
175+
res.push(chunk_pi_hash_preimage)
176+
}
177+
178+
res
179+
}
180+
181+
fn num_instance(&self) -> Vec<usize> {
182+
// 12 elements from the accumulators
183+
// 32 elements from batch_data_hash_digest
184+
vec![44]
185+
}
186+
187+
/// Compute the public inputs for this circuit
188+
/// which is the public_input_hash
189+
pub(crate) fn instances<F: Field>(&self) -> Vec<Vec<F>> {
190+
vec![self
191+
.public_input_hash
192+
.as_bytes()
193+
.iter()
194+
.map(|&x| F::from(x as u64))
195+
.collect()]
196+
}
57197
}

aggregator/src/proof_aggregation.rs

+28-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,35 @@ mod circuit;
44
mod circuit_ext;
55
/// Config for aggregation circuit
66
mod config;
7-
/// public input aggregation
8-
mod public_input_aggregation;
97

108
pub use circuit::AggregationCircuit;
119
pub use config::AggregationConfig;
1210

13-
pub(crate) use public_input_aggregation::*;
11+
// TODO(ZZ): update to the right degree
12+
pub(crate) const LOG_DEGREE: u32 = 19;
13+
14+
// ================================
15+
// indices for hash bytes
16+
// ================================
17+
//
18+
// the preimages are arranged as
19+
// - chain_id: 8 bytes
20+
// - prev_state_root 32 bytes
21+
// - post_state_root 32 bytes
22+
// - withdraw_root 32 bytes
23+
// - chunk_data_hash 32 bytes
24+
//
25+
// A chain_id is u64 and uses 8 bytes
26+
pub(crate) const CHAIN_ID_LEN: usize = 8;
27+
pub(crate) const PREV_STATE_ROOT_INDEX: usize = 8;
28+
pub(crate) const POST_STATE_ROOT_INDEX: usize = 40;
29+
pub(crate) const WITHDRAW_ROOT_INDEX: usize = 72;
30+
pub(crate) const CHUNK_DATA_HASH_INDEX: usize = 104;
31+
32+
// Each round requires (NUM_ROUNDS+1) * DEFAULT_KECCAK_ROWS = 300 rows.
33+
// This library is hard coded for this parameter.
34+
// Modifying the following parameters may result into bugs.
35+
// Adopted from keccak circuit
36+
pub(crate) const DEFAULT_KECCAK_ROWS: usize = 12;
37+
// Adopted from keccak circuit
38+
pub(crate) const NUM_ROUNDS: usize = 24;

aggregator/src/proof_aggregation/circuit.rs

+9-39
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use crate::{
2727
core::{assign_batch_hashes, extract_accumulators_and_proof},
2828
param::{ConfigParams, BITS, LIMBS},
2929
proof_aggregation::config::AggregationConfig,
30-
BatchHashCircuit, ChunkHash, CHAIN_ID_LEN, POST_STATE_ROOT_INDEX, PREV_STATE_ROOT_INDEX,
30+
BatchHash, ChunkHash, CHAIN_ID_LEN, POST_STATE_ROOT_INDEX, PREV_STATE_ROOT_INDEX,
3131
WITHDRAW_ROOT_INDEX,
3232
};
3333

@@ -43,7 +43,7 @@ pub struct AggregationCircuit {
4343
// accumulation scheme proof, private input
4444
pub(crate) as_proof: Value<Vec<u8>>,
4545
// batch hash circuit for which the snarks are generated
46-
pub(crate) batch_hash_circuit: BatchHashCircuit<Fr>,
46+
pub(crate) batch_hash: BatchHash,
4747
}
4848

4949
impl AggregationCircuit {
@@ -98,8 +98,8 @@ impl AggregationCircuit {
9898
.concat();
9999

100100
// extract the pi aggregation circuit's instances
101-
let batch_hash_circuit = BatchHashCircuit::construct(chunk_hashes);
102-
let pi_aggregation_instances = &batch_hash_circuit.instances()[0];
101+
let batch_hash = BatchHash::construct(chunk_hashes);
102+
let pi_aggregation_instances = &batch_hash.instances()[0];
103103

104104
let flattened_instances: Vec<Fr> = [
105105
acc_instances.as_slice(),
@@ -117,7 +117,7 @@ impl AggregationCircuit {
117117
snarks: snarks.iter().cloned().map_into().collect(),
118118
flattened_instances,
119119
as_proof: Value::known(as_proof),
120-
batch_hash_circuit,
120+
batch_hash,
121121
}
122122
}
123123

@@ -177,7 +177,7 @@ impl Circuit<Fr> for AggregationCircuit {
177177
// circuit
178178

179179
// ==============================================
180-
// Step 1: aggregation circuit
180+
// Step 1: snark aggregation circuit
181181
// ==============================================
182182
let mut accumulator_instances: Vec<AssignedValue<Fr>> = vec![];
183183
let mut snark_inputs: Vec<AssignedValue<Fr>> = vec![];
@@ -253,7 +253,7 @@ impl Circuit<Fr> for AggregationCircuit {
253253
let challenges = challenge.values(&layouter);
254254

255255
let timer = start_timer!(|| ("extract hash").to_string());
256-
let preimages = self.batch_hash_circuit.extract_hash_preimages();
256+
let preimages = self.batch_hash.extract_hash_preimages();
257257
end_timer!(timer);
258258

259259
let timer = start_timer!(|| ("load aux table").to_string());
@@ -330,51 +330,21 @@ impl Circuit<Fr> for AggregationCircuit {
330330
)?;
331331

332332
// ====================================================
333-
// Last step: Constraint the hash data matches the raw public input
333+
// Last step: Constraint the hash data matches the public input
334334
// ====================================================
335335
let acc_len = 12;
336336
{
337-
for i in 0..32 {
338-
// first_chunk_prev_state_root
339-
layouter.constrain_instance(
340-
hash_input_cells[2][PREV_STATE_ROOT_INDEX + i].cell(),
341-
config.instance,
342-
i + acc_len,
343-
)?;
344-
// last_chunk_post_state_root
345-
layouter.constrain_instance(
346-
hash_input_cells.last().unwrap()[POST_STATE_ROOT_INDEX + i].cell(),
347-
config.instance,
348-
i + 32 + acc_len,
349-
)?;
350-
// last_chunk_withdraw_root
351-
layouter.constrain_instance(
352-
hash_input_cells.last().unwrap()[WITHDRAW_ROOT_INDEX + i].cell(),
353-
config.instance,
354-
i + 64 + acc_len,
355-
)?;
356-
}
357337
// batch_public_input_hash
358338
for i in 0..4 {
359339
for j in 0..8 {
360340
// digest in circuit has a different endianness
361-
// 96 is the byte position for batch data hash
362341
layouter.constrain_instance(
363342
hash_output_cells[0][(3 - i) * 8 + j].cell(),
364343
config.instance,
365-
i * 8 + j + 96 + acc_len,
344+
i * 8 + j + acc_len,
366345
)?;
367346
}
368347
}
369-
// last 8 inputs are the chain id
370-
// chain_id is put at last here
371-
for i in 0..CHAIN_ID_LEN {
372-
layouter.constrain_instance(
373-
hash_input_cells[0][i].cell(),
374-
config.instance,
375-
128 + acc_len + i,
376-
)?;
377-
}
378348
}
379349

380350
end_timer!(witness_time);

aggregator/src/proof_aggregation/circuit_ext.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ impl CircuitExt<Fr> for AggregationCircuit {
77
fn num_instance(&self) -> Vec<usize> {
88
// accumulator [..lhs, ..rhs]
99
let acc_len = 4 * LIMBS;
10-
// public input
11-
let public_input_agg_instance_len = self.batch_hash_circuit.num_instance()[0];
12-
13-
vec![public_input_agg_instance_len + acc_len]
10+
// 32 elements for batch's public_input_hash
11+
vec![acc_len + 32]
1412
}
1513

1614
fn instances(&self) -> Vec<Vec<Fr>> {

aggregator/src/proof_aggregation/config.rs

+1-7
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,7 @@ pub struct AggregationConfig {
2929
pub keccak_circuit_config: KeccakCircuitConfig<Fr>,
3030
/// Instance for public input; stores
3131
/// - accumulator from aggregation (12 elements)
32-
/// - aggregated public inputs (136 elements):
33-
/// chunk\[0\].prev_state_root ||
34-
/// chunk\[k-1\].post_state_root ||
35-
/// chunk\[k-1\].withdraw_root ||
36-
/// batch_data_hash ||
37-
/// chain_id
38-
/// chain_id is put at last here for instance
32+
/// - batch_public_input_hash (32 elements)
3933
pub instance: Column<Instance>,
4034
}
4135

0 commit comments

Comments
 (0)