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 .into_iter()
33 .map(|tx| tx.build_unchecked().map(alloc::sync::Arc::new))
34 .collect::<Result<_, _>>()?;
35 let header = self.reference_block_header.build_unchecked()?;
36 let chain = self.partial_blockchain.build_unchecked()?;
37 let mut proofs = alloc::collections::BTreeMap::new();
38 let mut previous = None;
39 for proof in self.unauthenticated_note_proofs {
40 let (id, proof) = proof.verify()?;
41 if previous.is_some_and(|previous| id <= previous) {
42 return Err(ProposedBatchError::ProofOrder.into());
43 }
44 previous = Some(id);
45 proofs.insert(id, proof);
46 }
47 Ok(Self::Verified::new(transactions, header, chain, proofs, proof_security_level)?)
48 }
49}
50
51#[derive(Debug, thiserror::Error)]
52pub enum ProposedBatchError {
53 #[error("unauthenticated note proofs must have unique, ascending note IDs")]
54 ProofOrder,
55}
56
57pub use proto::transaction::DecodedProvenBatch as ProvenBatch;
58
59impl crate::BuildUnchecked for ProvenBatch {
62 type Output = miden_protocol::batch::ProvenBatch;
63 type Error = VerificationError;
64 fn build_unchecked(self) -> Result<Self::Output, Self::Error> {
65 let mut previous = None;
66 let mut updates = alloc::vec::Vec::new();
67 for update in self.account_updates {
68 let update = update.verify()?;
69 if previous.is_some_and(|previous| update.account_id() <= previous) {
70 return Err(ProvenBatchError::AccountOrder.into());
71 }
72 previous = Some(update.account_id());
73 updates.push(update);
74 }
75 let inputs = self
76 .input_notes
77 .into_iter()
78 .map(BuildUnchecked::build_unchecked)
79 .collect::<Result<_, _>>()?;
80 let outputs =
81 self.output_notes.into_iter().map(Verify::verify).collect::<Result<_, _>>()?;
82 let transactions = self
83 .transactions
84 .into_iter()
85 .map(BuildUnchecked::build_unchecked)
86 .collect::<Result<_, _>>()?;
87 Ok(Self::Output::new(
88 self.reference_block_commitment,
89 unwrap_infallible(self.reference_block_num.verify()),
90 updates,
91 miden_protocol::transaction::InputNotes::new_unchecked(inputs),
92 outputs,
93 unwrap_infallible(self.expiration_block_num.verify()),
94 miden_protocol::transaction::OrderedTransactionHeaders::new_unchecked(transactions),
95 self.proof,
96 )?)
97 }
98}
99
100#[derive(Debug, thiserror::Error)]
101pub enum ProvenBatchError {
102 #[error("account updates must have unique, ascending account IDs")]
103 AccountOrder,
104 #[error("{0} does not match proposal")]
105 ProposalMismatch(&'static str),
106}
107
108impl crate::VerifyWith<&miden_protocol::batch::ProposedBatch> for ProvenBatch {
111 type Verified = miden_protocol::batch::ProvenBatch;
112 type Error = VerificationError;
113 fn verify_with(
114 self,
115 proposed: &miden_protocol::batch::ProposedBatch,
116 ) -> Result<Self::Verified, Self::Error> {
117 let batch = self.build_unchecked()?;
118 let header = proposed.reference_block_header();
119 let mismatch = if batch.reference_block_num() != header.block_num() {
120 Some("reference block number")
121 } else if batch.reference_block_commitment() != header.commitment() {
122 Some("reference block commitment")
123 } else if batch.account_updates() != proposed.account_updates() {
124 Some("account updates")
125 } else if !batch.input_notes().iter().eq(proposed.input_notes().iter()) {
126 Some("input notes")
127 } else if batch.output_notes() != proposed.output_notes() {
128 Some("output notes")
129 } else if batch.batch_expiration_block_num() != proposed.batch_expiration_block_num() {
130 Some("expiration block")
131 } else if batch.transactions().as_slice() != proposed.transaction_headers().as_slice() {
132 Some("transaction headers")
133 } else {
134 None
135 };
136 if let Some(mismatch) = mismatch {
137 return Err(ProvenBatchError::ProposalMismatch(mismatch).into());
138 }
139 Ok(batch)
140 }
141}