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 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
29pub struct LocalTransactionProver {
34 mast_store: Arc<TransactionMastStore>,
35 proof_options: ProvingOptions,
36}
37
38impl LocalTransactionProver {
39 pub fn new(proof_options: ProvingOptions) -> Self {
41 Self {
42 mast_store: Arc::new(TransactionMastStore::new()),
43 proof_options,
44 }
45 }
46
47 fn build_proven_transaction(
48 &self,
49 input_notes: &InputNotes<InputNote>,
50 tx_outputs: TransactionOutputs,
51 pre_fee_account_delta: AccountDelta,
52 account: PartialAccount,
53 ref_block_num: BlockNumber,
54 ref_block_commitment: Word,
55 proof: ExecutionProof,
56 ) -> Result<ProvenTransaction, TransactionProverError> {
57 let fee = tx_outputs.fee();
58 let expiration_block_num = tx_outputs.expiration_block_num();
59 let (account_header, output_notes) = tx_outputs.into_parts();
60
61 let output_notes: Vec<_> = output_notes
63 .into_iter()
64 .map(|note| note.into_output_note())
65 .collect::<Result<Vec<_>, _>>()
66 .map_err(TransactionProverError::OutputNoteShrinkFailed)?;
67
68 let pre_fee_delta_commitment: Word = pre_fee_account_delta.to_commitment();
71
72 let mut post_fee_account_delta = pre_fee_account_delta;
74 post_fee_account_delta
75 .vault_mut()
76 .remove_asset(Asset::from(fee))
77 .map_err(TransactionProverError::RemoveFeeAssetFromDelta)?;
78
79 let account_update_details = if account.has_public_state() {
80 AccountUpdateDetails::Delta(post_fee_account_delta)
81 } else {
82 AccountUpdateDetails::Private
83 };
84
85 let account_update = TxAccountUpdate::new(
86 account.id(),
87 account.initial_commitment(),
88 account_header.to_commitment(),
89 pre_fee_delta_commitment,
90 account_update_details,
91 )
92 .map_err(TransactionProverError::ProvenTransactionBuildFailed)?;
93
94 ProvenTransaction::new(
95 account_update,
96 input_notes.iter(),
97 output_notes,
98 ref_block_num,
99 ref_block_commitment,
100 fee,
101 expiration_block_num,
102 proof,
103 )
104 .map_err(TransactionProverError::ProvenTransactionBuildFailed)
105 }
106
107 pub async fn prove(
108 &self,
109 tx_inputs: impl Into<TransactionInputs>,
110 ) -> Result<ProvenTransaction, TransactionProverError> {
111 let tx_inputs = tx_inputs.into();
112 let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs(&tx_inputs);
113
114 self.mast_store.load_account_code(tx_inputs.account().code());
115 for account_code in tx_inputs.foreign_account_code() {
116 self.mast_store.load_account_code(account_code);
117 }
118
119 let script_mast_store = ScriptMastForestStore::new(
120 tx_inputs.tx_script(),
121 tx_inputs.input_notes().iter().map(|n| n.note().script()),
122 );
123
124 let account_procedure_index_map = AccountProcedureIndexMap::new(
125 tx_inputs.foreign_account_code().iter().chain([tx_inputs.account().code()]),
126 );
127
128 let (partial_account, ref_block, _, input_notes, _) = tx_inputs.into_parts();
129 let mut host = TransactionProverHost::new(
130 &partial_account,
131 input_notes,
132 self.mast_store.as_ref(),
133 script_mast_store,
134 account_procedure_index_map,
135 );
136
137 let advice_inputs = advice_inputs.into_advice_inputs();
138
139 let (stack_outputs, proof) = prove(
140 &TransactionKernel::main(),
141 stack_inputs,
142 advice_inputs.clone(),
143 &mut host,
144 self.proof_options.clone(),
145 )
146 .await
147 .map_err(TransactionProverError::TransactionProgramExecutionFailed)?;
148
149 let (pre_fee_account_delta, input_notes, output_notes) = host.into_parts();
153 let tx_outputs =
154 TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes)
155 .map_err(TransactionProverError::TransactionOutputConstructionFailed)?;
156
157 self.build_proven_transaction(
158 &input_notes,
159 tx_outputs,
160 pre_fee_account_delta,
161 partial_account,
162 ref_block.block_num(),
163 ref_block.commitment(),
164 proof,
165 )
166 }
167}
168
169impl Default for LocalTransactionProver {
170 fn default() -> Self {
171 Self {
172 mast_store: Arc::new(TransactionMastStore::new()),
173 proof_options: Default::default(),
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}