miden_objects/transaction/inputs/
mod.rs1use core::fmt::Debug;
2
3use super::PartialBlockchain;
4use crate::account::{Account, AccountId};
5use crate::block::BlockHeader;
6use crate::note::{Note, NoteInclusionProof};
7use crate::utils::serde::{
8    ByteReader,
9    ByteWriter,
10    Deserializable,
11    DeserializationError,
12    Serializable,
13};
14use crate::{TransactionInputError, Word};
15
16mod account;
17pub use account::AccountInputs;
18
19mod notes;
20pub use notes::{InputNote, InputNotes, ToInputNoteCommitments};
21
22#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct TransactionInputs {
28    account: Account,
29    account_seed: Option<Word>,
30    block_header: BlockHeader,
31    block_chain: PartialBlockchain,
32    input_notes: InputNotes<InputNote>,
33}
34
35impl TransactionInputs {
36    pub fn new(
45        account: Account,
46        account_seed: Option<Word>,
47        block_header: BlockHeader,
48        block_chain: PartialBlockchain,
49        input_notes: InputNotes<InputNote>,
50    ) -> Result<Self, TransactionInputError> {
51        validate_account_seed(&account, account_seed)?;
53
54        let block_num = block_header.block_num();
56        if block_chain.chain_length() != block_header.block_num() {
57            return Err(TransactionInputError::InconsistentChainLength {
58                expected: block_header.block_num(),
59                actual: block_chain.chain_length(),
60            });
61        }
62
63        if block_chain.peaks().hash_peaks() != block_header.chain_commitment() {
64            return Err(TransactionInputError::InconsistentChainCommitment {
65                expected: block_header.chain_commitment(),
66                actual: block_chain.peaks().hash_peaks(),
67            });
68        }
69
70        for note in input_notes.iter() {
72            if let InputNote::Authenticated { note, proof } = note {
73                let note_block_num = proof.location().block_num();
74
75                let block_header = if note_block_num == block_num {
76                    &block_header
77                } else {
78                    block_chain.get_block(note_block_num).ok_or(
79                        TransactionInputError::InputNoteBlockNotInPartialBlockchain(note.id()),
80                    )?
81                };
82
83                validate_is_in_block(note, proof, block_header)?;
84            }
85        }
86
87        Ok(Self {
88            account,
89            account_seed,
90            block_header,
91            block_chain,
92            input_notes,
93        })
94    }
95
96    pub fn account(&self) -> &Account {
101        &self.account
102    }
103
104    pub fn account_seed(&self) -> Option<Word> {
106        self.account_seed
107    }
108
109    pub fn block_header(&self) -> &BlockHeader {
111        &self.block_header
112    }
113
114    pub fn blockchain(&self) -> &PartialBlockchain {
117        &self.block_chain
118    }
119
120    pub fn input_notes(&self) -> &InputNotes<InputNote> {
122        &self.input_notes
123    }
124
125    pub fn into_parts(
130        self,
131    ) -> (Account, Option<Word>, BlockHeader, PartialBlockchain, InputNotes<InputNote>) {
132        (
133            self.account,
134            self.account_seed,
135            self.block_header,
136            self.block_chain,
137            self.input_notes,
138        )
139    }
140}
141
142impl Serializable for TransactionInputs {
143    fn write_into<W: ByteWriter>(&self, target: &mut W) {
144        self.account.write_into(target);
145        self.account_seed.write_into(target);
146        self.block_header.write_into(target);
147        self.block_chain.write_into(target);
148        self.input_notes.write_into(target);
149    }
150}
151
152impl Deserializable for TransactionInputs {
153    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
154        let account = Account::read_from(source)?;
155        let account_seed = source.read()?;
156        let block_header = BlockHeader::read_from(source)?;
157        let block_chain = PartialBlockchain::read_from(source)?;
158        let input_notes = InputNotes::read_from(source)?;
159        Self::new(account, account_seed, block_header, block_chain, input_notes)
160            .map_err(|err| DeserializationError::InvalidValue(format!("{err}")))
161    }
162}
163
164fn validate_account_seed(
169    account: &Account,
170    account_seed: Option<Word>,
171) -> Result<(), TransactionInputError> {
172    match (account.is_new(), account_seed) {
173        (true, Some(seed)) => {
174            let account_id = AccountId::new(
175                seed,
176                account.id().version(),
177                account.code().commitment(),
178                account.storage().commitment(),
179            )
180            .map_err(TransactionInputError::InvalidAccountIdSeed)?;
181
182            if account_id != account.id() {
183                return Err(TransactionInputError::InconsistentAccountSeed {
184                    expected: account.id(),
185                    actual: account_id,
186                });
187            }
188
189            Ok(())
190        },
191        (true, None) => Err(TransactionInputError::AccountSeedNotProvidedForNewAccount),
192        (false, Some(_)) => Err(TransactionInputError::AccountSeedProvidedForExistingAccount),
193        (false, None) => Ok(()),
194    }
195}
196
197fn validate_is_in_block(
199    note: &Note,
200    proof: &NoteInclusionProof,
201    block_header: &BlockHeader,
202) -> Result<(), TransactionInputError> {
203    let note_index = proof.location().node_index_in_block().into();
204    let note_commitment = note.commitment();
205    proof
206        .note_path()
207        .verify(note_index, note_commitment, &block_header.note_root())
208        .map_err(|_| {
209            TransactionInputError::InputNoteNotInBlock(note.id(), proof.location().block_num())
210        })
211}