Эх сурвалжийг харах

drk: attach-fee added and fixed otc swap

skoupidi 2 жил өмнө
parent
commit
4661d797cd

+ 5 - 1
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<Transaction> {
-    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,

+ 17 - 9
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<smol::Executor<'static>>) -> 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<smol::Executor<'static>>) -> 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:#?}");

+ 103 - 9
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<ContractCall>) -> Result<Vec<Nullifier>> {
+        let mut nullifiers: Vec<Nullifier> = 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(())
+    }
 }

+ 41 - 16
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 = &params.outputs[0].note;
+        // We wil try to decrypt each note separately,
+        // since we might us the same key in both of them.
+        let mut found = false;
 
-        println!("Trying to decrypt note in outputs[0]");
-        let mut skey = None;
+        // Try to decrypt the first note
+        for secret in &secret_keys {
+            let Ok(note) = &params.outputs[0].note.decrypt::<MoneyNote>(secret) else { continue };
+
+            // Sign the swap transaction
+            let skey: SecretKey = deserialize_async(&note.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
+        }
 
+        // Try to decrypt the second note
         for secret in &secret_keys {
-            if let Ok(note) = encrypted_note.decrypt::<MoneyNote>(secret) {
-                let s: SecretKey = deserialize_async(&note.memo).await?;
-                println!("Successfully decrypted and found an ephemeral secret");
-                skey = Some(s);
-                break
+            let Ok(note) = &params.outputs[1].note.decrypt::<MoneyNote>(secret) else { continue };
+
+            // Sign the swap transaction
+            let skey: SecretKey = deserialize_async(&note.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
         }
 
-        let Some(skey) = skey else {
+        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(())
     }
 }

+ 22 - 20
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               |
 

+ 3 - 3
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,