unified-bridge 0.18.0

unified-bridge contains structs and traits for the unified bridge protocol.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
use agglayer_primitives::{
    keccak::{keccak256, keccak256_combine},
    Digest, Hashable, U256,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::{
    bridge_exit::BridgeExit, global_index::GlobalIndex, local_exit_tree::proof::LETMerkleProof,
    ImportedBridgeExitCommitmentVersion, RollupIndex,
};

impl Hashable for MerkleProof {
    #[inline]
    fn hash(&self) -> Digest {
        keccak256_combine([
            self.root.as_slice(),
            self.proof
                .siblings
                .iter()
                .flat_map(|v| v.0)
                .collect::<Vec<_>>()
                .as_slice(),
        ])
    }
}
impl Hashable for Claim {
    #[inline]
    fn hash(&self) -> Digest {
        match self {
            Claim::Mainnet(claim_from_mainnet) => claim_from_mainnet.hash(),
            Claim::Rollup(claim_from_rollup) => claim_from_rollup.hash(),
        }
    }
}

impl Hashable for ClaimFromMainnet {
    #[inline]
    fn hash(&self) -> Digest {
        keccak256_combine([
            self.proof_leaf_mer.hash(),
            self.proof_ger_l1root.hash(),
            self.l1_leaf.hash(),
        ])
    }
}

impl Hashable for ClaimFromRollup {
    #[inline]
    fn hash(&self) -> Digest {
        keccak256_combine([
            self.proof_leaf_ler.hash(),
            self.proof_ler_rer.hash(),
            self.proof_ger_l1root.hash(),
            self.l1_leaf.hash(),
        ])
    }
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "testutils", derive(arbitrary::Arbitrary))]
pub struct L1InfoTreeLeafInner {
    pub global_exit_root: Digest,
    pub block_hash: Digest,
    pub timestamp: u64,
}

impl L1InfoTreeLeafInner {
    #[inline]
    pub fn hash(&self, global_exit_root: Digest) -> Digest {
        keccak256_combine([
            global_exit_root.as_slice(),
            self.block_hash.as_slice(),
            &self.timestamp.to_be_bytes(),
        ])
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "testutils", derive(arbitrary::Arbitrary))]
pub struct L1InfoTreeLeaf {
    pub l1_info_tree_index: u32,
    pub rer: Digest,
    pub mer: Digest,
    pub inner: L1InfoTreeLeafInner,
}

impl L1InfoTreeLeaf {
    #[inline]
    pub fn ger(&self) -> Digest {
        keccak256_combine([self.mer, self.rer])
    }

    #[inline]
    pub fn hash(&self) -> Digest {
        self.inner.hash(self.ger())
    }
}

#[derive(Clone, Debug, Error, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename = "unified_bridge::Error")]
pub enum Error {
    /// The global index and the inclusion proof do not both correspond to the
    /// same network type: mainnet or rollup.
    #[error("Mismatch between the global index and the inclusion proof.")]
    MismatchGlobalIndexInclusionProof,

    /// The provided L1 info root does not match the one provided in the
    /// inclusion proof.
    #[error("Mismatch between the provided L1 root and the inclusion proof.")]
    MismatchL1Root,

    /// The provided L1 info leaf does not refer to the same MER as the
    /// inclusion proof.
    #[error("Mismatch on the MER between the L1 leaf and the inclusion proof.")]
    MismatchMER,

    /// The provided L1 info leaf does not refer to the same RER as the
    /// inclusion proof.
    #[error("Mismatch on the RER between the L1 leaf and the inclusion proof.")]
    MismatchRER,

    /// The inclusion proof from the leaf to the LER is invalid.
    #[error("Invalid merkle path from the leaf to the LER.")]
    InvalidMerklePathLeafToLER,

    /// The inclusion proof from the LER to the RER is invalid.
    #[error("Invalid merkle path from the LER to the RER.")]
    InvalidMerklePathLERToRER,

    /// The inclusion proof from the GER to the L1 info Root is invalid.
    #[error("Invalid merkle path from the GER to the L1 Info Root.")]
    InvalidMerklePathGERToL1Root,

    /// The provided imported bridge exit does not target the right destination
    /// network.
    #[error("Invalid imported bridge exit destination network.")]
    InvalidExitNetwork,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "testutils", derive(arbitrary::Arbitrary))]
pub struct MerkleProof {
    pub proof: LETMerkleProof,
    pub root: Digest,
}

impl MerkleProof {
    #[inline]
    pub fn new(root: Digest, siblings: [Digest; 32]) -> Self {
        Self {
            proof: LETMerkleProof { siblings },
            root,
        }
    }

