Skip to content

Commit d671a80

Browse files
refactor(l2): use hardcoded vk in Aligned mode (#3175)
**Motivation** We are passing the verification key every time we call `verifyBatchAligned()`. **Description** - Initializes `SP1_VERIFICATION_KEY` in the `OnChainProposer` contract with the Aligned vk and reuses it in `verifyBatchAligned()`. - Since `l1_proof_verifier` needs the vk for `aligned_sdk:check_proof_verification()`, it retrieves it from the contract as well. Closes #3030 --------- Co-authored-by: Ivan Litteri <[email protected]>
1 parent 196a17b commit d671a80

File tree

10 files changed

+92
-43
lines changed

10 files changed

+92
-43
lines changed

crates/l2/contracts/bin/deployer/main.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -493,20 +493,15 @@ async fn initialize_contracts(
493493
.to_str()
494494
.ok_or(DeployerError::FailedToGetStringFromPath)?,
495495
);
496-
let sp1_vk_string = read_to_string(&opts.sp1_vk_path).unwrap_or_else(|_| {
496+
497+
let sp1_vk = std::fs::read(&opts.sp1_vk_path)
498+
.unwrap_or_else(|_| {
497499
warn!(
498500
path = opts.sp1_vk_path,
499501
"Failed to read SP1 verification key file, will use 0x00..00, this is expected in dev mode"
500502
);
501-
"0x00".to_string()
502-
});
503-
let sp1_vk = hex::decode(sp1_vk_string.trim_start_matches("0x"))
504-
.map_err(|err| {
505-
DeployerError::DecodingError(format!(
506-
"failed to parse sp1_vk ({sp1_vk_string}) from hex: {err}"
507-
))
508-
})?
509-
.into();
503+
vec![0u8; 32]
504+
}).into();
510505

511506
let deployer_address = get_address_from_secret_key(&opts.private_key)?;
512507

crates/l2/contracts/src/l1/OnChainProposer.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,6 @@ contract OnChainProposer is
355355
function verifyBatchAligned(
356356
uint256 batchNumber,
357357
bytes calldata alignedPublicInputs,
358-
bytes32 alignedProgramVKey,
359358
bytes32[] calldata alignedMerkleProof
360359
) external override onlySequencer whenNotPaused {
361360
require(
@@ -377,7 +376,7 @@ contract OnChainProposer is
377376
bytes memory callData = abi.encodeWithSignature(
378377
"verifyProofInclusion(bytes32[],bytes32,bytes)",
379378
alignedMerkleProof,
380-
alignedProgramVKey,
379+
SP1_VERIFICATION_KEY,
381380
alignedPublicInputs
382381
);
383382
(bool callResult, bytes memory response) = ALIGNEDPROOFAGGREGATOR

crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,10 @@ interface IOnChainProposer {
8484

8585
/// @notice Method used to verify a batch of L2 blocks in Aligned.
8686
/// @param alignedPublicInputs The public inputs bytes of the proof.
87-
/// @param alignedProgramVKey The public verifying key.
8887
/// @param alignedMerkleProof The Merkle proof (sibling hashes) needed to reconstruct the Merkle root.
8988
function verifyBatchAligned(
9089
uint256 batchNumber,
9190
bytes calldata alignedPublicInputs,
92-
bytes32 alignedProgramVKey,
9391
bytes32[] calldata alignedMerkleProof
9492
) external;
9593

crates/l2/prover/src/backends/sp1.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ pub fn to_batch_proof(
9797
BatchProof::ProofBytes(ProofBytes {
9898
proof: bincode::serialize(&proof.proof)?,
9999
public_values: proof.proof.public_values.to_vec(),
100-
vk: proof.vk.hash_bytes().into(),
101100
})
102101
} else {
103102
BatchProof::ProofCalldata(to_calldata(proof))

crates/l2/prover/zkvm/interface/build.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
fn main() {
22
println!("cargo::rerun-if-changed=build.rs");
3+
println!("cargo:rerun-if-env-changed=PROVER_CLIENT_ALIGNED");
34

45
#[cfg(feature = "risc0")]
56
build_risc0_program();
@@ -53,8 +54,16 @@ fn build_sp1_program() {
5354
.expect("could not read SP1 elf file");
5455
let prover = ProverClient::from_env();
5556
let (_, vk) = prover.setup(&elf);
56-
let vk = vk.vk.bytes32();
57-
dbg!(&vk);
58-
std::fs::write("./sp1/out/riscv32im-succinct-zkvm-vk", &vk)
59-
.expect("could not write SP1 vk to file");
57+
58+
let aligned_mode = std::env::var("PROVER_CLIENT_ALIGNED").unwrap_or("false".to_string());
59+
60+
if aligned_mode == "true" {
61+
let vk = vk.vk.hash_bytes();
62+
std::fs::write("./sp1/out/riscv32im-succinct-zkvm-vk", &vk)
63+
.expect("could not write SP1 vk to file");
64+
} else {
65+
let vk = vk.vk.bytes32_raw();
66+
std::fs::write("./sp1/out/riscv32im-succinct-zkvm-vk", &vk)
67+
.expect("could not write SP1 vk to file");
68+
};
6069
}

crates/l2/sequencer/errors.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,6 @@ pub enum ProofVerifierError {
143143
InternalError(String),
144144
#[error("ProofVerifier failed to parse beacon url")]
145145
ParseBeaconUrl(String),
146-
#[error("ProofVerifier decoding error: {0}")]
147-
DecodingError(String),
148146
#[error("Failed with a SaveStateError: {0}")]
149147
SaveStateError(#[from] SaveStateError),
150148
#[error("Failed to encode calldata: {0}")]

crates/l2/sequencer/l1_proof_verifier.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ use super::{
2323
utils::{send_verify_tx, sleep_random},
2424
};
2525

26-
const ALIGNED_VERIFY_FUNCTION_SIGNATURE: &str =
27-
"verifyBatchAligned(uint256,bytes,bytes32,bytes32[])";
26+
const ALIGNED_VERIFY_FUNCTION_SIGNATURE: &str = "verifyBatchAligned(uint256,bytes,bytes32[])";
2827

2928
pub async fn start_l1_proof_verifier(cfg: SequencerConfig) -> Result<(), SequencerError> {
3029
let l1_proof_verifier = L1ProofVerifier::new(
@@ -46,6 +45,7 @@ struct L1ProofVerifier {
4645
on_chain_proposer_address: Address,
4746
proof_verify_interval_ms: u64,
4847
network: Network,
48+
sp1_vk: [u8; 32],
4949
}
5050

5151
impl L1ProofVerifier {
@@ -57,6 +57,10 @@ impl L1ProofVerifier {
5757
) -> Result<Self, ProofVerifierError> {
5858
let eth_client = EthClient::new_with_multiple_urls(eth_cfg.rpc_url.clone())?;
5959

60+
let sp1_vk = eth_client
61+
.get_sp1_vk(committer_cfg.on_chain_proposer_address)
62+
.await?;
63+
6064
Ok(Self {
6165
eth_client,
6266
beacon_url: aligned_cfg.beacon_url.clone(),
@@ -65,6 +69,7 @@ impl L1ProofVerifier {
6569
l1_private_key: proof_coordinator_cfg.l1_private_key,
6670
on_chain_proposer_address: committer_cfg.on_chain_proposer_address,
6771
proof_verify_interval_ms: aligned_cfg.aligned_verifier_interval_ms,
72+
sp1_vk,
6873
})
6974
}
7075

@@ -115,13 +120,9 @@ impl L1ProofVerifier {
115120
) -> Result<Option<H256>, ProofVerifierError> {
116121
let proof = read_proof(batch_number, StateFileType::BatchProof(ProverType::Aligned))?;
117122
let public_inputs = proof.public_values();
118-
// TODO: use a hardcoded vk
119-
let vk = proof.vk();
120123

121124
let verification_data = AggregationModeVerificationData::SP1 {
122-
vk: vk.clone().try_into().map_err(|e| {
123-
ProofVerifierError::DecodingError(format!("Failed to decode vk: {e:?}"))
124-
})?,
125+
vk: self.sp1_vk,
125126
public_inputs: public_inputs.clone(),
126127
};
127128

@@ -170,7 +171,6 @@ impl L1ProofVerifier {
170171
let calldata_values = [
171172
Value::Uint(U256::from(batch_number)),
172173
Value::Bytes(public_inputs.into()),
173-
Value::FixedBytes(vk.into()),
174174
Value::Array(merkle_path),
175175
];
176176

crates/l2/utils/prover/proving_systems.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,6 @@ impl BatchProof {
109109
BatchProof::ProofBytes(proof_bytes) => proof_bytes.public_values.clone(),
110110
}
111111
}
112-
113-
pub fn vk(&self) -> Vec<u8> {
114-
match self {
115-
BatchProof::ProofCalldata(_) => vec![],
116-
BatchProof::ProofBytes(proof_bytes) => proof_bytes.vk.clone(),
117-
}
118-
}
119112
}
120113

121114
/// Contains the Proof and the public values generated by the prover.
@@ -124,8 +117,6 @@ impl BatchProof {
124117
pub struct ProofBytes {
125118
pub proof: Vec<u8>,
126119
pub public_values: Vec<u8>,
127-
// TODO: use a hardcoded value for the vk
128-
pub vk: Vec<u8>,
129120
}
130121

131122
/// Contains the data ready to be sent to the on-chain verifiers.

crates/networking/rpc/clients/eth/mod.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,14 @@ impl EthClient {
10571057
.await
10581058
}
10591059

1060+
pub async fn get_sp1_vk(
1061+
&self,
1062+
on_chain_proposer_address: Address,
1063+
) -> Result<[u8; 32], EthClientError> {
1064+
self._call_bytes32_variable(b"SP1_VERIFICATION_KEY()", on_chain_proposer_address)
1065+
.await
1066+
}
1067+
10601068
pub async fn get_last_fetched_l1_block(
10611069
&self,
10621070
common_bridge_address: Address,
@@ -1167,6 +1175,27 @@ impl EthClient {
11671175
Ok(value)
11681176
}
11691177

1178+
async fn _call_bytes32_variable(
1179+
&self,
1180+
selector: &[u8],
1181+
contract_address: Address,
1182+
) -> Result<[u8; 32], EthClientError> {
1183+
let hex_string = self._generic_call(selector, contract_address).await?;
1184+
1185+
let hex = hex_string.strip_prefix("0x").ok_or(EthClientError::Custom(
1186+
"Couldn't strip '0x' prefix from hex string".to_owned(),
1187+
))?;
1188+
1189+
let bytes = hex::decode(hex)
1190+
.map_err(|e| EthClientError::Custom(format!("Failed to decode hex string: {}", e)))?;
1191+
1192+
let arr: [u8; 32] = bytes.try_into().map_err(|_| {
1193+
EthClientError::Custom("Failed to convert bytes to [u8; 32]".to_owned())
1194+
})?;
1195+
1196+
Ok(arr)
1197+
}
1198+
11701199
pub async fn wait_for_transaction_receipt(
11711200
&self,
11721201
tx_hash: H256,

docs/l2/aligned_mode.md

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,21 @@ This document explains how to run an Ethrex L2 node in **Aligned mode** and high
77
> [!IMPORTANT]
88
> For this guide we assumed that there is an L1 running with all Aligned environment set.
99
10-
### 1. Deploying L1 Contracts
10+
### 1. Generate the SP1 ELF Program and Verification Key
11+
12+
Run:
13+
14+
```bash
15+
cd ethrex/crates/l2
16+
SP1_PROVER=cuda make build-prover PROVER=sp1 PROVER_CLIENT_ALIGNED=true
17+
```
18+
19+
This will generate the SP1 ELF program and verification key under:
20+
- `crates/l2/prover/zkvm/interface/sp1/out/riscv32im-succinct-zkvm-elf`
21+
- `crates/l2/prover/zkvm/interface/sp1/out/riscv32im-succinct-zkvm-vk`
22+
23+
24+
### 2. Deploying L1 Contracts
1125

1226
In a console with `ethrex/crates/l2` as the current directory, run the following command:
1327

@@ -25,14 +39,15 @@ cargo run --release --bin ethrex_l2_l1_deployer --manifest-path contracts/Cargo.
2539
--bridge-owner <ADDRESS> \
2640
--on-chain-proposer-owner <ADDRESS> \
2741
--private-keys-file-path <PRIVATE_KEYS_FILE_PATH> \
28-
--sequencer-registry-owner <ADDRESS>
42+
--sequencer-registry-owner <ADDRESS> \
43+
--sp1-vk-path <SP1_VERIFICATION_KEY_PATH>
2944
```
3045

3146
> [!NOTE]
3247
> In this step we are initiallizing the `OnChainProposer` contract with the `ALIGNED_PROOF_AGGREGATOR_SERVICE_ADDRESS` and skipping the rest of verifiers.
3348
> Save the addresses of the deployed proxy contracts, as you will need them to run the L2 node.
3449
35-
### 2. Deposit funds to the `AlignedBatchePaymentService` contract from the proof sender
50+
### 3. Deposit funds to the `AlignedBatcherPaymentService` contract from the proof sender
3651

3752
```bash
3853
aligned \
@@ -44,7 +59,7 @@ aligned \
4459
> [!IMPORTANT]
4560
> Using the [Aligned CLI](https://docs.alignedlayer.com/guides/9_aligned_cli)
4661
47-
### 3. Running a node
62+
### 4. Running a node
4863

4964
In a console with `ethrex/crates/l2` as the current directory, run the following command:
5065

@@ -90,6 +105,10 @@ SP1_PROVER=cuda make init-prover PROVER=sp1 PROVER_CLIENT_ALIGNED=true
90105

91106
## How to Run Using an Aligned Dev Environment
92107

108+
> [!IMPORTANT]
109+
> This guide asumes you have already generated the SP1 ELF Program and Verification Key. See: [Generate the SP1 ELF Program and Verification Key](#1-generate-the-sp1-elf-program-and-verification-key)
110+
111+
93112
### Set Up the Aligned Environment
94113

95114
1. Clone the Aligned repository and checkout the currently supported release:
@@ -207,12 +226,24 @@ SP1_PROVER=cuda make init-prover PROVER=sp1 PROVER_CLIENT_ALIGNED=true
207226

208227
### Aggregate proofs:
209228

210-
After some time, you will see that the `l1_proof_verifier` is waiting for Aligned to aggregate the proofs. You can aggregate them by running:
229+
After some time, you will see that the `l1_proof_verifier` is waiting for Aligned to aggregate the proofs:
230+
```
231+
2025-06-18T22:03:53.470356Z INFO ethrex_l2::sequencer::l1_proof_verifier: Batch 1 has not yet been aggregated by Aligned. Waiting for 5 seconds
232+
```
233+
234+
You can aggregate them by running:
211235
```
212236
cd aligned_layer
213237
make start_proof_aggregator AGGREGATOR=sp1
214238
```
215239

240+
If successful, the `l1_proof_verifier` will print the following logs:
241+
242+
```
243+
INFO ethrex_l2::sequencer::l1_proof_verifier: Proof for batch 1 aggregated by Aligned with commitment 0xa9a0da5a70098b00f97d96cee43867c7aa8f5812ca5388da7378454580af2fb7 and Merkle root 0xa9a0da5a70098b00f97d96cee43867c7aa8f5812ca5388da7378454580af2fb7
244+
INFO ethrex_l2::sequencer::l1_proof_verifier: Batch 1 verified in AlignedProofAggregatorService, with transaction hash 0x731d27d81b2e0f1bfc0f124fb2dd3f1a67110b7b69473cacb6a61dea95e63321
245+
```
246+
216247
## Behavioral Differences in Aligned Mode
217248

218249
### Prover

0 commit comments

Comments
 (0)