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

Commit e76e424

Browse files
committed
[refactor] pi aggregation circuit
1 parent c4cda3f commit e76e424

24 files changed

+1041
-766
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
*.log
77
*.json
88
*.sh
9-
*.txt
9+
*.txt
10+
*.srs

Cargo.lock

Lines changed: 50 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ members = [
1111
"eth-types",
1212
"external-tracer",
1313
"mock",
14-
"testool"
14+
"testool",
15+
"aggregator"
1516
]
1617

1718
[patch."https://github.com/privacy-scaling-explorations/halo2.git"]

aggregator/Cargo.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[package]
2+
name = "aggregator"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
eth-types = { path = "../eth-types" }
10+
zkevm-circuits = { path = "../zkevm-circuits" }
11+
ethers-core = "0.17.0"
12+
rand = "0.8"
13+
ark-std = "0.4.0"
14+
log = "0.4"
15+
env_logger = "0.10.0"
16+
17+
halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2.git", tag = "v2023_02_02" }
18+
snark-verifier-sdk = { git = "https://github.com/scroll-tech/snark-verifier", branch = "halo2-ecc-snark-verifier-0323" }
19+
20+
[dev-dependencies]
21+
halo2-base = { git = "https://github.com/scroll-tech/halo2-lib", branch = "halo2-ecc-snark-verifier-0323", default-features=false, features=["halo2-pse","display"] }
22+
snark-verifier = { git = "https://github.com/scroll-tech/snark-verifier", branch = "halo2-ecc-snark-verifier-0323" }
23+
24+
25+
[features]
26+
default = []
27+
# default = [ "ark-std/print-trace" ]

