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#[derive(Default)]
38pub struct LocalTransactionProver {
39 proof_options: ProvingOptions,
40}
41
42impl LocalTransactionProver {
43 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 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 let pre_fee_delta_commitment: Word = pre_fee_account_delta.to_commitment();
72
73 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 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 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}