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 {
#[error("Mismatch between the global index and the inclusion proof.")]
MismatchGlobalIndexInclusionProof,
#[error("Mismatch between the provided L1 root and the inclusion proof.")]
MismatchL1Root,
#[error("Mismatch on the MER between the L1 leaf and the inclusion proof.")]
MismatchMER,
#[error("Mismatch on the RER between the L1 leaf and the inclusion proof.")]
MismatchRER,
#[error("Invalid merkle path from the leaf to the LER.")]
InvalidMerklePathLeafToLER,
#[error("Invalid merkle path from the LER to the RER.")]
InvalidMerklePathLERToRER,
#[error("Invalid merkle path from the GER to the L1 Info Root.")]
InvalidMerklePathGERToL1Root,
#[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 {
pub proof_leaf_mer: MerkleProof,
pub proof_ger_l1root: MerkleProof,
pub l1_leaf: L1InfoTreeLeaf,
}
impl ClaimFromMainnet {
#[inline]
pub fn verify(&self, leaf: Digest, leaf_index: u32, l1root: Digest) -> Result<(), Error> {
if l1root != self.proof_ger_l1root.root {
return Err(Error::MismatchL1Root);
}
if self.proof_leaf_mer.root != self.l1_leaf.mer {
return Err(Error::MismatchMER);
}
if !self.proof_leaf_mer.verify(leaf, leaf_index) {
return Err(Error::InvalidMerklePathLeafToLER);
}
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 {
pub proof_leaf_ler: MerkleProof,
pub proof_ler_rer: MerkleProof,
pub proof_ger_l1root: MerkleProof,
pub l1_leaf: L1InfoTreeLeaf,
}
impl ClaimFromRollup {
#[inline]
pub fn verify(
&self,
leaf: Digest,
leaf_index: u32,
rollup_index: RollupIndex,
l1root: Digest,
) -> Result<(), Error> {
if l1root != self.proof_ger_l1root.root {
return Err(Error::MismatchL1Root);
}
if self.proof_ler_rer.root != self.l1_leaf.rer {
return Err(Error::MismatchRER);
}
if !self.proof_leaf_ler.verify(leaf, leaf_index) {
return Err(Error::InvalidMerklePathLeafToLER);
}
if !self
.proof_ler_rer
.verify(self.proof_leaf_ler.root, rollup_index.to_u32())
{
return Err(Error::InvalidMerklePathLERToRER);
}
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 ImportedBridgeExit {
pub bridge_exit: BridgeExit,
pub claim_data: Claim,
pub global_index: GlobalIndex,
}
impl ImportedBridgeExit {
#[inline]
pub fn verify_path(&self, l1root: Digest) -> Result<(), Error> {
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(), l1root,
),
}
}
}
#[cfg(not(feature = "zkvm"))]
impl ImportedBridgeExit {
#[inline]
pub fn new(bridge_exit: BridgeExit, claim_data: Claim, global_index: GlobalIndex) -> Self {
Self {
bridge_exit,
global_index,
claim_data,
}
}
#[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,
}
}
#[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,
}
}
#[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])
}
}
}
pub fn to_indexed_exit_hash(&self) -> GlobalIndexWithLeafHash {
GlobalIndexWithLeafHash {
global_index: self.global_index.into(),
bridge_exit_hash: self.bridge_exit.hash(),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct GlobalIndexWithLeafHash {
pub global_index: U256,
pub bridge_exit_hash: Digest,
}
impl GlobalIndexWithLeafHash {
pub fn commitment(&self) -> Digest {
keccak256_combine([self.global_index.to_be_bytes(), self.bridge_exit_hash.0])
}
}
#[derive(Debug, Clone)]
pub struct ImportedBridgeExitCommitmentValues {
pub claims: Vec<GlobalIndexWithLeafHash>,
}
impl ImportedBridgeExitCommitmentValues {
#[inline]
pub fn commitment(&self, version: ImportedBridgeExitCommitmentVersion) -> Digest {
match version {
ImportedBridgeExitCommitmentVersion::V2 => {
keccak256_combine(
self.claims
.iter()
.map(|ibe| keccak256(ibe.global_index.as_le_slice())),
)
}
ImportedBridgeExitCommitmentVersion::V3 => {
keccak256_combine(self.claims.iter().map(|ibe| {
[
ibe.global_index.as_le_slice(),
ibe.bridge_exit_hash.as_slice(),
]
.concat()
}))
}
}
}
}