Skip to content

Commit 8f3e82a

Browse files
zhenfeizhanglightsingnoel2004lispckunxian-xia
authored
#711 + #713 remove num chunks from public input; relax the constraints for the dummy chunk proofs to avoid dummy chunk proofgen time (#712)
* partiall address review comments of #670 * disable disable_proof_agg by default * fix soundness issues with data hash * add fixed cells for chunk_is_valid * fix typo * well... need morning coffee * typo in readme * fix soundness issue in is_smaller_than * [feat] unify u8 u16 lookup table (#694) * add range table * replace Range256Table of rlp_circuit_fsm * replace u16_table of tx_circuit * use TableColumn * add type alias * use type alias * annotate lookup column * opt min_num_rows_block in poseidon_circuit (#700) prune debug prints and lint * speedup ci using verify_at_rows (#703) * speedup ci using verify_at_rows * use verify_at_rows to speedup super circuit ci tests * update super circuit row estimation API (#695) * remove num_of_chunks in aggregation circuit's instance column (#704) Co-authored-by: kunxian xia <[email protected]> * fix: lost tx_type when doing type conversion (#710) * fix condition (#708) * gates for zero checks * statement 1 is correct * reenable statements 3,6,7 * reenable statement 4 * everything seems to work again * update aggregation test accordingly * update spec * minor clean up * Fix fmt. * Make `ChunkHash` fields public. * fix decompose function * update figures * clean up * address comments * add is_final checks * update readme * constraint hash input length * fix clippy --------- Co-authored-by: Akase Cho <[email protected]> Co-authored-by: Ho <[email protected]> Co-authored-by: Zhang Zhuo <[email protected]> Co-authored-by: kunxian xia <[email protected]> Co-authored-by: Steven Gu <[email protected]>
1 parent 06a5172 commit 8f3e82a

14 files changed

+607
-379
lines changed

