Skip to content

Commit

Permalink
docs: add compression circuit outline
Browse files Browse the repository at this point in the history
  • Loading branch information
LHerskind committed Feb 14, 2024
1 parent 5f5bf16 commit d2faa06
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 33 deletions.
2 changes: 1 addition & 1 deletion yarn-project/circuits.js/src/structs/aggregation_object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { UInt32, Vector } from './shared.js';
import { G1AffineElement } from './verification_key.js';

/**
* Contains the aggregated proof of all the previous kernel iterations.
* Contains the aggregated elements to be used as public inputs for delayed final verification.
*
* See barretenberg/cpp/src/barretenberg/stdlib/recursion/aggregation_state/native_aggregation_state.hpp
* for more context.
Expand Down
5 changes: 1 addition & 4 deletions yellow-paper/docs/l1-smart-contracts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,10 +287,7 @@ As mentioned earlier, this is done to ensure that the messages are not used to D
Since we will be building the tree on L1, we need to use a gas-friendly hash-function such as SHA256.
However, as we need to allow users to prove inclusion in this tree, we cannot just insert the SHA256 tree into the rollup state, it requires too many constraints to be used by most small users.
Therefore, we need to "convert" the tree into a tree using a more snark-friendly hash.
This part is done in a to-be-defined circuit.
:::info TODO
Write about the `MessageCompression` circuits
:::
This part is done in the [message compression circuits](./../rollup-circuits/message-compression.md).

Furthermore, to build the tree on L1, we need to put some storage on L1 such that the insertions don't need to provide a lot of merkle-related data which could be cumbersome to do and prone to race-conditions.
For example two insertions based on inclusion paths that are created at the same time will invalidate each other.
Expand Down
103 changes: 81 additions & 22 deletions yellow-paper/docs/rollup-circuits/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,25 @@ title: Rollup Circuits
Together with the [validating light node](../l1-smart-contracts/index.md) the rollup circuits must ensure that incoming blocks are valid, that state is progressed correctly and that anyone can rebuild the state.

To support this, we construct a single proof for the entire block, which is then verified by the validating light node.
This single proof is constructed by recursively merging proofs together in a binary tree structure.
This single proof is constructed by recursively merging proofs together in two tree subtrees, and then combine these use those trees at the root .
This structure allows us to keep the workload of each individual proof small, while making it very parallelizable.
This works very well for the case where we want many actors to be able to participate in the proof generation.

The tree structure is outlined below, but the general idea is that we have a tree where all the leaves are transactions (kernel proofs) and through $\log(n)$ steps we can then "compress" them down to just a single root proof.
Note that we have two different types of "merger" circuit, namely:
The tree structure is outlined below, but the general idea is that we have one subtree tree where all the leaves are transactions (kernel proofs) and through $\log(n)$ steps we can then "compress" them down to just a single root proof.
And another tree where all the leaves are messages (cross-chain messages) that is also "compressed" down to just a single proof going into the final root.

Note that we have two different types of "merger" circuit, depending on what they are compressing.

For transaction compression we have:
- The merge rollup
- Merges two base rollup proofs OR two merge rollup proofs
- The root rollup
- Merges two merge rollup proofs

And for the message compression we have:
- The compress circuit
- Merges `N` compress or base compression proofs

In the diagram the size of the tree is limited for show, but a larger tree will have more layers of merge rollups proofs.
Circles mark the different types of proofs, while squares mark the different circuit types.

Expand Down Expand Up @@ -77,6 +84,54 @@ graph BT
style K1 fill:#1976D2;
style K2 fill:#1976D2;
style K3 fill:#1976D2;
R --> R_c
R((compress 4))
T0[compress_leaf]
T1[compress_leaf]
T2[compress_leaf]
T3[compress_leaf]
T0_P((compress 0))
T1_P((compress 1))
T2_P((compress 2))
T3_P((compress 3))
T4[compress]
I0 --> T0
I1 --> T1
I2 --> T2
I3 --> T3
T0 --> T0_P
T1 --> T1_P
T2 --> T2_P
T3 --> T3_P
T0_P --> T4
T1_P --> T4
T2_P --> T4
T3_P --> T4
T4 --> R
I0((MSG 0-3))
I1((MSG 4-7))
I2((MSG 8-11))
I3((MSG 12-15))
style R fill:#1976D2;
style T0_P fill:#1976D2;
style T1_P fill:#1976D2;
style T2_P fill:#1976D2;
style T3_P fill:#1976D2;
style I0 fill:#1976D2;
style I1 fill:#1976D2;
style I2 fill:#1976D2;
style I3 fill:#1976D2;
```

To understand what the circuits are doing and what checks they need to apply it is useful to understand what data is going into the circuits and what data is coming out.
Expand Down Expand Up @@ -313,37 +368,41 @@ class MergeRollupInputs {
MergeRollupInputs *-- ChildRollupData: left
MergeRollupInputs *-- ChildRollupData: right
class CompressBaseInputs {
msgs: List~Fr[2]~
}
class CompressPublicInput {
aggregation_object: AggregationObject
sha_root: Fr[2]
converted_root: Fr
}
class CompressInput {
msgs: List~CompressPublicInput~
}
CompressInput *-- CompressPublicInput: msgs
class TreeConversionData {
proof: Proof
public_inputs: CompressPublicInput
}
TreeConversionData *-- CompressPublicInput: public_inputs
class RootRollupInputs {
l1_to_l2_roots: MessageCompressionBaseOrMergePublicInputs
l1_to_l2_roots: TreeConversionData
l1_to_l2_msgs_sibling_path: List~Fr~
parent: Header,
parent_sibling_path: List~Fr~
archive_sibling_path: List~Fr~
left: ChildRollupData
right: ChildRollupData
}
RootRollupInputs *-- MessageCompressionBaseOrMergePublicInputs: l1_to_l2_roots
RootRollupInputs *-- TreeConversionData: l1_to_l2_roots
RootRollupInputs *-- ChildRollupData: left
RootRollupInputs *-- ChildRollupData: right
RootRollupInputs *-- Header : parent
class MessageCompressionBaseInputs {
l1_to_l2_msgs: List~Fr~
}
class MessageCompressionBaseOrMergePublicInputs {
sha_root: Fr[2]
converted_root: Fr
}
class MessageCompressionMergeInputs {
left: MessageCompressionBaseInputs
right: MessageCompressionBaseInputs
}
MessageCompressionMergeInputs *-- MessageCompressionBaseOrMergePublicInputs: left
MessageCompressionMergeInputs *-- MessageCompressionBaseOrMergePublicInputs: right
class RootRollupPublicInputs {
aggregation_object: AggregationObject
archive: Snapshot
Expand Down
120 changes: 120 additions & 0 deletions yellow-paper/docs/rollup-circuits/message-compression.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
title: Message Compression
---

To support easy consumption of l1 to l2 messages inside the proofs, we need to convert the tree of messages to a snark-friendly format.

If you recall back in [L1 smart contracts](./../l1-smart-contracts/index.md#inbox) we were building a message tree on the L1 - which meant that we were using SHA256.
As SHA256 is not snark-friendly, weak devices would not be able to prove inclusion of messages in the tree.

This circuit is responsible for converting the tree such that users can easily build the proofs.
We essentially use this circuit to front-load the work needed to prove the inclusion of messages in the tree.
As earlier we are using a tree-like structure.

```mermaid
graph BT
R((compress 4))
T0[compress_leaf]
T1[compress_leaf]
T2[compress_leaf]
T3[compress_leaf]
T0_P((compress 0))
T1_P((compress 1))
T2_P((compress 2))
T3_P((compress 3))
T4[compress]
I0 --> T0
I1 --> T1
I2 --> T2
I3 --> T3
T0 --> T0_P
T1 --> T1_P
T2 --> T2_P
T3 --> T3_P
T0_P --> T4
T1_P --> T4
T2_P --> T4
T3_P --> T4
T4 --> R
I0((MSG 0-3))
I1((MSG 4-7))
I2((MSG 8-11))
I3((MSG 12-15))
style R fill:#1976D2;
style T0_P fill:#1976D2;
style T1_P fill:#1976D2;
style T2_P fill:#1976D2;
style T3_P fill:#1976D2;
style I0 fill:#1976D2;
style I1 fill:#1976D2;
style I2 fill:#1976D2;
style I3 fill:#1976D2;
```

Practically, this will be using two circuits, similar to how we have the `base` and `merge` circuits for transactions.
The output of the "combined" circuit will be the `converted_root` which is the root of the snark-friendly message tree.
And the `sha_root` which must match the root of the sha256 message tree from the L1 Inbox.
The circuit must simply compute the two trees using the same inputs, and then we ensure that the elements of the trees match the inbox later at the [state transitioner](./../l1-smart-contracts/index.md#overview).


```mermaid
classDiagram
direction LR
class CompressPublicInput {
aggregation_object: AggregationObject
sha_root: Fr[2]
converted_root: Fr
}
class CompressInput {
msgs: List~TreeConversionData~
}
CompressInput *-- TreeConversionData: msgs
class TreeConversionData {
proof: Proof
public_inputs: CompressPublicInput
}
TreeConversionData *-- CompressPublicInput: public_inputs
class CompressBaseInputs {
msgs: List~Fr[2]~
}
```

The computations performed by the circuits are quite simple, they simple need to progress building two trees, the sha tree and the snark-friendly tree.
For optimization purposes, it can be useful to have the layers take more than 2 inputs to increase the task of every layer.
If each just take 2 inputs, the overhead of recursing through the layers might be higher than the actual work done.

```python
def compress_leaf(msgs: List[Fr[2]]) -> CompressPublicInput:
sha_root = MERKLE_TREE(msgs, SHA256);
converted_root MERKLE_TREE(msgs, SNARK_FRIENDLY_HASH_FUNCTION);
return CompressPublicInput(sha_root, converted_root)

def compress(msgs: List[TreeConversionData]) -> CompressPublicInput:
for msg in msgs:
assert msg.proof.verify(msg.public_inputs);

sha_root = MERKLE_TREE(
[msg.public_inputs.sha_root for msg in msgs],
SHA256
);
converted_root = MERKLE_TREE(
[msg.public_inputs.converted_root for msg in msgs],
SNARK_FRIENDLY_HASH_FUNCTION
);
return CompressPublicInput(sha_root, converted_root)
```


29 changes: 23 additions & 6 deletions yellow-paper/docs/rollup-circuits/root-rollup.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,21 +130,37 @@ class ChildRollupData {
}
ChildRollupData *-- BaseOrMergeRollupPublicInputs: public_inputs
class MessageCompressionBaseOrMergePublicInputs {
class CompressBaseInputs {
msgs: List~Fr[2]~
}
class CompressPublicInput {
aggregation_object: AggregationObject
sha_root: Fr[2]
converted_root: Fr
}
class CompressInput {
msgs: List~CompressPublicInput~
}
CompressInput *-- CompressPublicInput: msgs
class TreeConversionData {
proof: Proof
public_inputs: CompressPublicInput
}
TreeConversionData *-- CompressPublicInput: public_inputs
class RootRollupInputs {
l1_to_l2_roots: MessageCompressionBaseOrMergePublicInputs
l1_to_l2_roots: TreeConversionData
l1_to_l2_msgs_sibling_path: List~Fr~
parent: Header,
parent_sibling_path: List~Fr~
archive_sibling_path: List~Fr~
left: ChildRollupData
right: ChildRollupData
}
RootRollupInputs *-- MessageCompressionBaseOrMergePublicInputs: l1_to_l2_roots
RootRollupInputs *-- TreeConversionData: l1_to_l2_roots
RootRollupInputs *-- ChildRollupData: left
RootRollupInputs *-- ChildRollupData: right
RootRollupInputs *-- Header : parent
Expand All @@ -161,7 +177,7 @@ RootRollupPublicInputs *--Header : header

```python
def RootRollupCircuit(
l1_to_l2_roots: MessageCompressionBaseOrMergePublicInputs,
l1_to_l2_roots: TreeConversionData,
l1_to_l2_msgs_sibling_path: List[Fr],
parent: Header,
parent_sibling_path: List[Fr],
Expand All @@ -171,6 +187,7 @@ def RootRollupCircuit(
) -> RootRollupPublicInputs:
assert left.proof.is_valid(left.public_inputs)
assert right.proof.is_valid(right.public_inputs)
assert l1_to_l2_roots.proof.verify(l1_to_l2_roots.public_inputs)

assert left.public_inputs.constants == right.public_inputs.constants
assert left.public_inputs.end == right.public_inputs.start
Expand All @@ -190,7 +207,7 @@ def RootRollupCircuit(
# Update the l1 to l2 msg tree
l1_to_l2_msg_tree = merkle_insertion(
parent.state.l1_to_l2_message_tree,
l1_to_l2_roots.converted_root,
l1_to_l2_roots.public_inputs.converted_root,
l1_to_l2_msgs_sibling_path,
L1_TO_L2_SUBTREE_HEIGHT,
L1_To_L2_HEIGHT
Expand All @@ -201,7 +218,7 @@ def RootRollupCircuit(
content_commitment: ContentCommitment(
tx_tree_height = left.public_inputs.height_in_block_tree + 1,
tsx_hash = SHA256(left.public_inputs.txs_hash | right.public_inputs.txs_hash),
in_hash = l1_to_l2_roots.sha_root,
in_hash = l1_to_l2_roots.public_inputs.sha_root,
out_hash = SHA256(left.public_inputs.out_hash | right.public_inputs.out_hash),
),
state = StateReference(
Expand Down
1 change: 1 addition & 0 deletions yellow-paper/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ const sidebars = {
items: [
"rollup-circuits/base-rollup",
"rollup-circuits/merge-rollup",
"rollup-circuits/message-compression",
"rollup-circuits/root-rollup",
],
},
Expand Down

0 comments on commit d2faa06

Please sign in to comment.