1use std::{collections::HashSet, hash::RandomState};
20
21use darkfi::{
22 tx::{ContractCallLeaf, Transaction, TransactionBuilder},
23 validator::fees::compute_fee,
24 zk::{halo2::Field, Proof},
25 Result,
26};
27use darkfi_money_contract::{
28 client::{
29 compute_remainder_blind,
30 fee_v1::{create_fee_proof, FeeCallInput, FeeCallOutput, FEE_CALL_GAS},
31 MoneyNote, OwnCoin,
32 },
33 model::{token_id::DARK_TOKEN_ID, Input, MoneyFeeParamsV1, Output},
34 MoneyFunction, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
35};
36use darkfi_sdk::{
37 crypto::{
38 contract_id::MONEY_CONTRACT_ID, note::AeadEncryptedNote, BaseBlind, Blind, FuncId,
39 MerkleNode, ScalarBlind, SecretKey,
40 },
41 pasta::pallas,
42 ContractCall,
43};
44use darkfi_serial::AsyncEncodable;
45use log::{debug, info};
46use rand::rngs::OsRng;
47
48use super::{Holder, TestHarness};
49
50impl TestHarness {
51 pub async fn create_empty_fee_call(
55 &mut self,
56 holder: &Holder,
57 ) -> Result<(Transaction, MoneyFeeParamsV1)> {
58 let wallet = self.holders.get(holder).unwrap();
59
60 let required_fee = compute_fee(&FEE_CALL_GAS);
62
63 let coin = wallet
65 .unspent_money_coins
66 .iter()
67 .find(|x| x.note.token_id == *DARK_TOKEN_ID && x.note.value > required_fee)
68 .unwrap();
69
70 let input = FeeCallInput {
72 coin: coin.clone(),
73 merkle_path: wallet.money_merkle_tree.witness(coin.leaf_position, 0).unwrap(),
74 user_data_blind: Blind::random(&mut OsRng),
75 };
76
77 let output = FeeCallOutput {
78 public_key: wallet.keypair.public,
79 value: coin.note.value - required_fee,
80 token_id: coin.note.token_id,
81 blind: Blind::random(&mut OsRng),
82 spend_hook: FuncId::none(),
83 user_data: pallas::Base::ZERO,
84 };
85
86 let token_blind = BaseBlind::random(&mut OsRng);
88 let input_value_blind = ScalarBlind::random(&mut OsRng);
89 let fee_value_blind = ScalarBlind::random(&mut OsRng);
90 let output_value_blind = compute_remainder_blind(&[input_value_blind], &[fee_value_blind]);
91
92 let signature_secret = SecretKey::random(&mut OsRng);
94
95 info!("Creting FeeV1 ZK proof");
96 let (fee_pk, fee_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_FEE_NS_V1).unwrap();
97
98 let (proof, public_inputs) = create_fee_proof(
99 fee_zkbin,
100 fee_pk,
101 &input,
102 input_value_blind,
103 &output,
104 output_value_blind,
105 output.spend_hook,
106 output.user_data,
107 output.blind,
108 token_blind,
109 signature_secret,
110 )?;
111
112 let note = MoneyNote {
114 coin_blind: output.blind,
115 value: output.value,
116 token_id: output.token_id,
117 spend_hook: output.spend_hook,
118 user_data: output.user_data,
119 value_blind: output_value_blind,
120 token_blind,
121 memo: vec![],
122 };
123
124 let encrypted_note = AeadEncryptedNote::encrypt(¬e, &output.public_key, &mut OsRng)?;
125
126 let params = MoneyFeeParamsV1 {
127 input: Input {
128 value_commit: public_inputs.input_value_commit,
129 token_commit: public_inputs.token_commit,
130 nullifier: public_inputs.nullifier,
131 merkle_root: public_inputs.merkle_root,
132 user_data_enc: public_inputs.input_user_data_enc,
133 signature_public: public_inputs.signature_public,
134 },
135 output: Output {
136 value_commit: public_inputs.output_value_commit,
137 token_commit: public_inputs.token_commit,
138 coin: public_inputs.output_coin,
139 note: encrypted_note,
140 },
141 fee_value_blind,
142 token_blind,
143 };
144
145 let mut data = vec![MoneyFunction::FeeV1 as u8];
146 required_fee.encode_async(&mut data).await?;
147 params.encode_async(&mut data).await?;
148 let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
149 let mut tx_builder =
150 TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![proof] }, vec![])?;
151 let mut tx = tx_builder.build()?;
152 let sigs = tx.create_sigs(&[signature_secret])?;
153 tx.signatures = vec![sigs];
154
155 Ok((tx, params))
156 }
157
158 pub async fn execute_empty_fee_call_tx(
162 &mut self,
163 holder: &Holder,
164 tx: Transaction,
165 params: &MoneyFeeParamsV1,
166 block_height: u32,
167 ) -> Result<Vec<OwnCoin>> {
168 let wallet = self.holders.get_mut(holder).unwrap();
169
170 let nullifier = params.input.nullifier.inner();
171 wallet
172 .money_null_smt
173 .insert_batch(vec![(nullifier, nullifier)])
174 .expect("smt.insert_batch()");
175
176 wallet.add_transaction("money::fee", tx, block_height).await?;
177 wallet.money_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
178
179 let Ok(note) = params.output.note.decrypt::<MoneyNote>(&wallet.keypair.secret) else {
181 return Ok(vec![])
182 };
183
184 let owncoin = OwnCoin {
185 coin: params.output.coin,
186 note: note.clone(),
187 secret: wallet.keypair.secret,
188 leaf_position: wallet.money_merkle_tree.mark().unwrap(),
189 };
190
191 let spent_coin = wallet
192 .unspent_money_coins
193 .iter()
194 .find(|x| x.nullifier() == params.input.nullifier)
195 .unwrap()
196 .clone();
197
198 debug!("Found spent OwnCoin({}) for {:?}", spent_coin.coin, holder);
199 debug!("Found new OwnCoin({}) for {:?}", owncoin.coin, holder);
200
201 wallet.unspent_money_coins.retain(|x| x.nullifier() != params.input.nullifier);
202 wallet.spent_money_coins.push(spent_coin);
203 wallet.unspent_money_coins.push(owncoin.clone());
204
205 Ok(vec![owncoin])
206 }
207
208 pub async fn append_fee_call(
215 &mut self,
216 holder: &Holder,
217 tx: Transaction,
218 block_height: u32,
219 spent_coins: &[OwnCoin],
220 ) -> Result<(ContractCall, Vec<Proof>, Vec<SecretKey>, Vec<OwnCoin>, MoneyFeeParamsV1)> {
221 let wallet = self.holders.get(holder).unwrap();
224 let gas_used = wallet
225 .validator
226 .add_test_transactions(
227 &[tx],
228 block_height,
229 wallet.validator.consensus.module.read().await.target,
230 false,
231 false,
232 )
233 .await?
234 .0;
235
236 let required_fee = compute_fee(&(gas_used + FEE_CALL_GAS));
238
239 let spent_coins: HashSet<&OwnCoin, RandomState> = HashSet::from_iter(spent_coins);
242 let mut available_coins = wallet.unspent_money_coins.clone();
243 available_coins
244 .retain(|x| x.note.token_id == *DARK_TOKEN_ID && x.note.value > required_fee);
245 available_coins.retain(|x| !spent_coins.contains(x));
246 assert!(!available_coins.is_empty());
247
248 let coin = &available_coins[0];
249 let change_value = coin.note.value - required_fee;
250
251 let input = FeeCallInput {
253 coin: coin.clone(),
254 merkle_path: wallet.money_merkle_tree.witness(coin.leaf_position, 0).unwrap(),
255 user_data_blind: BaseBlind::random(&mut OsRng),
256 };
257
258 let output = FeeCallOutput {
259 public_key: wallet.keypair.public,
260 value: change_value,
261 token_id: coin.note.token_id,
262 blind: BaseBlind::random(&mut OsRng),
263 spend_hook: FuncId::none(),
264 user_data: pallas::Base::ZERO,
265 };
266
267 let token_blind = BaseBlind::random(&mut OsRng);
269 let input_value_blind = ScalarBlind::random(&mut OsRng);
270 let fee_value_blind = ScalarBlind::random(&mut OsRng);
271 let output_value_blind = compute_remainder_blind(&[input_value_blind], &[fee_value_blind]);
272
273 let signature_secret = SecretKey::random(&mut OsRng);
275
276 info!("Creating FeeV1 ZK proof");
277 let (fee_pk, fee_zkbin) = self.proving_keys.get(MONEY_CONTRACT_ZKAS_FEE_NS_V1).unwrap();
278
279 let (proof, public_inputs) = create_fee_proof(
280 fee_zkbin,
281 fee_pk,
282 &input,
283 input_value_blind,
284 &output,
285 output_value_blind,
286 output.spend_hook,
287 output.user_data,
288 output.blind,
289 token_blind,
290 signature_secret,
291 )?;
292
293 let note = MoneyNote {
295 coin_blind: output.blind,
296 value: output.value,
297 token_id: output.token_id,
298 spend_hook: output.spend_hook,
299 user_data: output.user_data,
300 value_blind: output_value_blind,
301 token_blind,
302 memo: vec![],
303 };
304
305 let encrypted_note = AeadEncryptedNote::encrypt(¬e, &output.public_key, &mut OsRng)?;
306
307 let params = MoneyFeeParamsV1 {
308 input: Input {
309 value_commit: public_inputs.input_value_commit,
310 token_commit: public_inputs.token_commit,
311 nullifier: public_inputs.nullifier,
312 merkle_root: public_inputs.merkle_root,
313 user_data_enc: public_inputs.input_user_data_enc,
314 signature_public: public_inputs.signature_public,
315 },
316 output: Output {
317 value_commit: public_inputs.output_value_commit,
318 token_commit: public_inputs.token_commit,
319 coin: public_inputs.output_coin,
320 note: encrypted_note,
321 },
322 fee_value_blind,
323 token_blind,
324 };
325
326 let mut data = vec![MoneyFunction::FeeV1 as u8];
328 required_fee.encode_async(&mut data).await?;
329 params.encode_async(&mut data).await?;
330 let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
331
332 Ok((call, vec![proof], vec![signature_secret], vec![coin.clone()], params))
333 }
334}