miden_objects/transaction/inputs/
mod.rs

1use core::fmt::Debug;
2
3use super::PartialBlockchain;
4use crate::{
5    TransactionInputError, Word,
6    account::{Account, AccountId},
7    block::BlockHeader,
8    note::{Note, NoteInclusionProof},
9    utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
10};
11
12mod account;
13pub use account::AccountInputs;
14
15mod notes;
16pub use notes::{InputNote, InputNotes, ToInputNoteCommitments};
17
18// TRANSACTION INPUTS
19// ================================================================================================
20
21/// Contains the data required to execute a transaction.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct TransactionInputs {
24    account: Account,
25    account_seed: Option<Word>,
26    block_header: BlockHeader,
27    block_chain: PartialBlockchain,
28    input_notes: InputNotes<InputNote>,
29}
30
31impl TransactionInputs {
32    // CONSTRUCTOR
33    // --------------------------------------------------------------------------------------------
34    /// Returns new [TransactionInputs] instantiated with the specified parameters.
35    ///
36    /// # Errors
37    /// Returns an error if:
38    /// - For a new account, account seed is not provided or the provided seed is invalid.
39    /// - For an existing account, account seed was provided.
40    pub fn new(
41        account: Account,
42        account_seed: Option<Word>,
43        block_header: BlockHeader,
44        block_chain: PartialBlockchain,
45        input_notes: InputNotes<InputNote>,
46    ) -> Result<Self, TransactionInputError> {
47        // validate the seed
48        validate_account_seed(&account, account_seed)?;
49
50        // check the block_chain and block_header are consistent
51        let block_num = block_header.block_num();
52        if block_chain.chain_length() != block_header.block_num() {
53            return Err(TransactionInputError::InconsistentChainLength {
54                expected: block_header.block_num(),
55                actual: block_chain.chain_length(),
56            });
57        }
58
59        if block_chain.peaks().hash_peaks() != block_header.chain_commitment() {
60            return Err(TransactionInputError::InconsistentChainCommitment {
61                expected: block_header.chain_commitment(),
62                actual: block_chain.peaks().hash_peaks(),
63            });
64        }
65
66        // check the authentication paths of the input notes.
67        for note in input_notes.iter() {
68            if let InputNote::Authenticated { note, proof } = note {
69                let note_block_num = proof.location().block_num();
70
71                let block_header = if note_block_num == block_num {
72                    &block_header
73                } else {
74                    block_chain.get_block(note_block_num).ok_or(
75                        TransactionInputError::InputNoteBlockNotInPartialBlockchain(note.id()),
76                    )?
77                };
78
79                validate_is_in_block(note, proof, block_header)?;
80            }
81        }
82
83        Ok(Self {
84            account,
85            account_seed,
86            block_header,
87            block_chain,
88            input_notes,
89        })
90    }
91
92    // PUBLIC ACCESSORS
93    // --------------------------------------------------------------------------------------------
94
95    /// Returns account against which the transaction is to be executed.
96    pub fn account(&self) -> &Account {
97        &self.account
98    }
99
100    /// For newly-created accounts, returns the account seed; for existing accounts, returns None.
101    pub fn account_seed(&self) -> Option<Word> {
102        self.account_seed
103    }
104
105    /// Returns block header for the block referenced by the transaction.
106    pub fn block_header(&self) -> &BlockHeader {
107        &self.block_header
108    }
109
110    /// Returns partial blockchain containing authentication paths for all notes consumed by the
111    /// transaction.
112    pub fn blockchain(&self) -> &PartialBlockchain {
113        &self.block_chain
114    }
115
116    /// Returns the notes to be consumed in the transaction.
117    pub fn input_notes(&self) -> &InputNotes<InputNote> {
118        &self.input_notes
119    }
120
121    // CONVERSIONS
122    // --------------------------------------------------------------------------------------------
123
124    /// Consumes these transaction inputs and returns their underlying components.
125    pub fn into_parts(
126        self,
127    ) -> (Account, Option<Word>, BlockHeader, PartialBlockchain, InputNotes<InputNote>) {
128        (
129            self.account,
130            self.account_seed,
131            self.block_header,
132            self.block_chain,
133            self.input_notes,
134        )
135    }
136}
137
138impl Serializable for TransactionInputs {
139    fn write_into<W: ByteWriter>(&self, target: &mut W) {
140        self.account.write_into(target);
141        self.account_seed.write_into(target);
142        self.block_header.write_into(target);
143        self.block_chain.write_into(target);
144        self.input_notes.write_into(target);
145    }
146}
147
148impl Deserializable for TransactionInputs {
149    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
150        let account = Account::read_from(source)?;
151        let account_seed = source.read()?;
152        let block_header = BlockHeader::read_from(source)?;
153        let block_chain = PartialBlockchain::read_from(source)?;
154        let input_notes = InputNotes::read_from(source)?;
155        Self::new(account, account_seed, block_header, block_chain, input_notes)
156            .map_err(|err| DeserializationError::InvalidValue(format!("{err}")))
157    }
158}
159
160// HELPER FUNCTIONS
161// ================================================================================================
162
163/// Validates that the provided seed is valid for this account.
164fn validate_account_seed(
165    account: &Account,
166    account_seed: Option<Word>,
167) -> Result<(), TransactionInputError> {
168    match (account.is_new(), account_seed) {
169        (true, Some(seed)) => {
170            let account_id = AccountId::new(
171                seed,
172                account.id().version(),
173                account.code().commitment(),
174                account.storage().commitment(),
175            )
176            .map_err(TransactionInputError::InvalidAccountIdSeed)?;
177
178            if account_id != account.id() {
179                return Err(TransactionInputError::InconsistentAccountSeed {
180                    expected: account.id(),
181                    actual: account_id,
182                });
183            }
184
185            Ok(())
186        },
187        (true, None) => Err(TransactionInputError::AccountSeedNotProvidedForNewAccount),
188        (false, Some(_)) => Err(TransactionInputError::AccountSeedProvidedForExistingAccount),
189        (false, None) => Ok(()),
190    }
191}
192
193/// Validates whether the provided note belongs to the note tree of the specified block.
194fn validate_is_in_block(
195    note: &Note,
196    proof: &NoteInclusionProof,
197    block_header: &BlockHeader,
198) -> Result<(), TransactionInputError> {
199    let note_index = proof.location().node_index_in_block().into();
200    let note_commitment = note.commitment();
201    proof
202        .note_path()
203        .verify(note_index, note_commitment, &block_header.note_root())
204        .map_err(|_| {
205            TransactionInputError::InputNoteNotInBlock(note.id(), proof.location().block_num())
206        })
207}