aggregator/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ snark-verifier-sdk = { git = "https://github.com/scroll-tech/snark-verifier", br
2525

2626

2727
[features]
28-
default = [ ]
28+
default = [ ]
2929
print-trace = [ "ark-std/print-trace" ]
3030
# This feature is useful for unit tests where we check the SAT of pi aggregation circuit
3131
disable_proof_aggregation = []

aggregator/README.md

+26-115
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,6 @@ Proof Aggregation
22
-----
33

44
![Architecture](./figures/architecture.jpg)
5-
<!--
6-
This repo does proof aggregations for zkEVM proofs.
7-
8-
## zkEVM circuit
9-
A zkEVM circuits generates a ZK proof for a chunk of blocks. It takes 64 field elements as its public input, consist of
10-
- chunk's data hash digest: each byte is encoded in an Fr element
11-
- chunk's public input hash digest: each byte is encoded in an Fr element
12-
The total size for a public input is 64 bytes, encoded in 64 Fr element
13-
14-
For the ease of testing, this repo implements a `MockCircuit` which hash same public input APIs as a zkEVM circuit.
15-
16-
## First compression circuit
17-
The first compression circuit takes in a fresh snark proof and generates a new (potentially small) snark proof.
18-
The public inputs to the new snark proof consists of
19-
- 12 elements from the accumulators
20-
- an accumulator consists of 2 G1 elements, which are the left and right inputs to the pairing
21-
- this is treated as 4 Fq elements, each decomposed into 3 limbs and encoded in Fr
22-
- 64 elements from previous snark
23-
- re-expose the same public inputs as the original snark
24-
25-
The first compression circuit is configured [wide config file](./configs/compression_wide.config).
26-
27-
## Second compression circuit
28-
29-
The second compression circuit takes in a compressed snark proof and generates a new (potentially small) snark proof.
30-
The public inputs to the new snark proof consists of
31-
- 12 elements from the accumulators
32-
- an accumulator consists of 2 G1 elements, which are the left and right inputs to the pairing
33-
- this is treated as 4 Fq elements, each decomposed into 3 limbs and encoded in Fr
34-
- accumulator from the previous snark is accumulated into the current accumulator
35-
- 64 elements from previous snark
36-
- skipping the first 12 elements which are previous accumulator, as they are already accumulated
37-
- re-expose the rest 64 field elements as the public inputs
38-
39-
The second compression circuit is configured [thin config file](./configs/compression_thin.config).
40-
41-
## Aggregation circuit
42-
An aggregation circuit takes in a batch of `k` proofs, each for a chunk of blocks.
43-
It generates a single proof asserting the validity of all the proofs.
44-
45-
It also performs public input aggregation, i.e., reducing the `64k` public elements into a fixed number of `144` elements:
46-
- 12 elements from accumulators, which accumulates all the previous `k` accumulators from each snark
47-
- 132 elements from the hashes
48-
- first_chunk_prev_state_root: 32 Field elements
49-
- last_chunk_post_state_root: 32 Field elements
50-
- last_chunk_withdraw_root: 32 Field elements
51-
- batch_public_input_hash: 32 Field elements
52-
- chain_id: 8 Field elements
53-
54-
In addition, it attests that, for chunks indexed from `0` to `k-1`,
55-
- batch_data_hash := keccak(chunk_0.data_hash || ... || chunk_k-1.data_hash) where chunk_i.data_hash is a public input to the i-th batch snark circuit
56-
- chunk_pi_hash := keccak(chain_id || prev_state_root || post_state_root || withdraw_root || chunk_data_hash) where chunk_data_hash is a public input to the i-th batch snark circuit
57-
- and the related field matches public input
58-
59-
See [public input aggregation](./src/proof_aggregation/public_input_aggregation.rs) for the details of public input aggregation. -->
60-
61-
<!-- # Spec for Dynamic aggregator -->
62-
635
# Params
646
|param|meaning |
657
|:---:|:---|
@@ -93,17 +35,17 @@ c_i.post_state_root == c_{i+1}.prev_state_root
9335
```
9436
for $i \in [1, k-1]$.
9537

96-
## Empty chunk
97-
An __empty chunk__ is a chunk that does not contain any transactions. It is used for padding.
98-
If $k< n$, $(n-k)$ empty chunks are padded to the list. An empty chunk has the same data fields as a real chunk, and the parameters are set as
99-
- state root before this chunk: `c_k.post_state_root`
100-
- state root after this chunk: `c_k.post_state_root`
101-
- the withdraw root of this chunk: `c_k.withdraw_root`
102-
- the data hash of this chunk: `keccak("")`
38+
## Padded chunk
39+
A __padded chunk__ is a chunk that repeats last valid chunk. It is used for padding.
40+
If $k< n$, $(n-k)$ padded chunks are padded to the list. A padded chunk has the same data fields as the last real chunk, and the parameters are set as
41+
- state root before this chunk: `c_{k}.prev_state_root`
42+
- state root after this chunk: `c_{k}.post_state_root`
43+
- the withdraw root of this chunk: `c_{k}.withdraw_root`
44+
- the data hash of this chunk: `c_{k}.data_hash`
10345

10446
## Batch
10547

106-
A __batch__ consists of continuous chunks of size `n`. If the input chunks' size `k` is less than `n`, we pad the input with `(n-k)` empty chunks using the above logic.
48+
A __batch__ consists of continuous chunks of size `k`. If the input chunks' size `k` is less than `n`, we pad the input with `(n-k)` chunks identical to `chunk[k]`.
10749

10850
# Circuits
10951

@@ -114,20 +56,16 @@ Circuit proving the relationship for a chunk is indeed the zkEVM circuit. It wil
11456
- 12 from accumulators
11557
- 32 from public input hash
11658

117-
## Empty chunk circuit
118-
An empty chunk circuit also takes 44 elements as public inputs.
119-
In our design it is curial that __a same circuit__ is used for both real chunk circuit and empty chunk circuit. In other words, an empty chunk circuit will also go through the same compressions before it is aggregated.
120-
12159

12260
![Architecture](./figures/hashes.jpg)
12361

12462
## Aggregation Circuit
12563

126-
We want to aggregate `k` snarks, each from a valid chunk. We generate `(n-k)` empty chunks, and obtain a total of `n` snarks.
64+
We want to aggregate `k` snarks, each from a valid chunk. We generate `(n-k)` padded chunks, and obtain a total of `n` snarks.
12765

128-
In the above example, we have `k = 2` valid chunks, and `2` empty chunks.
66+
In the above example, we have `k = 2` valid chunks, and `2` padded chunks.
12967

130-
> Interlude: we just need to generate 1 empty snark, and the rest `n-k-1` will be identical for the same batch. We cannot pre-compute it though, as the witness `c_k.post_state_root` and `c_k.withdraw_root` are batch dependent.
68+
The padded snarks are identical the the last valid snark, so the aggregator does not need to generate snarks for padded chunks.
13169

13270
### Configuration
13371

@@ -140,7 +78,6 @@ There will be three configurations for Aggregation circuit.
14078
The public input of the aggregation circuit consists of
14179
- 12 elements from accumulator
14280
- 32 elements of `batch_pi_hash`
143-
- 1 element of `k`
14481

14582
### Statements
14683
For snarks $s_1,\dots,s_k,\dots, s_n$ the aggregation circuit argues the following statements.
@@ -162,9 +99,9 @@ for i in 1 ... __n__
16299

163100
This is done by compute the RLCs of chunk[i]'s data_hash for `i=0..k`, and then check the RLC matches the one from the keccak table.
164101

165-
4. chunks are continuous: they are linked via the state roots. __Static__.
102+
4. chunks are continuous when they are not padded: they are linked via the state roots.
166103

167-
for i in 1 ... __n-1__
104+
for i in 1 ... __k-1__
168105
```
169106
c_i.post_state_root == c_{i+1}.prev_state_root
170107
```
@@ -175,17 +112,18 @@ for i in 1 ... __n__
175112
batch.chain_id == chunk[i].chain_id
176113
```
177114

178-
6. The last `(n-k)` chunk[i]'s prev_state_root == post_state_root when chunk[i] is padded
115+
6. The last `(n-k)` chunk[i] are padding
179116
```
180117
for i in 1 ... n:
181-
is_padding = (i > k) // k is a public input
182118
if is_padding:
183-
chunk_i.prev_state_root == chunk_i.post_state_root
184-
chunk_i.withdraw_root == chunk_{i-1}.withdraw_root
185-
chunk_i.data_hash == [0u8; 32]
119+
chunk[i]'s chunk_pi_hash_rlc_cells == chunk[i-1].chunk_pi_hash_rlc_cells
186120
```
187-
7. chunk[i]'s data_hash len is `0` when chunk[i] is padded
188-
121+
This is done via comparing the `data_rlc` of `chunk_{i-1}` and ` chunk_{i}`.
122+
7. the hash input length are correct
123+
- first MAX_AGG_SNARKS + 1 hashes all have 136 bytes input
124+
- batch's data_hash length is 32 * number_of_valid_snarks
125+
8. batch data hash is correct w.r.t. its RLCs
126+
9. is_final_cells are set correctly
189127

190128
### Handling dynamic inputs
191129

@@ -201,11 +139,13 @@ Suppose we target for `MAX_AGG_SNARK = 10`. Then, the last hash function will ta
201139
We also know in the circuit if a chunk is an empty one or not. This is given by a flag `is_padding`.
202140

203141
For the input of the final data hash
204-
- we extract `32 * MAX_AGG_SNARK` number of cells (__static__ here) from the last hash. We then compute the RLC of those `32 * MAX_AGG_SNARK` when the corresponding `is_padding` is not set. We constraint this RLC matches the `data_rlc` from the keccak table.
205-
142+
- we extract `32 * MAX_AGG_SNARK` number of cells (__static__ here) from the last hash. We then compute the RLC of those `32 * MAX_AGG_SNARK` when the corresponding `is_padding` is not set. We constrain this RLC matches the `data_rlc` from the keccak table.
206143

207144
For the output of the final data hash
208145
- we extract all three hash digest cells from last 3 rounds. We then constraint that the actual data hash matches one of the three hash digest cells with proper flags defined as follows.
146+
- if the num_of_valid_snarks <= 4, which only needs 1 keccak-f round. Therefore the batch's data hash (input, len, data_rlc, output_rlc) are in the first 300 keccak rows;
147+
- else if the num_of_valid_snarks <= 8, which needs 2 keccak-f rounds. Therefore the batch's data hash (input, len, data_rlc, output_rlc) are in the 2nd 300 keccak rows;
148+
- else the num_of_valid_snarks <= 12, which needs 3 keccak-f rounds. Therefore the batch's data hash (input, len, data_rlc, output_rlc) are in the 3rd 300 keccak rows;
209149

210150
|#valid snarks | offset of data hash | flags|
211151
|---| ---| ---|
@@ -214,33 +154,4 @@ For the output of the final data hash
214154
|9,10 | 64 | 0, 0, 1|
215155

216156
Additional checks for dummy chunk
217-
- if `is_padding` for `i`-th chunk, we constrain `chunk[i].prev_state_root = chunk[i].post_state_root`
218-
- if `is_padding` for `i`-th chunk, we constrain `chunk[i-1].withdraw_root = chunk[i].withdraw_root`
219-
- if `is_padding` for `i`-th chunk, we constrain `chunk[i-1].data_hash.len() == 0`
220-
221-
<!--
222-
1. Extact the final `data_rlc` cell from each round. There are maximum $t$ of this, denoted by $r_1,\dots r_t$
223-
- __caveat__: will need to make sure the circuit is padded as if there are $t$ rounds, if the actual number of rounds is less than $t$. This is done by keccak table already:
224-
all columns of keccak table are padded to `1<<LOG_DEGREE` by construction (__need to double check this is circuit dependent__)
225-
2. Extract a challenge and then compute `rlc:= RLC(chunk_1.data_hash || ... || chunk_k.data_hash)` using a __phase 2__ column
226-
3. assert `rlc` is valid via a lookup argument
227-
- constrain `rlc` cell is within the "data_rlc" column of keccak table via standard lookup API
228-
- potential optimization: avoid using lookup API. There is only $t$ elements as $rlc \in \{r_1,\dots r_t\}$ and we may check equality one by one.
229-
-->
230-
231-
<!--
232-
Circuit witnesses:
233-
- a list of k __real__ CHUNKs, each with 44 elements of public inputs (12 from accumulators and
234-
32 from public input hash)
235-
-
236-
- Those 4 hashes are obtained from the caller.
237-
- It's public input hash is
238-
- chunk_pi_hash := keccak(chain_id || prev_state_root || post_state_root || withdraw_root ||
239-
chunk_data_hash)
240-
Circuit public inputs:
241-
- an accumulator of 12 elements
242-
- a batch public input hash of 32 elements
243-
- the value k, 1 element
244-
245-
The aggregation circuit aggregates MAX_AGG_NUM snarks.
246-
If k < MAX_AGG_NUM, dummy snarks will be padded -->
157+
- if `is_padding` for `i`-th chunk, we constrain `chunk[i]'s chunk_pi_hash_rlc_cells == chunk[i-1].chunk_pi_hash_rlc_cells`

aggregator/figures/architecture.jpg

315 KB
Loading

aggregator/figures/hashes.jpg

-1.15 KB
Loading

aggregator/src/aggregation/circuit.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ impl Circuit<Fr> for AggregationCircuit {
249249

250250
let timer = start_timer!(|| "load aux table");
251251

252-
let (hash_digest_cells, num_valid_snarks) = {
252+
let hash_digest_cells = {
253253
config
254254
.keccak_circuit_config
255255
.load_aux_tables(&mut layouter)?;
@@ -269,16 +269,22 @@ impl Circuit<Fr> for AggregationCircuit {
269269
end_timer!(timer);
270270

271271
let timer = start_timer!(|| ("assign hash cells").to_string());
272-
let (hash_digest_cells, num_valid_snarks) = assign_batch_hashes(
272+
let chunks_are_valid = self
273+
.batch_hash
274+
.chunks_with_padding
275+
.iter()
276+
.map(|chunk| !chunk.is_padding)
277+
.collect::<Vec<_>>();
278+
let hash_digest_cells = assign_batch_hashes(
273279
&config,
274280
&mut layouter,
275281
challenges,
282+
&chunks_are_valid,
276283
&preimages,
277-
self.batch_hash.number_of_valid_chunks,
278284
)
279285
.map_err(|_e| Error::ConstraintSystemFailure)?;
280286
end_timer!(timer);
281-
(hash_digest_cells, num_valid_snarks)
287+
hash_digest_cells
282288
};
283289
// digests
284290
let (batch_pi_hash_digest, chunk_pi_hash_digests, _potential_batch_data_hash_digest) =
@@ -331,6 +337,8 @@ impl Circuit<Fr> for AggregationCircuit {
331337
);
332338

333339
region.constrain_equal(
340+
// in the keccak table, the input and output date have different
341+
// endianess
334342
chunk_pi_hash_digests[i][j * 8 + k].cell(),
335343
snark_inputs[i * DIGEST_LEN + (3 - j) * 8 + k].cell(),
336344
)?;
@@ -371,8 +379,6 @@ impl Circuit<Fr> for AggregationCircuit {
371379
}
372380
}
373381

374-
log::trace!("number of valid snarks: {:?}", num_valid_snarks.value());
375-
376382
end_timer!(witness_time);
377383
Ok(())
378384
}

aggregator/src/aggregation/config.rs

+2
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ impl AggregationConfig {
101101
meta.enable_equality(keccak_circuit_config.keccak_table.input_rlc);
102102
// enable equality for the input data len column
103103
meta.enable_equality(keccak_circuit_config.keccak_table.input_len);
104+
// enable equality for the is_final column
105+
meta.enable_equality(keccak_circuit_config.keccak_table.is_final);
104106

105107
// Instance column stores public input column
106108
// - the accumulator

aggregator/src/aggregation/rlc/config.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ impl RlcConfig {
6464
let q2 = meta.query_selector(enable_challenge);
6565
let cs2 = q2 * (a - challenge_expr.keccak_input());
6666

67-
vec![cs1 + cs2]
67+
vec![cs1, cs2]
6868
});
6969
Self {
7070
#[cfg(test)]

0 commit comments

Comments
 (0)