From 4661d797cd9406b16d6152706fbe3a564594d9b0 Mon Sep 17 00:00:00 2001 From: skoupidi Date: Fri, 17 May 2024 20:04:32 +0300 Subject: [PATCH] drk: attach-fee added and fixed otc swap --- bin/drk/src/cli_util.rs | 6 +- bin/drk/src/main.rs | 26 ++-- bin/drk/src/money.rs | 112 ++++++++++++++++-- bin/drk/src/swap.rs | 59 ++++++--- .../localnet/darkfid-single-node/README.md | 42 +++---- src/contract/money/src/client/swap_v1.rs | 6 +- 6 files changed, 192 insertions(+), 59 deletions(-) diff --git a/bin/drk/src/cli_util.rs b/bin/drk/src/cli_util.rs index d3f99e0da..5c6954dcb 100644 --- a/bin/drk/src/cli_util.rs +++ b/bin/drk/src/cli_util.rs @@ -38,7 +38,6 @@ use crate::{money::BALANCE_BASE10_DECIMALS, Drk}; /// Auxiliary function to parse a base64 encoded transaction from stdin. pub async fn parse_tx_from_stdin() -> Result { - println!("Reading transaction from stdin..."); let mut buf = String::new(); stdin().read_to_string(&mut buf)?; let Some(bytes) = base64::decode(buf.trim()) else { @@ -223,6 +222,10 @@ pub fn generate_completions(shell: &str) -> Result<()> { .about("OTC atomic swap") .subcommands(vec![init, join, inspect, sign]); + // AttachFee + let attach_fee = SubCommand::with_name("attach-fee") + .about("Attach the fee call to a transaction given from stdin"); + // Inspect let inspect = SubCommand::with_name("inspect").about("Inspect a transaction from stdin"); @@ -467,6 +470,7 @@ pub fn generate_completions(shell: &str) -> Result<()> { unspend, transfer, otc, + attach_fee, inspect, broadcast, subscribe, diff --git a/bin/drk/src/main.rs b/bin/drk/src/main.rs index 32f07a017..addf2737f 100644 --- a/bin/drk/src/main.rs +++ b/bin/drk/src/main.rs @@ -34,7 +34,6 @@ use url::Url; use darkfi::{ async_daemonize, cli_desc, rpc::{client::RpcClient, jsonrpc::JsonRequest, util::JsonValue}, - tx::Transaction, util::{ encoding::base64, parse::{decode_base10, encode_base10}, @@ -216,6 +215,9 @@ enum Subcmd { command: OtcSubcmd, }, + /// Attach the fee call to a transaction given from stdin + AttachFee, + /// Inspect a transaction from stdin Inspect, @@ -992,14 +994,7 @@ async fn realmain(args: Args, ex: Arc>) -> Result<()> { } OtcSubcmd::Sign => { - let mut buf = String::new(); - stdin().read_to_string(&mut buf)?; - let Some(bytes) = base64::decode(buf.trim()) else { - eprintln!("Failed to decode swap transaction"); - exit(2); - }; - - let mut tx: Transaction = deserialize_async(&bytes).await?; + let mut tx = parse_tx_from_stdin().await?; let drk = Drk::new(args.wallet_path, args.wallet_pass, None, ex).await?; if let Err(e) = drk.sign_swap(&mut tx).await { @@ -1290,6 +1285,19 @@ async fn realmain(args: Args, ex: Arc>) -> Result<()> { } }, + Subcmd::AttachFee => { + let mut tx = parse_tx_from_stdin().await?; + + let drk = Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?; + if let Err(e) = drk.attach_fee(&mut tx).await { + eprintln!("Failed to attach the fee call to the transaction: {e:?}"); + exit(2); + }; + + println!("{}", base64::encode(&serialize_async(&tx).await)); + Ok(()) + } + Subcmd::Inspect => { let tx = parse_tx_from_stdin().await?; println!("{tx:#?}"); diff --git a/bin/drk/src/money.rs b/bin/drk/src/money.rs index fd2928c93..66407e054 100644 --- a/bin/drk/src/money.rs +++ b/bin/drk/src/money.rs @@ -24,7 +24,7 @@ use rusqlite::types::Value; use darkfi::{ tx::Transaction, - zk::{halo2::Field, proof::ProvingKey, Proof}, + zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses, Proof}, zkas::ZkBinary, Error, Result, }; @@ -39,7 +39,7 @@ use darkfi_money_contract::{ MoneyGenesisMintParamsV1, MoneyPoWRewardParamsV1, MoneyTokenMintParamsV1, MoneyTransferParamsV1, Nullifier, Output, TokenId, DARK_TOKEN_ID, }, - MoneyFunction, + MoneyFunction, MONEY_CONTRACT_ZKAS_FEE_NS_V1, }; use darkfi_sdk::{ bridgetree, @@ -687,8 +687,8 @@ impl Drk { Ok(height) } - /// Auxiliary function to grab all the nullifiers, coins, notes and freezes from - /// transaction money call. + /// Auxiliary function to grab all the nullifiers, coins, notes and freezes from + /// a transaction money call. async fn parse_money_call( &self, call_idx: usize, @@ -760,11 +760,11 @@ impl Drk { println!("[parse_money_call] Found Money::TokenMintV1 call"); let params: MoneyTokenMintParamsV1 = deserialize_async(&data[1..]).await?; coins.push(params.coin); - // Grab the note from the parent auth call - let parent_idx = call.parent_index.unwrap(); - let parent_call = &calls[parent_idx]; + // Grab the note from the child auth call + let child_idx = call.children_indexes[0]; + let child_call = &calls[child_idx]; let params: MoneyAuthTokenMintParamsV1 = - deserialize_async(&parent_call.data.data[1..]).await?; + deserialize_async(&child_call.data.data[1..]).await?; notes.push(params.enc_note); } } @@ -882,6 +882,36 @@ impl Drk { Ok(()) } + /// Auxiliary function to grab all the nullifiers from a transaction money call. + async fn money_call_nullifiers(&self, call: &DarkLeaf) -> Result> { + let mut nullifiers: Vec = vec![]; + + let data = &call.data.data; + match MoneyFunction::try_from(data[0])? { + MoneyFunction::FeeV1 => { + let params: MoneyFeeParamsV1 = deserialize_async(&data[9..]).await?; + nullifiers.push(params.input.nullifier); + } + MoneyFunction::TransferV1 => { + let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?; + + for input in params.inputs { + nullifiers.push(input.nullifier); + } + } + MoneyFunction::OtcSwapV1 => { + let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?; + + for input in params.inputs { + nullifiers.push(input.nullifier); + } + } + _ => { /* Do nothing */ } + } + + Ok(nullifiers) + } + /// Mark provided transaction input coins as spent. pub async fn mark_tx_spend(&self, tx: &Transaction) -> Result<()> { let tx_hash = tx.hash().to_string(); @@ -892,7 +922,7 @@ impl Drk { } println!("[mark_tx_spend] Found Money contract in call {i}"); - let (nullifiers, _, _, _) = self.parse_money_call(i, &tx.calls).await?; + let nullifiers = self.money_call_nullifiers(call).await?; self.mark_spent_coins(&nullifiers, &tx_hash).await?; } @@ -1089,4 +1119,68 @@ impl Drk { Ok((call, vec![proof], vec![signature_secret])) } + + /// Create and attach the fee call to given transaction. + pub async fn attach_fee(&self, tx: &mut Transaction) -> Result<()> { + // Grab spent coins nullifiers of the transactions and check no other fee call exists + let mut tx_nullifiers = vec![]; + for call in &tx.calls { + if call.data.contract_id != *MONEY_CONTRACT_ID { + continue + } + + match MoneyFunction::try_from(call.data.data[0])? { + MoneyFunction::FeeV1 => { + return Err(Error::Custom("Fee call already exists".to_string())) + } + _ => { /* Do nothing */ } + } + + let nullifiers = self.money_call_nullifiers(call).await?; + tx_nullifiers.extend_from_slice(&nullifiers); + } + + // Grab all native owncoins to check if any is spent + let mut spent_coins = vec![]; + let available_coins = self.get_token_coins(&DARK_TOKEN_ID).await?; + for coin in available_coins { + if tx_nullifiers.contains(&coin.nullifier()) { + spent_coins.push(coin); + } + } + + // 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(&MONEY_CONTRACT_ID).await?; + + let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1) + else { + return Err(Error::Custom("Fee circuit not found".to_string())) + }; + + let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?; + + let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin); + + // Creating Fee circuits proving keys + let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit); + + // We first have to execute the fee-less tx to gather its used gas, and then we feed + // it into the fee-creating function. + let tree = self.get_money_tree().await?; + let secret = self.default_secret().await?; + let fee_public = PublicKey::from_secret(secret); + let (fee_call, fee_proofs, fee_secrets) = self + .append_fee_call(tx, fee_public, &tree, &fee_pk, &fee_zkbin, Some(&spent_coins)) + .await?; + + // Append the fee call to the transaction + tx.calls.push(DarkLeaf { data: fee_call, parent_index: None, children_indexes: vec![] }); + tx.proofs.push(fee_proofs); + let sigs = tx.create_sigs(&fee_secrets)?; + tx.signatures.push(sigs); + + Ok(()) + } } diff --git a/bin/drk/src/swap.rs b/bin/drk/src/swap.rs index 8e61fc25e..a7fa7d7ec 100644 --- a/bin/drk/src/swap.rs +++ b/bin/drk/src/swap.rs @@ -423,39 +423,64 @@ impl Drk { Ok(()) } - /// Sign a given transaction by retrieving the secret key from the encrypted + /// Sign given swap 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 + // We need our secret keys to try and decrypt the notes let secret_keys = self.get_money_secrets().await?; let params: MoneyTransferParamsV1 = deserialize_async(&tx.calls[0].data.data[1..]).await?; - // Our output should be outputs[0] so we try to decrypt that. - let encrypted_note = ¶ms.outputs[0].note; - - println!("Trying to decrypt note in outputs[0]"); - let mut skey = None; + // We wil try to decrypt each note separately, + // since we might us the same key in both of them. + let mut found = false; + // Try to decrypt the first note for secret in &secret_keys { - if let Ok(note) = encrypted_note.decrypt::(secret) { - let s: SecretKey = deserialize_async(¬e.memo).await?; - println!("Successfully decrypted and found an ephemeral secret"); - skey = Some(s); - break + let Ok(note) = ¶ms.outputs[0].note.decrypt::(secret) else { continue }; + + // Sign the swap transaction + let skey: SecretKey = deserialize_async(¬e.memo).await?; + let sigs = tx.create_sigs(&[skey])?; + + // If transaction contains both signatures, replace the first one, + // otherwise insert signature on first position. + if tx.signatures[0].len() == 2 { + tx.signatures[0][0] = sigs[0]; + } else { + tx.signatures[0].insert(0, sigs[0]); } + + found = true; + break } - let Some(skey) = skey else { + // Try to decrypt the second note + for secret in &secret_keys { + let Ok(note) = ¶ms.outputs[1].note.decrypt::(secret) else { continue }; + + // Sign the swap transaction + let skey: SecretKey = deserialize_async(¬e.memo).await?; + let sigs = tx.create_sigs(&[skey])?; + + // If transaction contains both signatures, replace the second one, + // otherwise replace the first one. + if tx.signatures[0].len() == 2 { + tx.signatures[0][1] = sigs[0]; + } else { + tx.signatures[0][0] = sigs[0]; + } + + found = true; + break + } + + if !found { eprintln!("Error: Failed to decrypt note with any of our secret keys"); return Err(Error::Custom( "Failed to decrypt note with any of our secret keys".to_string(), )) }; - println!("Signing swap transaction"); - let sigs = tx.create_sigs(&[skey])?; - tx.signatures[0].insert(0, sigs[0]); - Ok(()) } } diff --git a/contrib/localnet/darkfid-single-node/README.md b/contrib/localnet/darkfid-single-node/README.md index 497d826b3..dc5ce19dd 100644 --- a/contrib/localnet/darkfid-single-node/README.md +++ b/contrib/localnet/darkfid-single-node/README.md @@ -55,24 +55,26 @@ of the guide can be added for future regressions. | 10 | Alias add | alias add {ALIAS} {TOKEN} | Pass | | 11 | Aliases retrieval | alias show | Pass | | 12 | Mint generation | token mint {ALIAS} {AMOUNT} {ADDR} | Pass | -| 13 | Transfer | transfer {AMOUNT} {ALIAS} {ADDR} | Pass | -| 14 | Coins retrieval | wallet --coins | Pass | -| 15 | OTC initialization | otc init -v {AMOUNT}:{AMOUNT} -t {ALIAS}:{ALIAS} | Failure: needs #12 | -| 16 | OTC join | otc join | Failure: needs #15 | -| 17 | OTC sign | otc sign | Failure: needs #16 | -| 18 | DAO create | dao create {LIMIT} {QUORUM} {RATIO} {TOKEN} | Failure: needs #12 | -| 19 | DAO view | dao view | Failure: needs #18 | -| 20 | DAO import | dao import | Failure: needs #18 | -| 21 | DAO list | dao sign | Failure: needs #18 | -| 22 | DAO mint | dao mint {DAO} | Failure: needs #18 | -| 23 | DAO balance | dao balance {DAO} | Failure: needs #18 | -| 24 | DAO propose | dao propose {DAO} {ADDR} {AMOUNT} {TOKEN} | Failure: needs #18 | -| 25 | DAO proposals retrieval | dao proposals {DAO} | Failure: needs #24 | -| 26 | DAO proposal retrieval | dao proposal {DAO} {PROPOSAL_ID} | Failure: needs #24 | -| 27 | DAO vote | dao vote {DAO} {PROPOSAL_ID} {VOTE} {WEIGHT} | Failure: needs #24 | -| 28 | DAO proposal execution | dao exec {DAO} {PROPOSAL_ID} | Failure: needs #27 | -| 29 | Coins unspend | unspend {COIN} | Pass | -| 30 | Transaction inspect | inspect | Pass | -| 31 | Transaction simulate | explorer simulate-tx | Pass | -| 31 | Transaction broadcast | broadcast | Pass | +| 13 | Token freeze | token freeze {ALIAS} | Pass | +| 14 | Transfer | transfer {AMOUNT} {ALIAS} {ADDR} | Pass | +| 15 | Coins retrieval | wallet --coins | Pass | +| 16 | OTC initialization | otc init -v {AMOUNT}:{AMOUNT} -t {ALIAS}:{ALIAS} | Pass | +| 17 | OTC join | otc join | Pass | +| 18 | OTC sign | otc sign | Pass | +| 19 | DAO create | dao create {LIMIT} {QUORUM} {RATIO} {TOKEN} | Failure: needs #12 | +| 20 | DAO view | dao view | Failure: needs #18 | +| 21 | DAO import | dao import | Failure: needs #18 | +| 22 | DAO list | dao sign | Failure: needs #18 | +| 23 | DAO mint | dao mint {DAO} | Failure: needs #18 | +| 24 | DAO balance | dao balance {DAO} | Failure: needs #18 | +| 25 | DAO propose | dao propose {DAO} {ADDR} {AMOUNT} {TOKEN} | Failure: needs #18 | +| 26 | DAO proposals retrieval | dao proposals {DAO} | Failure: needs #24 | +| 27 | DAO proposal retrieval | dao proposal {DAO} {PROPOSAL_ID} | Failure: needs #24 | +| 28 | DAO vote | dao vote {DAO} {PROPOSAL_ID} {VOTE} {WEIGHT} | Failure: needs #24 | +| 29 | DAO proposal execution | dao exec {DAO} {PROPOSAL_ID} | Failure: needs #27 | +| 30 | Coins unspend | unspend {COIN} | Pass | +| 31 | Transaction inspect | inspect | Pass | +| 32 | Transaction simulate | explorer simulate-tx | Pass | +| 33 | Transaction broadcast | broadcast | Pass | +| 34 | Transaction attach fee | attach-fee | Pass | diff --git a/src/contract/money/src/client/swap_v1.rs b/src/contract/money/src/client/swap_v1.rs index 2e8ce8da8..453e849b6 100644 --- a/src/contract/money/src/client/swap_v1.rs +++ b/src/contract/money/src/client/swap_v1.rs @@ -31,7 +31,7 @@ use darkfi_sdk::{ pasta::pallas, }; use darkfi_serial::serialize; -use log::{debug, info}; +use log::debug; use rand::rngs::OsRng; use crate::{ @@ -130,7 +130,7 @@ impl SwapCallBuilder { let signature_secret = SecretKey::random(&mut OsRng); let mut proofs = vec![]; - info!("Creating burn proof for input"); + debug!("Creating burn proof for input"); let (proof, public_inputs) = create_transfer_burn_proof( &self.burn_zkbin, &self.burn_pk, @@ -154,7 +154,7 @@ impl SwapCallBuilder { // For the output, we create a new coin blind let coin_blind = Blind::random(&mut OsRng); - info!("Creating mint proof for output"); + debug!("Creating mint proof for output"); let (proof, public_inputs) = create_transfer_mint_proof( &self.mint_zkbin, &self.mint_pk,