Skip to main content

miden_tx/prover/
mod.rs

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