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}