Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 33 additions & 29 deletions docs/docs/protocol-specs/state/wonky-tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ For example, using a balanced merkle tree to rollup 5 transactions requires padd
```mermaid
graph BT
R_c[Root]

M4_c[Merge]
M5_c[Merge]
M4_c --> R_c
Expand All @@ -27,12 +27,12 @@ graph BT
B1_c --> M0_c
B2_c --> M1_c
B3_c --> M1_c

M2_c[Merge]
M3_c[Merge*]
M2_c --> M5_c
M3_c --> M5_c

B4_c[Base]
B5_c[Base*]
B6_c[Base*]
Expand Down Expand Up @@ -62,7 +62,7 @@ Our wonky tree implementation instead gives the below structure for 5 transactio
```mermaid
graph BT
R_c[Root]

M4_c[Merge]
M4_c --> R_c

Expand All @@ -80,8 +80,8 @@ graph BT
B1_c --> M0_c
B2_c --> M1_c
B3_c --> M1_c


B4_c[Base]
B4_c --> R_c

Expand Down Expand Up @@ -115,7 +115,7 @@ graph
graph BT
M0_c[Merge 0]
M1_c[Merge 1]

B0_c[Base 0]
B1_c[Base 1]
B2_c[Base 2]
Expand All @@ -124,7 +124,7 @@ graph BT
B1_c --> M0_c
B2_c --> M1_c
B3_c --> M1_c

B4_c[Base 4]
```

Expand All @@ -135,7 +135,7 @@ graph BT
M0_c[Merge 0]
M1_c[Merge 1]
M2_c[Merge 2]

B0_c[Base 0]
B1_c[Base 1]
B2_c[Base 2]
Expand All @@ -144,10 +144,10 @@ graph BT
B1_c --> M0_c
B2_c --> M1_c
B3_c --> M1_c

M0_c --> M2_c
M1_c --> M2_c

B4_c[Base 4]
```

Expand All @@ -156,11 +156,11 @@ Once paired, the base layer has length 4, the next merge layer has 2, and the fi
```mermaid
graph BT
R_c[Root]

M0_c[Merge 0]
M1_c[Merge 1]
M2_c[Merge 2]

B0_c[Base 0]
B1_c[Base 1]
B2_c[Base 2]
Expand All @@ -169,17 +169,18 @@ graph BT
B1_c --> M0_c
B2_c --> M1_c
B3_c --> M1_c

M0_c --> M2_c
M1_c --> M2_c

B4_c[Base 4]
M2_c --> R_c
B4_c --> R_c
```

Since we have processed all base circuits, this final pair will be input to a root circuit.

Filling from left to right means that we can easily reconstruct the tree only from the number of transactions `n`. The above method ensures that the final tree is a combination of *balanced* subtrees of descending size. The widths of these subtrees are given by the decomposition of `n` into powers of 2. For example, 5 transactions:
Filling from left to right means that we can easily reconstruct the tree only from the number of transactions `n`. The above method ensures that the final tree is a combination of _balanced_ subtrees of descending size. The widths of these subtrees are given by the decomposition of `n` into powers of 2. For example, 5 transactions:

```
Subtrees: [4, 1] ->
Expand All @@ -189,6 +190,7 @@ Subtrees: [4, 1] ->
```

For 31 transactions:

```
Subtrees: [16, 8, 4, 2, 1] ->
Merge D: left_subtree_root = balanced_tree(txs[0..16])
Expand All @@ -207,6 +209,7 @@ Subtrees: [16, 8, 4, 2, 1] ->
}
root = left_subtree_root | right_subtree_root
```

An unrolled recursive algorithm is not the easiest thing to read. This diagram represents the 31 transactions rolled up in our wonky structure, where each `Merge <num>` is a 'subroot' above:

```mermaid
Expand All @@ -215,36 +218,37 @@ graph BT
M3_c[Merge D
Subtree of 16 txs]
R_c[Root]


B4_c[Merge C
Subtree of 8 txs]
B5_c[Merge 1]

B4_c --> M2_c
B5_c --> M2_c

B6_c[Merge B
Subtree of 4 txs]
B7_c[Merge 0]

B6_c --> B5_c
B7_c --> B5_c

B8_c[Merge A
Subtree of 2 txs]
B9_c[Base 30]

B8_c --> B7_c
B9_c --> B7_c


M3_c --> R_c
M2_c --> R_c
```

