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#[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 pub fn new(prover: Prover) -> Self {
54 Self {
55 prover,
56 execution_options: ExecutionOptions::default(),
57 }
58 }
59
60 #[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 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 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 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 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 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 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 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#[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}