Skip to main content

miden_objects/decoded/transaction/
batch.rs

1use 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
25/// Verifies transaction proofs and batch consistency, not trust in the supplied reference chain.
26impl 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
57/// Checks local batch invariants, but not proof validity, note aggregation, or transaction
58/// ordering.
59impl 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
97/// Checks all fields duplicated from an already-verified proposal. The batch execution proof
98/// still needs verification by the consuming service; this only establishes proposal agreement.
99impl 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}