    #[inline]
    pub fn verify(&self, leaf: Digest, leaf_index: u32) -> bool {
        self.proof.verify(leaf, leaf_index, self.root)
    }

    #[inline]
    pub fn siblings(&self) -> &[Digest; 32] {
        &self.proof.siblings
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "testutils", derive(arbitrary::Arbitrary))]
pub enum Claim {
    Mainnet(Box<ClaimFromMainnet>),
    Rollup(Box<ClaimFromRollup>),
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "testutils", derive(arbitrary::Arbitrary))]
pub struct ClaimFromMainnet {
    /// Proof from bridge exit leaf to MER
    pub proof_leaf_mer: MerkleProof,

    /// Proof from GER to L1Root
    pub proof_ger_l1root: MerkleProof,

    /// L1InfoTree leaf
    pub l1_leaf: L1InfoTreeLeaf,
}

impl ClaimFromMainnet {
    #[inline]
    pub fn verify(&self, leaf: Digest, leaf_index: u32, l1root: Digest) -> Result<(), Error> {
        // Check the consistency on the l1 root
        if l1root != self.proof_ger_l1root.root {
            return Err(Error::MismatchL1Root);
        }

        // Check the consistency on the declared MER
        if self.proof_leaf_mer.root != self.l1_leaf.mer {
            return Err(Error::MismatchMER);
        }

        // Check the inclusion proof of the leaf to the LER (here LER is the MER)
        if !self.proof_leaf_mer.verify(leaf, leaf_index) {
            return Err(Error::InvalidMerklePathLeafToLER);
        }

        // Check the inclusion proof of the L1 leaf to L1Root
        if !self
            .proof_ger_l1root
            .verify(self.l1_leaf.hash(), self.l1_leaf.l1_info_tree_index)
        {
            return Err(Error::InvalidMerklePathGERToL1Root);
        }

        Ok(())
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "testutils", derive(arbitrary::Arbitrary))]
pub struct ClaimFromRollup {
    /// Proof from bridge exit leaf to LER
    pub proof_leaf_ler: MerkleProof,
    /// Proof from LER to RER
    pub proof_ler_rer: MerkleProof,
    /// Proof from GER to L1Root
    pub proof_ger_l1root: MerkleProof,
    /// L1InfoTree leaf
    pub l1_leaf: L1InfoTreeLeaf,
}

impl ClaimFromRollup {
    #[inline]
    pub fn verify(
        &self,
        leaf: Digest,
        leaf_index: u32,
        rollup_index: RollupIndex,
        l1root: Digest,
    ) -> Result<(), Error> {
        // Check the consistency on the l1 root
        if l1root != self.proof_ger_l1root.root {
            return Err(Error::MismatchL1Root);
        }

        // Check the consistency on the declared RER
        if self.proof_ler_rer.root != self.l1_leaf.rer {
            return Err(Error::MismatchRER);
        }

        // Check the inclusion proof of the leaf to the LER
        if !self.proof_leaf_ler.verify(leaf, leaf_index) {
            return Err(Error::InvalidMerklePathLeafToLER);
        }

        // Check the inclusion proof of the LER to the RER
        if !self
            .proof_ler_rer
            .verify(self.proof_leaf_ler.root, rollup_index.to_u32())
        {
            return Err(Error::InvalidMerklePathLERToRER);
        }

        // Check the inclusion proof of the L1 leaf to L1Root
        if !self
            .proof_ger_l1root
            .verify(self.l1_leaf.hash(), self.l1_leaf.l1_info_tree_index)
        {
            return Err(Error::InvalidMerklePathGERToL1Root);
        }

        Ok(())
    }
}

/// Represents a token bridge exit originating on another network but claimed on
/// the current network.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "testutils", derive(arbitrary::Arbitrary))]
pub struct ImportedBridgeExit {
    /// The bridge exit initiated on another network, called the "sending"
    /// network. Need to verify that the destination network matches the
    /// current network, and that the bridge exit is included in an imported
    /// LER
    pub bridge_exit: BridgeExit,

    /// The claim data
    pub claim_data: Claim,

    /// The global index of the imported bridge exit.
    pub global_index: GlobalIndex,
}

