Skip to main content

miden_tx/prover/
mod.rs

1use alloc::vec::Vec;
2
3use miden_processor::ExecutionOptions;
4use miden_protocol::account::{AccountPatch, AccountUpdateDetails, PartialAccount};
5use miden_protocol::block::BlockNumber;
6use miden_protocol::transaction::{
7    InputNote,
8    InputNotes,
9    ProvenTransaction,
10    TransactionInputs,
11    TransactionKernel,
12    TransactionOutputs,
13    TxAccountUpdate,
14};
15use miden_prover::HashFunction::Poseidon2;
16pub use miden_prover::ProvingOptions;
17use miden_prover::{ExecutionProof, Word, prove_sync};
18
19use super::TransactionProverError;
20use crate::host::{AccountProcedureIndexMap, ScriptMastForestStore};
21
22mod prover_host;
23pub use prover_host::TransactionProverHost;
24
25mod mast_store;
26pub use mast_store::TransactionMastStore;
27
28// LOCAL TRANSACTION PROVER
29// ------------------------------------------------------------------------------------------------
30
31/// Local Transaction prover is a stateless component which is responsible for proving transactions.
32///
33/// Each `prove()` call creates a fresh [`TransactionMastStore`] loaded with only the current
34/// transaction's account code, ensuring no state accumulates across calls. This is important
35/// in WASM environments where accumulated MAST forests fragment the linear memory.
36#[derive(Debug, Clone)]
37pub struct LocalTransactionProver {
38    proof_options: ProvingOptions,
39}
40
41impl Default for LocalTransactionProver {
42    fn default() -> Self {
43        Self {
44            proof_options: ProvingOptions::new(Poseidon2),
45        }
46    }
47}
48
49impl LocalTransactionProver {
50    /// Creates a new [LocalTransactionProver] instance.
51    pub fn new(proof_options: ProvingOptions) -> Self {
52        Self { proof_options }
53    }
54
55    fn build_proven_transaction(
56        &self,
57        input_notes: &InputNotes<InputNote>,
58        tx_outputs: TransactionOutputs,
59        account_patch: AccountPatch,
60        account: PartialAccount,
61        ref_block_num: BlockNumber,
62        ref_block_commitment: Word,
63        proof: ExecutionProof,
64    ) -> Result<ProvenTransaction, TransactionProverError> {
65        let expiration_block_num = tx_outputs.expiration_block_num();
66        let (account_header, output_notes) = tx_outputs.into_parts();
67
68        // erase private note information (convert private full notes to just headers)
69        let output_notes: Vec<_> = output_notes
70            .into_iter()
71            .map(|note| note.into_output_note())
72            .collect::<Result<Vec<_>, _>>()
73            .map_err(TransactionProverError::OutputNoteShrinkFailed)?;
74
75        // Compute the commitment of the patch, which goes into the proven transaction since it is
76        // the output of the transaction and so is needed for proof verification.
77        let patch_commitment: Word = account_patch.to_commitment();
78
79        let account_update_details = if account.id().is_public() {
80            AccountUpdateDetails::Public(account_patch)
81        } else {
82            AccountUpdateDetails::Private
83        };
84
85        let account_update = TxAccountUpdate::new(
86            account.id(),
87            account.initial_commitment(),
88            account_header.to_commitment(),
89            patch_commitment,
90            account_update_details,
91        )
92        .map_err(TransactionProverError::ProvenTransactionBuildFailed)?;
93
94        ProvenTransaction::new(
95            account_update,
96            input_notes.iter(),
97            output_notes,
98            ref_block_num,
99            ref_block_commitment,
100            expiration_block_num,
101            proof,
102        )
103        .map_err(TransactionProverError::ProvenTransactionBuildFailed)
104    }
105
106    pub fn prove(
107        &self,
108        tx_inputs: impl Into<TransactionInputs>,
109    ) -> Result<ProvenTransaction, TransactionProverError> {
110        let tx_inputs = tx_inputs.into();
111        let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs(&tx_inputs);
112
113        // Create a per-call MAST store to avoid accumulating forests across prove
114        // calls. Using the shared self.mast_store would grow monotonically (each
115        // call adds account code that is never removed), fragmenting WASM linear
116        // memory and eventually causing capacity_overflow panics. A per-call store
117        // also avoids races: prove() takes &self, so concurrent calls would
118        // conflict on a shared mutable store.
119        let mast_store = TransactionMastStore::new();
120        mast_store.load_account_code(tx_inputs.account().code());
121        for account_code in tx_inputs.foreign_account_code() {
122            mast_store.load_account_code(account_code);
123        }
124
125        let script_mast_store = ScriptMastForestStore::new(
126            tx_inputs.tx_script(),
127            tx_inputs.input_notes().iter().map(|n| n.note().script()),
128        );
129
130        let account_procedure_index_map = AccountProcedureIndexMap::new(
131            tx_inputs.foreign_account_code().iter().chain([tx_inputs.account().code()]),
132        );
133
134        let (partial_account, ref_block, _, input_notes, _) = tx_inputs.into_parts();
135        let mut host = TransactionProverHost::new(
136            &partial_account,
137            input_notes,
138            &mast_store,
139            script_mast_store,
140            account_procedure_index_map,
141        );
142
143        let advice_inputs = advice_inputs.into_advice_inputs();
144
145        let (stack_outputs, proof) = prove_sync(
146            &TransactionKernel::main(),
147            stack_inputs,
148            advice_inputs.clone(),
149            &mut host,
150            ExecutionOptions::default(),
151            self.proof_options.clone(),
152        )
153        .map_err(TransactionProverError::TransactionProgramExecutionFailed)?;
154
155        // Extract transaction outputs and process transaction data.
156        let (account_patch, input_notes, output_notes) = host.into_parts();
157        let tx_outputs =
158            TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes)
159                .map_err(TransactionProverError::TransactionOutputConstructionFailed)?;
160
161        self.build_proven_transaction(
162            &input_notes,
163            tx_outputs,
164            account_patch,
165            partial_account,
166            ref_block.block_num(),
167            ref_block.commitment(),
168            proof,
169        )
170    }
171}
172
173#[cfg(any(feature = "testing", test))]
174impl LocalTransactionProver {
175    pub fn prove_dummy(
176        &self,
177        executed_transaction: miden_protocol::transaction::ExecutedTransaction,
178    ) -> Result<ProvenTransaction, TransactionProverError> {
179        let (tx_inputs, tx_outputs, account_patch, _) = executed_transaction.into_parts();
180
181        let (partial_account, ref_block, _, input_notes, _) = tx_inputs.into_parts();
182
183        self.build_proven_transaction(
184            &input_notes,
185            tx_outputs,
186            account_patch,
187            partial_account,
188            ref_block.block_num(),
189            ref_block.commitment(),
190            ExecutionProof::new_dummy(),
191        )
192    }
193}