The tree is reconstructed to check the `txs_effects_hash` (= the root of a wonky tree given by leaves of each tx's `tx_effects`) on L1. We also reconstruct it to provide a membership path against the stored `out_hash` (= the root of a wonky tree given by leaves of each tx's L2 to L1 message tree root) for consuming a L2 to L1 message.

Currently, this tree is built via the [orchestrator](../../../../yarn-project/prover-client/src/orchestrator/proving-state.ts#74) given the number of transactions to rollup (`this.totalNumTxs`). Each 'node' is assigned a level (0 at the root) and index in that level. The below function finds the parent level:
Currently, this tree is built via the orchestrator given the number of transactions to rollup. Each 'node' is assigned a level (0 at the root) and index in that level. The below function finds the parent level:

```
// Calculates the index and level of the parent rollup circuit
Expand Down Expand Up @@ -272,14 +276,14 @@ Currently, this tree is built via the [orchestrator](../../../../yarn-project/pr
return [mergeLevel - 1n, thisIndex >> 1n, thisIndex & 1n];
}
```
For example, `Base 4` above starts with `level = 3` and `index = 4`. Since we have an odd number of transactions at this level, `thisLevelSize` is set to 4 with `shiftUp = true`.

The while loop triggers and shifts up our node to `level = 2` and `index = 2`. This level (containing `Merge 0` and `Merge 1`) is of even length, so the loop continues. The next iteration shifts up to `level = 1` and `index = 1` - we now have an odd level, so the loop stops. The actual position of `Base 4` is therefore at `level = 1` and `index = 1`. This function returns the parent level of the input node, so we return `level = 0`, `index = 0`, correctly indicating that the parent of `Base 4` is the root.
For example, `Base 4` above starts with `level = 3` and `index = 4`. Since we have an odd number of transactions at this level, `thisLevelSize` is set to 4 with `shiftUp = true`.

The while loop triggers and shifts up our node to `level = 2` and `index = 2`. This level (containing `Merge 0` and `Merge 1`) is of even length, so the loop continues. The next iteration shifts up to `level = 1` and `index = 1` - we now have an odd level, so the loop stops. The actual position of `Base 4` is therefore at `level = 1` and `index = 1`. This function returns the parent level of the input node, so we return `level = 0`, `index = 0`, correctly indicating that the parent of `Base 4` is the root.

### Flexible wonky trees

We can also encode the structure of *any* binary merkle tree by tracking `number_of_branches` and `number_of_leaves` for each node in the tree. This encoding was originally designed for [logs](../logs/index.md) before they were included in the `txs_effects_hash`, so the below explanation references the leaves stored in relation to logs and transactions.
We can also encode the structure of _any_ binary merkle tree by tracking `number_of_branches` and `number_of_leaves` for each node in the tree. This encoding was originally designed for [logs](../logs/index.md) before they were included in the `txs_effects_hash`, so the below explanation references the leaves stored in relation to logs and transactions.

The benefit of this method as opposed to the one above is allowing for any binary structure and therefore allowing for 'skipping' leaves with no information. However, the encoding grows as the tree grows, by at least 2 bytes per node. The above implementation only requires the number of leaves to be encoded, which will likely only require a single field to store.

Expand Down Expand Up @@ -419,4 +423,4 @@ function hash_tx_logs_data(logs_data) {
}
return res;
}
```
```
4 changes: 1 addition & 3 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -365,10 +365,8 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
// new_archive.next_available_leaf_index: the new archive next available index
publicInputs[3] = bytes32(header.globalVariables.blockNumber + 1);

// TODO(#7346): Currently previous block hash is unchecked, but will be checked in batch rollup (block merge -> root).
// block-building-helpers.ts is injecting as 0 for now, replicating here.
// previous_block_hash: the block hash just preceding this block (will eventually become the end_block_hash of the prev batch)
publicInputs[4] = bytes32(0);
publicInputs[4] = blocks[header.globalVariables.blockNumber - 1].blockHash;

