Add L1 Block Info to L2 context #12
Replies: 6 comments 2 replies
-
|
Draft AZIP here: https://github.com/joeandrews/governance/blob/623e77ff0e1726b3ddac87204bdb0f9d594ccced/AZIPs/azip-4.md |
Beta Was this translation helpful? Give feedback.
-
|
@joeandrews @The-CTra1n (Conor) had an interesting idea - instead of relying on 6 fields, we do rely only on |
Beta Was this translation helpful? Give feedback.
-
|
Unless I'm missing the following adds additional DX complexity.
In addition to above, unless I'm missing something the following could be pain point: how is caller gonna know which L1 blocks Aztec has commited to? #[external("public")]
fn prove_deposit(
recipient: AztecAddress,
amount: u128,
l1_header: L1BlockHeaderData,
deposit_proof: BridgeDepositProof,
) {
let receipts_root = l1_header.receipts_root;
let l1_timestamp = l1_header.timestamp;
let current_commitment = self.context.l1_block_header_data_commitment();
let opened_commitment =
compute_l1_block_header_data_commitment(l1_header);
let header_matches_current_checkpoint =
opened_commitment == current_commitment;
assert(
header_matches_current_checkpoint,
"Wrong L1 header for current checkpoint",
);
let deposit_is_real = verify_bridge_deposit(
receipts_root,
deposit_proof,
recipient,
amount,
);
assert(deposit_is_real, "Missing deposit receipt on Ethereum");
}In order to call it we need to provide
A bit ugly, but doable.
So let's say we are sending tx at start of new checkpoint, how can caller of |
Beta Was this translation helpful? Give feedback.
-
|
One more thing, regarding Historical lookups. This example fn verify_l1_storage(
context: &mut PrivateContext,
l2_block_number: u32,
l1_block_header_data: L1BlockHeaderData,
account: EthAddress,
slot: Field,
expected_value: Field,
account_proof: MPTProof,
storage_proof: MPTProof,
) {
let header = context.get_block_header_at(l2_block_number);
assert(poseidon2_hash(l1_block_header_data.serialize()) == header.global_variables.l1_block_header_data_commitment);
let acct = mpt::verify_account(l1_block_header_data.state_root, account, account_proof);
mpt::verify_storage(acct.storage_root, slot, expected_value, storage_proof);
}Implies caller to have access to ethereum archive node, because one can pass in pretty old |
Beta Was this translation helpful? Give feedback.
-
|
On Worth flagging more explicitly that
Worked example: a prevRandao % 100 == 0 lottery has a base win rate of 1%. A self-dealing sequencer proposing the checkpoint that includes their own ticket sees 1 − 0.99^12 ≈ 11.4% — an ~11x grind, undetectable on-chain since all 12 candidates are valid L1 blocks. This is a specialization of the MEV surface already noted in con no 5, but I think it deserves its own callout because the affected audience (randomness consumers — lotteries, fair mints, randomized auctions, gaming) is exactly the audience the field's description invites. There is a viable in-AZIP fallback for apps that need grinding-resistant randomness: don't read the committed prevRandao directly — instead, use the also-committed parent_beacon_block_root to open an SSZ proof to a finalized epoch's randao_mixes[] entry. That reduces the proposer's grinding window to essentially zero, at a cost of ~600k gates per the AZIP's own beacon-proof estimate. Two ways forward seem reasonable:
|
Beta Was this translation helpful? Give feedback.
-
|
A few personal remarks on the proposal:
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Aztec contracts can't read Ethereum state directly. If you want to verify an L1 balance, check a Uniswap price, or confirm a bridge deposit landed, you either trust an oracle or must send a message through the L1→L2 inbox.
Aztec is an Ethereum rollup so access to this data is easy to include directly from L1.
Proposal
Add a single Poseidon2 commitment to six L1 block header fields (state_root, receipts_root, block_number, timestamp, prev_randao, parent_beacon_block_root) as one Field
l1_block-hashin GlobalVariables. L2 Contracts can open the commitment with a preimage and use the raw fields for MPT proofs, receipt proofs, or beacon chain state proofs.The mechanism piggybacks on the existing L1→L2 message infrastructure using the same dual-hash pattern (SHA256 on L1, Poseidon2 in circuits), same block root first rollup integration point, same propagation through merges. The addition costs ~10k gas per checkpoint
Motivation
Trustless L1 state reads — verify any account, balance, storage slot, or contract code via MPT proof against state_root
Event proofs — prove an L1 event was emitted via receipts_root, enabling trust-minimized bridge designs
Beacon chain access — prove validator sets, staking balances, and consensus-layer data via parent_beacon_block_root
L1 clock + randomness — timestamp for freshness enforcement, prev_randao for public entropy
Feedback wanted
The AZIP excludes transactions_root, parent_hash and gas fields. Should any of these be included?
Is the freshness bound (MAX_L1_BLOCK_LAG) of ~144 seconds needed / correct?
Are there use cases we're missing that would change the design?
Beta Was this translation helpful? Give feedback.
All reactions