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
26 changes: 26 additions & 0 deletions aggregation_mode/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,32 @@ impl ProofAggregator {
.map_err(AggregatedProofSubmissionError::ReceiptError)
}

/// ### Blob capacity
///
/// As dictated in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844), each blob can hold:
///
/// - `FIELD_ELEMENTS_PER_BLOB = 4096`
/// - `BYTES_PER_FIELD_ELEMENT = 32`
///
/// This gives a total theoretical capacity of:
///
/// `FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT = 4096 * 32 = 131072 bytes`
///
/// However, this full capacity isn't usable due to the encoding of KZG commitments to elliptic curve points.
/// Specifically:
///
/// - Ethereum uses the BLS12-381 curve, whose scalar field modulus is slightly less than `2^256`
/// (closer to `2^255`).
/// - Therefore, 32-byte field elements can't represent all 256-bit values.
/// - To ensure values are within the field modulus, we **pad with a leading `0x00` byte**,
/// effectively capping values below the modulus.
/// - This reduces the usable payload to **31 bytes per field element**.
///
/// So, the _actual usable capacity_ per blob is:
///
/// `4096 * 31 = 126976 bytes`
///
/// Meaning that we can send as much as 126976 / 32 = 3968 proofs per blob
async fn construct_blob(
&self,
leaves: Vec<[u8; 32]>,
Expand Down
91 changes: 91 additions & 0 deletions docs/2_architecture/agg_mode_components/1_deep_dive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Aggregation Mode Deep Dive

The Aggregation Mode runs **once every 24 hours** and performs the following steps:

1. **Fetch Proofs from the Verification Layer**
Queries `NewBatchV3` events from the `AlignedLayerServiceManager` and downloads the batches from `S3`, starting from the last processed block of the previous run.

2. **Filter Proofs**
Filters proofs by supported verifiers and proof types.

3. **Aggregate Proofs in the zkVM**
Selected proofs are aggregated using a zkVM.

4. **Construct the Blob**
A blob is built containing the [commitments](#proof-commitment) of the aggregated proofs.

5. **Send Aggregated Proof**
The final aggregated proof and its blob are sent to the `AlignedProofAggregationService` contract for verification.

> [Note]
> Currently if you want your proof to be verified in the `AggregationMode` you need to submit it via the `VerificationLayer`. In the future, users will be able to decide if they want to use any of the modes in particular or both of them

## Aggregators and Supported Proof Types

Two separate aggregators are run every 24 hours:

- **Risc0**: Aggregates proofs of types `Composite` and `Succinct`.
- **SP1**: Aggregates proofs of type `Compressed`.

## Proof Commitment

The **proof commitment** is a hash that uniquely identifies a proof. It is defined as the keccak of the proof public inputs + program ID:

- **For SP1**:
The commitment is computed as: `keccak(proof_public_inputs_bytes || vk_hash_bytes)`
- **For Risc0**:
The commitment is computed as: `keccack(receipt_public_inputs_bytes || image_id_bytes)`

## Multilayer Aggregation

To scale aggregation without exhausting zkVM memory, aggregation is split in two programs:

1. **User Proof Aggregator**
Processes chunks of `n` user proofs. Each run creates an aggregated proof that commits to a Merkle root of the user proofs inputs. This step is repeated for as many chunks as needed. Usually each chunks contains `256` proofs but it can be lowered based on the machine specs.

2. **Chunk Aggregator**
Aggregates all chunk-level proofs into a single final proof. It receives:

- The chunked proofs
- The original [proofs commitments](#proof-commitment) included each chunk received

During verification, it checks that each chunk’s committed Merkle root matches the reconstructed root to ensure input correctness. The final Merkle root, representing all user [proofs commitments](#proof-commitment), is then committed as a public input.

## Verification

Once aggregated, the proof is sent to Ethereum and verified via the `AlignedProofAggregationService` contract. Depending on the proving system, the contract invokes:

- `verifySP1` for SP1 proofs
- `verifyRisc0` for Risc0 proofs

Each function receives:

- The public inputs
- The proof binary

The program ID is hardcoded in the contract to ensure only trusted aggregation programs (`chunk_aggregator`) are accepted.

If verification succeeds, the new proof is added to the `aggregatedProofs` map in contract storage.

### Proof Inclusion Verification

To verify a user’s proof on-chain, the following must be provided:

- The proof bytes
- The proof public inputs
- The program ID
- A Merkle proof

The Merkle root is computed and checked for existence in the contract using the `verifyProofInclusion` function of the `ProofAggregationServiceContract`, which:

1. Computes the merkle root
2. Returns `true` or `false` depending if there exists an `aggregatedProof` with the computed root.

## Data Availability

When submitting the aggregated proof to Ethereum, we include a **blob** that contains the [commitments](#proof-commitment) of all the individual proofs that were aggregated. This blob serves two main purposes:

- It makes the [proof commitments](#proof-commitment) publicly available for **18 days**.
- It allows users to:
- Inspect which proofs were aggregated
- Get a Merkle proof to verify that their proof is included in the aggregated proof
208 changes: 208 additions & 0 deletions docs/3_guides/3.1_aggregation_mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
## Aggregation Mode L2 integration example

This guide demonstrates how to build a dummy L2 application that integrates with Aligned Aggregation Mode. The L2 does not post state diffs or any data to Ethereum, only commitments. The prover has to prove that:

1. The state database used in the proof must match the commitment stored in the on-chain contract. This is validated by computing the commitment of the received data in the zkvm and then exposing it as a public input.
2. The users performing the transfers have enough balance

After processing the transfers, the vm computes the commitment of the post state, which is exposed as a public input. The smart contract then updates the on-chain state root. If a user later wants to retrieve their state, the application must return it along with a Merkle proof, so they can verify it against the contract’s state root.

Notice a lot of checks that a real L2 should have are missing, since the focus are on the integration of Aligned.

The code can be viewed at `examples/l2`.

## L2 workflow overview

This Layer 2 (L2) system operates in two main steps:

- Off-chain execution and proof generation + verification with Aligned Verification Layer (a.k.a Fast Mode).
- On-chain state update via proof verification with Aligned Aggregation Mode.

In Step 1, we execute user transfers and generate a zkVM-based proof of the state transition, which is submitted to Aligned’s verification layer.

In Step 2, once the proof is aggregated (every 24 hours), it is verified on-chain to update the global state.

### Step 1: Off-Chain Execution + Proof Generation

1. Initialize State: Load or initialize the current system state.
2. Load Transfers: Retrieve or receive the user transfer data for this batch.
3. Execute in zkVM: Run the zkVM with the loaded transfers to compute the new state.
4. Generate Proof: Produce a zk-proof for the executed state transition committing the commitment of the received + the commitment of the new state.
5. Submit Proof to Aligned: Send the proof to Aligned Verification Layer
6. Save the binary proof locally for later on-chain verification.

### Step 2: Proof Verification + On-Chain State Update

7. Load the proof binary: Retrieve the saved proof binary from disk.
8. Update On-Chain State: Call the smart contract method `updateStateTransition`, which:

- Internally calls `verifyProofInclusion` on AlignedProofAggregationService which:
1. Computes the proof commitment from the proof `public_inputs` and `program_id`.
2. Uses the Merkle proof to reconstruct and validate the Merkle root.
3. Confirms whether there exists and aggregated proof with that root.
- Validates that the `initial_state_root` proof public input matches the on-chain state.
- If valid, updates the on-chain state root to the `post_state_root`.

# Usage

### Requirements

1. [Rust](https://www.rust-lang.org/tools/install): we have tested in v1.85.1
2. [Foundry](https://book.getfoundry.sh/getting-started/installation)
3. [Docker](https://docs.docker.com/engine/): for SP1 prover

Submodules of the repo should be imported by running on the root folder:

```shell
make submodules
```

You can run the example on:

- [Holesky](#setup-holeksy)
- [Localnet](#setup-localnet)

## Setup Holeksy

### 1. Create keystore

You can use cast to create a local keystore. If you already have one you can skip this step.

```bash
cast wallet new-mnemonic
```

Then you can import your created keystore using:

```bash
cast wallet import --interactive <path_to_keystore.json>
```

Then you need to obtain some funds to pay for gas and proof verification.
You can do this by using this [faucet](https://cloud.google.com/application/web3/faucet/ethereum/holesky)

_This same wallet is used to send the proof via aligned, so you'll also need to fund it on aligned. Follow this [guide](https://docs.alignedlayer.com/guides/0_submitting_proofs#id-2.-send-funds-to-aligned)._

### 2. Deploy the contract

- Generate the base `.env`:

```shell
make gen_env_contract_holesky
```

- Get the program ID of the l2 program you are proving:

```shell
make generate_program_id
```

- Complete the following fields `contracts/.env` file:

- `PROGRAM_ID=` (use the previously generated ID, you can re check with a `cat ./crates/l2/programs_ids.json` )
- `PRIVATE_KEY`: the private key used for the deployment, it needs to have some funds to pay for the deployment.
- `OWNER_ADDRESS`: you have to provide the _address of the wallet created in step `1.`_.

- Deploy the contracts with:

```shell
make deploy_contract
```

_Save the output contract address._

### 3. Setup the L2

- Generate the base `.env` run:

```shell
make gen_env_l2_holesky
```

- Complete the missing fields on the `.env`:

- `PRIVATE_KEY_STORE_PATH`: The path to the keystore created in `1.`.
- `PRIVATE_KEY_STORE_PASSWORD`: The password of the keystore crated in step `1.`.
- `STATE_TRANSITION_CONTRACT_ADDRESS`: The address of the contract deployed in step `2.`

Finally [run the l2](#running-the-l2).

## Setup Localnet

You can also run this example on a local devnet. To get started, navigate to the root of the Aligned repository

- Start Ethereum package and the Batcher

```shell
# This will start the local net
make ethereum_package_start
# Start the batcher
make batcher_start_ethereum_package
```

- Navigate back to the example directory:

```shell
cd examples/l2
```

- Generate the `.env` files for the contracts and L2:

```shell
make gen_env_contract_devnet
make gen_env_l2_devnet
```

- Generate a pre funded wallet (or create one as specified [previously here](#1-create-keystore)):

```shell
# This will generate the keystore and fund it on aligned
make gen_devnet_owner_wallet
```

- Generate the program ID of the program that is going to be proven:

```shell
make generate_program_id
```

- Set the generated program ID on `contracts/.env`.

- Deploy the contract

```shell
make deploy_contract
```

- Set the output address of the contract in `.env`

- [run the l2](#running-the-l2)

## Running the L2

- Set up the initial State

```shell
make init_state
```

- Perform the L2 account updates and prove them in the zkvm:

```shell
make prove_state_transition
```

- Wait 24 hs for the proof to be aggregated, or if running locally, run the aggregator with either:

```make start_proof_aggregator_ethereum_package AGGREGATOR=sp1```

or with cuda:
`make start_proof_aggregator_gpu_ethereum_package AGGREGATOR=sp1`

- Update state transition on chain:

```shell
make update_state_on_chain
```

You should see a transaction receipt in the console and after the stateRoot updated on-chain.
2 changes: 2 additions & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
* [Aggregator](./2_architecture/components/5_aggregator.md)
* [Explorer](./2_architecture/components/6_explorer.md)
* [Aggregation mode](2_architecture/2_aggregation_mode.md)
* [Deep Dive](2_architecture/agg_mode_components/1_deep_dive.md)

## Guides

* [Submitting proofs](3_guides/0_submitting_proofs.md)
* [Build your first Aligned Application](3_guides/2_build_your_first_aligned_application.md)
* [Modify ZkQuiz Questions](3_guides/2.2_modify_zkquiz_questions.md)
* [Validating public input](3_guides/3_validating_public_input.md)
* [Aggregation Mode](3_guides/3.1_aggregation_mode.md)
* [SDK Intro](3_guides/1_SDK_how_to.md)
* [SDK API Reference](3_guides/1.2_SDK_api_reference.md)
* [Generating proofs for Aligned](3_guides/4_generating_proofs.md)
Expand Down
Loading