Skip to main content

miden_tx/prover/
mod.rs

1use alloc::sync::Arc;
2use alloc::vec::Vec;
3
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    OutputNote,
12    ProvenTransaction,
13    ProvenTransactionBuilder,
14    TransactionInputs,
15    TransactionKernel,
16    TransactionOutputs,
17};
18pub use miden_prover::ProvingOptions;
19use miden_prover::{ExecutionProof, Word, prove};
20
21use super::TransactionProverError;
22use crate::host::{AccountProcedureIndexMap, ScriptMastForestStore};
23
24mod prover_host;
25pub use prover_host::TransactionProverHost;
26
27mod mast_store;
28pub use mast_store::TransactionMastStore;
29
30// LOCAL TRANSACTION PROVER
31// ------------------------------------------------------------------------------------------------
32
33/// Local Transaction prover is a stateless component which is responsible for proving transactions.
34pub struct LocalTransactionProver {
35    mast_store: Arc<TransactionMastStore>,
36    proof_options: ProvingOptions,
37}
38
39impl LocalTransactionProver {
40    /// Creates a new [LocalTransactionProver] instance.
41    pub fn new(proof_options: ProvingOptions) -> Self {
42        Self {
43            mast_store: Arc::new(TransactionMastStore::new()),
44            proof_options,
45        }
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        // erase private note information (convert private full notes to just headers)
59        let output_notes: Vec<_> = tx_outputs.output_notes.iter().map(OutputNote::shrink).collect();
60
61        // Compute the commitment of the pre-fee delta, which goes into the proven transaction,
62        // since it is the output of the transaction and so is needed for proof verification.
63        let pre_fee_delta_commitment: Word = pre_fee_account_delta.to_commitment();
64
65        let builder = ProvenTransactionBuilder::new(
66            account.id(),
67            account.initial_commitment(),
68            tx_outputs.account.commitment(),
69            pre_fee_delta_commitment,
70            ref_block_num,
71            ref_block_commitment,
72            tx_outputs.fee,
73            tx_outputs.expiration_block_num,
74            proof,
75        )
76        .add_input_notes(input_notes)
77        .add_output_notes(output_notes);
78
79        // The full transaction delta is the pre fee delta with the fee asset removed.
80        let mut post_fee_account_delta = pre_fee_account_delta;
81        post_fee_account_delta
82            .vault_mut()
83            .remove_asset(Asset::from(tx_outputs.fee))
84            .map_err(TransactionProverError::RemoveFeeAssetFromDelta)?;
85
86        let builder = match account.has_public_state() {
87            true => {
88                let account_update_details = AccountUpdateDetails::Delta(post_fee_account_delta);
89                builder.account_update_details(account_update_details)
90            },
91            false => builder,
92        };
93
94        builder.build().map_err(TransactionProverError::ProvenTransactionBuildFailed)
95    }
96
97    pub fn prove(
98        &self,
99        tx_inputs: impl Into<TransactionInputs>,
100    ) -> Result<ProvenTransaction, TransactionProverError> {
101        let tx_inputs = tx_inputs.into();
102        let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs(&tx_inputs);
103
104        self.mast_store.load_account_code(tx_inputs.account().code());
105        for account_code in tx_inputs.foreign_account_code() {
106            self.mast_store.load_account_code(account_code);
107        }
108
109        let script_mast_store = ScriptMastForestStore::new(
110            tx_inputs.tx_script(),
111            tx_inputs.input_notes().iter().map(|n| n.note().script()),
112        );
113
114        let account_procedure_index_map = AccountProcedureIndexMap::new(
115            tx_inputs.foreign_account_code().iter().chain([tx_inputs.account().code()]),
116        );
117
118        let (partial_account, ref_block, _, input_notes, _) = tx_inputs.into_parts();
119        let mut host = TransactionProverHost::new(
120            &partial_account,
121            input_notes,
122            self.mast_store.as_ref(),
123            script_mast_store,
124            account_procedure_index_map,
125        );
126
127        let advice_inputs = advice_inputs.into_advice_inputs();
128
129        let (stack_outputs, proof) = prove(
130            &TransactionKernel::main(),
131            stack_inputs,
132            advice_inputs.clone(),
133            &mut host,
134            self.proof_options.clone(),
135        )
136        .map_err(TransactionProverError::TransactionProgramExecutionFailed)?;
137
138        // Extract transaction outputs and process transaction data.
139        // Note that the account delta does not contain the removed transaction fee, so it is the
140        // "pre-fee" delta of the transaction.
141        let (pre_fee_account_delta, input_notes, output_notes) = host.into_parts();
142        let tx_outputs =
143            TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes)
144                .map_err(TransactionProverError::TransactionOutputConstructionFailed)?;
145
146        self.build_proven_transaction(
147            &input_notes,
148            tx_outputs,
149            pre_fee_account_delta,
150            partial_account,
151            ref_block.block_num(),
152            ref_block.commitment(),
153            proof,
154        )
155    }
156}
157
158impl Default for LocalTransactionProver {
159    fn default() -> Self {
160        Self {
161            mast_store: Arc::new(TransactionMastStore::new()),
162            proof_options: Default::default(),
163        }
164    }
165}
166
167#[cfg(any(feature = "testing", test))]
168impl LocalTransactionProver {
169    pub fn prove_dummy(
170        &self,
171        executed_transaction: miden_protocol::transaction::ExecutedTransaction,
172    ) -> Result<ProvenTransaction, TransactionProverError> {
173        let (tx_inputs, tx_outputs, account_delta, _) = executed_transaction.into_parts();
174
175        let (partial_account, ref_block, _, input_notes, _) = tx_inputs.into_parts();
176
177        self.build_proven_transaction(
178            &input_notes,
179            tx_outputs,
180            account_delta,
181            partial_account,
182            ref_block.block_num(),
183            ref_block.commitment(),
184            ExecutionProof::new_dummy(),
185        )
186    }
187}