Skip to main content

miden_agglayer/
claim_note.rs

1use alloc::vec;
2use alloc::vec::Vec;
3
4use miden_assembly::Library;
5use miden_assembly::serde::Deserializable;
6use miden_core::{Felt, Word};
7use miden_protocol::account::AccountId;
8use miden_protocol::crypto::SequentialCommit;
9use miden_protocol::crypto::rand::FeltRng;
10use miden_protocol::errors::NoteError;
11use miden_protocol::note::{
12    Note,
13    NoteAssets,
14    NoteAttachment,
15    NoteAttachments,
16    NoteRecipient,
17    NoteScript,
18    NoteScriptRoot,
19    NoteStorage,
20    NoteType,
21    PartialNoteMetadata,
22};
23use miden_standards::note::{NetworkAccountTarget, NoteExecutionHint};
24use miden_utils_sync::LazyLock;
25
26use crate::utils::Keccak256Output;
27use crate::{EthAddress, EthAmount, GlobalIndex, MetadataHash};
28
29// NOTE SCRIPT
30// ================================================================================================
31
32// Initialize the CLAIM note script only once
33static CLAIM_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
34    let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/claim.masl"));
35    let library =
36        Library::read_from_bytes(bytes).expect("shipped CLAIM script library is well-formed");
37    NoteScript::from_library(&library).expect("shipped CLAIM script is well-formed")
38});
39
40// CLAIM NOTE
41// ================================================================================================
42
43/// CLAIM (Bridge from AggLayer) note.
44///
45/// This note instructs the AggLayer bridge to validate a claim against its registered GERs and
46/// emit a corresponding MINT note for the AggLayer faucet. CLAIM notes are always public.
47pub struct ClaimNote;
48
49impl ClaimNote {
50    // PUBLIC ACCESSORS
51    // --------------------------------------------------------------------------------------------
52
53    /// Returns the CLAIM (Bridge from AggLayer) note script.
54    pub fn script() -> NoteScript {
55        CLAIM_SCRIPT.clone()
56    }
57
58    /// Returns the CLAIM note script root.
59    pub fn script_root() -> NoteScriptRoot {
60        CLAIM_SCRIPT.root()
61    }
62
63    // BUILDERS
64    // --------------------------------------------------------------------------------------------
65
66    /// Creates a CLAIM note — a note that instructs the bridge to validate a claim and create
67    /// a MINT note for the AggLayer Faucet. CLAIM notes are always public.
68    ///
69    /// # Parameters
70    /// - `storage`: The core storage for creating the CLAIM note
71    /// - `target_bridge_id`: The account ID of the bridge that should consume this note. Encoded as
72    ///   a `NetworkAccountTarget` attachment on the note metadata.
73    /// - `sender_account_id`: The account ID of the CLAIM note creator
74    /// - `rng`: Random number generator for creating the CLAIM note serial number
75    ///
76    /// # Errors
77    /// Returns an error if note creation fails.
78    pub fn create<R: FeltRng>(
79        storage: ClaimNoteStorage,
80        target_bridge_id: AccountId,
81        sender_account_id: AccountId,
82        rng: &mut R,
83    ) -> Result<Note, NoteError> {
84        let note_storage = NoteStorage::try_from(storage)?;
85
86        let attachment = NetworkAccountTarget::new(target_bridge_id, NoteExecutionHint::Always)
87            .map_err(|error| {
88                NoteError::other_with_source("failed to create claim network account target", error)
89            })?;
90        let attachments = NoteAttachments::from(NoteAttachment::from(attachment));
91
92        let metadata = PartialNoteMetadata::new(sender_account_id, NoteType::Public);
93
94        let recipient = NoteRecipient::new(rng.draw_word(), Self::script(), note_storage);
95        let assets = NoteAssets::new(vec![])?;
96
97        Ok(Note::with_attachments(assets, metadata, recipient, attachments))
98    }
99}
100
101// CLAIM NOTE TYPE ALIASES
102// ================================================================================================
103
104/// SMT node representation (32-byte Keccak256 hash)
105pub type SmtNode = Keccak256Output;
106
107/// Exit root representation (32-byte Keccak256 hash)
108pub type ExitRoot = Keccak256Output;
109
110/// Leaf value representation (32-byte Keccak256 hash)
111pub type LeafValue = Keccak256Output;
112
113/// Claimed Global Index (CGI) chain hash representation (32-byte Keccak256 hash)
114pub type CgiChainHash = Keccak256Output;
115
116/// Proof data for CLAIM note creation.
117/// Contains SMT proofs and root hashes using typed representations.
118#[derive(Clone)]
119pub struct ProofData {
120    /// SMT proof for local exit root (32 SMT nodes)
121    pub smt_proof_local_exit_root: [SmtNode; 32],
122    /// SMT proof for rollup exit root (32 SMT nodes)
123    pub smt_proof_rollup_exit_root: [SmtNode; 32],
124    /// Global index (uint256 as 32 bytes)
125    pub global_index: GlobalIndex,
126    /// Mainnet exit root hash
127    pub mainnet_exit_root: ExitRoot,
128    /// Rollup exit root hash
129    pub rollup_exit_root: ExitRoot,
130}
131
132impl SequentialCommit for ProofData {
133    type Commitment = Word;
134
135    fn to_elements(&self) -> Vec<Felt> {
136        const PROOF_DATA_ELEMENT_COUNT: usize = 536; // 32*8 + 32*8 + 8 + 8 + 8 (proofs + global_index + 2 exit roots)
137        let mut elements = Vec::with_capacity(PROOF_DATA_ELEMENT_COUNT);
138
139        // Convert SMT proof elements to felts (each node is 8 felts)
140        for node in self.smt_proof_local_exit_root.iter() {
141            elements.extend(node.to_elements());
142        }
143
144        for node in self.smt_proof_rollup_exit_root.iter() {
145            elements.extend(node.to_elements());
146        }
147
148        // Global index (uint256 as 32 bytes)
149        elements.extend(self.global_index.to_elements());
150
151        // Mainnet and rollup exit roots
152        elements.extend(self.mainnet_exit_root.to_elements());
153        elements.extend(self.rollup_exit_root.to_elements());
154
155        elements
156    }
157}
158
159/// Leaf data for CLAIM note creation.
160/// Contains network, address, amount, and metadata using typed representations.
161#[derive(Clone)]
162pub struct LeafData {
163    /// Origin network identifier (uint32)
164    pub origin_network: u32,
165    /// Origin token address
166    pub origin_token_address: EthAddress,
167    /// Destination network identifier (uint32)
168    pub destination_network: u32,
169    /// Destination address
170    pub destination_address: EthAddress,
171    /// Amount of tokens (uint256)
172    pub amount: EthAmount,
173    /// Metadata hash (32 bytes)
174    pub metadata_hash: MetadataHash,
175}
176
177impl SequentialCommit for LeafData {
178    type Commitment = Word;
179
180    fn to_elements(&self) -> Vec<Felt> {
181        const LEAF_DATA_ELEMENT_COUNT: usize = 32; // 1 + 1 + 5 + 1 + 5 + 8 + 8 + 3 (leafType + networks + addresses + amount + metadata + padding)
182        let mut elements = Vec::with_capacity(LEAF_DATA_ELEMENT_COUNT);
183
184        // LeafType (uint32 as Felt): 0u32 for transfer Ether / ERC20 tokens, 1u32 for message
185        // passing.
186        // for a `CLAIM` note, leafType is always 0 (transfer Ether / ERC20 tokens)
187        elements.push(Felt::ZERO);
188
189        // Origin network (encode as little-endian bytes for keccak)
190        let origin_network = u32::from_le_bytes(self.origin_network.to_be_bytes());
191        elements.push(Felt::from(origin_network));
192
193        // Origin token address (5 u32 felts)
194        elements.extend(self.origin_token_address.to_elements());
195
196        // Destination network (encode as little-endian bytes for keccak)
197        let destination_network = u32::from_le_bytes(self.destination_network.to_be_bytes());
198        elements.push(Felt::from(destination_network));
199
200        // Destination address (5 u32 felts)
201        elements.extend(self.destination_address.to_elements());
202
203        // Amount (uint256 as 8 u32 felts)
204        elements.extend(self.amount.to_elements());
205
206        // Metadata hash (8 u32 felts)
207        elements.extend(self.metadata_hash.to_elements());
208
209        // Padding
210        elements.extend(vec![Felt::ZERO; 3]);
211
212        elements
213    }
214}
215
216/// Data for creating a CLAIM note.
217///
218/// This struct groups the core data needed to create a CLAIM note that exactly
219/// matches the agglayer claimAsset function signature.
220#[derive(Clone)]
221pub struct ClaimNoteStorage {
222    /// Proof data containing SMT proofs and root hashes
223    pub proof_data: ProofData,
224    /// Leaf data containing network, address, amount, and metadata
225    pub leaf_data: LeafData,
226    /// Miden claim amount (scaled-down token amount as Felt)
227    pub miden_claim_amount: Felt,
228}
229
230impl TryFrom<ClaimNoteStorage> for NoteStorage {
231    type Error = NoteError;
232
233    fn try_from(storage: ClaimNoteStorage) -> Result<Self, Self::Error> {
234        // proof_data + leaf_data + miden_claim_amount
235        // 536 + 32 + 1
236        let mut claim_storage = Vec::with_capacity(569);
237
238        claim_storage.extend(storage.proof_data.to_elements());
239        claim_storage.extend(storage.leaf_data.to_elements());
240        claim_storage.push(storage.miden_claim_amount);
241
242        NoteStorage::new(claim_storage)
243    }
244}