impl ImportedBridgeExit {
    /// Verifies that the provided inclusion path is valid and consistent with
    /// the provided LER
    #[inline]
    pub fn verify_path(&self, l1root: Digest) -> Result<(), Error> {
        // Check that the inclusion proof and the global index both refer to mainnet or
        // rollup
        if self.global_index.is_mainnet() != matches!(self.claim_data, Claim::Mainnet(_)) {
            return Err(Error::MismatchGlobalIndexInclusionProof);
        }

        match &self.claim_data {
            Claim::Mainnet(claim) => claim.verify(
                self.bridge_exit.hash(),
                self.global_index.leaf_index(),
                l1root,
            ),
            Claim::Rollup(claim) => claim.verify(
                self.bridge_exit.hash(),
                self.global_index.leaf_index(),
                self.global_index.rollup_index().unwrap(), // Checked just above
                l1root,
            ),
        }
    }
}

#[cfg(not(feature = "zkvm"))]
impl ImportedBridgeExit {
    /// Creates a new [`ImportedBridgeExit`].
    #[inline]
    pub fn new(bridge_exit: BridgeExit, claim_data: Claim, global_index: GlobalIndex) -> Self {
        Self {
            bridge_exit,
            global_index,
            claim_data,
        }
    }
    /// Returns the considered L1 Info Root against which the claim is done.
    #[inline]
    pub fn l1_info_root(&self) -> Digest {
        match &self.claim_data {
            Claim::Mainnet(claim) => claim.proof_ger_l1root.root,
            Claim::Rollup(claim) => claim.proof_ger_l1root.root,
        }
    }

    /// Returns the considered L1 Info Tree leaf index against which the claim
    /// is done.
    #[inline]
    pub fn l1_leaf_index(&self) -> u32 {
        match &self.claim_data {
            Claim::Mainnet(claim) => claim.l1_leaf.l1_info_tree_index,
            Claim::Rollup(claim) => claim.l1_leaf.l1_info_tree_index,
        }
    }

    /// Hash the entire data structure.
    #[inline]
    pub fn hash(&self) -> Digest {
        keccak256_combine([
            self.bridge_exit.hash(),
            self.claim_data.hash(),
            self.global_index.hash(),
        ])
    }
}

impl ImportedBridgeExit {
    pub fn valid_claim(&self) -> bool {
        match &self.claim_data {
            Claim::Mainnet(claim) => {
                claim.l1_leaf.inner.global_exit_root
                    == keccak256_combine([claim.l1_leaf.mer, claim.l1_leaf.rer])
            }
            Claim::Rollup(claim) => {
                claim.l1_leaf.inner.global_exit_root
                    == keccak256_combine([claim.l1_leaf.mer, claim.l1_leaf.rer])
            }
        }
    }

    /// Returns the global index and the underlying bridge exit leaf hash.
    pub fn to_indexed_exit_hash(&self) -> GlobalIndexWithLeafHash {
        GlobalIndexWithLeafHash {
            global_index: self.global_index.into(),
            bridge_exit_hash: self.bridge_exit.hash(),
        }
    }
}

/// Refers to one claim as per its global index and the hash of the underlying
/// bridge exit.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct GlobalIndexWithLeafHash {
    /// Global index of the claimed bridge exit.
    pub global_index: U256,

    /// Hash of the claimed bridge exit.
    pub bridge_exit_hash: Digest,
}

impl GlobalIndexWithLeafHash {
    /// Compute the bridge exit commitment used for the hash chain.
    pub fn commitment(&self) -> Digest {
        keccak256_combine([self.global_index.to_be_bytes(), self.bridge_exit_hash.0])
    }
}

/// The values which compose the commitment on the imported bridge exits.
#[derive(Debug, Clone)]
pub struct ImportedBridgeExitCommitmentValues {
    pub claims: Vec<GlobalIndexWithLeafHash>,
}

impl ImportedBridgeExitCommitmentValues {
    /// Returns the expected signed commitment for the provided version.
    #[inline]
    pub fn commitment(&self, version: ImportedBridgeExitCommitmentVersion) -> Digest {
        match version {
            ImportedBridgeExitCommitmentVersion::V2 => {
                // Commits solely to the global index of each imported bridge exit. Designed
                // prior to having any notion of aggchain proof.
                keccak256_combine(
                    self.claims
                        .iter()
                        .map(|ibe| keccak256(ibe.global_index.as_le_slice())),
                )
            }
            ImportedBridgeExitCommitmentVersion::V3 => {
                // Adds the bridge exit hashes in the commitment to ensure that the aggchain
                // proof and PP talk about the exact same set of imported bridge exits.
                keccak256_combine(self.claims.iter().map(|ibe| {
                    [
                        ibe.global_index.as_le_slice(),
                        ibe.bridge_exit_hash.as_slice(),
                    ]
                    .concat()
                }))
            }
        }
    }
}