/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2023 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use anyhow::{anyhow, Result}; use darkfi::{ tx::Transaction, util::parse::encode_base10, zk::{proof::ProvingKey, vm::ZkCircuit, vm_stack::empty_witnesses, Proof}, zkas::ZkBinary, }; use darkfi_money_contract::{ client::{build_half_swap_tx, EncryptedNote, Note}, model::MoneyTransferParams, MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1, }; use darkfi_sdk::{ crypto::{ contract_id::MONEY_CONTRACT_ID, pedersen::{pedersen_commitment_base, pedersen_commitment_u64, ValueBlind}, poseidon_hash, PublicKey, SecretKey, TokenId, }, pasta::pallas, tx::ContractCall, }; use darkfi_serial::{deserialize, Encodable, SerialDecodable, SerialEncodable}; use rand::rngs::OsRng; use super::Drk; #[derive(SerialEncodable, SerialDecodable)] /// Half of the swap data, includes the coin that is supposed to be sent, /// and the coin that is supposed to be received. pub struct PartialSwapData { params: MoneyTransferParams, proofs: Vec, value_pair: (u64, u64), token_pair: (TokenId, TokenId), value_blinds: Vec, token_blinds: Vec, } impl Drk { /// Initialize the first half of an atomic swap pub async fn init_swap( &self, value_send: u64, token_send: TokenId, value_recv: u64, token_recv: TokenId, ) -> Result { // First we'll fetch all of our unspent coins from the wallet. let mut owncoins = self.get_coins(false).await?; // Then we see if we have one that we can send. owncoins.retain(|x| { x.0.note.value == value_send && x.0.note.token_id == token_send && x.0.note.spend_hook == pallas::Base::zero() }); if owncoins.is_empty() { return Err(anyhow!( "Did not find any unspent coins of value {} and token_id {}", value_send, token_send )) } // If there are any, we'll just spend the first one we see. let burn_coin = owncoins[0].0.clone(); // Fetch our default address let address = self.wallet_address(1).await?; // We'll also need our Merkle tree let tree = self.get_money_tree().await?; let contract_id = *MONEY_CONTRACT_ID; // Now we need to do a lookup for the zkas proof bincodes, and create // the circuit objects and proving keys so we can build the transaction. // We also do this through the RPC. let zkas_bins = self.lookup_zkas(&contract_id).await?; let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1) else { return Err(anyhow!("Mint circuit not found")) }; let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1) else { return Err(anyhow!("Burn circuit not found")) }; let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?; let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?; let k = 13; let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin), mint_zkbin.clone()); let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin), burn_zkbin.clone()); eprintln!("Creating Mint circuit proving key"); let mint_pk = ProvingKey::build(k, &mint_circuit); eprintln!("Creating Burn circuit proving key"); let burn_pk = ProvingKey::build(k, &burn_circuit); // Now we should have everything we need to build the swap half eprintln!("Building first half of the swap transaction"); let (half_params, half_proofs, _half_keys, _spent_coins, value_blinds, token_blinds) = build_half_swap_tx( &address, value_send, token_send, value_recv, token_recv, &[], &[], &[burn_coin], &tree, &mint_zkbin, &mint_pk, &burn_zkbin, &burn_pk, )?; // Now we have the half, so we can build `PartialSwapData` and return it. let ret = PartialSwapData { params: half_params, proofs: half_proofs, value_pair: (value_send, value_recv), token_pair: (token_send, token_recv), value_blinds, token_blinds, }; Ok(ret) } /// Create a full transaction by inspecting and verifying given partial swap data, /// making the other half, and joining all this into a `Transaction` object. pub async fn join_swap(&self, partial: PartialSwapData) -> Result { // Our side of the tx in the pairs is the second half, so we try to find // an unspent coin like that in our wallet. let mut owncoins = self.get_coins(false).await?; owncoins.retain(|x| { x.0.note.value == partial.value_pair.1 && x.0.note.token_id == partial.token_pair.1 }); if owncoins.is_empty() { return Err(anyhow!( "Did not find any unspent coins of value {} and token_id {}", partial.value_pair.1, partial.token_pair.1 )) } // If there are any, we'll just spend the first one we see. let burn_coin = owncoins[0].0.clone(); // Fetch our default address let address = self.wallet_address(1).await?; // We'll also need our Merkle tree let tree = self.get_money_tree().await?; let contract_id = *MONEY_CONTRACT_ID; // Now we need to do a lookup for the zkas proof bincodes, and create // the circuit objects and proving keys so we can build the transaction. // We also do this through the RPC. let zkas_bins = self.lookup_zkas(&contract_id).await?; let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_MINT_NS_V1) else { return Err(anyhow!("Mint circuit not found")) }; let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_BURN_NS_V1) else { return Err(anyhow!("Burn circuit not found")) }; let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?; let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?; let k = 13; let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin), mint_zkbin.clone()); let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin), burn_zkbin.clone()); eprintln!("Creating Mint circuit proving key"); let mint_pk = ProvingKey::build(k, &mint_circuit); eprintln!("Creating Burn circuit proving key"); let burn_pk = ProvingKey::build(k, &burn_circuit); // TODO: Maybe some kind of verification at this point // Now we should have everything we need to build the swap half eprintln!("Building second half of the swap transaction"); let (half_params, half_proofs, half_keys, _spent_coins, _value_blinds, _token_blinds) = build_half_swap_tx( &address, partial.value_pair.1, partial.token_pair.1, partial.value_pair.0, partial.token_pair.0, &partial.value_blinds, &partial.token_blinds, &[burn_coin], &tree, &mint_zkbin, &mint_pk, &burn_zkbin, &burn_pk, )?; let full_params = MoneyTransferParams { clear_inputs: vec![], inputs: vec![partial.params.inputs[0].clone(), half_params.inputs[0].clone()], outputs: vec![partial.params.outputs[0].clone(), half_params.outputs[0].clone()], }; let full_proofs = vec![ partial.proofs[0].clone(), half_proofs[0].clone(), partial.proofs[1].clone(), half_proofs[1].clone(), ]; let mut data = vec![MoneyFunction::OtcSwap as u8]; full_params.encode(&mut data)?; let mut tx = Transaction { calls: vec![ContractCall { contract_id, data }], proofs: vec![full_proofs], signatures: vec![], }; eprintln!("Signing swap transaction"); let sigs = tx.create_sigs(&mut OsRng, &half_keys)?; tx.signatures = vec![sigs]; Ok(tx) } /// Inspect and verify a given swap (half or full) transaction pub async fn inspect_swap(&self, bytes: Vec) -> Result<()> { let mut full: Option = None; let mut _half: Option = None; if let Ok(v) = deserialize(&bytes) { full = Some(v) }; match deserialize(&bytes) { Ok(v) => _half = Some(v), Err(_) => { if full.is_none() { return Err(anyhow!("Failed to deserialize to Transaction or PartialSwapData")) } } } if let Some(tx) = full { // We're inspecting a full transaction if tx.calls.len() != 1 { eprintln!( "Found {} contract calls in the transaction, there should be 1", tx.calls.len() ); return Err(anyhow!("Inspection failed")) } let params: MoneyTransferParams = deserialize(&tx.calls[0].data[1..])?; eprintln!("Parameters:\n{:#?}", params); if params.inputs.len() != 2 { eprintln!("Found {} inputs, there should be 2", params.inputs.len()); return Err(anyhow!("Inspection failed")) } if params.outputs.len() != 2 { eprintln!("Found {} outputs, there should be 2", params.outputs.len()); return Err(anyhow!("Inspection failed")) } // Try to decrypt one of the outputs. let secret_keys = self.get_money_secrets().await?; let mut skey: Option = None; let mut note: Option = None; let mut output_idx = 0; for output in ¶ms.outputs { let ciphertext = output.ciphertext.clone(); let ephem_public = output.ephem_public; let e_note = EncryptedNote { ciphertext, ephem_public }; eprintln!("Trying to decrypt note in output {}", output_idx); for secret in &secret_keys { if let Ok(d_note) = e_note.decrypt(secret) { let s: SecretKey = deserialize(&d_note.memo)?; skey = Some(s); note = Some(d_note); eprintln!("Successfully decrypted and found an ephemeral secret"); break } } if note.is_some() { break } output_idx += 1; } let Some(note) = note else { eprintln!("Error: Could not decrypt notes of either output"); return Err(anyhow!("Inspection failed")) }; eprintln!( "Output[{}] value: {} ({})", output_idx, note.value, encode_base10(note.value, 8) ); eprintln!("Output[{}] token ID: {}", output_idx, note.token_id); let skey = skey.unwrap(); let (pub_x, pub_y) = PublicKey::from_secret(skey).xy(); let coin = poseidon_hash([ pub_x, pub_y, pallas::Base::from(note.value), note.token_id.inner(), note.serial, note.coin_blind, ]); if coin == params.outputs[output_idx].coin { eprintln!("Output[{}] coin matches decrypted note metadata", output_idx); } else { eprintln!("Error: Output[{}] coin does not match note metadata", output_idx); return Err(anyhow!("Inspection failed")) } let valcom = pedersen_commitment_u64(note.value, note.value_blind); let tokcom = pedersen_commitment_base(note.token_id.inner(), note.token_blind); if valcom != params.outputs[output_idx].value_commit { eprintln!( "Error: Output[{}] value commitment does not match note metadata", output_idx ); return Err(anyhow!("Inspection failed")) } if tokcom != params.outputs[output_idx].token_commit { eprintln!( "Error: Output[{}] token commitment does not match note metadata", output_idx ); return Err(anyhow!("Inspection failed")) } eprintln!("Value and token commitments match decrypted note metadata"); // Verify that the output commitments match the other input commitments match output_idx { 0 => { if valcom != params.inputs[1].value_commit || tokcom != params.inputs[1].token_commit { eprintln!("Error: Value/Token commits of output[0] do not match input[1]"); return Err(anyhow!("Inspection failed")) } } 1 => { if valcom != params.inputs[0].value_commit || tokcom != params.inputs[0].token_commit { eprintln!("Error: Value/Token commits of output[1] do not match input[0]"); return Err(anyhow!("Inspection failed")) } } _ => unreachable!(), } eprintln!("Found matching pedersen commitments for outputs and inputs"); // TODO: Verify signature // TODO: Verify ZK proofs return Ok(()) } // TODO: Inspect PartialSwapData todo!("Inspect PartialSwapData"); } /// Sign a given transaction by retrieving the secret key from the encrypted /// note and prepending it to the transaction's signatures. pub async fn sign_swap(&self, tx: &mut Transaction) -> Result<()> { // We need our secret keys to try and decrypt the note let secret_keys = self.get_money_secrets().await?; let params: MoneyTransferParams = deserialize(&tx.calls[0].data[1..])?; // Our output should be outputs[0] so we try to decrypt that. let ciphertext = params.outputs[0].ciphertext.clone(); let ephem_public = params.outputs[0].ephem_public; let encrypted_note = EncryptedNote { ciphertext, ephem_public }; eprintln!("Trying to decrypt note in outputs[0]"); let mut skey = None; for secret in &secret_keys { if let Ok(note) = encrypted_note.decrypt(secret) { let s: SecretKey = deserialize(¬e.memo)?; eprintln!("Successfully decrypted and found an ephemeral secret"); skey = Some(s); break } } let Some(skey) = skey else { eprintln!("Error: Failed to decrypt note with any of our secret keys"); return Err(anyhow!("Failed to decrypt note with any of our secret keys")) }; eprintln!("Signing swap transaction"); let sigs = tx.create_sigs(&mut OsRng, &[skey])?; tx.signatures[0].insert(0, sigs[0]); Ok(()) } }