swap.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::fmt;
  19. use rand::rngs::OsRng;
  20. use darkfi::{
  21. tx::{ContractCallLeaf, Transaction, TransactionBuilder},
  22. util::parse::encode_base10,
  23. zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses, Proof},
  24. zkas::ZkBinary,
  25. Error, Result,
  26. };
  27. use darkfi_money_contract::{
  28. client::{swap_v1::SwapCallBuilder, MoneyNote},
  29. model::{Coin, MoneyTransferParamsV1, TokenId},
  30. MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
  31. };
  32. use darkfi_sdk::{
  33. crypto::{
  34. contract_id::MONEY_CONTRACT_ID, pedersen::pedersen_commitment_u64, poseidon_hash,
  35. BaseBlind, Blind, FuncId, PublicKey, ScalarBlind, SecretKey,
  36. },
  37. pasta::pallas,
  38. tx::ContractCall,
  39. };
  40. use darkfi_serial::{
  41. async_trait, deserialize_async, AsyncEncodable, SerialDecodable, SerialEncodable,
  42. };
  43. use super::{money::BALANCE_BASE10_DECIMALS, Drk};
  44. #[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
  45. /// Half of the swap data, includes the coin that is supposed to be sent,
  46. /// and the coin that is supposed to be received.
  47. pub struct PartialSwapData {
  48. params: MoneyTransferParamsV1,
  49. proofs: Vec<Proof>,
  50. value_pair: (u64, u64),
  51. token_pair: (TokenId, TokenId),
  52. value_blinds: Vec<ScalarBlind>,
  53. token_blinds: Vec<BaseBlind>,
  54. }
  55. impl fmt::Display for PartialSwapData {
  56. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  57. let s =
  58. format!(
  59. "{:#?}\nValue pair: {}:{}\nToken pair: {}:{}\nValue blinds: {:?}\nToken blinds: {:?}\n",
  60. self.params, self.value_pair.0, self.value_pair.1, self.token_pair.0, self.token_pair.1,
  61. self.value_blinds, self.token_blinds,
  62. );
  63. write!(f, "{s}")
  64. }
  65. }
  66. impl Drk {
  67. /// Initialize the first half of an atomic swap
  68. pub async fn init_swap(
  69. &self,
  70. value_pair: (u64, u64),
  71. token_pair: (TokenId, TokenId),
  72. user_data_blind_send: Option<BaseBlind>,
  73. spend_hook_recv: Option<FuncId>,
  74. user_data_recv: Option<pallas::Base>,
  75. ) -> Result<PartialSwapData> {
  76. // First get all unspent OwnCoins to see what our balance is
  77. let owncoins = self.get_token_coins(&token_pair.0).await?;
  78. if owncoins.is_empty() {
  79. return Err(Error::Custom(format!(
  80. "Did not find any unspent coins with token ID: {}",
  81. token_pair.0
  82. )))
  83. }
  84. // Find one with the correct value
  85. let mut burn_coin = None;
  86. for coin in owncoins {
  87. if coin.note.value == value_pair.0 {
  88. burn_coin = Some(coin);
  89. break
  90. }
  91. }
  92. let Some(burn_coin) = burn_coin else {
  93. return Err(Error::Custom(format!(
  94. "Did not find any unspent coins of value {} and token_id {}",
  95. value_pair.0, token_pair.0,
  96. )))
  97. };
  98. // Fetch our default address
  99. let address = self.default_address().await?;
  100. // We'll also need our Merkle tree
  101. let tree = self.get_money_tree().await?;
  102. // Now we need to do a lookup for the zkas proof bincodes, and create
  103. // the circuit objects and proving keys so we can build the transaction.
  104. // We also do this through the RPC.
  105. let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
  106. let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1)
  107. else {
  108. return Err(Error::Custom("Mint circuit not found".to_string()))
  109. };
  110. let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1)
  111. else {
  112. return Err(Error::Custom("Burn circuit not found".to_string()))
  113. };
  114. let mint_zkbin = ZkBinary::decode(&mint_zkbin.1, false)?;
  115. let burn_zkbin = ZkBinary::decode(&burn_zkbin.1, false)?;
  116. let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
  117. let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
  118. // Creating Mint and Burn circuits proving keys
  119. let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
  120. let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
  121. // Since we're creating the first half, we generate the blinds.
  122. let value_blinds = [Blind::random(&mut OsRng), Blind::random(&mut OsRng)];
  123. let token_blinds = [Blind::random(&mut OsRng), Blind::random(&mut OsRng)];
  124. // Now we should have everything we need to build the swap half
  125. let builder = SwapCallBuilder {
  126. pubkey: address,
  127. value_send: value_pair.0,
  128. token_id_send: token_pair.0,
  129. value_recv: value_pair.1,
  130. token_id_recv: token_pair.1,
  131. user_data_blind_send: user_data_blind_send.unwrap_or(Blind::random(&mut OsRng)),
  132. spend_hook_recv: spend_hook_recv.unwrap_or(FuncId::none()),
  133. user_data_recv: user_data_recv.unwrap_or(pallas::Base::ZERO),
  134. value_blinds,
  135. token_blinds,
  136. coin: burn_coin,
  137. tree,
  138. mint_zkbin,
  139. mint_pk,
  140. burn_zkbin,
  141. burn_pk,
  142. };
  143. let debris = builder.build()?;
  144. // Now we have the half, so we can build `PartialSwapData` and return it.
  145. let ret = PartialSwapData {
  146. params: debris.params,
  147. proofs: debris.proofs,
  148. value_pair,
  149. token_pair,
  150. value_blinds: value_blinds.to_vec(),
  151. token_blinds: token_blinds.to_vec(),
  152. };
  153. Ok(ret)
  154. }
  155. /// Create a full transaction by inspecting and verifying given partial swap data,
  156. /// making the other half, and joining all this into a `Transaction` object.
  157. pub async fn join_swap(
  158. &self,
  159. partial: PartialSwapData,
  160. user_data_blind_send: Option<BaseBlind>,
  161. spend_hook_recv: Option<FuncId>,
  162. user_data_recv: Option<pallas::Base>,
  163. ) -> Result<Transaction> {
  164. // Our side of the tx in the pairs is the second half, so we try to find
  165. // an unspent coin like that in our wallet.
  166. let owncoins = self.get_token_coins(&partial.token_pair.1).await?;
  167. if owncoins.is_empty() {
  168. return Err(Error::Custom(format!(
  169. "Did not find any unspent coins with token ID: {}",
  170. partial.token_pair.1
  171. )))
  172. }
  173. // Find one with the correct value
  174. let mut burn_coin = None;
  175. for coin in owncoins {
  176. if coin.note.value == partial.value_pair.1 {
  177. burn_coin = Some(coin);
  178. break
  179. }
  180. }
  181. let Some(burn_coin) = burn_coin else {
  182. return Err(Error::Custom(format!(
  183. "Did not find any unspent coins of value {} and token_id {}",
  184. partial.value_pair.1, partial.token_pair.1,
  185. )))
  186. };
  187. // Fetch our default address
  188. let address = self.default_address().await?;
  189. // We'll also need our Merkle tree
  190. let tree = self.get_money_tree().await?;
  191. // Now we need to do a lookup for the zkas proof bincodes, and create
  192. // the circuit objects and proving keys so we can build the transaction.
  193. // We also do this through the RPC.
  194. let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
  195. let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1)
  196. else {
  197. return Err(Error::Custom("Mint circuit not found".to_string()))
  198. };
  199. let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1)
  200. else {
  201. return Err(Error::Custom("Burn circuit not found".to_string()))
  202. };
  203. let mint_zkbin = ZkBinary::decode(&mint_zkbin.1, false)?;
  204. let burn_zkbin = ZkBinary::decode(&burn_zkbin.1, false)?;
  205. let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
  206. let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
  207. // Creating Mint and Burn circuits proving keys
  208. let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
  209. let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
  210. // Now we should have everything we need to build the swap half
  211. let builder = SwapCallBuilder {
  212. pubkey: address,
  213. value_send: partial.value_pair.1,
  214. token_id_send: partial.token_pair.1,
  215. value_recv: partial.value_pair.0,
  216. token_id_recv: partial.token_pair.0,
  217. user_data_blind_send: user_data_blind_send.unwrap_or(Blind::random(&mut OsRng)),
  218. spend_hook_recv: spend_hook_recv.unwrap_or(FuncId::none()),
  219. user_data_recv: user_data_recv.unwrap_or(pallas::Base::ZERO),
  220. value_blinds: [partial.value_blinds[1], partial.value_blinds[0]],
  221. token_blinds: [partial.token_blinds[1], partial.token_blinds[0]],
  222. coin: burn_coin,
  223. tree,
  224. mint_zkbin,
  225. mint_pk,
  226. burn_zkbin,
  227. burn_pk,
  228. };
  229. let debris = builder.build()?;
  230. // Build the full transaction
  231. let full_params = MoneyTransferParamsV1 {
  232. inputs: vec![partial.params.inputs[0].clone(), debris.params.inputs[0].clone()],
  233. outputs: vec![partial.params.outputs[0].clone(), debris.params.outputs[0].clone()],
  234. };
  235. let full_proofs = vec![
  236. partial.proofs[0].clone(),
  237. debris.proofs[0].clone(),
  238. partial.proofs[1].clone(),
  239. debris.proofs[1].clone(),
  240. ];
  241. let mut data = vec![MoneyFunction::OtcSwapV1 as u8];
  242. full_params.encode_async(&mut data).await?;
  243. let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
  244. let mut tx_builder =
  245. TransactionBuilder::new(ContractCallLeaf { call, proofs: full_proofs }, vec![])?;
  246. let mut tx = tx_builder.build()?;
  247. // Sign the transaction and return it
  248. let sigs = tx.create_sigs(&[debris.signature_secret])?;
  249. tx.signatures = vec![sigs];
  250. Ok(tx)
  251. }
  252. /// Inspect and verify a given swap (half or full) transaction
  253. pub async fn inspect_swap(&self, bytes: Vec<u8>, output: &mut Vec<String>) -> Result<()> {
  254. // First we check if its a partial swap
  255. if let Ok(partial) = deserialize_async::<PartialSwapData>(&bytes).await {
  256. // Inspect the PartialSwapData
  257. output.push(format!("{partial}"));
  258. return Ok(())
  259. }
  260. // Try to deserialize a full swap transaction
  261. let Ok(tx) = deserialize_async::<Transaction>(&bytes).await else {
  262. return Err(Error::Custom(
  263. "Failed to deserialize to Transaction or PartialSwapData".to_string(),
  264. ))
  265. };
  266. // Default error to return in case insection fails
  267. let insection_error = Err(Error::Custom("Inspection failed".to_string()));
  268. // We're inspecting a full transaction
  269. if tx.calls.len() != 1 {
  270. output.push(format!(
  271. "Found {} contract calls in the transaction, there should be 1",
  272. tx.calls.len()
  273. ));
  274. return insection_error
  275. }
  276. let params: MoneyTransferParamsV1 = deserialize_async(&tx.calls[0].data.data[1..]).await?;
  277. output.push(format!("Parameters:\n{params:#?}"));
  278. if params.inputs.len() != 2 {
  279. output.push(format!("Found {} inputs, there should be 2", params.inputs.len()));
  280. return insection_error
  281. }
  282. if params.outputs.len() != 2 {
  283. output.push(format!("Found {} outputs, there should be 2", params.outputs.len()));
  284. return insection_error
  285. }
  286. // Try to decrypt one of the outputs.
  287. let secret_keys = self.get_money_secrets().await?;
  288. let mut skey: Option<SecretKey> = None;
  289. let mut note: Option<MoneyNote> = None;
  290. let mut param_output_idx = 0;
  291. for param_output in &params.outputs {
  292. output.push(format!("Trying to decrypt note in output {param_output_idx}"));
  293. for secret in &secret_keys {
  294. if let Ok(d_note) = param_output.note.decrypt::<MoneyNote>(secret) {
  295. let s: SecretKey = deserialize_async(&d_note.memo).await?;
  296. skey = Some(s);
  297. note = Some(d_note);
  298. output
  299. .push(String::from("Successfully decrypted and found an ephemeral secret"));
  300. break
  301. }
  302. }
  303. if note.is_some() {
  304. break
  305. }
  306. param_output_idx += 1;
  307. }
  308. let Some(note) = note else {
  309. output.push(String::from("Error: Could not decrypt notes of either output"));
  310. return insection_error
  311. };
  312. output.push(format!(
  313. "Output[{param_output_idx}] value: {} ({})",
  314. note.value,
  315. encode_base10(note.value, BALANCE_BASE10_DECIMALS)
  316. ));
  317. output.push(format!("Output[{param_output_idx}] token ID: {}", note.token_id));
  318. let skey = skey.unwrap();
  319. let (pub_x, pub_y) = PublicKey::from_secret(skey).xy();
  320. let coin = Coin::from(poseidon_hash([
  321. pub_x,
  322. pub_y,
  323. pallas::Base::from(note.value),
  324. note.token_id.inner(),
  325. note.coin_blind.inner(),
  326. ]));
  327. if coin == params.outputs[param_output_idx].coin {
  328. output.push(format!("Output[{param_output_idx}] coin matches decrypted note metadata"));
  329. } else {
  330. output.push(format!(
  331. "Error: Output[{param_output_idx}] coin does not match note metadata"
  332. ));
  333. return insection_error
  334. }
  335. let valcom = pedersen_commitment_u64(note.value, note.value_blind);
  336. let tokcom = poseidon_hash([note.token_id.inner(), note.token_blind.inner()]);
  337. if valcom != params.outputs[param_output_idx].value_commit {
  338. output.push(format!(
  339. "Error: Output[{param_output_idx}] value commitment does not match note metadata"
  340. ));
  341. return insection_error
  342. }
  343. if tokcom != params.outputs[param_output_idx].token_commit {
  344. output.push(format!(
  345. "Error: Output[{param_output_idx}] token commitment does not match note metadata"
  346. ));
  347. return insection_error
  348. }
  349. output.push(String::from("Value and token commitments match decrypted note metadata"));
  350. // Verify that the param output commitments match the other input commitments
  351. match param_output_idx {
  352. 0 => {
  353. if valcom != params.inputs[1].value_commit ||
  354. tokcom != params.inputs[1].token_commit
  355. {
  356. output.push(String::from(
  357. "Error: Value/Token commits of output[0] do not match input[1]",
  358. ));
  359. return insection_error
  360. }
  361. }
  362. 1 => {
  363. if valcom != params.inputs[0].value_commit ||
  364. tokcom != params.inputs[0].token_commit
  365. {
  366. output.push(String::from(
  367. "Error: Value/Token commits of output[1] do not match input[0]",
  368. ));
  369. return insection_error
  370. }
  371. }
  372. _ => unreachable!(),
  373. }
  374. output.push(String::from("Found matching pedersen commitments for outputs and inputs"));
  375. Ok(())
  376. }
  377. /// Sign given swap transaction by retrieving the secret key from the encrypted
  378. /// note and prepending it to the transaction's signatures.
  379. pub async fn sign_swap(&self, tx: &mut Transaction) -> Result<()> {
  380. // We need our secret keys to try and decrypt the notes
  381. let secret_keys = self.get_money_secrets().await?;
  382. let params: MoneyTransferParamsV1 = deserialize_async(&tx.calls[0].data.data[1..]).await?;
  383. // We wil try to decrypt each note separately,
  384. // since we might us the same key in both of them.
  385. let mut found = false;
  386. // Try to decrypt the first note
  387. for secret in &secret_keys {
  388. let Ok(note) = &params.outputs[0].note.decrypt::<MoneyNote>(secret) else { continue };
  389. // Sign the swap transaction
  390. let skey: SecretKey = deserialize_async(&note.memo).await?;
  391. let sigs = tx.create_sigs(&[skey])?;
  392. // If transaction contains both signatures, replace the first one,
  393. // otherwise insert signature on first position.
  394. if tx.signatures[0].len() == 2 {
  395. tx.signatures[0][0] = sigs[0];
  396. } else {
  397. tx.signatures[0].insert(0, sigs[0]);
  398. }
  399. found = true;
  400. break
  401. }
  402. // Try to decrypt the second note
  403. for secret in &secret_keys {
  404. let Ok(note) = &params.outputs[1].note.decrypt::<MoneyNote>(secret) else { continue };
  405. // Sign the swap transaction
  406. let skey: SecretKey = deserialize_async(&note.memo).await?;
  407. let sigs = tx.create_sigs(&[skey])?;
  408. // If transaction contains both signatures, replace the second one,
  409. // otherwise replace the first one.
  410. if tx.signatures[0].len() == 2 {
  411. tx.signatures[0][1] = sigs[0];
  412. } else {
  413. tx.signatures[0][0] = sigs[0];
  414. }
  415. found = true;
  416. break
  417. }
  418. if !found {
  419. return Err(Error::Custom(
  420. "Failed to decrypt note with any of our secret keys".to_string(),
  421. ))
  422. };
  423. Ok(())
  424. }
  425. }