1use std::fmt;
19
20use rand::rngs::OsRng;
21
22use darkfi::{
23 tx::{ContractCallLeaf, Transaction, TransactionBuilder},
24 util::parse::encode_base10,
25 zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses, Proof},
26 zkas::ZkBinary,
27 Error, Result,
28};
29use darkfi_money_contract::{
30 client::{swap_v1::SwapCallBuilder, MoneyNote},
31 model::{Coin, MoneyTransferParamsV1, TokenId},
32 MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
33};
34use darkfi_sdk::{
35 crypto::{
36 contract_id::MONEY_CONTRACT_ID, pedersen::pedersen_commitment_u64, poseidon_hash,
37 BaseBlind, Blind, FuncId, PublicKey, ScalarBlind, SecretKey,
38 },
39 pasta::pallas,
40 tx::ContractCall,
41};
42use darkfi_serial::{
43 async_trait, deserialize_async, AsyncEncodable, SerialDecodable, SerialEncodable,
44};
45
46use super::{money::BALANCE_BASE10_DECIMALS, Drk};
47
48#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
49pub struct PartialSwapData {
52 params: MoneyTransferParamsV1,
53 proofs: Vec<Proof>,
54 value_pair: (u64, u64),
55 token_pair: (TokenId, TokenId),
56 value_blinds: Vec<ScalarBlind>,
57 token_blinds: Vec<BaseBlind>,
58}
59
60impl fmt::Display for PartialSwapData {
61 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
62 let s =
63 format!(
64 "{:#?}\nValue pair: {}:{}\nToken pair: {}:{}\nValue blinds: {:?}\nToken blinds: {:?}\n",
65 self.params, self.value_pair.0, self.value_pair.1, self.token_pair.0, self.token_pair.1,
66 self.value_blinds, self.token_blinds,
67 );
68
69 write!(f, "{}", s)
70 }
71}
72
73impl Drk {
74 pub async fn init_swap(
76 &self,
77 value_pair: (u64, u64),
78 token_pair: (TokenId, TokenId),
79 user_data_blind_send: Option<BaseBlind>,
80 spend_hook_recv: Option<FuncId>,
81 user_data_recv: Option<pallas::Base>,
82 ) -> Result<PartialSwapData> {
83 let owncoins = self.get_token_coins(&token_pair.0).await?;
85 if owncoins.is_empty() {
86 return Err(Error::Custom(format!(
87 "Did not find any unspent coins with token ID: {}",
88 token_pair.0
89 )))
90 }
91
92 let mut burn_coin = None;
94 for coin in owncoins {
95 if coin.note.value == value_pair.0 {
96 burn_coin = Some(coin);
97 break
98 }
99 }
100 let Some(burn_coin) = burn_coin else {
101 return Err(Error::Custom(format!(
102 "Did not find any unspent coins of value {} and token_id {}",
103 value_pair.0, token_pair.0,
104 )))
105 };
106
107 let address = self.default_address().await?;
109
110 let tree = self.get_money_tree().await?;
112
113 let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
117
118 let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1)
119 else {
120 return Err(Error::Custom("Mint circuit not found".to_string()))
121 };
122
123 let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1)
124 else {
125 return Err(Error::Custom("Burn circuit not found".to_string()))
126 };
127
128 let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
129 let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
130
131 let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
132 let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
133
134 let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
136 let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
137
138 let value_blinds = [Blind::random(&mut OsRng), Blind::random(&mut OsRng)];
140 let token_blinds = [Blind::random(&mut OsRng), Blind::random(&mut OsRng)];
141
142 let builder = SwapCallBuilder {
144 pubkey: address,
145 value_send: value_pair.0,
146 token_id_send: token_pair.0,
147 value_recv: value_pair.1,
148 token_id_recv: token_pair.1,
149 user_data_blind_send: user_data_blind_send.unwrap_or(Blind::random(&mut OsRng)),
150 spend_hook_recv: spend_hook_recv.unwrap_or(FuncId::none()),
151 user_data_recv: user_data_recv.unwrap_or(pallas::Base::ZERO),
152 value_blinds,
153 token_blinds,
154 coin: burn_coin,
155 tree,
156 mint_zkbin,
157 mint_pk,
158 burn_zkbin,
159 burn_pk,
160 };
161 let debris = builder.build()?;
162
163 let ret = PartialSwapData {
165 params: debris.params,
166 proofs: debris.proofs,
167 value_pair,
168 token_pair,
169 value_blinds: value_blinds.to_vec(),
170 token_blinds: token_blinds.to_vec(),
171 };
172
173 Ok(ret)
174 }
175
176 pub async fn join_swap(
179 &self,
180 partial: PartialSwapData,
181 user_data_blind_send: Option<BaseBlind>,
182 spend_hook_recv: Option<FuncId>,
183 user_data_recv: Option<pallas::Base>,
184 ) -> Result<Transaction> {
185 let owncoins = self.get_token_coins(&partial.token_pair.1).await?;
188 if owncoins.is_empty() {
189 return Err(Error::Custom(format!(
190 "Did not find any unspent coins with token ID: {}",
191 partial.token_pair.1
192 )))
193 }
194
195 let mut burn_coin = None;
197 for coin in owncoins {
198 if coin.note.value == partial.value_pair.1 {
199 burn_coin = Some(coin);
200 break
201 }
202 }
203 let Some(burn_coin) = burn_coin else {
204 return Err(Error::Custom(format!(
205 "Did not find any unspent coins of value {} and token_id {}",
206 partial.value_pair.1, partial.token_pair.1,
207 )))
208 };
209
210 let address = self.default_address().await?;
212
213 let tree = self.get_money_tree().await?;
215
216 let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
220
221 let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1)
222 else {
223 return Err(Error::Custom("Mint circuit not found".to_string()))
224 };
225
226 let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1)
227 else {
228 return Err(Error::Custom("Burn circuit not found".to_string()))
229 };
230
231 let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
232 let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
233
234 let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
235 let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
236
237 let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
239 let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
240
241 let builder = SwapCallBuilder {
243 pubkey: address,
244 value_send: partial.value_pair.1,
245 token_id_send: partial.token_pair.1,
246 value_recv: partial.value_pair.0,
247 token_id_recv: partial.token_pair.0,
248 user_data_blind_send: user_data_blind_send.unwrap_or(Blind::random(&mut OsRng)),
249 spend_hook_recv: spend_hook_recv.unwrap_or(FuncId::none()),
250 user_data_recv: user_data_recv.unwrap_or(pallas::Base::ZERO),
251 value_blinds: [partial.value_blinds[1], partial.value_blinds[0]],
252 token_blinds: [partial.token_blinds[1], partial.token_blinds[0]],
253 coin: burn_coin,
254 tree,
255 mint_zkbin,
256 mint_pk,
257 burn_zkbin,
258 burn_pk,
259 };
260 let debris = builder.build()?;
261
262 let full_params = MoneyTransferParamsV1 {
264 inputs: vec![partial.params.inputs[0].clone(), debris.params.inputs[0].clone()],
265 outputs: vec![partial.params.outputs[0].clone(), debris.params.outputs[0].clone()],
266 };
267
268 let full_proofs = vec![
269 partial.proofs[0].clone(),
270 debris.proofs[0].clone(),
271 partial.proofs[1].clone(),
272 debris.proofs[1].clone(),
273 ];
274
275 let mut data = vec![MoneyFunction::OtcSwapV1 as u8];
276 full_params.encode_async(&mut data).await?;
277 let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
278 let mut tx_builder =
279 TransactionBuilder::new(ContractCallLeaf { call, proofs: full_proofs }, vec![])?;
280 let mut tx = tx_builder.build()?;
281
282 let sigs = tx.create_sigs(&[debris.signature_secret])?;
284 tx.signatures = vec![sigs];
285
286 Ok(tx)
287 }
288
289 pub async fn inspect_swap(&self, bytes: Vec<u8>) -> Result<()> {
291 if let Ok(partial) = deserialize_async::<PartialSwapData>(&bytes).await {
293 println!("{partial}");
295 return Ok(())
296 }
297
298 let Ok(tx) = deserialize_async::<Transaction>(&bytes).await else {
300 return Err(Error::Custom(
301 "Failed to deserialize to Transaction or PartialSwapData".to_string(),
302 ))
303 };
304
305 let insection_error = Err(Error::Custom("Inspection failed".to_string()));
307
308 if tx.calls.len() != 1 {
310 eprintln!(
311 "Found {} contract calls in the transaction, there should be 1",
312 tx.calls.len()
313 );
314 return insection_error
315 }
316
317 let params: MoneyTransferParamsV1 = deserialize_async(&tx.calls[0].data.data[1..]).await?;
318 println!("Parameters:\n{:#?}", params);
319
320 if params.inputs.len() != 2 {
321 eprintln!("Found {} inputs, there should be 2", params.inputs.len());
322 return insection_error
323 }
324
325 if params.outputs.len() != 2 {
326 eprintln!("Found {} outputs, there should be 2", params.outputs.len());
327 return insection_error
328 }
329
330 let secret_keys = self.get_money_secrets().await?;
332 let mut skey: Option<SecretKey> = None;
333 let mut note: Option<MoneyNote> = None;
334 let mut output_idx = 0;
335
336 for output in ¶ms.outputs {
337 println!("Trying to decrypt note in output {output_idx}");
338
339 for secret in &secret_keys {
340 if let Ok(d_note) = output.note.decrypt::<MoneyNote>(secret) {
341 let s: SecretKey = deserialize_async(&d_note.memo).await?;
342 skey = Some(s);
343 note = Some(d_note);
344 println!("Successfully decrypted and found an ephemeral secret");
345 break
346 }
347 }
348
349 if note.is_some() {
350 break
351 }
352
353 output_idx += 1;
354 }
355
356 let Some(note) = note else {
357 eprintln!("Error: Could not decrypt notes of either output");
358 return insection_error
359 };
360
361 println!(
362 "Output[{output_idx}] value: {} ({})",
363 note.value,
364 encode_base10(note.value, BALANCE_BASE10_DECIMALS)
365 );
366 println!("Output[{output_idx}] token ID: {}", note.token_id);
367
368 let skey = skey.unwrap();
369 let (pub_x, pub_y) = PublicKey::from_secret(skey).xy();
370 let coin = Coin::from(poseidon_hash([
371 pub_x,
372 pub_y,
373 pallas::Base::from(note.value),
374 note.token_id.inner(),
375 note.coin_blind.inner(),
376 ]));
377
378 if coin == params.outputs[output_idx].coin {
379 println!("Output[{output_idx}] coin matches decrypted note metadata");
380 } else {
381 eprintln!("Error: Output[{output_idx}] coin does not match note metadata");
382 return insection_error
383 }
384
385 let valcom = pedersen_commitment_u64(note.value, note.value_blind);
386 let tokcom = poseidon_hash([note.token_id.inner(), note.token_blind.inner()]);
387
388 if valcom != params.outputs[output_idx].value_commit {
389 eprintln!("Error: Output[{output_idx}] value commitment does not match note metadata");
390 return insection_error
391 }
392
393 if tokcom != params.outputs[output_idx].token_commit {
394 eprintln!("Error: Output[{output_idx}] token commitment does not match note metadata");
395 return insection_error
396 }
397
398 println!("Value and token commitments match decrypted note metadata");
399
400 match output_idx {
402 0 => {
403 if valcom != params.inputs[1].value_commit ||
404 tokcom != params.inputs[1].token_commit
405 {
406 eprintln!("Error: Value/Token commits of output[0] do not match input[1]");
407 return insection_error
408 }
409 }
410 1 => {
411 if valcom != params.inputs[0].value_commit ||
412 tokcom != params.inputs[0].token_commit
413 {
414 eprintln!("Error: Value/Token commits of output[1] do not match input[0]");
415 return insection_error
416 }
417 }
418 _ => unreachable!(),
419 }
420
421 println!("Found matching pedersen commitments for outputs and inputs");
422
423 Ok(())
424 }
425
426 pub async fn sign_swap(&self, tx: &mut Transaction) -> Result<()> {
429 let secret_keys = self.get_money_secrets().await?;
431 let params: MoneyTransferParamsV1 = deserialize_async(&tx.calls[0].data.data[1..]).await?;
432
433 let mut found = false;
436
437 for secret in &secret_keys {
439 let Ok(note) = ¶ms.outputs[0].note.decrypt::<MoneyNote>(secret) else { continue };
440
441 let skey: SecretKey = deserialize_async(¬e.memo).await?;
443 let sigs = tx.create_sigs(&[skey])?;
444
445 if tx.signatures[0].len() == 2 {
448 tx.signatures[0][0] = sigs[0];
449 } else {
450 tx.signatures[0].insert(0, sigs[0]);
451 }
452
453 found = true;
454 break
455 }
456
457 for secret in &secret_keys {
459 let Ok(note) = ¶ms.outputs[1].note.decrypt::<MoneyNote>(secret) else { continue };
460
461 let skey: SecretKey = deserialize_async(¬e.memo).await?;
463 let sigs = tx.create_sigs(&[skey])?;
464
465 if tx.signatures[0].len() == 2 {
468 tx.signatures[0][1] = sigs[0];
469 } else {
470 tx.signatures[0][0] = sigs[0];
471 }
472
473 found = true;
474 break
475 }
476
477 if !found {
478 eprintln!("Error: Failed to decrypt note with any of our secret keys");
479 return Err(Error::Custom(
480 "Failed to decrypt note with any of our secret keys".to_string(),
481 ))
482 };
483
484 Ok(())
485 }
486}