aggregator/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// public input aggregation
2+
mod public_input_aggregation;
3+
/// utilities
4+
mod util;
5+
6+
pub use public_input_aggregation::*;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//! This module implements circuits that aggregates public inputs of many chunks into a single
2+
//! 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+
//! - chunk_pi_hash := keccak(chain_id || prev_state_root || post_state_root || withdraw_root
15+
//! || chunk_data_hash)
16+
//!
17+
//! A batch is a list of continuous chunks. It consists of 2 hashes
18+
//! - batch_data_hash := keccak(chunk_0.data_hash || ... ||
19+
//! chunk_k-1.data_hash)
20+
//!
21+
//! - batch_pi_hash := keccak(chain_id || chunk_0.prev_state_root ||
22+
//! chunk_k-1.post_state_root || chunk_k-1.withdraw_root || batch_data_hash)
23+
//!
24+
//! Note that chain_id is used for all public input hashes. But not for any data hashes.
25+
//!
26+
//! # Circuit
27+
//!
28+
//! A BatchHashCircuit asserts that the batch is well-formed.
29+
//!
30+
//! ## Public Input
31+
//! The public inputs of the circuit (132 Field elements) is constructed as
32+
//! - first_chunk_prev_state_root: 32 Field elements
33+
//! - last_chunk_post_state_root: 32 Field elements
34+
//! - last_chunk_withdraw_root: 32 Field elements
35+
//! - batch_public_input_hash: 32 Field elements
36+
//! - chain_id: 4 Field elements
37+
//!
38+
//! ## Constraints
39+
//! The circuit attests the following statements:
40+
//!
41+
//! 1. all hashes are computed correctly
42+
//! 2. the relations between hash preimages and digests are satisfied
43+
//! - batch_data_hash is part of the input to compute batch_pi_hash
44+
//! - batch_pi_hash used same roots as chunk_pi_hash
45+
//! - same data_hash is used to compute batch_data_hash and chunk_pi_hash for all chunks
46+
//! - chunks are continuous: they are linked via the state roots
47+
//! - all hashes uses a same chain_id
48+
//! 3. the hash data matches the circuit's public input (132 field elements) above
49+
//!
50+
//! # Example
51+
//!
52+
//! See tests::test_pi_aggregation_circuit
53+
54+
// This module implements `Chunk` related data types.
55+
// A chunk is a list of blocks.
56+
mod chunk;
57+
// This module implements `Batch` related data types.
58+
// A batch is a list of chunk.
59+
mod batch;
60+
// Circuit implementation of `BatchHashCircuit`.
61+
mod circuit;
62+
// SubCircuit implementation of `BatchHashCircuit`.
63+
mod sub_circuit;
64+
// CircuitExt implementation of `BatchHashCircuit`.
65+
mod circuit_ext;
66+
// Circuit and SubCircuit configurations
67+
mod config;
68+
69+
pub use batch::BatchHash;
70+
pub use chunk::ChunkHash;
71+
pub use circuit::{BatchHashCircuit, BatchHashCircuitPublicInput};
72+
pub use config::{BatchCircuitConfig, BatchCircuitConfigArgs};
73+
74+
// TODO(ZZ): update to the right degree
75+
pub(crate) const LOG_DEGREE: u32 = 19;
76+
77+
// Each round requires (NUM_ROUNDS+1) * DEFAULT_KECCAK_ROWS = 300 rows.
78+
// This library is hard coded for this parameter.
79+
// Modifying the following parameters may result into bugs.
80+
// Adopted from keccak circuit
81+
pub(crate) const DEFAULT_KECCAK_ROWS: usize = 12;
82+
// Adopted from keccak circuit
83+
pub(crate) const NUM_ROUNDS: usize = 24;
84+
85+
#[cfg(test)]
86+
mod tests;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use eth_types::H256;
2+
use ethers_core::utils::keccak256;
3+
4+
use super::chunk::ChunkHash;
5+
6+
#[derive(Default, Debug, Clone)]
7+
/// A batch is a set of continuous chunks.
8+
/// A BatchHash consists of 2 hashes.
9+
pub struct BatchHash {
10+
pub(crate) data_hash: H256,
11+
pub(crate) public_input_hash: H256,
12+
}
13+
14+
impl BatchHash {
15+
/// Build Batch hash from a list of chunks
16+
pub(crate) fn construct(chunk_hashes: &[ChunkHash]) -> Self {
17+
// sanity: the chunks are continuous
18+
for i in 0..chunk_hashes.len() - 1 {
19+
assert_eq!(
20+
chunk_hashes[i].post_state_root,
21+
chunk_hashes[i + 1].prev_state_root,
22+
);
23+
assert_eq!(chunk_hashes[i].chain_id, chunk_hashes[i + 1].chain_id,)
24+
}
25+
26+
// batch's data hash is build as
27+
// keccak( chunk[0].data_hash || ... || chunk[k-1].data_hash)
28+
let preimage = chunk_hashes
29+
.iter()
30+
.flat_map(|chunk_hash| chunk_hash.data_hash.0.iter())
31+
.cloned()
32+
.collect::<Vec<_>>();
33+
let data_hash = keccak256(preimage);
34+
35+
// public input hash is build as
36+
// keccak(
37+
// chain_id ||
38+
// chunk[0].prev_state_root ||
39+
// chunk[k-1].post_state_root ||
40+
// chunk[k-1].withdraw_root ||
41+
// batch_data_hash )
42+
let preimage = [
43+
chunk_hashes[0].chain_id.to_le_bytes().as_ref(),
44+
chunk_hashes[0].prev_state_root.as_bytes(),
45+
chunk_hashes.last().unwrap().post_state_root.as_bytes(),
46+
chunk_hashes.last().unwrap().withdraw_root.as_bytes(),
47+
data_hash.as_slice(),
48+
]
49+
.concat();
50+
let public_input_hash = keccak256(preimage);
51+
52+
Self {
53+
data_hash: data_hash.into(),
54+
public_input_hash: public_input_hash.into(),
55+
}
56+
}
57+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//! This module implements `Chunk` related data types.
2+
//! A chunk is a list of blocks.
3+
use eth_types::H256;
4+
use ethers_core::utils::keccak256;
5+
6+
#[derive(Default, Debug, Clone)]
7+
/// A chunk is a set of continuous blocks.
8+
/// A ChunkHash consists of 4 hashes, representing the changes incurred by this chunk of blocks:
9+
/// - state root before this chunk
10+
/// - state root after this chunk
11+
/// - the withdraw root of this chunk
12+
/// - the data hash of this chunk
13+
pub struct ChunkHash {
14+
/// Chain identifier
15+
pub(crate) chain_id: u32,
16+
/// state root before this chunk
17+
pub(crate) prev_state_root: H256,
18+
/// state root after this chunk
19+
pub(crate) post_state_root: H256,
20+
/// the withdraw root of this chunk
21+
pub(crate) withdraw_root: H256,
22+
/// the data hash of this chunk
23+
pub(crate) data_hash: H256,
24+
}
25+
26+
impl ChunkHash {
27+
/// Sample a chunk hash from random (for testing)
28+
#[cfg(test)]
29+
pub(crate) fn mock_chunk_hash<R: rand::RngCore>(r: &mut R) -> Self {
30+
let mut prev_state_root = [0u8; 32];
31+
r.fill_bytes(&mut prev_state_root);
32+
let mut post_state_root = [0u8; 32];
33+
r.fill_bytes(&mut post_state_root);
34+
let mut withdraw_root = [0u8; 32];
35+
r.fill_bytes(&mut withdraw_root);
36+
let mut data_hash = [0u8; 32];
37+
r.fill_bytes(&mut data_hash);
38+
Self {
39+
chain_id: 0,
40+
prev_state_root: prev_state_root.into(),
41+
post_state_root: post_state_root.into(),
42+
withdraw_root: withdraw_root.into(),
43+
data_hash: data_hash.into(),
44+
}
45+
}
46+
47+
/// Public input hash for a given chunk is defined as
48+
/// keccak( chain id || prev state root || post state root || withdraw root || data hash )
49+
pub fn public_input_hash(&self) -> H256 {
50+
let preimage = [
51+
self.chain_id.to_le_bytes().as_ref(),
52+
self.prev_state_root.as_bytes(),
53+
self.post_state_root.as_bytes(),
54+
self.withdraw_root.as_bytes(),
55+
self.data_hash.as_bytes(),
56+
]
57+
.concat();
58+
keccak256::<&[u8]>(preimage.as_ref()).into()
59+
}
60+
}

0 commit comments

Comments
 (0)