miden_objects/transaction/inputs/
mod.rs1use 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#[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 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_account_seed(&account, account_seed)?;
49
50 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 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 pub fn account(&self) -> &Account {
97 &self.account
98 }
99
100 pub fn account_seed(&self) -> Option<Word> {
102 self.account_seed
103 }
104
105 pub fn block_header(&self) -> &BlockHeader {
107 &self.block_header
108 }
109
110 pub fn blockchain(&self) -> &PartialBlockchain {
113 &self.block_chain
114 }
115
116 pub fn input_notes(&self) -> &InputNotes<InputNote> {
118 &self.input_notes
119 }
120
121 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
160fn 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
193fn 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}