Skip to main content

miden_tx/prover/
mod.rs

1use alloc::vec::Vec;
2
3use miden_processor::{ExecutionError, ExecutionOptions, FastProcessor};
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::Prover;
17use miden_prover::{ExecutionProof, Word};
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/// The produced proof covers the VM execution only. Precompile claims are left deferred, because a
34/// batch settles the claims of all its transactions with a single precompile proof.
35///
36/// Each `prove()` call creates a fresh [`TransactionMastStore`] loaded with only the current
37/// transaction's account code, ensuring no state accumulates across calls. This is important
38/// in WASM environments where accumulated MAST forests fragment the linear memory.
39#[derive(Debug, Clone)]
40pub struct LocalTransactionProver {
41    prover: Prover,
42    execution_options: ExecutionOptions,
43}
44
45impl Default for LocalTransactionProver {
46    fn default() -> Self {
47        Self::new(Prover::new().with_hash_fn(Poseidon2))
48    }
49}
50
51impl LocalTransactionProver {
52    /// Creates a new [LocalTransactionProver] instance with the default [`ExecutionOptions`].
53    pub fn new(prover: Prover) -> Self {
54        Self {
55            prover,
56            execution_options: ExecutionOptions::default(),
57        }
58    }
59
60    /// Sets the [`ExecutionOptions`] used while proving and returns the resulting prover.
61    ///
62    /// This lets a caller tune the VM limits enforced during proving, so that proving can use the
63    /// same options as execution.
64    ///
65    /// This will overwrite any previously set options.
66    #[must_use]
67    pub fn with_execution_options(mut self, execution_options: ExecutionOptions) -> Self {
68        self.execution_options = execution_options;
69        self
70    }
71
72    /// Returns the [`ExecutionOptions`] this prover uses.
73    pub fn execution_options(&self) -> ExecutionOptions {
74        self.execution_options
75    }
76
77    fn build_proven_transaction(
78        &self,
79        input_notes: &InputNotes<InputNote>,
80        tx_outputs: TransactionOutputs,
81        account_patch: AccountPatch,
82        account: PartialAccount,
83        ref_block_num: BlockNumber,
84        ref_block_commitment: Word,
85        proof: ExecutionProof,
86    ) -> Result<ProvenTransaction, TransactionProverError> {
87        let expiration_block_num = tx_outputs.expiration_block_num();
88        let (account_header, output_notes) = tx_outputs.into_parts();
89
90        // erase private note information (convert private full notes to just headers)
91        let output_notes: Vec<_> = output_notes
92            .into_iter()
93            .map(|note| note.into_output_note())
94            .collect::<Result<Vec<_>, _>>()
95            .map_err(TransactionProverError::OutputNoteShrinkFailed)?;
96
97        // Compute the commitment of the patch, which goes into the proven transaction since it is
98        // the output of the transaction and so is needed for proof verification.
99        let patch_commitment: Word = account_patch.to_commitment();
100
101        let account_update_details = if account.id().is_public() {
102            AccountUpdateDetails::Public(account_patch)
103        } else {
104            AccountUpdateDetails::Private
105        };
106
107        let account_update = TxAccountUpdate::new(
108            account.id(),
109            account.initial_commitment(),
110            account_header.to_commitment(),
111            patch_commitment,
112            account_update_details,
113        )
114        .map_err(TransactionProverError::ProvenTransactionBuildFailed)?;
115
116        ProvenTransaction::new(
117            account_update,
118            input_notes.iter(),
119            output_notes,
120            ref_block_num,
121            ref_block_commitment,
122            expiration_block_num,
123            proof,
124        )
125        .map_err(TransactionProverError::ProvenTransactionBuildFailed)
126    }
127
128    pub fn prove(
129        &self,
130        tx_inputs: impl Into<TransactionInputs>,
131    ) -> Result<ProvenTransaction, TransactionProverError> {
132        let tx_inputs = tx_inputs.into();
133        let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs(&tx_inputs);
134
135        // Create a per-call MAST store to avoid accumulating forests across prove
136        // calls. Using the shared self.mast_store would grow monotonically (each
137        // call adds account code that is never removed), fragmenting WASM linear
138        // memory and eventually causing capacity_overflow panics. A per-call store
139        // also avoids races: prove() takes &self, so concurrent calls would
140        // conflict on a shared mutable store.
141        let mast_store = TransactionMastStore::new();
142        mast_store.load_account_code(tx_inputs.account().code());
143        for account_code in tx_inputs.foreign_account_code() {
144            mast_store.load_account_code(account_code);
145        }
146
147        let script_mast_store = ScriptMastForestStore::new(
148            tx_inputs.tx_script(),
149            tx_inputs.input_notes().iter().map(|n| n.note().script()),
150        );
151
152        let account_procedure_index_map = AccountProcedureIndexMap::new(
153            tx_inputs.foreign_account_code().iter().chain([tx_inputs.account().code()]),
154        );
155
156        let block_commitments = tx_inputs.collect_block_commitments();
157
158        let (partial_account, ref_block, _, input_notes, _) = tx_inputs.into_parts();
159        let mut host = TransactionProverHost::new(
160            &partial_account,
161            input_notes,
162            block_commitments,
163            &mast_store,
164            script_mast_store,
165            account_procedure_index_map,
166        );
167
168        let advice_inputs = advice_inputs.into_advice_inputs();
169
170        let processor = FastProcessor::new_with_options(
171            stack_inputs,
172            advice_inputs.clone(),
173            self.execution_options,
174        )
175        .map_err(ExecutionError::advice_error_no_context)
176        .map_err(TransactionProverError::TransactionProgramExecutionFailed)?;
177
178        let witness = processor
179            .execute_for_proving_sync(&TransactionKernel::main(), &mut host)
180            .map_err(TransactionProverError::TransactionProgramExecutionFailed)?;
181        let stack_outputs = *witness.claim().stack_outputs();
182
183        let proof = self
184            .prover
185            .prove(witness)
186            .map_err(TransactionProverError::TransactionProofGenerationFailed)?;
187
188        // Extract transaction outputs and process transaction data.
189        let (account_patch, input_notes, output_notes) = host.into_parts();
190        let tx_outputs =
191            TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes)
192                .map_err(TransactionProverError::TransactionOutputConstructionFailed)?;
193
194        self.build_proven_transaction(
195            &input_notes,
196            tx_outputs,
197            account_patch,
198            partial_account,
199            ref_block.block_num(),
200            ref_block.commitment(),
201            proof,
202        )
203    }
204}
205
206#[cfg(any(feature = "testing", test))]
207impl LocalTransactionProver {
208    pub fn prove_dummy(
209        &self,
210        executed_transaction: miden_protocol::transaction::ExecutedTransaction,
211    ) -> Result<ProvenTransaction, TransactionProverError> {
212        self.prove_with_dummy(
213            executed_transaction,
214            miden_protocol::testing::dummy_execution_proof(),
215        )
216    }
217
218    /// Returns a proven transaction carrying a structurally incomplete proof for verifier tests.
219    pub fn prove_dummy_deferred(
220        &self,
221        executed_transaction: miden_protocol::transaction::ExecutedTransaction,
222    ) -> Result<ProvenTransaction, TransactionProverError> {
223        self.prove_with_dummy(
224            executed_transaction,
225            miden_protocol::testing::dummy_deferred_execution_proof(),
226        )
227    }
228
229    /// Returns a proven transaction carrying a complete proof with precompile work for verifier
230    /// tests.
231    pub fn prove_dummy_precompile(
232        &self,
233        executed_transaction: miden_protocol::transaction::ExecutedTransaction,
234    ) -> Result<ProvenTransaction, TransactionProverError> {
235        self.prove_with_dummy(
236            executed_transaction,
237            miden_protocol::testing::dummy_precompile_execution_proof(),
238        )
239    }
240
241    fn prove_with_dummy(
242        &self,
243        executed_transaction: miden_protocol::transaction::ExecutedTransaction,
244        proof: ExecutionProof,
245    ) -> Result<ProvenTransaction, TransactionProverError> {
246        let (tx_inputs, tx_outputs, account_patch, _) = executed_transaction.into_parts();
247
248        let (partial_account, ref_block, _, input_notes, _) = tx_inputs.into_parts();
249
250        self.build_proven_transaction(
251            &input_notes,
252            tx_outputs,
253            account_patch,
254            partial_account,
255            ref_block.block_num(),
256            ref_block.commitment(),
257            proof,
258        )
259    }
260}
261
262// TESTS
263// ================================================================================================
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268
269    #[test]
270    fn default_execution_options_are_used_unless_replaced() {
271        let prover = LocalTransactionProver::default();
272        assert_eq!(prover.execution_options(), ExecutionOptions::default());
273
274        let custom_options = ExecutionOptions::default().with_max_advice_size_bytes(1);
275        let prover = prover.with_execution_options(custom_options);
276        assert_eq!(prover.execution_options(), custom_options);
277    }
278}