1use agglayer_primitives::{
2 keccak::{keccak256, keccak256_combine},
3 Digest, Hashable, U256,
4};
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8use crate::{
9 bridge_exit::BridgeExit, global_index::GlobalIndex, local_exit_tree::proof::LETMerkleProof,
10 ImportedBridgeExitCommitmentVersion, RollupIndex,
11};
12
13impl Hashable for MerkleProof {
14 #[inline]
15 fn hash(&self) -> Digest {
16 keccak256_combine([
17 self.root.as_slice(),
18 self.proof
19 .siblings
20 .iter()
21 .flat_map(|v| v.0)
22 .collect::<Vec<_>>()
23 .as_slice(),
24 ])
25 }
26}
27impl Hashable for Claim {
28 #[inline]
29 fn hash(&self) -> Digest {
30 match self {
31 Claim::Mainnet(claim_from_mainnet) => claim_from_mainnet.hash(),
32 Claim::Rollup(claim_from_rollup) => claim_from_rollup.hash(),
33 }
34 }
35}
36
37impl Hashable for ClaimFromMainnet {
38 #[inline]
39 fn hash(&self) -> Digest {
40 keccak256_combine([
41 self.proof_leaf_mer.hash(),
42 self.proof_ger_l1root.hash(),
43 self.l1_leaf.hash(),
44 ])
45 }
46}
47
48impl Hashable for ClaimFromRollup {
49 #[inline]
50 fn hash(&self) -> Digest {
51 keccak256_combine([
52 self.proof_leaf_ler.hash(),
53 self.proof_ler_rer.hash(),
54 self.proof_ger_l1root.hash(),
55 self.l1_leaf.hash(),
56 ])
57 }
58}
59#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
60#[cfg_attr(feature = "testutils", derive(arbitrary::Arbitrary))]
61pub struct L1InfoTreeLeafInner {
62 pub global_exit_root: Digest,
63 pub block_hash: Digest,
64 pub timestamp: u64,
65}
66
67impl L1InfoTreeLeafInner {
68 #[inline]
69 pub fn hash(&self, global_exit_root: Digest) -> Digest {
70 keccak256_combine([
71 global_exit_root.as_slice(),
72 self.block_hash.as_slice(),
73 &self.timestamp.to_be_bytes(),
74 ])
75 }
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
79#[cfg_attr(feature = "testutils", derive(arbitrary::Arbitrary))]
80pub struct L1InfoTreeLeaf {
81 pub l1_info_tree_index: u32,
82 pub rer: Digest,
83 pub mer: Digest,
84 pub inner: L1InfoTreeLeafInner,
85}
86
87impl L1InfoTreeLeaf {
88 #[inline]
89 pub fn ger(&self) -> Digest {
90 keccak256_combine([self.mer, self.rer])
91 }
92
93 #[inline]
94 pub fn hash(&self) -> Digest {
95 self.inner.hash(self.ger())
96 }
97}
98
99#[derive(Clone, Debug, Error, Serialize, Deserialize, PartialEq, Eq)]
100#[serde(rename = "unified_bridge::Error")]
101pub enum Error {
102 #[error("Mismatch between the global index and the inclusion proof.")]
105 MismatchGlobalIndexInclusionProof,
106
107 #[error("Mismatch between the provided L1 root and the inclusion proof.")]
110 MismatchL1Root,
111
112 #[error("Mismatch on the MER between the L1 leaf and the inclusion proof.")]
115 MismatchMER,
116
117 #[error("Mismatch on the RER between the L1 leaf and the inclusion proof.")]
120 MismatchRER,
121
122 #[error("Invalid merkle path from the leaf to the LER.")]
124 InvalidMerklePathLeafToLER,
125
126 #[error("Invalid merkle path from the LER to the RER.")]
128 InvalidMerklePathLERToRER,
129
130 #[error("Invalid merkle path from the GER to the L1 Info Root.")]
132 InvalidMerklePathGERToL1Root,
133
134 #[error("Invalid imported bridge exit destination network.")]
137 InvalidExitNetwork,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
141#[cfg_attr(feature = "testutils", derive(arbitrary::Arbitrary))]
142pub struct MerkleProof {
143 pub proof: LETMerkleProof,
144 pub root: Digest,
145}
146
147impl MerkleProof {
148 #[inline]
149 pub fn new(root: Digest, siblings: [Digest; 32]) -> Self {
150 Self {
151 proof: LETMerkleProof { siblings },
152 root,
153 }
154 }
155
156 #[inline]
157 pub fn verify(&self, leaf: Digest, leaf_index: u32) -> bool {
158 self.proof.verify(leaf, leaf_index, self.root)
159 }
160
161 #[inline]
162 pub fn siblings(&self) -> &[Digest; 32] {
163 &self.proof.siblings
164 }
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
168#[cfg_attr(feature = "testutils", derive(arbitrary::Arbitrary))]
169pub enum Claim {
170 Mainnet(Box<ClaimFromMainnet>),
171 Rollup(Box<ClaimFromRollup>),
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
175#[cfg_attr(feature = "testutils", derive(arbitrary::Arbitrary))]
176pub struct ClaimFromMainnet {
177 pub proof_leaf_mer: MerkleProof,
179
180 pub proof_ger_l1root: MerkleProof,
182
183 pub l1_leaf: L1InfoTreeLeaf,
185}
186
187impl ClaimFromMainnet {
188 #[inline]
189 pub fn verify(&self, leaf: Digest, leaf_index: u32, l1root: Digest) -> Result<(), Error> {
190 if l1root != self.proof_ger_l1root.root {
192 return Err(Error::MismatchL1Root);
193 }
194
195 if self.proof_leaf_mer.root != self.l1_leaf.mer {
197 return Err(Error::MismatchMER);
198 }
199
200 if !self.proof_leaf_mer.verify(leaf, leaf_index) {
202 return Err(Error::InvalidMerklePathLeafToLER);
203 }
204
205 if !self
207 .proof_ger_l1root
208 .verify(self.l1_leaf.hash(), self.l1_leaf.l1_info_tree_index)
209 {
210 return Err(Error::InvalidMerklePathGERToL1Root);
211 }
212
213 Ok(())
214 }
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
218#[cfg_attr(feature = "testutils", derive(arbitrary::Arbitrary))]
219pub struct ClaimFromRollup {
220 pub proof_leaf_ler: MerkleProof,
222 pub proof_ler_rer: MerkleProof,
224 pub proof_ger_l1root: MerkleProof,
226 pub l1_leaf: L1InfoTreeLeaf,
228}
229
230impl ClaimFromRollup {
231 #[inline]
232 pub fn verify(
233 &self,
234 leaf: Digest,
235 leaf_index: u32,
236 rollup_index: RollupIndex,
237 l1root: Digest,
238 ) -> Result<(), Error> {
239 if l1root != self.proof_ger_l1root.root {
241 return Err(Error::MismatchL1Root);
242 }
243
244 if self.proof_ler_rer.root != self.l1_leaf.rer {
246 return Err(Error::MismatchRER);
247 }
248
249 if !self.proof_leaf_ler.verify(leaf, leaf_index) {
251 return Err(Error::InvalidMerklePathLeafToLER);
252 }
253
254 if !self
256 .proof_ler_rer
257 .verify(self.proof_leaf_ler.root, rollup_index.to_u32())
258 {
259 return Err(Error::InvalidMerklePathLERToRER);
260 }
261
262 if !self
264 .proof_ger_l1root
265 .verify(self.l1_leaf.hash(), self.l1_leaf.l1_info_tree_index)
266 {
267 return Err(Error::InvalidMerklePathGERToL1Root);
268 }
269
270 Ok(())
271 }
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
277#[cfg_attr(feature = "testutils", derive(arbitrary::Arbitrary))]
278pub struct ImportedBridgeExit {
279 pub bridge_exit: BridgeExit,
284
285 pub claim_data: Claim,
287
288 pub global_index: GlobalIndex,
290}
291
292impl ImportedBridgeExit {
293 #[inline]
296 pub fn verify_path(&self, l1root: Digest) -> Result<(), Error> {
297 if self.global_index.is_mainnet() != matches!(self.claim_data, Claim::Mainnet(_)) {
300 return Err(Error::MismatchGlobalIndexInclusionProof);
301 }
302
303 match &self.claim_data {
304 Claim::Mainnet(claim) => claim.verify(
305 self.bridge_exit.hash(),
306 self.global_index.leaf_index(),
307 l1root,
308 ),
309 Claim::Rollup(claim) => claim.verify(
310 self.bridge_exit.hash(),
311 self.global_index.leaf_index(),
312 self.global_index.rollup_index().unwrap(), l1root,
314 ),
315 }
316 }
317}
318
319#[cfg(not(feature = "zkvm"))]
320impl ImportedBridgeExit {
321 #[inline]
323 pub fn new(bridge_exit: BridgeExit, claim_data: Claim, global_index: GlobalIndex) -> Self {
324 Self {
325 bridge_exit,
326 global_index,
327 claim_data,
328 }
329 }
330 #[inline]
332 pub fn l1_info_root(&self) -> Digest {
333 match &self.claim_data {
334 Claim::Mainnet(claim) => claim.proof_ger_l1root.root,
335 Claim::Rollup(claim) => claim.proof_ger_l1root.root,
336 }
337 }
338
339 #[inline]
342 pub fn l1_leaf_index(&self) -> u32 {
343 match &self.claim_data {
344 Claim::Mainnet(claim) => claim.l1_leaf.l1_info_tree_index,
345 Claim::Rollup(claim) => claim.l1_leaf.l1_info_tree_index,
346 }
347 }
348
349 #[inline]
351 pub fn hash(&self) -> Digest {
352 keccak256_combine([
353 self.bridge_exit.hash(),
354 self.claim_data.hash(),
355 self.global_index.hash(),
356 ])
357 }
358}
359
360impl ImportedBridgeExit {
361 pub fn valid_claim(&self) -> bool {
362 match &self.claim_data {
363 Claim::Mainnet(claim) => {
364 claim.l1_leaf.inner.global_exit_root
365 == keccak256_combine([claim.l1_leaf.mer, claim.l1_leaf.rer])
366 }
367 Claim::Rollup(claim) => {
368 claim.l1_leaf.inner.global_exit_root
369 == keccak256_combine([claim.l1_leaf.mer, claim.l1_leaf.rer])
370 }
371 }
372 }
373
374 pub fn to_indexed_exit_hash(&self) -> GlobalIndexWithLeafHash {
376 GlobalIndexWithLeafHash {
377 global_index: self.global_index.into(),
378 bridge_exit_hash: self.bridge_exit.hash(),
379 }
380 }
381}
382
383#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
386pub struct GlobalIndexWithLeafHash {
387 pub global_index: U256,
389
390 pub bridge_exit_hash: Digest,
392}
393
394impl GlobalIndexWithLeafHash {
395 pub fn commitment(&self) -> Digest {
397 keccak256_combine([self.global_index.to_be_bytes(), self.bridge_exit_hash.0])
398 }
399}
400
401#[derive(Debug, Clone)]
403pub struct ImportedBridgeExitCommitmentValues {
404 pub claims: Vec<GlobalIndexWithLeafHash>,
405}
406
407impl ImportedBridgeExitCommitmentValues {
408 #[inline]
410 pub fn commitment(&self, version: ImportedBridgeExitCommitmentVersion) -> Digest {
411 match version {
412 ImportedBridgeExitCommitmentVersion::V2 => {
413 keccak256_combine(
416 self.claims
417 .iter()
418 .map(|ibe| keccak256(ibe.global_index.as_le_slice())),
419 )
420 }
421 ImportedBridgeExitCommitmentVersion::V3 => {
422 keccak256_combine(self.claims.iter().map(|ibe| {
425 [
426 ibe.global_index.as_le_slice(),
427 ibe.bridge_exit_hash.as_slice(),
428 ]
429 .concat()
430 }))
431 }
432 }
433 }
434}