1use alloc::boxed::Box;
2use alloc::vec::Vec;
3
4use miden_processor::{ExecutionError, ExecutionOptions, FastProcessor};
5use miden_protocol::account::{AccountPatch, AccountUpdateDetails, PartialAccount};
6use miden_protocol::block::BlockNumber;
7use miden_protocol::transaction::{
8 InputNote,
9 InputNotes,
10 ProvenTransaction,
11 TransactionInputs,
12 TransactionKernel,
13 TransactionOutputs,
14 TxAccountUpdate,
15};
16use miden_prover::HashFunction::Poseidon2;
17pub use miden_prover::Prover;
18use miden_prover::{ExecutionProof, Word};
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#[derive(Debug, Clone)]
41pub struct LocalTransactionProver {
42 prover: Prover,
43 execution_options: ExecutionOptions,
44}
45
46impl Default for LocalTransactionProver {
47 fn default() -> Self {
48 Self::new(Prover::new().with_hash_fn(Poseidon2))
49 }
50}
51
52impl LocalTransactionProver {
53 pub fn new(prover: Prover) -> Self {
55 Self {
56 prover,
57 execution_options: ExecutionOptions::default(),
58 }
59 }
60
61 #[must_use]
68 pub fn with_execution_options(mut self, execution_options: ExecutionOptions) -> Self {
69 self.execution_options = execution_options;
70 self
71 }
72
73 pub fn execution_options(&self) -> ExecutionOptions {
75 self.execution_options
76 }
77
78 fn build_proven_transaction(
79 &self,
80 input_notes: &InputNotes<InputNote>,
81 tx_outputs: TransactionOutputs,
82 account_patch: AccountPatch,
83 account: PartialAccount,
84 ref_block_num: BlockNumber,
85 ref_block_commitment: Word,
86 proof: ExecutionProof,
87 ) -> Result<ProvenTransaction, TransactionProverError> {
88 let expiration_block_num = tx_outputs.expiration_block_num();
89 let (account_header, output_notes) = tx_outputs.into_parts();
90
91 let output_notes: Vec<_> = output_notes
93 .into_iter()
94 .map(|note| note.into_output_note())
95 .collect::<Result<Vec<_>, _>>()
96 .map_err(TransactionProverError::OutputNoteShrinkFailed)?;
97
98 let patch_commitment: Word = account_patch.to_commitment();
101
102 let account_update_details = if account.id().is_public() {
103 AccountUpdateDetails::Public(account_patch)
104 } else {
105 AccountUpdateDetails::Private
106 };
107
108 let account_update = TxAccountUpdate::new(
109 account.id(),
110 account.initial_commitment(),
111 account_header.to_commitment(),
112 patch_commitment,
113 account_update_details,
114 )
115 .map_err(TransactionProverError::ProvenTransactionBuildFailed)?;
116
117 ProvenTransaction::new(
118 account_update,
119 input_notes.iter(),
120 output_notes,
121 ref_block_num,
122 ref_block_commitment,
123 expiration_block_num,
124 proof,
125 )
126 .map_err(TransactionProverError::ProvenTransactionBuildFailed)
127 }
128
129 pub fn prove(
130 &self,
131 tx_inputs: impl Into<TransactionInputs>,
132 ) -> Result<ProvenTransaction, TransactionProverError> {
133 let tx_inputs = tx_inputs.into();
134 let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs(&tx_inputs);
135
136 let mast_store = TransactionMastStore::new();
143 mast_store.load_account_code(tx_inputs.account().code());
144 for account_code in tx_inputs.foreign_account_code() {
145 mast_store.load_account_code(account_code);
146 }
147
148 let script_mast_store = ScriptMastForestStore::new(
149 tx_inputs.tx_script(),
150 tx_inputs.input_notes().iter().map(|n| n.note().script()),
151 );
152
153 let account_procedure_index_map = AccountProcedureIndexMap::new(
154 tx_inputs.foreign_account_code().iter().chain([tx_inputs.account().code()]),
155 );
156
157 let block_commitments = tx_inputs.collect_block_commitments();
158
159 let (partial_account, ref_block, _, input_notes, _) = tx_inputs.into_parts();
160 let mut host = TransactionProverHost::new(
161 &partial_account,
162 input_notes,
163 block_commitments,
164 &mast_store,
165 script_mast_store,
166 account_procedure_index_map,
167 )
168 .map_err(|err| TransactionProverError::TransactionHostCreationFailed(Box::new(err)))?;
169
170 let advice_inputs = advice_inputs.into_advice_inputs();
171
172 let processor = FastProcessor::new_with_options(
173 stack_inputs,
174 advice_inputs.clone(),
175 self.execution_options,
176 )
177 .map_err(ExecutionError::advice_error_no_context)
178 .map_err(TransactionProverError::TransactionProgramExecutionFailed)?;
179
180 let witness = processor
181 .execute_for_proving_sync(&TransactionKernel::main(), &mut host)
182 .map_err(TransactionProverError::TransactionProgramExecutionFailed)?;
183 let stack_outputs = *witness.claim().stack_outputs();
184
185 let proof = self
186 .prover
187 .prove(witness)
188 .map_err(TransactionProverError::TransactionProofGenerationFailed)?;
189
190 let (account_patch, input_notes, output_notes) = host.into_parts();
192 let tx_outputs =
193 TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes)
194 .map_err(TransactionProverError::TransactionOutputConstructionFailed)?;
195
196 self.build_proven_transaction(
197 &input_notes,
198 tx_outputs,
199 account_patch,
200 partial_account,
201 ref_block.block_num(),
202 ref_block.commitment(),
203 proof,
204 )
205 }
206}
207
208#[cfg(any(feature = "testing", test))]
209impl LocalTransactionProver {
210 pub fn prove_dummy(
211 &self,
212 executed_transaction: miden_protocol::transaction::ExecutedTransaction,
213 ) -> Result<ProvenTransaction, TransactionProverError> {
214 self.prove_with_dummy(
215 executed_transaction,
216 miden_protocol::testing::dummy_execution_proof(),
217 )
218 }
219
220 pub fn prove_dummy_deferred(
222 &self,
223 executed_transaction: miden_protocol::transaction::ExecutedTransaction,
224 ) -> Result<ProvenTransaction, TransactionProverError> {
225 self.prove_with_dummy(
226 executed_transaction,
227 miden_protocol::testing::dummy_deferred_execution_proof(),
228 )
229 }
230
231 pub fn prove_dummy_precompile(
234 &self,
235 executed_transaction: miden_protocol::transaction::ExecutedTransaction,
236 ) -> Result<ProvenTransaction, TransactionProverError> {
237 self.prove_with_dummy(
238 executed_transaction,
239 miden_protocol::testing::dummy_precompile_execution_proof(),
240 )
241 }
242
243 fn prove_with_dummy(
244 &self,
245 executed_transaction: miden_protocol::transaction::ExecutedTransaction,
246 proof: ExecutionProof,
247 ) -> Result<ProvenTransaction, TransactionProverError> {
248 let (tx_inputs, tx_outputs, account_patch, _) = executed_transaction.into_parts();
249
250 let (partial_account, ref_block, _, input_notes, _) = tx_inputs.into_parts();
251
252 self.build_proven_transaction(
253 &input_notes,
254 tx_outputs,
255 account_patch,
256 partial_account,
257 ref_block.block_num(),
258 ref_block.commitment(),
259 proof,
260 )
261 }
262}
263
264#[cfg(test)]
268mod tests {
269 use super::*;
270
271 #[test]
272 fn default_execution_options_are_used_unless_replaced() {
273 let prover = LocalTransactionProver::default();
274 assert_eq!(prover.execution_options(), ExecutionOptions::default());
275
276 let custom_options = ExecutionOptions::default().with_max_advice_size_bytes(1);
277 let prover = prover.with_execution_options(custom_options);
278 assert_eq!(prover.execution_options(), custom_options);
279 }
280}