swap.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 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_send: u64,
  71. token_send: TokenId,
  72. value_recv: u64,
  73. token_recv: TokenId,
  74. ) -> Result<PartialSwapData> {
  75. // First we'll fetch all of our unspent coins from the wallet.
  76. let mut owncoins = self.get_coins(false).await?;
  77. // Then we see if we have one that we can send.
  78. owncoins.retain(|x| {
  79. x.0.note.value == value_send &&
  80. x.0.note.token_id == token_send &&
  81. x.0.note.spend_hook == FuncId::none()
  82. });
  83. if owncoins.is_empty() {
  84. return Err(Error::Custom(format!(
  85. "Did not find any unspent coins of value {value_send} and token_id {token_send}",
  86. )))
  87. }
  88. // If there are any, we'll just spend the first one we see.
  89. let burn_coin = owncoins[0].0.clone();
  90. // Fetch our default address
  91. let address = self.default_address().await?;
  92. // We'll also need our Merkle tree
  93. let tree = self.get_money_tree().await?;
  94. let contract_id = *MONEY_CONTRACT_ID;
  95. // Now we need to do a lookup for the zkas proof bincodes, and create
  96. // the circuit objects and proving keys so we can build the transaction.
  97. // We also do this through the RPC.
  98. let zkas_bins = self.lookup_zkas(&contract_id).await?;
  99. let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1)
  100. else {
  101. return Err(Error::Custom("Mint circuit not found".to_string()))
  102. };
  103. let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1)
  104. else {
  105. return Err(Error::Custom("Burn circuit not found".to_string()))
  106. };
  107. let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
  108. let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
  109. let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
  110. let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
  111. // Since we're creating the first half, we generate the blinds.
  112. let value_blinds = [Blind::random(&mut OsRng), Blind::random(&mut OsRng)];
  113. let token_blinds = [Blind::random(&mut OsRng), Blind::random(&mut OsRng)];
  114. // Now we should have everything we need to build the swap half
  115. println!("Creating Mint and Burn circuit proving keys");
  116. let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
  117. let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
  118. let builder = SwapCallBuilder {
  119. pubkey: address,
  120. value_send,
  121. token_id_send: token_send,
  122. value_recv,
  123. token_id_recv: token_recv,
  124. user_data_blind_send: Blind::random(&mut OsRng), // <-- FIXME: Perhaps should be passed in
  125. spend_hook_recv: FuncId::none(), // <-- FIXME: Should be passed in
  126. user_data_recv: pallas::Base::ZERO, // <-- FIXME: Should be passed in
  127. value_blinds,
  128. token_blinds,
  129. coin: burn_coin,
  130. tree,
  131. mint_zkbin,
  132. mint_pk,
  133. burn_zkbin,
  134. burn_pk,
  135. };
  136. println!("Building first half of the swap transaction");
  137. let debris = builder.build()?;
  138. // Now we have the half, so we can build `PartialSwapData` and return it.
  139. let ret = PartialSwapData {
  140. params: debris.params,
  141. proofs: debris.proofs,
  142. value_pair: (value_send, value_recv),
  143. token_pair: (token_send, token_recv),
  144. value_blinds: value_blinds.to_vec(),
  145. token_blinds: token_blinds.to_vec(),
  146. };
  147. Ok(ret)
  148. }
  149. /// Create a full transaction by inspecting and verifying given partial swap data,
  150. /// making the other half, and joining all this into a `Transaction` object.
  151. pub async fn join_swap(&self, partial: PartialSwapData) -> Result<Transaction> {
  152. // Our side of the tx in the pairs is the second half, so we try to find
  153. // an unspent coin like that in our wallet.
  154. let mut owncoins = self.get_coins(false).await?;
  155. owncoins.retain(|x| {
  156. x.0.note.value == partial.value_pair.1 && x.0.note.token_id == partial.token_pair.1
  157. });
  158. if owncoins.is_empty() {
  159. return Err(Error::Custom(format!(
  160. "Did not find any unspent coins of value {} and token_id {}",
  161. partial.value_pair.1, partial.token_pair.1
  162. )))
  163. }
  164. // If there are any, we'll just spend the first one we see.
  165. let burn_coin = owncoins[0].0.clone();
  166. // Fetch our default address
  167. let address = self.default_address().await?;
  168. // We'll also need our Merkle tree
  169. let tree = self.get_money_tree().await?;
  170. let contract_id = *MONEY_CONTRACT_ID;
  171. // Now we need to do a lookup for the zkas proof bincodes, and create
  172. // the circuit objects and proving keys so we can build the transaction.
  173. // We also do this through the RPC.
  174. let zkas_bins = self.lookup_zkas(&contract_id).await?;
  175. let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1)
  176. else {
  177. return Err(Error::Custom("Mint circuit not found".to_string()))
  178. };
  179. let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1)
  180. else {
  181. return Err(Error::Custom("Burn circuit not found".to_string()))
  182. };
  183. let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
  184. let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
  185. let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
  186. let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
  187. // TODO: Maybe some kind of verification at this point
  188. // Now we should have everything we need to build the swap half
  189. println!("Creating Mint and Burn circuit proving keys");
  190. let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
  191. let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
  192. let builder = SwapCallBuilder {
  193. pubkey: address,
  194. value_send: partial.value_pair.1,
  195. token_id_send: partial.token_pair.1,
  196. value_recv: partial.value_pair.0,
  197. token_id_recv: partial.token_pair.0,
  198. user_data_blind_send: Blind::random(&mut OsRng), // <-- FIXME: Perhaps should be passed in
  199. spend_hook_recv: FuncId::none(), // <-- FIXME: Should be passed in
  200. user_data_recv: pallas::Base::ZERO, // <-- FIXME: Should be passed in
  201. value_blinds: [partial.value_blinds[1], partial.value_blinds[0]],
  202. token_blinds: [partial.token_blinds[1], partial.token_blinds[0]],
  203. coin: burn_coin,
  204. tree,
  205. mint_zkbin,
  206. mint_pk,
  207. burn_zkbin,
  208. burn_pk,
  209. };
  210. println!("Building second half of the swap transaction");
  211. let debris = builder.build()?;
  212. let full_params = MoneyTransferParamsV1 {
  213. inputs: vec![partial.params.inputs[0].clone(), debris.params.inputs[0].clone()],
  214. outputs: vec![partial.params.outputs[0].clone(), debris.params.outputs[0].clone()],
  215. };
  216. let full_proofs = vec![
  217. partial.proofs[0].clone(),
  218. debris.proofs[0].clone(),
  219. partial.proofs[1].clone(),
  220. debris.proofs[1].clone(),
  221. ];
  222. let mut data = vec![MoneyFunction::OtcSwapV1 as u8];
  223. full_params.encode_async(&mut data).await?;
  224. let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
  225. let mut tx_builder =
  226. TransactionBuilder::new(ContractCallLeaf { call, proofs: full_proofs }, vec![])?;
  227. let mut tx = tx_builder.build()?;
  228. println!("Signing swap transaction");
  229. let sigs = tx.create_sigs(&[debris.signature_secret])?;
  230. tx.signatures = vec![sigs];
  231. Ok(tx)
  232. }
  233. /// Inspect and verify a given swap (half or full) transaction
  234. pub async fn inspect_swap(&self, bytes: Vec<u8>) -> Result<()> {
  235. // Default error to return in case insection fails
  236. let insection_error = Err(Error::Custom("Inspection failed".to_string()));
  237. let mut full: Option<Transaction> = None;
  238. let mut half: Option<PartialSwapData> = None;
  239. if let Ok(v) = deserialize_async(&bytes).await {
  240. full = Some(v)
  241. };
  242. match deserialize_async(&bytes).await {
  243. Ok(v) => half = Some(v),
  244. Err(_) => {
  245. if full.is_none() {
  246. return Err(Error::Custom(
  247. "Failed to deserialize to Transaction or PartialSwapData".to_string(),
  248. ))
  249. }
  250. }
  251. }
  252. if let Some(tx) = full {
  253. // We're inspecting a full transaction
  254. if tx.calls.len() != 1 {
  255. eprintln!(
  256. "Found {} contract calls in the transaction, there should be 1",
  257. tx.calls.len()
  258. );
  259. return insection_error
  260. }
  261. let params: MoneyTransferParamsV1 =
  262. deserialize_async(&tx.calls[0].data.data[1..]).await?;
  263. println!("Parameters:\n{:#?}", params);
  264. if params.inputs.len() != 2 {
  265. eprintln!("Found {} inputs, there should be 2", params.inputs.len());
  266. return insection_error
  267. }
  268. if params.outputs.len() != 2 {
  269. eprintln!("Found {} outputs, there should be 2", params.outputs.len());
  270. return insection_error
  271. }
  272. // Try to decrypt one of the outputs.
  273. let secret_keys = self.get_money_secrets().await?;
  274. let mut skey: Option<SecretKey> = None;
  275. let mut note: Option<MoneyNote> = None;
  276. let mut output_idx = 0;
  277. for output in &params.outputs {
  278. println!("Trying to decrypt note in output {output_idx}");
  279. for secret in &secret_keys {
  280. if let Ok(d_note) = output.note.decrypt::<MoneyNote>(secret) {
  281. let s: SecretKey = deserialize_async(&d_note.memo).await?;
  282. skey = Some(s);
  283. note = Some(d_note);
  284. println!("Successfully decrypted and found an ephemeral secret");
  285. break
  286. }
  287. }
  288. if note.is_some() {
  289. break
  290. }
  291. output_idx += 1;
  292. }
  293. let Some(note) = note else {
  294. eprintln!("Error: Could not decrypt notes of either output");
  295. return insection_error
  296. };
  297. println!(
  298. "Output[{output_idx}] value: {} ({})",
  299. note.value,
  300. encode_base10(note.value, BALANCE_BASE10_DECIMALS)
  301. );
  302. println!("Output[{output_idx}] token ID: {}", note.token_id);
  303. let skey = skey.unwrap();
  304. let (pub_x, pub_y) = PublicKey::from_secret(skey).xy();
  305. let coin = Coin::from(poseidon_hash([
  306. pub_x,
  307. pub_y,
  308. pallas::Base::from(note.value),
  309. note.token_id.inner(),
  310. note.coin_blind.inner(),
  311. ]));
  312. if coin == params.outputs[output_idx].coin {
  313. println!("Output[{output_idx}] coin matches decrypted note metadata");
  314. } else {
  315. eprintln!("Error: Output[{output_idx}] coin does not match note metadata");
  316. return insection_error
  317. }
  318. let valcom = pedersen_commitment_u64(note.value, note.value_blind);
  319. let tokcom = poseidon_hash([note.token_id.inner(), note.token_blind.inner()]);
  320. if valcom != params.outputs[output_idx].value_commit {
  321. eprintln!(
  322. "Error: Output[{output_idx}] value commitment does not match note metadata"
  323. );
  324. return insection_error
  325. }
  326. if tokcom != params.outputs[output_idx].token_commit {
  327. eprintln!(
  328. "Error: Output[{output_idx}] token commitment does not match note metadata"
  329. );
  330. return insection_error
  331. }
  332. println!("Value and token commitments match decrypted note metadata");
  333. // Verify that the output commitments match the other input commitments
  334. match output_idx {
  335. 0 => {
  336. if valcom != params.inputs[1].value_commit ||
  337. tokcom != params.inputs[1].token_commit
  338. {
  339. eprintln!("Error: Value/Token commits of output[0] do not match input[1]");
  340. return insection_error
  341. }
  342. }
  343. 1 => {
  344. if valcom != params.inputs[0].value_commit ||
  345. tokcom != params.inputs[0].token_commit
  346. {
  347. eprintln!("Error: Value/Token commits of output[1] do not match input[0]");
  348. return insection_error
  349. }
  350. }
  351. _ => unreachable!(),
  352. }
  353. println!("Found matching pedersen commitments for outputs and inputs");
  354. // TODO: Verify signature
  355. // TODO: Verify ZK proofs
  356. return Ok(())
  357. }
  358. // Inspect PartialSwapData
  359. let partial = half.unwrap();
  360. println!("{partial}");
  361. Ok(())
  362. }
  363. /// Sign a given transaction by retrieving the secret key from the encrypted
  364. /// note and prepending it to the transaction's signatures.
  365. pub async fn sign_swap(&self, tx: &mut Transaction) -> Result<()> {
  366. // We need our secret keys to try and decrypt the note
  367. let secret_keys = self.get_money_secrets().await?;
  368. let params: MoneyTransferParamsV1 = deserialize_async(&tx.calls[0].data.data[1..]).await?;
  369. // Our output should be outputs[0] so we try to decrypt that.
  370. let encrypted_note = &params.outputs[0].note;
  371. println!("Trying to decrypt note in outputs[0]");
  372. let mut skey = None;
  373. for secret in &secret_keys {
  374. if let Ok(note) = encrypted_note.decrypt::<MoneyNote>(secret) {
  375. let s: SecretKey = deserialize_async(&note.memo).await?;
  376. println!("Successfully decrypted and found an ephemeral secret");
  377. skey = Some(s);
  378. break
  379. }
  380. }
  381. let Some(skey) = skey else {
  382. eprintln!("Error: Failed to decrypt note with any of our secret keys");
  383. return Err(Error::Custom(
  384. "Failed to decrypt note with any of our secret keys".to_string(),
  385. ))
  386. };
  387. println!("Signing swap transaction");
  388. let sigs = tx.create_sigs(&[skey])?;
  389. tx.signatures[0].insert(0, sigs[0]);
  390. Ok(())
  391. }
  392. }