miden_objects/decoded/transaction/
batch.rs1use miden_protobuf::unwrap_infallible;
2pub use proto::transaction::DecodedBatchAccountUpdate as BatchAccountUpdate;
3
4use crate::decoded::VerificationError;
5use crate::{BuildUnchecked, Verify, proto};
6
7#[cfg(test)]
8mod tests;
9
10impl Verify for BatchAccountUpdate {
11 type Verified = miden_protocol::batch::BatchAccountUpdate;
12 type Error = VerificationError;
13 fn verify(self) -> Result<Self::Verified, Self::Error> {
14 Ok(Self::Verified::new(
15 self.account_id.verify()?,
16 self.initial_state_commitment,
17 self.final_state_commitment,
18 self.details.verify()?,
19 )?)
20 }
21}
22
23pub use proto::transaction::DecodedProposedBatch as ProposedBatch;
24
25impl crate::VerifyWith<u32> for ProposedBatch {
27 type Verified = miden_protocol::batch::ProposedBatch;
28 type Error = VerificationError;
29 fn verify_with(self, proof_security_level: u32) -> Result<Self::Verified, Self::Error> {
30 let transactions = self
31 .transactions
32 .try_map(|tx| tx.build_unchecked().map(alloc::sync::Arc::new))?;
33 let header = self.reference_block_header.build_unchecked()?;
34 let chain = self.partial_blockchain.build_unchecked()?;
35 let mut proofs = alloc::collections::BTreeMap::new();
36 let mut previous = None;
37 for proof in self.unauthenticated_note_proofs.into_inner() {
38 let (id, proof) = proof.verify()?;
39 if previous.is_some_and(|previous| id <= previous) {
40 return Err(ProposedBatchError::ProofOrder.into());
41 }
42 previous = Some(id);
43 proofs.insert(id, proof);
44 }
45 Ok(Self::Verified::new(transactions, header, chain, proofs, proof_security_level)?)
46 }
47}
48
49#[derive(Debug, thiserror::Error)]
50pub enum ProposedBatchError {
51 #[error("unauthenticated note proofs must have unique, ascending note IDs")]
52 ProofOrder,
53}
54
55pub use proto::transaction::DecodedProvenBatch as ProvenBatch;
56
57impl crate::BuildUnchecked for ProvenBatch {
60 type Output = miden_protocol::batch::ProvenBatch;
61 type Error = VerificationError;
62 fn build_unchecked(self) -> Result<Self::Output, Self::Error> {
63 let mut previous = None;
64 let mut updates = alloc::vec::Vec::new();
65 for update in self.account_updates.into_inner() {
66 let update = update.verify()?;
67 if previous.is_some_and(|previous| update.account_id() <= previous) {
68 return Err(ProvenBatchError::AccountOrder.into());
69 }
70 previous = Some(update.account_id());
71 updates.push(update);
72 }
73 let inputs = self.input_notes.build_unchecked()?;
74 let outputs = self.output_notes.verify()?;
75 let transactions = self.transactions.build_unchecked()?;
76 Ok(Self::Output::new(
77 self.reference_block_commitment,
78 unwrap_infallible(self.reference_block_num.verify()),
79 updates,
80 miden_protocol::transaction::InputNotes::new_unchecked(inputs),
81 outputs,
82 unwrap_infallible(self.expiration_block_num.verify()),
83 miden_protocol::transaction::OrderedTransactionHeaders::new_unchecked(transactions),
84 self.proof,
85 )?)
86 }
87}
88
89#[derive(Debug, thiserror::Error)]
90pub enum ProvenBatchError {
91 #[error("account updates must have unique, ascending account IDs")]
92 AccountOrder,
93 #[error("{0} does not match proposal")]
94 ProposalMismatch(&'static str),
95}
96
97impl crate::VerifyWith<&miden_protocol::batch::ProposedBatch> for ProvenBatch {
100 type Verified = miden_protocol::batch::ProvenBatch;
101 type Error = VerificationError;
102 fn verify_with(
103 self,
104 proposed: &miden_protocol::batch::ProposedBatch,
105 ) -> Result<Self::Verified, Self::Error> {
106 let batch = self.build_unchecked()?;
107 let header = proposed.reference_block_header();
108 let mismatch = if batch.reference_block_num() != header.block_num() {
109 Some("reference block number")
110 } else if batch.reference_block_commitment() != header.commitment() {
111 Some("reference block commitment")
112 } else if batch.account_updates() != proposed.account_updates() {
113 Some("account updates")
114 } else if !batch.input_notes().iter().eq(proposed.input_notes().iter()) {
115 Some("input notes")
116 } else if batch.output_notes() != proposed.output_notes() {
117 Some("output notes")
118 } else if batch.batch_expiration_block_num() != proposed.batch_expiration_block_num() {
119 Some("expiration block")
120 } else if batch.transactions().as_slice() != proposed.transaction_headers().as_slice() {
121 Some("transaction headers")
122 } else {
123 None
124 };
125 if let Some(mismatch) = mismatch {
126 return Err(ProvenBatchError::ProposalMismatch(mismatch).into());
127 }
128 Ok(batch)
129 }
130}