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#[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 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_account_seed(&account, &block_header, &block_chain, account_seed)?;
47
48 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 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 pub fn account(&self) -> &Account {
95 &self.account
96 }
97
98 pub fn account_seed(&self) -> Option<Word> {
100 self.account_seed
101 }
102
103 pub fn block_header(&self) -> &BlockHeader {
105 &self.block_header
106 }
107
108 pub fn block_chain(&self) -> &PartialBlockchain {
111 &self.block_chain
112 }
113
114 pub fn input_notes(&self) -> &InputNotes<InputNote> {
116 &self.input_notes
117 }
118
119 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
158pub trait ToInputNoteCommitments {
168 fn nullifier(&self) -> Nullifier;
169 fn note_commitment(&self) -> Option<Digest>;
170}
171
172#[derive(Debug, Clone)]
181pub struct InputNotes<T> {
182 notes: Vec<T>,
183 commitment: Digest,
184}
185
186impl<T: ToInputNoteCommitments> InputNotes<T> {
187 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(¬es);
208
209 Ok(Self { notes, commitment })
210 }
211
212 pub fn new_unchecked(notes: Vec<T>) -> Self {
221 let commitment = build_input_note_commitment(¬es);
222 Self { notes, commitment }
223 }
224
225 pub fn commitment(&self) -> Digest {
237 self.commitment
238 }
239
240 pub fn num_notes(&self) -> usize {
242 self.notes.len()
243 }
244
245 pub fn is_empty(&self) -> bool {
247 self.notes.is_empty()
248 }
249
250 pub fn get_note(&self, idx: usize) -> &T {
252 &self.notes[idx]
253 }
254
255 pub fn iter(&self) -> impl Iterator<Item = &T> {
260 self.notes.iter()
261 }
262
263 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
307impl<T: Serializable> Serializable for InputNotes<T> {
311 fn write_into<W: ByteWriter>(&self, target: &mut W) {
312 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
327fn build_input_note_commitment<T: ToInputNoteCommitments>(notes: &[T]) -> Digest {
331 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
349const AUTHENTICATED: u8 = 0;
353const UNAUTHENTICATED: u8 = 1;
354
355#[derive(Debug, Clone, PartialEq, Eq)]
357pub enum InputNote {
358 Authenticated { note: Note, proof: NoteInclusionProof },
360
361 Unauthenticated { note: Note },
364}
365
366impl InputNote {
367 pub fn authenticated(note: Note, proof: NoteInclusionProof) -> Self {
372 Self::Authenticated { note, proof }
373 }
374
375 pub fn unauthenticated(note: Note) -> Self {
377 Self::Unauthenticated { note }
378 }
379
380 pub fn id(&self) -> NoteId {
385 self.note().id()
386 }
387
388 pub fn note(&self) -> &Note {
390 match self {
391 Self::Authenticated { note, .. } => note,
392 Self::Unauthenticated { note } => note,
393 }
394 }
395
396 pub fn proof(&self) -> Option<&NoteInclusionProof> {
398 match self {
399 Self::Authenticated { proof, .. } => Some(proof),
400 Self::Unauthenticated { .. } => None,
401 }
402 }
403
404 pub fn location(&self) -> Option<&NoteLocation> {
406 self.proof().map(|proof| proof.location())
407 }
408}
409
410fn 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
449impl 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
485pub 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#[derive(Clone, Debug, PartialEq, Eq)]
547pub struct AccountInputs {
548 partial_account: PartialAccount,
550 witness: AccountWitness,
552}
553
554impl AccountInputs {
555 pub fn new(partial_account: PartialAccount, witness: AccountWitness) -> AccountInputs {
557 AccountInputs { partial_account, witness }
558 }
559
560 pub fn id(&self) -> AccountId {
562 self.partial_account.id()
563 }
564
565 pub fn account(&self) -> &PartialAccount {
567 &self.partial_account
568 }
569
570 pub fn code(&self) -> &AccountCode {
572 self.partial_account.code()
573 }
574
575 pub fn storage(&self) -> &PartialStorage {
577 self.partial_account.storage()
578 }
579
580 pub fn vault(&self) -> &PartialVault {
582 self.partial_account.vault()
583 }
584
585 pub fn witness(&self) -> &AccountWitness {
587 &self.witness
588 }
589
590 pub fn into_parts(self) -> (PartialAccount, AccountWitness) {
592 (self.partial_account, self.witness)
593 }
594
595 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}