miden_objects/transaction/
inputs.rs

1use alloc::{collections::BTreeSet, vec::Vec};
2use core::fmt::Debug;
3
4use miden_crypto::merkle::{SmtProof, SmtProofError};
5
6use super::{BlockHeader, Digest, Felt, Hasher, PartialBlockchain, Word};
7use crate::{
8    MAX_INPUT_NOTES_PER_TX, TransactionInputError,
9    account::{Account, AccountCode, AccountId, AccountIdAnchor, PartialAccount, PartialStorage},
10    asset::PartialVault,
11    block::{AccountWitness, BlockNumber},
12    note::{Note, NoteId, NoteInclusionProof, NoteLocation, Nullifier},
13    utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
14};
15
16// TRANSACTION INPUTS
17// ================================================================================================
18
19/// Contains the data required to execute a transaction.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct TransactionInputs {
22    account: Account,
23    account_seed: Option<Word>,
24    block_header: BlockHeader,
25    block_chain: PartialBlockchain,
26    input_notes: InputNotes<InputNote>,
27}
28
29impl TransactionInputs {
30    // CONSTRUCTOR
31    // --------------------------------------------------------------------------------------------
32    /// Returns new [TransactionInputs] instantiated with the specified parameters.
33    ///
34    /// # Errors
35    /// Returns an error if:
36    /// - For a new account, account seed is not provided or the provided seed is invalid.
37    /// - For an existing account, account seed was provided.
38    pub fn new(
39        account: Account,
40        account_seed: Option<Word>,
41        block_header: BlockHeader,
42        block_chain: PartialBlockchain,
43        input_notes: InputNotes<InputNote>,
44    ) -> Result<Self, TransactionInputError> {
45        // validate the seed
46        validate_account_seed(&account, &block_header, &block_chain, account_seed)?;
47
48        // check the block_chain and block_header are consistent
49        let block_num = block_header.block_num();
50        if block_chain.chain_length() != block_header.block_num() {
51            return Err(TransactionInputError::InconsistentChainLength {
52                expected: block_header.block_num(),
53                actual: block_chain.chain_length(),
54            });
55        }
56
57        if block_chain.peaks().hash_peaks() != block_header.chain_commitment() {
58            return Err(TransactionInputError::InconsistentChainCommitment {
59                expected: block_header.chain_commitment(),
60                actual: block_chain.peaks().hash_peaks(),
61            });
62        }
63
64        // check the authentication paths of the input notes.
65        for note in input_notes.iter() {
66            if let InputNote::Authenticated { note, proof } = note {
67                let note_block_num = proof.location().block_num();
68
69                let block_header = if note_block_num == block_num {
70                    &block_header
71                } else {
72                    block_chain.get_block(note_block_num).ok_or(
73                        TransactionInputError::InputNoteBlockNotInPartialBlockchain(note.id()),
74                    )?
75                };
76
77                validate_is_in_block(note, proof, block_header)?;
78            }
79        }
80
81        Ok(Self {
82            account,
83            account_seed,
84            block_header,
85            block_chain,
86            input_notes,
87        })
88    }
89
90    // PUBLIC ACCESSORS
91    // --------------------------------------------------------------------------------------------
92
93    /// Returns account against which the transaction is to be executed.
94    pub fn account(&self) -> &Account {
95        &self.account
96    }
97
98    /// For newly-created accounts, returns the account seed; for existing accounts, returns None.
99    pub fn account_seed(&self) -> Option<Word> {
100        self.account_seed
101    }
102
103    /// Returns block header for the block referenced by the transaction.
104    pub fn block_header(&self) -> &BlockHeader {
105        &self.block_header
106    }
107
108    /// Returns partial blockchain containing authentication paths for all notes consumed by the
109    /// transaction.
110    pub fn block_chain(&self) -> &PartialBlockchain {
111        &self.block_chain
112    }
113
114    /// Returns the notes to be consumed in the transaction.
115    pub fn input_notes(&self) -> &InputNotes<InputNote> {
116        &self.input_notes
117    }
118
119    // CONVERSIONS
120    // --------------------------------------------------------------------------------------------
121
122    /// Consumes these transaction inputs and returns their underlying components.
123    pub fn into_parts(
124        self,
125    ) -> (Account, Option<Word>, BlockHeader, PartialBlockchain, InputNotes<InputNote>) {
126        (
127            self.account,
128            self.account_seed,
129            self.block_header,
130            self.block_chain,
131            self.input_notes,
132        )
133    }
134}
135
136impl Serializable for TransactionInputs {
137    fn write_into<W: ByteWriter>(&self, target: &mut W) {
138        self.account.write_into(target);
139        self.account_seed.write_into(target);
140        self.block_header.write_into(target);
141        self.block_chain.write_into(target);
142        self.input_notes.write_into(target);
143    }
144}
145
146impl Deserializable for TransactionInputs {
147    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
148        let account = Account::read_from(source)?;
149        let account_seed = source.read()?;
150        let block_header = BlockHeader::read_from(source)?;
151        let block_chain = PartialBlockchain::read_from(source)?;
152        let input_notes = InputNotes::read_from(source)?;
153        Self::new(account, account_seed, block_header, block_chain, input_notes)
154            .map_err(|err| DeserializationError::InvalidValue(format!("{}", err)))
155    }
156}
157
158// TO INPUT NOTE COMMITMENT
159// ================================================================================================
160
161/// Specifies the data used by the transaction kernel to commit to a note.
162///
163/// The commitment is composed of:
164///
165/// - nullifier, which prevents double spend and provides unlinkability.
166/// - an optional note commitment, which allows for delayed note authentication.
167pub trait ToInputNoteCommitments {
168    fn nullifier(&self) -> Nullifier;
169    fn note_commitment(&self) -> Option<Digest>;
170}
171
172// INPUT NOTES
173// ================================================================================================
174
175/// Input notes for a transaction, empty if the transaction does not consume notes.
176///
177/// This structure is generic over `T`, so it can be used to create the input notes for transaction
178/// execution, which require the note's details to run the transaction kernel, and the input notes
179/// for proof verification, which require only the commitment data.
180#[derive(Debug, Clone)]
181pub struct InputNotes<T> {
182    notes: Vec<T>,
183    commitment: Digest,
184}
185
186impl<T: ToInputNoteCommitments> InputNotes<T> {
187    // CONSTRUCTOR
188    // --------------------------------------------------------------------------------------------
189    /// Returns new [InputNotes] instantiated from the provided vector of notes.
190    ///
191    /// # Errors
192    /// Returns an error if:
193    /// - The total number of notes is greater than [`MAX_INPUT_NOTES_PER_TX`].
194    /// - The vector of notes contains duplicates.
195    pub fn new(notes: Vec<T>) -> Result<Self, TransactionInputError> {
196        if notes.len() > MAX_INPUT_NOTES_PER_TX {
197            return Err(TransactionInputError::TooManyInputNotes(notes.len()));
198        }
199
200        let mut seen_notes = BTreeSet::new();
201        for note in notes.iter() {
202            if !seen_notes.insert(note.nullifier().inner()) {
203                return Err(TransactionInputError::DuplicateInputNote(note.nullifier()));
204            }
205        }
206
207        let commitment = build_input_note_commitment(&notes);
208
209        Ok(Self { notes, commitment })
210    }
211
212    /// Returns new [`InputNotes`] instantiated from the provided vector of notes without checking
213    /// their validity.
214    ///
215    /// This is exposed for use in transaction batches, but should generally not be used.
216    ///
217    /// # Warning
218    ///
219    /// This does not run the checks from [`InputNotes::new`], so the latter should be preferred.
220    pub fn new_unchecked(notes: Vec<T>) -> Self {
221        let commitment = build_input_note_commitment(&notes);
222        Self { notes, commitment }
223    }
224
225    // PUBLIC ACCESSORS
226    // --------------------------------------------------------------------------------------------
227
228    /// Returns a sequential hash of nullifiers for all notes.
229    ///
230    /// For non empty lists the commitment is defined as:
231    ///
232    /// > hash(nullifier_0 || noteid0_or_zero || nullifier_1 || noteid1_or_zero || .. || nullifier_n
233    /// > || noteidn_or_zero)
234    ///
235    /// Otherwise defined as ZERO for empty lists.
236    pub fn commitment(&self) -> Digest {
237        self.commitment
238    }
239
240    /// Returns total number of input notes.
241    pub fn num_notes(&self) -> usize {
242        self.notes.len()
243    }
244
245    /// Returns true if this [InputNotes] does not contain any notes.
246    pub fn is_empty(&self) -> bool {
247        self.notes.is_empty()
248    }
249
250    /// Returns a reference to the note located at the specified index.
251    pub fn get_note(&self, idx: usize) -> &T {
252        &self.notes[idx]
253    }
254
255    // ITERATORS
256    // --------------------------------------------------------------------------------------------
257
258    /// Returns an iterator over notes in this [InputNotes].
259    pub fn iter(&self) -> impl Iterator<Item = &T> {
260        self.notes.iter()
261    }
262
263    // CONVERSIONS
264    // --------------------------------------------------------------------------------------------
265
266    /// Converts self into a vector of input notes.
267    pub fn into_vec(self) -> Vec<T> {
268        self.notes
269    }
270}
271
272impl<T> IntoIterator for InputNotes<T> {
273    type Item = T;
274    type IntoIter = alloc::vec::IntoIter<Self::Item>;
275
276    fn into_iter(self) -> Self::IntoIter {
277        self.notes.into_iter()
278    }
279}
280
281impl<'a, T> IntoIterator for &'a InputNotes<T> {
282    type Item = &'a T;
283    type IntoIter = alloc::slice::Iter<'a, T>;
284
285    fn into_iter(self) -> alloc::slice::Iter<'a, T> {
286        self.notes.iter()
287    }
288}
289
290impl<T: PartialEq> PartialEq for InputNotes<T> {
291    fn eq(&self, other: &Self) -> bool {
292        self.notes == other.notes
293    }
294}
295
296impl<T: Eq> Eq for InputNotes<T> {}
297
298impl<T: ToInputNoteCommitments> Default for InputNotes<T> {
299    fn default() -> Self {
300        Self {
301            notes: Vec::new(),
302            commitment: build_input_note_commitment::<T>(&[]),
303        }
304    }
305}
306
307// SERIALIZATION
308// ================================================================================================
309
310impl<T: Serializable> Serializable for InputNotes<T> {
311    fn write_into<W: ByteWriter>(&self, target: &mut W) {
312        // assert is OK here because we enforce max number of notes in the constructor
313        assert!(self.notes.len() <= u16::MAX.into());
314        target.write_u16(self.notes.len() as u16);
315        target.write_many(&self.notes);
316    }
317}
318
319impl<T: Deserializable + ToInputNoteCommitments> Deserializable for InputNotes<T> {
320    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
321        let num_notes = source.read_u16()?;
322        let notes = source.read_many::<T>(num_notes.into())?;
323        Self::new(notes).map_err(|err| DeserializationError::InvalidValue(format!("{}", err)))
324    }
325}
326
327// HELPER FUNCTIONS
328// ================================================================================================
329
330fn build_input_note_commitment<T: ToInputNoteCommitments>(notes: &[T]) -> Digest {
331    // Note: This implementation must be kept in sync with the kernel's `process_input_notes_data`
332    if notes.is_empty() {
333        return Digest::default();
334    }
335
336    let mut elements: Vec<Felt> = Vec::with_capacity(notes.len() * 2);
337    for commitment_data in notes {
338        let nullifier = commitment_data.nullifier();
339        let empty_word_or_note_commitment = &commitment_data
340            .note_commitment()
341            .map_or(Word::default(), |note_id| note_id.into());
342
343        elements.extend_from_slice(nullifier.as_elements());
344        elements.extend_from_slice(empty_word_or_note_commitment);
345    }
346    Hasher::hash_elements(&elements)
347}
348
349// INPUT NOTE
350// ================================================================================================
351
352const AUTHENTICATED: u8 = 0;
353const UNAUTHENTICATED: u8 = 1;
354
355/// An input note for a transaction.
356#[derive(Debug, Clone, PartialEq, Eq)]
357pub enum InputNote {
358    /// Input notes whose existences in the chain is verified by the transaction kernel.
359    Authenticated { note: Note, proof: NoteInclusionProof },
360
361    /// Input notes whose existence in the chain is not verified by the transaction kernel, but
362    /// instead is delegated to the rollup kernels.
363    Unauthenticated { note: Note },
364}
365
366impl InputNote {
367    // CONSTRUCTORS
368    // -------------------------------------------------------------------------------------------
369
370    /// Returns an authenticated [InputNote].
371    pub fn authenticated(note: Note, proof: NoteInclusionProof) -> Self {
372        Self::Authenticated { note, proof }
373    }
374
375    /// Returns an unauthenticated [InputNote].
376    pub fn unauthenticated(note: Note) -> Self {
377        Self::Unauthenticated { note }
378    }
379
380    // ACCESSORS
381    // -------------------------------------------------------------------------------------------
382
383    /// Returns the ID of the note.
384    pub fn id(&self) -> NoteId {
385        self.note().id()
386    }
387
388    /// Returns a reference to the underlying note.
389    pub fn note(&self) -> &Note {
390        match self {
391            Self::Authenticated { note, .. } => note,
392            Self::Unauthenticated { note } => note,
393        }
394    }
395
396    /// Returns a reference to the inclusion proof of the note.
397    pub fn proof(&self) -> Option<&NoteInclusionProof> {
398        match self {
399            Self::Authenticated { proof, .. } => Some(proof),
400            Self::Unauthenticated { .. } => None,
401        }
402    }
403
404    /// Returns a reference to the location of the note.
405    pub fn location(&self) -> Option<&NoteLocation> {
406        self.proof().map(|proof| proof.location())
407    }
408}
409
410/// Validates whether the provided note belongs to the note tree of the specified block.
411fn validate_is_in_block(
412    note: &Note,
413    proof: &NoteInclusionProof,
414    block_header: &BlockHeader,
415) -> Result<(), TransactionInputError> {
416    let note_index = proof.location().node_index_in_block().into();
417    let note_commitment = note.commitment();
418    proof
419        .note_path()
420        .verify(note_index, note_commitment, &block_header.note_root())
421        .map_err(|_| {
422            TransactionInputError::InputNoteNotInBlock(note.id(), proof.location().block_num())
423        })
424}
425
426impl ToInputNoteCommitments for InputNote {
427    fn nullifier(&self) -> Nullifier {
428        self.note().nullifier()
429    }
430
431    fn note_commitment(&self) -> Option<Digest> {
432        match self {
433            InputNote::Authenticated { .. } => None,
434            InputNote::Unauthenticated { note } => Some(note.commitment()),
435        }
436    }
437}
438
439impl ToInputNoteCommitments for &InputNote {
440    fn nullifier(&self) -> Nullifier {
441        (*self).nullifier()
442    }
443
444    fn note_commitment(&self) -> Option<Digest> {
445        (*self).note_commitment()
446    }
447}
448
449// SERIALIZATION
450// ------------------------------------------------------------------------------------------------
451
452impl Serializable for InputNote {
453    fn write_into<W: ByteWriter>(&self, target: &mut W) {
454        match self {
455            Self::Authenticated { note, proof } => {
456                target.write(AUTHENTICATED);
457                target.write(note);
458                target.write(proof);
459            },
460            Self::Unauthenticated { note } => {
461                target.write(UNAUTHENTICATED);
462                target.write(note);
463            },
464        }
465    }
466}
467
468impl Deserializable for InputNote {
469    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
470        match source.read_u8()? {
471            AUTHENTICATED => {
472                let note = Note::read_from(source)?;
473                let proof = NoteInclusionProof::read_from(source)?;
474                Ok(Self::Authenticated { note, proof })
475            },
476            UNAUTHENTICATED => {
477                let note = Note::read_from(source)?;
478                Ok(Self::Unauthenticated { note })
479            },
480            v => Err(DeserializationError::InvalidValue(format!("invalid input note type: {v}"))),
481        }
482    }
483}
484
485// INPUT NOTE
486// ================================================================================================
487
488/// Validates that the provided seed is valid for this account.
489pub fn validate_account_seed(
490    account: &Account,
491    block_header: &BlockHeader,
492    block_chain: &PartialBlockchain,
493    account_seed: Option<Word>,
494) -> Result<(), TransactionInputError> {
495    match (account.is_new(), account_seed) {
496        (true, Some(seed)) => {
497            let anchor_block_number = BlockNumber::from_epoch(account.id().anchor_epoch());
498
499            let anchor_block_commitment = if block_header.block_num() == anchor_block_number {
500                block_header.commitment()
501            } else {
502                let anchor_block_header =
503                    block_chain.get_block(anchor_block_number).ok_or_else(|| {
504                        TransactionInputError::AnchorBlockHeaderNotProvidedForNewAccount(
505                            account.id().anchor_epoch(),
506                        )
507                    })?;
508                anchor_block_header.commitment()
509            };
510
511            let anchor = AccountIdAnchor::new(anchor_block_number, anchor_block_commitment)
512                .map_err(TransactionInputError::InvalidAccountIdSeed)?;
513
514            let account_id = AccountId::new(
515                seed,
516                anchor,
517                account.id().version(),
518                account.code().commitment(),
519                account.storage().commitment(),
520            )
521            .map_err(TransactionInputError::InvalidAccountIdSeed)?;
522
523            if account_id != account.id() {
524                return Err(TransactionInputError::InconsistentAccountSeed {
525                    expected: account.id(),
526                    actual: account_id,
527                });
528            }
529
530            Ok(())
531        },
532        (true, None) => Err(TransactionInputError::AccountSeedNotProvidedForNewAccount),
533        (false, Some(_)) => Err(TransactionInputError::AccountSeedProvidedForExistingAccount),
534        (false, None) => Ok(()),
535    }
536}
537
538// ACCOUNT INPUTS
539// ================================================================================================
540
541/// Contains information about an account, with everything required to execute a transaction.
542///
543/// `AccountInputs` combines a partial account representation with the merkle proof that verifies
544/// the account's inclusion in the account tree. The partial account should contain verifiable
545/// access to the parts of the state of the account of which the transaction will make use.
546#[derive(Clone, Debug, PartialEq, Eq)]
547pub struct AccountInputs {
548    /// Partial representation of the account's state.
549    partial_account: PartialAccount,
550    /// Proof of the account's inclusion in the account tree for this account's state commitment.
551    witness: AccountWitness,
552}
553
554impl AccountInputs {
555    /// Creates a new instance of `AccountInputs` with the specified partial account and witness.
556    pub fn new(partial_account: PartialAccount, witness: AccountWitness) -> AccountInputs {
557        AccountInputs { partial_account, witness }
558    }
559
560    /// Returns the account ID.
561    pub fn id(&self) -> AccountId {
562        self.partial_account.id()
563    }
564
565    /// Returns a reference to the partial account representation.
566    pub fn account(&self) -> &PartialAccount {
567        &self.partial_account
568    }
569
570    /// Returns a reference to the account code.
571    pub fn code(&self) -> &AccountCode {
572        self.partial_account.code()
573    }
574
575    /// Returns a reference to the partial representation of the account storage.
576    pub fn storage(&self) -> &PartialStorage {
577        self.partial_account.storage()
578    }
579
580    /// Returns a reference to the partial vault representation of the account.
581    pub fn vault(&self) -> &PartialVault {
582        self.partial_account.vault()
583    }
584
585    /// Returns a reference to the account's witness.
586    pub fn witness(&self) -> &AccountWitness {
587        &self.witness
588    }
589
590    /// Decomposes the `AccountInputs` into its constituent parts.
591    pub fn into_parts(self) -> (PartialAccount, AccountWitness) {
592        (self.partial_account, self.witness)
593    }
594
595    /// Computes the account root based on the account witness.
596    /// This root should be equal to the account root in the reference block header.
597    pub fn compute_account_root(&self) -> Result<Digest, SmtProofError> {
598        let smt_merkle_path = self.witness.path().clone();
599        let smt_leaf = self.witness.leaf();
600        let root = SmtProof::new(smt_merkle_path, smt_leaf)?.compute_root();
601
602        Ok(root)
603    }
604}
605
606impl Serializable for AccountInputs {
607    fn write_into<W: ByteWriter>(&self, target: &mut W) {
608        target.write(&self.partial_account);
609        target.write(&self.witness);
610    }
611}
612
613impl Deserializable for AccountInputs {
614    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
615        let partial_account = source.read()?;
616        let witness = source.read()?;
617
618        Ok(AccountInputs { partial_account, witness })
619    }
620}
621
622#[cfg(test)]
623mod tests {
624    use alloc::vec::Vec;
625
626    use miden_crypto::merkle::MerklePath;
627    use vm_core::{
628        Felt,
629        utils::{Deserializable, Serializable},
630    };
631    use vm_processor::SMT_DEPTH;
632
633    use crate::{
634        account::{Account, AccountCode, AccountId, AccountStorage},
635        asset::AssetVault,
636        block::AccountWitness,
637        testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
638        transaction::AccountInputs,
639    };
640
641    #[test]
642    fn serde_roundtrip() {
643        let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap();
644        let code = AccountCode::mock();
645        let vault = AssetVault::new(&[]).unwrap();
646        let storage = AccountStorage::new(vec![]).unwrap();
647        let account = Account::from_parts(id, vault, storage, code, Felt::new(10));
648
649        let commitment = account.commitment();
650
651        let mut merkle_nodes = Vec::with_capacity(SMT_DEPTH as usize);
652        for _ in 0..(SMT_DEPTH as usize) {
653            merkle_nodes.push(commitment);
654        }
655        let merkle_path = MerklePath::new(merkle_nodes);
656
657        let fpi_inputs = AccountInputs::new(
658            account.into(),
659            AccountWitness::new(id, commitment, merkle_path).unwrap(),
660        );
661
662        let serialized = fpi_inputs.to_bytes();
663        let deserialized = AccountInputs::read_from_bytes(&serialized).unwrap();
664        assert_eq!(deserialized, fpi_inputs);
665    }
666}