// end_block_hash: the current block hash (will eventually become the hash of the final block proven in a batch)
publicInputs[5] = blocks[header.globalVariables.blockNumber].blockHash;
Expand Down
1 change: 1 addition & 0 deletions l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ library Constants {
uint256 internal constant BLOCK_ROOT_ROLLUP_INDEX = 22;
uint256 internal constant BLOCK_MERGE_ROLLUP_INDEX = 23;
uint256 internal constant ROOT_ROLLUP_INDEX = 24;
uint256 internal constant BLOCK_ROOT_ROLLUP_FINAL_INDEX = 25;
uint256 internal constant FUNCTION_SELECTOR_NUM_BYTES = 4;
uint256 internal constant INITIALIZATION_SLOT_SEPARATOR = 1000000000;
uint256 internal constant INITIAL_L2_BLOCK_NUM = 1;
Expand Down
1 change: 1 addition & 0 deletions noir-projects/noir-protocol-circuits/Nargo.template.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ members = [
"crates/rollup-base-simulated",
"crates/rollup-block-merge",
"crates/rollup-block-root",
"crates/rollup-block-root-final",
"crates/rollup-root",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "rollup_block_root_final"
type = "bin"
authors = [""]
compiler_version = ">=0.18.0"

[dependencies]
rollup_lib = { path = "../rollup-lib" }
types = { path = "../types" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use dep::rollup_lib::block_root::{BlockRootRollupInputs, BlockRootOrBlockMergePublicInputs};

// This is a non-recursive variant of the rollup-block-root. We use it so we can generate proofs that can be verified on L1, until we
// drop support for proving single blocks and move to epoch proving completely.
fn main(inputs: BlockRootRollupInputs) -> pub BlockRootOrBlockMergePublicInputs {
inputs.block_root_rollup_circuit()
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use dep::rollup_lib::block_root::{BlockRootRollupInputs, BlockRootOrBlockMergePublicInputs};

#[recursive]
fn main(inputs: BlockRootRollupInputs) -> pub BlockRootOrBlockMergePublicInputs {
inputs.block_root_rollup_circuit()
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ global MERGE_ROLLUP_INDEX: u32 = 21;
global BLOCK_ROOT_ROLLUP_INDEX: u32 = 22;
global BLOCK_MERGE_ROLLUP_INDEX: u32 = 23;
global ROOT_ROLLUP_INDEX: u32 = 24;
global BLOCK_ROOT_ROLLUP_FINAL_INDEX: u32 = 25;

// MISC CONSTANTS
global FUNCTION_SELECTOR_NUM_BYTES: Field = 4;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::constants::{
EMPTY_NESTED_INDEX, PRIVATE_KERNEL_EMPTY_INDEX, PUBLIC_KERNEL_INNER_INDEX,
PUBLIC_KERNEL_MERGE_INDEX, PUBLIC_KERNEL_TAIL_INDEX, BASE_PARITY_INDEX, ROOT_PARITY_INDEX,
BASE_ROLLUP_INDEX, MERGE_ROLLUP_INDEX, BLOCK_ROOT_ROLLUP_INDEX, BLOCK_MERGE_ROLLUP_INDEX,
ROOT_ROLLUP_INDEX, PRIVATE_KERNEL_RESET_TINY_INDEX
ROOT_ROLLUP_INDEX, PRIVATE_KERNEL_RESET_TINY_INDEX, BLOCK_ROOT_ROLLUP_FINAL_INDEX
};
use crate::merkle_tree::merkle_tree::MerkleTree;

Expand Down Expand Up @@ -41,6 +41,7 @@ pub fn get_vk_merkle_tree() -> MerkleTree<VK_TREE_WIDTH> {
leaves[BLOCK_ROOT_ROLLUP_INDEX] = 22;
leaves[BLOCK_MERGE_ROLLUP_INDEX] = 23;
leaves[ROOT_ROLLUP_INDEX] = 24;
leaves[BLOCK_ROOT_ROLLUP_FINAL_INDEX] = 25;

MerkleTree::new(leaves)
}
5 changes: 4 additions & 1 deletion yarn-project/bb-prover/src/honk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { type ProtocolArtifact } from '@aztec/noir-protocol-circuits-types';

export type UltraHonkFlavor = 'ultra_honk' | 'ultra_keccak_honk';

const UltraKeccakHonkCircuits = ['BlockRootRollupArtifact'] as const;
const UltraKeccakHonkCircuits = [
'BlockRootRollupFinalArtifact',
'RootRollupArtifact',
] as const satisfies ProtocolArtifact[];
type UltraKeccakHonkCircuits = (typeof UltraKeccakHonkCircuits)[number];
type UltraHonkCircuits = Exclude<ProtocolArtifact, UltraKeccakHonkCircuits>;

Expand Down
2 changes: 2 additions & 0 deletions yarn-project/bb-prover/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export * from './test/index.js';
export * from './verifier/index.js';
export * from './config.js';
export * from './bb/execute.js';

export { type ClientProtocolCircuitVerifier } from '@aztec/circuit-types';
33 changes: 28 additions & 5 deletions yarn-project/bb-prover/src/prover/bb_prover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,20 +365,43 @@ export class BBNativeRollupProver implements ServerCircuitProver {
public async getBlockRootRollupProof(
input: BlockRootRollupInputs,
): Promise<PublicInputsAndRecursiveProof<BlockRootOrBlockMergePublicInputs>> {
// TODO(#7346): When batch rollups are integrated, we probably want the below to be this.createRecursiveProof
// since we will no longer be verifying it directly on L1
const { circuitOutput, proof } = await this.createProof(
const { circuitOutput, proof } = await this.createRecursiveProof(
input,
'BlockRootRollupArtifact',
NESTED_RECURSIVE_PROOF_LENGTH,
convertBlockRootRollupInputsToWitnessMap,
convertBlockRootRollupOutputsFromWitnessMap,
);

const verificationKey = await this.getVerificationKeyDataForCircuit('BlockRootRollupArtifact');

await this.verifyProof('BlockRootRollupArtifact', proof.binaryProof);

return makePublicInputsAndRecursiveProof(circuitOutput, proof, verificationKey);
}

/**
* Simulates the block root rollup circuit from its inputs.
* Returns a non-recursive proof to verify on L1.
* @dev TODO(palla/prover): This is a temporary workaround to get the proof to L1 with the old block flow.
* @param input - Inputs to the circuit.
* @returns The public inputs as outputs of the simulation.
*/
public async getBlockRootRollupFinalProof(
input: BlockRootRollupInputs,
): Promise<PublicInputsAndRecursiveProof<BlockRootOrBlockMergePublicInputs>> {
const { circuitOutput, proof } = await this.createProof(
input,
'BlockRootRollupFinalArtifact',
convertBlockRootRollupInputsToWitnessMap,
convertBlockRootRollupOutputsFromWitnessMap,
);

const recursiveProof = makeRecursiveProofFromBinary(proof, NESTED_RECURSIVE_PROOF_LENGTH);

const verificationKey = await this.getVerificationKeyDataForCircuit('BlockRootRollupArtifact');
const verificationKey = await this.getVerificationKeyDataForCircuit('BlockRootRollupFinalArtifact');

await this.verifyProof('BlockRootRollupArtifact', proof);
await this.verifyProof('BlockRootRollupFinalArtifact', proof);

return makePublicInputsAndRecursiveProof(circuitOutput, recursiveProof, verificationKey);
}
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/bb-prover/src/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export function mapProtocolArtifactNameToCircuitName(
return 'empty-nested';
case 'PrivateKernelEmptyArtifact':
return 'private-kernel-empty';
case 'BlockRootRollupFinalArtifact':
return 'block-root-rollup-final';
default: {
const _foo: never = artifact;
throw new Error(`Unknown circuit type: ${artifact}`);
Expand Down
6 changes: 6 additions & 0 deletions yarn-project/bb-prover/src/test/test_circuit_prover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,12 @@ export class TestCircuitProver implements ServerCircuitProver {
);
}

public getBlockRootRollupFinalProof(
input: BlockRootRollupInputs,
): Promise<PublicInputsAndRecursiveProof<BlockRootOrBlockMergePublicInputs>> {
return this.getBlockRootRollupProof(input);
}

/**
* Simulates the block merge rollup circuit from its inputs.
* @param input - Inputs to the circuit.
Expand Down
6 changes: 5 additions & 1 deletion yarn-project/circuit-types/src/interfaces/block-prover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface BlockSimulator extends ProcessedTxHandler {
startNewBlock(numTxs: number, globalVariables: GlobalVariables, l1ToL2Messages: Fr[]): Promise<ProvingTicket>;

/** Cancels the block currently being processed. Processes already in progress built may continue but further proofs should not be started. */
cancelBlock(): void;
cancel(): void;

/** Performs the final archive tree insertion for this block and returns the L2Block. */
finaliseBlock(): Promise<SimulationBlockResult>;
Expand All @@ -72,3 +72,7 @@ export interface BlockProver extends BlockSimulator {
/** Performs the final archive tree insertion for this block and returns the L2Block. */
finaliseBlock(): Promise<ProvingBlockResult>;
}

export interface EpochProver extends BlockProver {
startNewEpoch(epochNumber: number, totalNumBlocks: number): ProvingTicket;
}
Loading