Просмотр исходного кода

tx: improve TransactionBuilder

- Add `secrets` field and `new()` method to ContractCallLeaf
- Replace manual create_sigs/signatures pattern with `build_signed()` method
- `append_fee_call()` now mutates a builder
x 3 дней назад
Родитель
Сommit
d319b17615

+ 5 - 5
bin/darkfid/src/registry/model.rs

@@ -419,11 +419,11 @@ fn generate_transaction(
     let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
     debris.params.encode(&mut data)?;
     let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
-    let mut tx_builder =
-        TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
-    let mut tx = tx_builder.build()?;
-    let sigs = tx.create_sigs(&[block_signing_keypair.secret])?;
-    tx.signatures = vec![sigs];
+    let mut tx_builder = TransactionBuilder::new(
+        ContractCallLeaf::new(call, debris.proofs, vec![block_signing_keypair.secret]),
+        vec![],
+    )?;
+    let tx = tx_builder.build_signed()?;
 
     Ok(tx)
 }

+ 5 - 5
bin/darkfid/src/tests/harness.rs

@@ -234,11 +234,11 @@ impl Harness {
         let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
         debris.params.encode(&mut data)?;
         let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[keypair.secret])?;
-        tx.signatures = vec![sigs];
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, debris.proofs, vec![keypair.secret]),
+            vec![],
+        )?;
+        let tx = tx_builder.build_signed()?;
 
         // We increment timestamp so we don't have to use sleep
         let timestamp = previous.header.timestamp.checked_add(1.into())?;

+ 17 - 33
bin/drk/src/cli_util.rs

@@ -44,7 +44,7 @@ use darkfi_sdk::{
     crypto::{
         keypair::{Address, Network},
         pasta_prelude::PrimeField,
-        FuncId, SecretKey,
+        FuncId,
     },
     dark_tree::DarkTree,
     pasta::pallas,
@@ -862,10 +862,11 @@ pub fn display_mining_config(
 
 /// Cast `ContractCallImport` to `ContractCallLeaf`
 fn to_leaf(call: &ContractCallImport) -> ContractCallLeaf {
-    ContractCallLeaf {
-        call: call.call().clone(),
-        proofs: call.proofs().iter().map(|p| Proof::new(p.clone())).collect(),
-    }
+    ContractCallLeaf::new(
+        call.call().clone(),
+        call.proofs().iter().map(|p| Proof::new(p.clone())).collect(),
+        call.secrets().to_vec(),
+    )
 }
 
 /// Recursively build subtree for a DarkTree
@@ -882,27 +883,11 @@ fn build_subtree(
     DarkTree::new(to_leaf(&calls[idx]), children, None, None)
 }
 
-/// Recursively retrieve the signature keys in Post order traversal
-fn retrieve_signature_keys(
-    idx: usize,
-    calls: &[ContractCallImport],
-    children_map: &HashMap<usize, &Vec<usize>>,
-    sig_keys: &mut Vec<Vec<SecretKey>>,
-) {
-    let children_idx = children_map.get(&idx).map(|v| v.as_slice()).unwrap_or(&[]);
-
-    for i in children_idx {
-        retrieve_signature_keys(*i, calls, children_map, sig_keys)
-    }
-
-    sig_keys.push(calls[idx].secrets().to_vec());
-}
-
 /// Build a `Transaction` given a slice of calls and their mapping
 pub fn tx_from_calls_mapped(
     calls: &[ContractCallImport],
     map: &[(usize, Vec<usize>)],
-) -> Result<(TransactionBuilder, Vec<Vec<SecretKey>>)> {
+) -> Result<TransactionBuilder> {
     assert_eq!(calls.len(), map.len());
 
     let children_map: HashMap<usize, &Vec<usize>> = map.iter().map(|(k, v)| (*k, v)).collect();
@@ -925,12 +910,7 @@ pub fn tx_from_calls_mapped(
         tx_builder.append(to_leaf(&calls[*root_idx]), root_children)?;
     }
 
-    let mut signature_secrets: Vec<Vec<SecretKey>> = vec![];
-    for idx in root_idxs {
-        retrieve_signature_keys(idx, calls, &children_map, &mut signature_secrets);
-    }
-
-    Ok((tx_builder, signature_secrets))
+    Ok(tx_builder)
 }
 
 /// Auxiliary function to parse a contract call mapping.
@@ -1030,7 +1010,7 @@ fn check_cycles(entries: &[(usize, Vec<usize>)]) -> std::result::Result<(), Stri
 mod tests {
     use super::*;
     use darkfi_sdk::{
-        crypto::{pasta_prelude::Field, ContractId},
+        crypto::{pasta_prelude::Field, ContractId, SecretKey},
         ContractCall,
     };
     use rand::rngs::OsRng;
@@ -1121,11 +1101,12 @@ mod tests {
         );
 
         // Transaction with 3 root calls, each with no children
-        let (mut tx_builder, sig_keys) = tx_from_calls_mapped(
+        let mut tx_builder = tx_from_calls_mapped(
             &[call0.clone(), call1.clone(), call2.clone()],
             &parse_tree("{0 : [], 1: [], 2: []}").unwrap(),
         )
         .unwrap();
+        let sig_keys = tx_builder.get_secrets().unwrap();
         let leafs = tx_builder.calls.build_vec().unwrap();
 
         assert_eq!(leafs.len(), 3);
@@ -1138,11 +1119,12 @@ mod tests {
         assert_eq!(sig_keys[2].len(), 1);
 
         // Transaction with 2 root calls, the second call is child of the first
-        let (mut tx_builder, sig_keys) = tx_from_calls_mapped(
+        let mut tx_builder = tx_from_calls_mapped(
             &[call0.clone(), call1.clone(), call2.clone()],
             &parse_tree("{0 : [1], 1: [], 2: []}").unwrap(),
         )
         .unwrap();
+        let sig_keys = tx_builder.get_secrets().unwrap();
         let leafs = tx_builder.calls.build_vec().unwrap();
 
         assert_eq!(leafs.len(), 3);
@@ -1155,11 +1137,12 @@ mod tests {
         assert_eq!(sig_keys[2].len(), 1);
 
         // Transaction with 1 root call, the second and third are the children of the first
-        let (mut tx_builder, sig_keys) = tx_from_calls_mapped(
+        let mut tx_builder = tx_from_calls_mapped(
             &[call0.clone(), call1.clone(), call2.clone()],
             &parse_tree("{0 : [1, 2], 1: [], 2: []}").unwrap(),
         )
         .unwrap();
+        let sig_keys = tx_builder.get_secrets().unwrap();
         let leafs = tx_builder.calls.build_vec().unwrap();
 
         assert_eq!(leafs.len(), 3);
@@ -1173,11 +1156,12 @@ mod tests {
 
         // Transaction with 1 root call, the first is the child of the second, the second is the
         // child of the third
-        let (mut tx_builder, sig_keys) = tx_from_calls_mapped(
+        let mut tx_builder = tx_from_calls_mapped(
             &[call0, call1, call2],
             &parse_tree("{0 : [], 1: [0], 2: [1]}").unwrap(),
         )
         .unwrap();
+        let sig_keys = tx_builder.get_secrets().unwrap();
         let leafs = tx_builder.calls.build_vec().unwrap();
 
         assert_eq!(leafs.len(), 3);

+ 55 - 223
bin/drk/src/dao.rs

@@ -51,8 +51,7 @@ use darkfi_dao_contract::{
 use darkfi_money_contract::{
     client::transfer_v1::{select_coins, TransferCallBuilder, TransferCallInput},
     model::{CoinAttributes, Nullifier, TokenId},
-    MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
-    MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+    MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
 };
 use darkfi_sdk::{
     bridgetree,
@@ -2144,22 +2143,7 @@ impl Drk {
 
         // 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. First we grab the fee call from money.
-        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, false)?;
-
-        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
-
-        // Creating Fee circuit proving key
-        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
-
-        // Now we grab the DAO mint
+        // We also do this through the RPC. First we grab the DAO mint.
         let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
 
         let Some(dao_mint_zkbin) = zkas_bins.iter().find(|x| x.0 == DAO_CONTRACT_ZKAS_MINT_NS)
@@ -2192,29 +2176,16 @@ impl Drk {
         let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
 
         // Create the TransactionBuilder containing above call
-        let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
-
-        // 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 mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[notes_secret_key])?;
-        tx.signatures.push(sigs);
-
-        let tree = self.get_money_tree().await?;
-        let (fee_call, fee_proofs, fee_secrets) =
-            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
-
-        // Append the fee call to the transaction
-        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, proofs, vec![notes_secret_key]),
+            vec![],
+        )?;
 
-        // Now build the actual transaction and sign it with all necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[notes_secret_key])?;
-        tx.signatures.push(sigs);
-        let sigs = tx.create_sigs(&fee_secrets)?;
-        tx.signatures.push(sigs);
+        // Add fee - marks tx_local output if needed and appends fee call
+        self.append_fee_call(&mut tx_builder).await?;
 
-        Ok(tx)
+        // Build and sign the transaction
+        tx_builder.build_signed()
     }
 
     /// Create a DAO transfer proposal.
@@ -2480,24 +2451,7 @@ impl Drk {
 
         // 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. First we grab the fee call from money.
-        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(
-                "[dao_transfer_proposal_tx] Fee circuit not found".to_string(),
-            ))
-        };
-
-        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?;
-
-        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
-
-        // Creating Fee circuit proving key
-        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
-
-        // Now we grab the DAO bins
+        // We also do this through the RPC. First we grab the DAO bins.
         let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
 
         let Some(propose_burn_zkbin) =
@@ -2585,29 +2539,16 @@ impl Drk {
         let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
 
         // Create the TransactionBuilder containing above call
-        let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
-
-        // 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 mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&signature_secrets)?;
-        tx.signatures.push(sigs);
-
-        let tree = self.get_money_tree().await?;
-        let (fee_call, fee_proofs, fee_secrets) =
-            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
-
-        // Append the fee call to the transaction
-        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, proofs, signature_secrets),
+            vec![],
+        )?;
 
-        // Now build the actual transaction and sign it with all necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&signature_secrets)?;
-        tx.signatures.push(sigs);
-        let sigs = tx.create_sigs(&fee_secrets)?;
-        tx.signatures.push(sigs);
+        // Add fee - marks tx_local output if needed and appends fee call
+        self.append_fee_call(&mut tx_builder).await?;
 
-        Ok(tx)
+        // Build and sign the transaction
+        tx_builder.build_signed()
     }
 
     /// Create a DAO generic proposal transaction.
@@ -2663,22 +2604,7 @@ impl Drk {
 
         // 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. First we grab the fee call from money.
-        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("[dao_generic_proposal_tx] Fee circuit not found".to_string()))
-        };
-
-        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?;
-
-        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
-
-        // Creating Fee circuit proving key
-        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
-
-        // Now we grab the DAO bins
+        // We also do this through the RPC. First we grab the DAO bins.
         let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
 
         let Some(propose_burn_zkbin) =
@@ -2766,29 +2692,16 @@ impl Drk {
         let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
 
         // Create the TransactionBuilder containing above call
-        let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
-
-        // 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 mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&signature_secrets)?;
-        tx.signatures.push(sigs);
-
-        let tree = self.get_money_tree().await?;
-        let (fee_call, fee_proofs, fee_secrets) =
-            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
-
-        // Append the fee call to the transaction
-        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, proofs, signature_secrets),
+            vec![],
+        )?;
 
-        // Now build the actual transaction and sign it with all necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&signature_secrets)?;
-        tx.signatures.push(sigs);
-        let sigs = tx.create_sigs(&fee_secrets)?;
-        tx.signatures.push(sigs);
+        // Add fee - marks tx_local output if needed and appends fee call
+        self.append_fee_call(&mut tx_builder).await?;
 
-        Ok(tx)
+        // Build and sign the transaction
+        tx_builder.build_signed()
     }
 
     /// Vote on a DAO proposal
@@ -2868,22 +2781,7 @@ impl Drk {
 
         // 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. First we grab the fee call from money.
-        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("[dao_vote] Fee circuit not found".to_string()))
-        };
-
-        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?;
-
-        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
-
-        // Creating Fee circuit proving key
-        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
-
-        // Now we grab the DAO bins
+        // We also do this through the RPC. First we grab the DAO bins.
         let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
 
         let Some(dao_vote_burn_zkbin) =
@@ -2984,29 +2882,16 @@ impl Drk {
         let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
 
         // Create the TransactionBuilder containing above call
-        let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
-
-        // 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 mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&signature_secrets)?;
-        tx.signatures.push(sigs);
-
-        let tree = self.get_money_tree().await?;
-        let (fee_call, fee_proofs, fee_secrets) =
-            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
-
-        // Append the fee call to the transaction
-        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, proofs, signature_secrets),
+            vec![],
+        )?;
 
-        // Now build the actual transaction and sign it with all necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&signature_secrets)?;
-        tx.signatures.push(sigs);
-        let sigs = tx.create_sigs(&fee_secrets)?;
-        tx.signatures.push(sigs);
+        // Add fee - marks tx_local output if needed and appends fee call
+        self.append_fee_call(&mut tx_builder).await?;
 
-        Ok(tx)
+        // Build and sign the transaction
+        tx_builder.build_signed()
     }
 
     /// Execute a DAO transfer proposal.
@@ -3150,23 +3035,15 @@ impl Drk {
             return Err(Error::Custom("[dao_exec_transfer] Burn circuit not found".to_string()))
         };
 
-        let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
-        else {
-            return Err(Error::Custom("[dao_exec_transfer] Fee circuit not found".to_string()))
-        };
-
         let mint_zkbin = ZkBinary::decode(&mint_zkbin.1, false)?;
         let burn_zkbin = ZkBinary::decode(&burn_zkbin.1, false)?;
-        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?;
 
         let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
         let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
-        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
 
         // Creating Mint, Burn and Fee circuits proving keys
         let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
         let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
-        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
 
         // Now we grab the DAO bins
         let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
@@ -3330,16 +3207,20 @@ impl Drk {
 
         // Create the TransactionBuilder containing above calls
         let mut tx_builder = TransactionBuilder::new(
-            ContractCallLeaf { call: exec_call, proofs: exec_proofs },
+            ContractCallLeaf::new(exec_call, exec_proofs, vec![exec_signature_secret]),
             vec![
                 DarkTree::new(
-                    ContractCallLeaf { call: auth_transfer_call, proofs: auth_transfer_proofs },
+                    ContractCallLeaf::new(auth_transfer_call, auth_transfer_proofs, vec![]),
                     vec![],
                     None,
                     None,
                 ),
                 DarkTree::new(
-                    ContractCallLeaf { call: transfer_call, proofs: transfer_secrets.proofs },
+                    ContractCallLeaf::new(
+                        transfer_call,
+                        transfer_secrets.proofs,
+                        transfer_secrets.signature_secrets,
+                    ),
                     vec![],
                     None,
                     None,
@@ -3347,32 +3228,11 @@ impl Drk {
             ],
         )?;
 
-        // 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 mut tx = tx_builder.build()?;
-        let auth_transfer_sigs = tx.create_sigs(&[])?;
-        let transfer_sigs = tx.create_sigs(&transfer_secrets.signature_secrets)?;
-        let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
-        tx.signatures = vec![auth_transfer_sigs, transfer_sigs, exec_sigs];
-
-        let (fee_call, fee_proofs, fee_secrets) =
-            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
-
-        // Append the fee call to the transaction
-        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-
-        // Now build the actual transaction and sign it with all necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[])?;
-        tx.signatures.push(sigs);
-        let sigs = tx.create_sigs(&transfer_secrets.signature_secrets)?;
-        tx.signatures.push(sigs);
-        let sigs = tx.create_sigs(&[exec_signature_secret])?;
-        tx.signatures.push(sigs);
-        let sigs = tx.create_sigs(&fee_secrets)?;
-        tx.signatures.push(sigs);
-
-        Ok(tx)
+        // Add fee - marks tx_local output if needed and appends fee call
+        self.append_fee_call(&mut tx_builder).await?;
+
+        // Build and sign the transaction
+        tx_builder.build_signed()
     }
 
     /// Execute a DAO generic proposal.
@@ -3463,17 +3323,7 @@ impl Drk {
 
         // 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. First we grab the calls from money.
-        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("[dao_exec_generic] Fee circuit not found".to_string()))
-        };
-        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?;
-        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
-        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
-
-        // Now we grab the DAO bins
+        // We also do this through the RPC. First we grab the DAO bins.
         let zkas_bins = self.lookup_zkas(&DAO_CONTRACT_ID).await?;
 
         let (namespace, early_exec_secret_key) = match early {
@@ -3492,9 +3342,6 @@ impl Drk {
         let dao_exec_circuit = ZkCircuit::new(empty_witnesses(&dao_exec_zkbin)?, &dao_exec_zkbin);
         let dao_exec_pk = ProvingKey::build(dao_exec_zkbin.k, &dao_exec_circuit);
 
-        // Fetch our money Merkle tree
-        let tree = self.get_money_tree().await?;
-
         // Retrieve next block height and current block time target,
         // to compute their window.
         let next_block_height = self.get_next_block_height().await?;
@@ -3538,29 +3385,14 @@ impl Drk {
 
         // Create the TransactionBuilder containing above calls
         let mut tx_builder = TransactionBuilder::new(
-            ContractCallLeaf { call: exec_call, proofs: exec_proofs },
+            ContractCallLeaf::new(exec_call, exec_proofs, vec![exec_signature_secret]),
             vec![],
         )?;
 
-        // 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 mut tx = tx_builder.build()?;
-        let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
-        tx.signatures = vec![exec_sigs];
-
-        let (fee_call, fee_proofs, fee_secrets) =
-            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
-
-        // Append the fee call to the transaction
-        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-
-        // Now build the actual transaction and sign it with all necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[exec_signature_secret])?;
-        tx.signatures.push(sigs);
-        let sigs = tx.create_sigs(&fee_secrets)?;
-        tx.signatures.push(sigs);
+        // Add fee - marks tx_local output if needed and appends fee call
+        self.append_fee_call(&mut tx_builder).await?;
 
-        Ok(tx)
+        // Build and sign the transaction
+        tx_builder.build_signed()
     }
 }

+ 21 - 88
bin/drk/src/deploy.rs

@@ -23,8 +23,6 @@ use rand::rngs::OsRng;
 
 use darkfi::{
     tx::{ContractCallLeaf, Transaction, TransactionBuilder},
-    zk::{proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
-    zkas::ZkBinary,
     Error, Result,
 };
 use darkfi_deployooor_contract::{
@@ -32,11 +30,8 @@ use darkfi_deployooor_contract::{
     model::LockParamsV1,
     DeployFunction,
 };
-use darkfi_money_contract::MONEY_CONTRACT_ZKAS_FEE_NS_V1;
 use darkfi_sdk::{
-    crypto::{
-        ContractId, Keypair, PublicKey, SecretKey, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID,
-    },
+    crypto::{ContractId, Keypair, PublicKey, SecretKey, DEPLOYOOOR_CONTRACT_ID},
     deploy::DeployParamsV1,
     tx::TransactionHash,
     ContractCall,
@@ -545,23 +540,6 @@ impl Drk {
             return Err(Error::Custom("[deploy_contract] Contract is locked".to_string()))
         }
 
-        // 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("[deploy_contract] Fee circuit not found".to_string()))
-        };
-
-        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?;
-
-        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
-
-        // Creating Fee circuit proving keys
-        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
-
         // Create the contract call
         let deploy_call = DeployCallBuilder { deploy_keypair, wasm_bincode, deploy_ix };
         let deploy_debris = deploy_call.build()?;
@@ -572,30 +550,16 @@ impl Drk {
         let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
 
         // Create the TransactionBuilder containing above call
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
-
-        // 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 mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
-        tx.signatures.push(sigs);
-
-        let tree = self.get_money_tree().await?;
-        let (fee_call, fee_proofs, fee_secrets) =
-            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
-
-        // Append the fee call to the transaction
-        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-
-        // Now build the actual transaction and sign it with all necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
-        tx.signatures.push(sigs);
-        let sigs = tx.create_sigs(&fee_secrets)?;
-        tx.signatures.push(sigs);
-
-        Ok(tx)
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, vec![], vec![deploy_keypair.secret]),
+            vec![],
+        )?;
+
+        // Add fee - marks tx_local output if needed and appends fee call
+        self.append_fee_call(&mut tx_builder).await?;
+
+        // Build and sign the transaction
+        tx_builder.build_signed()
     }
 
     /// Create a feeless contract redeployment lock transaction.
@@ -608,23 +572,6 @@ impl Drk {
             return Err(Error::Custom("[lock_contract] Contract is already locked".to_string()))
         }
 
-        // 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("[lock_contract] Fee circuit not found".to_string()))
-        };
-
-        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?;
-
-        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
-
-        // Creating Fee circuit proving keys
-        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
-
         // Create the contract call
         let lock_call = LockCallBuilder { deploy_keypair };
         let lock_debris = lock_call.build()?;
@@ -635,29 +582,15 @@ impl Drk {
         let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
 
         // Create the TransactionBuilder containing above call
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
-
-        // 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 mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
-        tx.signatures.push(sigs);
-
-        let tree = self.get_money_tree().await?;
-        let (fee_call, fee_proofs, fee_secrets) =
-            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
-
-        // Append the fee call to the transaction
-        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-
-        // Now build the actual transaction and sign it with all necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
-        tx.signatures.push(sigs);
-        let sigs = tx.create_sigs(&fee_secrets)?;
-        tx.signatures.push(sigs);
-
-        Ok(tx)
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, vec![], vec![deploy_keypair.secret]),
+            vec![],
+        )?;
+
+        // Add fee - marks tx_local output if needed and appends fee call
+        self.append_fee_call(&mut tx_builder).await?;
+
+        // Build and sign the transaction
+        tx_builder.build_signed()
     }
 }

+ 11 - 39
bin/drk/src/interactive.rs

@@ -2418,57 +2418,29 @@ async fn handle_tx_from_calls(
     }
 
     // Create a transaction from the mapped calls
-    let (mut tx_builder, signature_secrets) = match tx_from_calls_mapped(&calls, &calls_map) {
-        Ok(pair) => pair,
+    let mut tx_builder = match tx_from_calls_mapped(&calls, &calls_map) {
+        Ok(builder) => builder,
         Err(e) => {
             output.push(format!("Failed to create a transaction from the mapped calls: {e}"));
             return
         }
     };
 
-    // Now build and sign the fee-less tx
-    let mut tx = match tx_builder.build() {
+    // Append the fee call
+    if let Err(e) = drk.read().await.append_fee_call(&mut tx_builder).await {
+        output.push(format!("Failed to append the fee call to the transaction: {e}"));
+        return
+    }
+
+    // Now build and sign the tx
+    let tx = match tx_builder.build_signed() {
         Ok(tx) => tx,
         Err(e) => {
-            output.push(format!("Failed to build the transaction: {e}"));
+            output.push(format!("Failed to build the signed transaction: {e}"));
             return
         }
     };
 
-    for secrets in &signature_secrets {
-        let sigs = match tx.create_sigs(secrets) {
-            Ok(s) => s,
-            Err(e) => {
-                output.push(format!("Failed to create the transaction signatures: {e}"));
-                return
-            }
-        };
-        tx.signatures.push(sigs);
-    }
-
-    // Attach its fee and grab its signature
-    if let Err(e) = drk.read().await.attach_fee(&mut tx).await {
-        output.push(format!("Failed to attach the fee call to the transaction: {e}"));
-        return
-    }
-    // Its safe to unwrap here since we know the fee signature
-    // is in the last position.
-    let fee_signature = tx.signatures.last().unwrap().clone();
-
-    // Re-sign the tx using the calls secrets
-    tx.signatures = vec![];
-    for secrets in &signature_secrets {
-        let sigs = match tx.create_sigs(secrets) {
-            Ok(s) => s,
-            Err(e) => {
-                output.push(format!("Failed to create the transaction signatures: {e}"));
-                return
-            }
-        };
-        tx.signatures.push(sigs);
-    }
-    tx.signatures.push(fee_signature);
-
     output.push(base64::encode(&serialize_async(&tx).await));
 }
 

+ 6 - 19
bin/drk/src/main.rs

@@ -2016,13 +2016,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
             }
 
             // Create a transaction from the mapped calls
-            let (mut tx_builder, signature_secrets) = tx_from_calls_mapped(&calls, &calls_map)?;
-
-            // Now build the fee-less tx
-            let mut tx = tx_builder.build()?;
-            for secrets in &signature_secrets {
-                tx.signatures.push(tx.create_sigs(secrets)?);
-            }
+            let mut tx_builder = tx_from_calls_mapped(&calls, &calls_map)?;
 
             // Attach its fee and grab its signature
             let drk = new_wallet(
@@ -2035,20 +2029,13 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 args.fun,
             )
             .await;
-            if let Err(e) = drk.attach_fee(&mut tx).await {
-                eprintln!("Failed to attach the fee call to the transaction: {e}");
+            if let Err(e) = drk.append_fee_call(&mut tx_builder).await {
+                eprintln!("Failed to append the fee call to the transaction: {e}");
                 exit(2);
             };
-            // Its safe to unwrap here since we know the fee signature
-            // is in the last position.
-            let fee_signature = tx.signatures.last().unwrap().clone();
-
-            // Re-sign the tx using the calls secrets
-            tx.signatures = vec![];
-            for secrets in &signature_secrets {
-                tx.signatures.push(tx.create_sigs(secrets)?);
-            }
-            tx.signatures.push(fee_signature);
+
+            // Build the signed transaction
+            let tx = tx_builder.build_signed()?;
 
             println!("{}", base64::encode(&serialize_async(&tx).await));
 

+ 91 - 69
bin/drk/src/money.rs

@@ -25,7 +25,7 @@ use lazy_static::lazy_static;
 use rand::rngs::OsRng;
 
 use darkfi::{
-    tx::Transaction,
+    tx::{ContractCallLeaf, Transaction, TransactionBuilder},
     util::encoding::base64,
     zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses, Proof},
     zkas::ZkBinary,
@@ -1276,36 +1276,27 @@ impl Drk {
         Ok(TokenId::from_str(input.as_str())?)
     }
 
-    /// Create and append a `Money::Fee` call to a given [`Transaction`].
-    ///
-    /// Optionally takes a set of spent coins in order not to reuse them here.
-    ///
-    /// Returns the `Fee` call, and all necessary data and parameters related.
-    pub async fn append_fee_call(
+    /// Generate fee call components for a transaction.
+    /// Returns (fee_call, fee_proofs, fee_secret) for the given required fee.
+    async fn generate_fee_call(
         &self,
-        tx: &Transaction,
+        required_fee: u64,
+        coin: &OwnCoin,
         money_merkle_tree: &MerkleTree,
-        fee_pk: &ProvingKey,
-        fee_zkbin: &ZkBinary,
-        spent_coins: Option<&[OwnCoin]>,
-    ) -> Result<(ContractCall, Vec<Proof>, Vec<SecretKey>)> {
-        // First we verify the fee-less transaction to see how much fee it requires for execution
-        // and verification.
-        let required_fee = compute_fee(&FEE_CALL_GAS) + self.get_tx_fee(tx, false).await?;
+        is_tx_local: bool,
+    ) -> Result<(ContractCall, Vec<Proof>, SecretKey)> {
+        let change_value = coin.note.value - required_fee;
 
-        // Knowing the total gas, we can now find an OwnCoin of enough value
-        // so that we can create a valid Money::Fee call.
-        let mut available_coins = self.get_token_coins(&DARK_TOKEN_ID).await?;
-        available_coins.retain(|x| x.note.value > required_fee);
-        if let Some(spent_coins) = spent_coins {
-            available_coins.retain(|x| !spent_coins.contains(x));
-        }
-        if available_coins.is_empty() {
-            return Err(Error::Custom("Not enough native tokens to pay for fees".to_string()))
-        }
+        // Build fee circuit
+        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 coin = &available_coins[0];
-        let change_value = coin.note.value - required_fee;
+        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?;
+        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
+        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
 
         // Input and output setup
         let input = FeeCallInput {
@@ -1333,9 +1324,9 @@ impl Drk {
         let signature_secret = SecretKey::random(&mut OsRng);
 
         // Create the actual fee proof
-        let (proof, public_inputs) = create_fee_proof(
-            fee_zkbin,
-            fee_pk,
+        let (fee_proof, public_inputs) = create_fee_proof(
+            &fee_zkbin,
+            &fee_pk,
             &input,
             input_value_blind,
             &output,
@@ -1369,7 +1360,7 @@ impl Drk {
                 merkle_root: public_inputs.merkle_root,
                 user_data_enc: public_inputs.input_user_data_enc,
                 signature_public: public_inputs.signature_public,
-                tx_local: false,
+                tx_local: is_tx_local,
             },
             output: Output {
                 value_commit: public_inputs.output_value_commit,
@@ -1386,68 +1377,99 @@ impl Drk {
         let mut data = vec![MoneyFunction::FeeV1 as u8];
         required_fee.encode_async(&mut data).await?;
         params.encode_async(&mut data).await?;
-        let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
+        let fee_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
 
-        Ok((call, vec![proof], vec![signature_secret]))
+        Ok((fee_call, vec![fee_proof], signature_secret))
     }
 
-    /// Create and attach the fee call to given transaction.
-    pub async fn attach_fee(&self, tx: &mut Transaction) -> Result<()> {
+    /// Create and append a `Money::Fee` call to a given [`TransactionBuilder`].
+    pub async fn append_fee_call(&self, tx_builder: &mut TransactionBuilder) -> Result<()> {
+        let tx = tx_builder.build_signed()?; // Need a signed tx for self.get_tx_fee() to work
+
+        // First we verify the fee-less transaction to see how much fee it requires for execution
+        // and verification.
+        let required_fee = compute_fee(&FEE_CALL_GAS) + self.get_tx_fee(&tx, false).await?;
+
         // Grab spent coins nullifiers of the transactions and check no other fee call exists
-        let mut tx_nullifiers = vec![];
+        let mut spent_nullifiers = vec![];
         for call in &tx.calls {
-            if call.data.contract_id != *MONEY_CONTRACT_ID {
-                continue
+            if call.data.is_money_fee() {
+                return Err(Error::Custom("Fee call already exists".to_string()))
             }
 
-            match MoneyFunction::try_from(call.data.data[0])? {
-                MoneyFunction::FeeV1 => {
-                    return Err(Error::Custom("Fee call already exists".to_string()))
+            if call.data.contract_id == *MONEY_CONTRACT_ID {
+                if let Ok(nullifiers) = self.money_call_nullifiers(call).await {
+                    spent_nullifiers.extend(nullifiers);
                 }
-                _ => { /* 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);
-            }
+        // Knowing the total gas, we can now find an OwnCoin of enough value
+        // so that we can create a valid Money::Fee call.
+        let mut available_coins = self.get_token_coins(&DARK_TOKEN_ID).await?;
+        available_coins
+            .retain(|x| x.note.value > required_fee && !spent_nullifiers.contains(&x.nullifier()));
+        if available_coins.is_empty() {
+            return Err(Error::Custom("Not enough native tokens to pay for fees".to_string()))
         }
 
-        // 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 money_merkle_tree = self.get_money_tree().await?;
+        let coin = &available_coins[0];
 
-        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()))
-        };
+        // Generate fee call data
+        let (fee_call, fee_proofs, signature_secret) =
+            self.generate_fee_call(required_fee, coin, &money_merkle_tree, false).await?;
 
-        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?;
+        // Append the fee call
+        tx_builder
+            .append(ContractCallLeaf::new(fee_call, fee_proofs, vec![signature_secret]), vec![])?;
 
-        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
+        Ok(())
+    }
 
-        // Creating Fee circuits proving keys
-        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
+    /// Create and attach the fee call to given transaction.
+    pub async fn attach_fee(&self, tx: &mut Transaction) -> Result<()> {
+        // Calculate required fee using the existing (signed) transaction
+        let required_fee = compute_fee(&FEE_CALL_GAS) + self.get_tx_fee(tx, false).await?;
+
+        // Grab spent coins nullifiers of the transactions and check no other fee call exists
+        let mut spent_nullifiers = vec![];
+        for call in &tx.calls {
+            if call.data.is_money_fee() {
+                return Err(Error::Custom("Fee call already exists".to_string()))
+            }
+
+            if call.data.contract_id == *MONEY_CONTRACT_ID {
+                if let Ok(nullifiers) = self.money_call_nullifiers(call).await {
+                    spent_nullifiers.extend(nullifiers);
+                }
+            }
+        }
+
+        // Knowing the total gas, we can now find an OwnCoin of enough value
+        // so that we can create a valid Money::Fee call.
+        let mut available_coins = self.get_token_coins(&DARK_TOKEN_ID).await?;
+        available_coins
+            .retain(|x| x.note.value > required_fee && !spent_nullifiers.contains(&x.nullifier()));
+        if available_coins.is_empty() {
+            return Err(Error::Custom("Not enough native tokens to pay for fees".to_string()))
+        }
+
+        let tree = self.get_money_tree().await?;
+        let coin = &available_coins[0];
 
         // 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 (fee_call, fee_proofs, fee_secrets) =
-            self.append_fee_call(tx, &tree, &fee_pk, &fee_zkbin, Some(&spent_coins)).await?;
+        let (fee_call, fee_proofs, signature_secret) =
+            self.generate_fee_call(required_fee, coin, &tree, false).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);
+
+        // Create signature for the fee call
+        let fee_sigs = tx.create_sigs(&[signature_secret])?;
+        tx.signatures.push(fee_sigs);
 
         Ok(())
     }

+ 5 - 7
bin/drk/src/swap.rs

@@ -275,13 +275,11 @@ impl Drk {
         let mut data = vec![MoneyFunction::TransferV1 as u8];
         full_params.encode_async(&mut data).await?;
         let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: full_proofs }, vec![])?;
-        let mut tx = tx_builder.build()?;
-
-        // Sign the transaction and return it
-        let sigs = tx.create_sigs(&[debris.signature_secret])?;
-        tx.signatures = vec![sigs];
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, full_proofs, vec![debris.signature_secret]),
+            vec![],
+        )?;
+        let tx = tx_builder.build_signed()?;
 
         Ok(tx)
     }

+ 13 - 65
bin/drk/src/token.rs

@@ -32,8 +32,7 @@ use darkfi_money_contract::{
     },
     model::{CoinAttributes, TokenAttributes, TokenId},
     MoneyFunction, MONEY_CONTRACT_ZKAS_AUTH_TOKEN_FREEZE_NS_V1,
-    MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
-    MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
+    MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
 };
 use darkfi_sdk::{
     crypto::{
@@ -302,24 +301,16 @@ impl Drk {
             return Err(Error::Custom("Auth token mint circuit not found".to_string()))
         };
 
-        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 mint_zkbin = ZkBinary::decode(&mint_zkbin.1, false)?;
         let auth_mint_zkbin = ZkBinary::decode(&auth_mint_zkbin.1, false)?;
-        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?;
 
         let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
         let auth_mint_circuit =
             ZkCircuit::new(empty_witnesses(&auth_mint_zkbin)?, &auth_mint_zkbin);
-        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
 
         // Creating TokenMint, AuthTokenMint and Fee circuits proving keys
         let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
         let auth_mint_pk = ProvingKey::build(auth_mint_zkbin.k, &auth_mint_circuit);
-        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
 
         // Build the coin attributes
         let coin_attrs = CoinAttributes {
@@ -353,39 +344,20 @@ impl Drk {
 
         // Create the TransactionBuilder containing above calls
         let mut tx_builder = TransactionBuilder::new(
-            ContractCallLeaf { call: mint_call, proofs: mint_debris.proofs },
+            ContractCallLeaf::new(mint_call, mint_debris.proofs, vec![]),
             vec![DarkTree::new(
-                ContractCallLeaf { call: auth_call, proofs: auth_debris.proofs },
+                ContractCallLeaf::new(auth_call, auth_debris.proofs, vec![mint_authority.secret]),
                 vec![],
                 None,
                 None,
             )],
         )?;
 
-        // 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 mut tx = tx_builder.build()?;
-        let auth_sigs = tx.create_sigs(&[mint_authority.secret])?;
-        let mint_sigs = tx.create_sigs(&[])?;
-        tx.signatures = vec![auth_sigs, mint_sigs];
-
-        let tree = self.get_money_tree().await?;
-        let (fee_call, fee_proofs, fee_secrets) =
-            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
-
-        // Append the fee call to the transaction
-        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-
-        // Now build the actual transaction and sign it with all necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[mint_authority.secret])?;
-        tx.signatures.push(sigs);
-        let sigs = tx.create_sigs(&[])?;
-        tx.signatures.push(sigs);
-        let sigs = tx.create_sigs(&fee_secrets)?;
-        tx.signatures.push(sigs);
-
-        Ok(tx)
+        // Add fee - marks tx_local output if needed and appends fee call
+        self.append_fee_call(&mut tx_builder).await?;
+
+        // Build and sign the transaction
+        tx_builder.build_signed()
     }
 
     /// Create a token freeze transaction. Returns the transaction object on success.
@@ -410,21 +382,13 @@ impl Drk {
             return Err(Error::Custom("Auth token freeze circuit not found".to_string()))
         };
 
-        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 auth_freeze_zkbin = ZkBinary::decode(&auth_freeze_zkbin.1, false)?;
-        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?;
 
         let auth_freeze_circuit =
             ZkCircuit::new(empty_witnesses(&auth_freeze_zkbin)?, &auth_freeze_zkbin);
-        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
 
         // Creating AuthTokenFreeze and Fee circuits proving keys
         let auth_freeze_pk = ProvingKey::build(auth_freeze_zkbin.k, &auth_freeze_circuit);
-        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
 
         // Create the freeze call
         let builder = AuthTokenFreezeCallBuilder {
@@ -440,30 +404,14 @@ impl Drk {
 
         // Create the TransactionBuilder containing above call
         let mut tx_builder = TransactionBuilder::new(
-            ContractCallLeaf { call: freeze_call, proofs: freeze_debris.proofs },
+            ContractCallLeaf::new(freeze_call, freeze_debris.proofs, vec![mint_authority.secret]),
             vec![],
         )?;
 
-        // 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 mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[mint_authority.secret])?;
-        tx.signatures.push(sigs);
-
-        let tree = self.get_money_tree().await?;
-        let (fee_call, fee_proofs, fee_secrets) =
-            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
-
-        // Append the fee call to the transaction
-        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-
-        // Now build the actual transaction and sign it with all necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[mint_authority.secret])?;
-        tx.signatures.push(sigs);
-        let sigs = tx.create_sigs(&fee_secrets)?;
-        tx.signatures.push(sigs);
+        // Add fee - marks tx_local output if needed and appends fee call
+        self.append_fee_call(&mut tx_builder).await?;
 
-        Ok(tx)
+        // Build and sign the transaction
+        tx_builder.build_signed()
     }
 }

+ 12 - 38
bin/drk/src/transfer.rs

@@ -25,7 +25,7 @@ use darkfi::{
 };
 use darkfi_money_contract::{
     client::transfer_v1::make_transfer_call, model::TokenId, MoneyFunction,
-    MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_FEE_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+    MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
 };
 use darkfi_sdk::{
     crypto::{contract_id::MONEY_CONTRACT_ID, FuncId, Keypair, PublicKey},
@@ -90,26 +90,18 @@ impl Drk {
             return Err(Error::Custom("Burn circuit not found".to_string()))
         };
 
-        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 mint_zkbin = ZkBinary::decode(&mint_zkbin.1, false)?;
         let burn_zkbin = ZkBinary::decode(&burn_zkbin.1, false)?;
-        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1, false)?;
 
         let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
         let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin)?, &burn_zkbin);
-        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
 
         // Creating Mint, Burn and Fee circuits proving keys
         let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
         let burn_pk = ProvingKey::build(burn_zkbin.k, &burn_circuit);
-        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
 
         // Building transaction parameters
-        let (params, secrets, spent_coins) = make_transfer_call(
+        let (params, secrets, _) = make_transfer_call(
             keypair,
             recipient,
             amount,
@@ -131,33 +123,15 @@ impl Drk {
         let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
 
         // Create the TransactionBuilder containing the `Transfer` call
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: secrets.proofs }, vec![])?;
-
-        // We first have to execute the fee-less tx to gather its used gas, and then we feed
-        // it into the fee-creating function.
-        // We also tell it about any spent coins so we don't accidentally reuse them in the
-        // fee call.
-        // TODO: We have to build a proper coin selection algorithm so that we can utilize
-        // the Money::Transfer to merge any coins which would give us a coin with enough
-        // value for paying the transaction fee.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&secrets.signature_secrets)?;
-        tx.signatures.push(sigs);
-
-        let (fee_call, fee_proofs, fee_secrets) =
-            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, Some(&spent_coins)).await?;
-
-        // Append the fee call to the transaction
-        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-
-        // Now build the actual transaction and sign it with all necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&secrets.signature_secrets)?;
-        tx.signatures.push(sigs);
-        let sigs = tx.create_sigs(&fee_secrets)?;
-        tx.signatures.push(sigs);
-
-        Ok(tx)
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, secrets.proofs, secrets.signature_secrets),
+            vec![],
+        )?;
+
+        // Add fee - marks tx_local output if needed and appends fee call
+        self.append_fee_call(&mut tx_builder).await?;
+
+        // Build and sign the transaction
+        tx_builder.build_signed()
     }
 }

+ 2 - 4
script/research/gg/src/main.rs

@@ -301,12 +301,10 @@ fn main() -> Result<()> {
                 debris.params.encode_async(&mut data).await?;
                 let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
                 let mut tx_builder = TransactionBuilder::new(
-                    ContractCallLeaf { call, proofs: debris.proofs },
+                    ContractCallLeaf::new(call, debris.proofs, vec![signature_secret]),
                     vec![],
                 )?;
-                let mut tx = tx_builder.build()?;
-                let sigs = tx.create_sigs(&[signature_secret])?;
-                tx.signatures = vec![sigs];
+                let tx = tx_builder.build_signed()?;
 
                 println!("{}", base64::encode(&serialize_async(&tx).await));
             }

+ 9 - 12
src/contract/money/tests/dep8.rs

@@ -157,12 +157,12 @@ fn dep8() -> Result<()> {
         let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
 
         // Create the TransactionBuilder containing the Transfer call.
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: secrets.proofs }, vec![])?;
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, secrets.proofs, secrets.signature_secrets),
+            vec![],
+        )?;
 
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&secrets.signature_secrets)?;
-        tx.signatures = vec![sigs];
+        let tx = tx_builder.build_signed()?;
 
         // First we verify the fee-less transaction to see how much gas it
         // uses for execution and verification.
@@ -248,7 +248,7 @@ fn dep8() -> Result<()> {
                 merkle_root: public_inputs.merkle_root,
                 user_data_enc: public_inputs.input_user_data_enc,
                 signature_public: public_inputs.signature_public,
-                // Here we mark the Input tx-local since the Ouput it
+                // Here we mark the Input tx-local since the Output it
                 // comes from (the previous Transfer call) creates it.
                 tx_local: true,
             },
@@ -272,15 +272,12 @@ fn dep8() -> Result<()> {
         let fee_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
 
         // Append the fee call to the transaction
-        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: vec![proof] }, vec![])?;
+        tx_builder
+            .append(ContractCallLeaf::new(fee_call, vec![proof], vec![signature_secret]), vec![])?;
         let alice_fee_params = Some(fee_call_params);
 
         // Now build the actual transaction and sign it with all necessary keys
-        let mut alice_tx = tx_builder.build()?;
-        let sigs = alice_tx.create_sigs(&secrets.signature_secrets)?;
-        alice_tx.signatures = vec![sigs];
-        let sigs = alice_tx.create_sigs(&[signature_secret])?;
-        alice_tx.signatures.push(sigs);
+        let alice_tx = tx_builder.build_signed()?;
 
         th.execute_transfer_tx(
             &Alice,

+ 7 - 20
src/contract/test-harness/src/contract_deploy.rs

@@ -50,34 +50,21 @@ impl TestHarness {
         let mut data = vec![DeployFunction::DeployV1 as u8];
         debris.params.encode(&mut data)?;
         let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, vec![], vec![deploy_keypair.secret]),
+            vec![],
+        )?;
 
         // If we have tx fees enabled, make an offering
         let mut fee_params = None;
-        let mut fee_signature_secrets = None;
         if self.verify_fees {
-            let mut tx = tx_builder.build()?;
-            let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
-            tx.signatures = vec![sigs];
-
-            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
-                self.append_fee_call(holder, tx, block_height, &[]).await?;
-
-            // Append the fee call to the transaction
-            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-            fee_signature_secrets = Some(fee_secrets);
+            let (_fee_call, _spent_fee_coins, fee_call_params) =
+                self.append_fee_call(holder, &mut tx_builder, block_height).await?;
             fee_params = Some(fee_call_params);
         }
 
         // Now build the actual transaction and sign it with necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
-        tx.signatures = vec![sigs];
-        if let Some(fee_signature_secrets) = fee_signature_secrets {
-            let sigs = tx.create_sigs(&fee_signature_secrets)?;
-            tx.signatures.push(sigs);
-        }
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, debris.params, fee_params))
     }

+ 7 - 20
src/contract/test-harness/src/contract_lock.rs

@@ -49,34 +49,21 @@ impl TestHarness {
         let mut data = vec![DeployFunction::LockV1 as u8];
         debris.params.encode(&mut data)?;
         let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, vec![], vec![deploy_keypair.secret]),
+            vec![],
+        )?;
 
         // If we have tx fees enabled, make an offering
         let mut fee_params = None;
-        let mut fee_signature_secrets = None;
         if self.verify_fees {
-            let mut tx = tx_builder.build()?;
-            let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
-            tx.signatures = vec![sigs];
-
-            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
-                self.append_fee_call(holder, tx, block_height, &[]).await?;
-
-            // Append the fee call to the transaction
-            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-            fee_signature_secrets = Some(fee_secrets);
+            let (_fee_call, _spent_fee_coins, fee_call_params) =
+                self.append_fee_call(holder, &mut tx_builder, block_height).await?;
             fee_params = Some(fee_call_params);
         }
 
         // Now build the actual transaction and sign it with necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
-        tx.signatures = vec![sigs];
-        if let Some(fee_signature_secrets) = fee_signature_secrets {
-            let sigs = tx.create_sigs(&fee_signature_secrets)?;
-            tx.signatures.push(sigs);
-        }
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, debris.params, fee_params))
     }

+ 14 - 46
src/contract/test-harness/src/dao_exec.rs

@@ -200,16 +200,20 @@ impl TestHarness {
         //
 
         let mut tx_builder = TransactionBuilder::new(
-            ContractCallLeaf { call: exec_call, proofs: exec_proofs },
+            ContractCallLeaf::new(exec_call, exec_proofs, vec![exec_signature_secret]),
             vec![
                 DarkTree::new(
-                    ContractCallLeaf { call: auth_xfer_call, proofs: auth_xfer_proofs },
+                    ContractCallLeaf::new(auth_xfer_call, auth_xfer_proofs, vec![]),
                     vec![],
                     None,
                     None,
                 ),
                 DarkTree::new(
-                    ContractCallLeaf { call: xfer_call, proofs: xfer_secrets.proofs },
+                    ContractCallLeaf::new(
+                        xfer_call,
+                        xfer_secrets.proofs,
+                        xfer_secrets.signature_secrets,
+                    ),
                     vec![],
                     None,
                     None,
@@ -219,34 +223,14 @@ impl TestHarness {
 
         // If fees are enabled, make an offering
         let mut fee_params = None;
-        let mut fee_signature_secrets = None;
         if self.verify_fees {
-            let mut tx = tx_builder.build()?;
-            let auth_xfer_sigs = vec![];
-            let xfer_sigs = tx.create_sigs(&xfer_secrets.signature_secrets)?;
-            let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
-            tx.signatures = vec![auth_xfer_sigs, xfer_sigs, exec_sigs];
-
-            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
-                self.append_fee_call(holder, tx, block_height, &[]).await?;
-
-            // Append the fee call to the transaction
-            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-            fee_signature_secrets = Some(fee_secrets);
+            let (_fee_call, _spent_fee_coins, fee_call_params) =
+                self.append_fee_call(holder, &mut tx_builder, block_height).await?;
             fee_params = Some(fee_call_params);
         }
 
         // Now build the actual transaction and sign it with necessary keys.
-        let mut tx = tx_builder.build()?;
-        let auth_xfer_sigs = vec![];
-        let xfer_sigs = tx.create_sigs(&xfer_secrets.signature_secrets)?;
-        let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
-        tx.signatures = vec![auth_xfer_sigs, xfer_sigs, exec_sigs];
-
-        if let Some(fee_signature_secrets) = fee_signature_secrets {
-            let sigs = tx.create_sigs(&fee_signature_secrets)?;
-            tx.signatures.push(sigs);
-        }
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, xfer_params, fee_params))
     }
@@ -301,36 +285,20 @@ impl TestHarness {
 
         // Create the TransactionBuilder containing the `DAO::Exec` call
         let mut tx_builder = TransactionBuilder::new(
-            ContractCallLeaf { call: exec_call, proofs: exec_proofs },
+            ContractCallLeaf::new(exec_call, exec_proofs, vec![exec_signature_secret]),
             vec![],
         )?;
 
         // If fees are enabled, make an offering
         let mut fee_params = None;
-        let mut fee_signature_secrets = None;
         if self.verify_fees {
-            let mut tx = tx_builder.build()?;
-            let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
-            tx.signatures = vec![exec_sigs];
-
-            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
-                self.append_fee_call(holder, tx, block_height, &[]).await?;
-
-            // Append the fee call to the transaction
-            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-            fee_signature_secrets = Some(fee_secrets);
+            let (_fee_call, _spent_fee_coins, fee_call_params) =
+                self.append_fee_call(holder, &mut tx_builder, block_height).await?;
             fee_params = Some(fee_call_params);
         }
 
         // Now build the actual transaction and sign it with necessary keys.
-        let mut tx = tx_builder.build()?;
-        let exec_sigs = tx.create_sigs(&[exec_signature_secret])?;
-        tx.signatures = vec![exec_sigs];
-
-        if let Some(fee_signature_secrets) = fee_signature_secrets {
-            let sigs = tx.create_sigs(&fee_signature_secrets)?;
-            tx.signatures.push(sigs);
-        }
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, fee_params))
     }

+ 7 - 19
src/contract/test-harness/src/dao_mint.rs

@@ -72,33 +72,21 @@ impl TestHarness {
         let mut data = vec![DaoFunction::Mint as u8];
         params.encode(&mut data)?;
         let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
-        let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, proofs, vec![*dao_notes_secret_key]),
+            vec![],
+        )?;
 
         // If fees are enabled, make an offering
         let mut fee_params = None;
-        let mut fee_signature_secrets = None;
         if self.verify_fees {
-            let mut tx = tx_builder.build()?;
-            let sigs = tx.create_sigs(&[*dao_notes_secret_key])?;
-            tx.signatures = vec![sigs];
-
-            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
-                self.append_fee_call(holder, tx, block_height, &[]).await?;
-
-            // Append the fee call to the transaction
-            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-            fee_signature_secrets = Some(fee_secrets);
+            let (_fee_call, _spent_fee_coins, fee_call_params) =
+                self.append_fee_call(holder, &mut tx_builder, block_height).await?;
             fee_params = Some(fee_call_params);
         }
 
         // Now build the actual transaction and sign it with necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[*dao_notes_secret_key])?;
-        tx.signatures = vec![sigs];
-        if let Some(fee_signature_secrets) = fee_signature_secrets {
-            let sigs = tx.create_sigs(&fee_signature_secrets)?;
-            tx.signatures.push(sigs);
-        }
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, params, fee_params))
     }

+ 14 - 38
src/contract/test-harness/src/dao_propose.rs

@@ -157,33 +157,21 @@ impl TestHarness {
         let mut data = vec![DaoFunction::Propose as u8];
         params.encode(&mut data)?;
         let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
-        let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, proofs, signature_secrets),
+            vec![],
+        )?;
 
         // If fees are enabled, make an offering
         let mut fee_params = None;
-        let mut fee_signature_secrets = None;
         if self.verify_fees {
-            let mut tx = tx_builder.build()?;
-            let sigs = tx.create_sigs(&signature_secrets)?;
-            tx.signatures.push(sigs);
-
-            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
-                self.append_fee_call(proposer, tx, block_height, &[]).await?;
-
-            // Append the fee call to the transaction
-            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-            fee_signature_secrets = Some(fee_secrets);
+            let (_fee_call, _spent_fee_coins, fee_call_params) =
+                self.append_fee_call(proposer, &mut tx_builder, block_height).await?;
             fee_params = Some(fee_call_params);
         }
 
         // Now build the actual transaction and sign it with necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&signature_secrets)?;
-        tx.signatures.push(sigs);
-        if let Some(fee_signature_secrets) = fee_signature_secrets {
-            let sigs = tx.create_sigs(&fee_signature_secrets)?;
-            tx.signatures.push(sigs);
-        }
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, params, fee_params, proposal))
     }
@@ -262,33 +250,21 @@ impl TestHarness {
         let mut data = vec![DaoFunction::Propose as u8];
         params.encode(&mut data)?;
         let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
-        let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, proofs, signature_secrets),
+            vec![],
+        )?;
 
         // If fees are enabled, make an offering
         let mut fee_params = None;
-        let mut fee_signature_secrets = None;
         if self.verify_fees {
-            let mut tx = tx_builder.build()?;
-            let sigs = tx.create_sigs(&signature_secrets)?;
-            tx.signatures.push(sigs);
-
-            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
-                self.append_fee_call(proposer, tx, block_height, &[]).await?;
-
-            // Append the fee call to the transaction
-            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-            fee_signature_secrets = Some(fee_secrets);
+            let (_fee_call, _spent_fee_coins, fee_call_params) =
+                self.append_fee_call(proposer, &mut tx_builder, block_height).await?;
             fee_params = Some(fee_call_params);
         }
 
         // Now build the actual transaction and sign it with necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&signature_secrets)?;
-        tx.signatures.push(sigs);
-        if let Some(fee_signature_secrets) = fee_signature_secrets {
-            let sigs = tx.create_sigs(&fee_signature_secrets)?;
-            tx.signatures.push(sigs);
-        }
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, params, fee_params, proposal))
     }

+ 7 - 19
src/contract/test-harness/src/dao_vote.rs

@@ -89,33 +89,21 @@ impl TestHarness {
         let mut data = vec![DaoFunction::Vote as u8];
         params.encode(&mut data)?;
         let call = ContractCall { contract_id: *DAO_CONTRACT_ID, data };
-        let mut tx_builder = TransactionBuilder::new(ContractCallLeaf { call, proofs }, vec![])?;
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, proofs, signature_secrets),
+            vec![],
+        )?;
 
         // If fees are enabled, make an offering
         let mut fee_params = None;
-        let mut fee_signature_secrets = None;
         if self.verify_fees {
-            let mut tx = tx_builder.build()?;
-            let sigs = tx.create_sigs(&signature_secrets)?;
-            tx.signatures.push(sigs);
-
-            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
-                self.append_fee_call(voter, tx, block_height, &[]).await?;
-
-            // Append the fee call to the transaction
-            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-            fee_signature_secrets = Some(fee_secrets);
+            let (_fee_call, _spent_fee_coins, fee_call_params) =
+                self.append_fee_call(voter, &mut tx_builder, block_height).await?;
             fee_params = Some(fee_call_params);
         }
 
         // Now build the actual transaction and sign it with necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&signature_secrets)?;
-        tx.signatures.push(sigs);
-        if let Some(fee_signature_secrets) = fee_signature_secrets {
-            let sigs = tx.create_sigs(&fee_signature_secrets)?;
-            tx.signatures.push(sigs);
-        }
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, params, fee_params))
     }

+ 30 - 3
src/contract/test-harness/src/lib.rs

@@ -43,8 +43,8 @@ use darkfi_dao_contract::model::{Dao, DaoBulla, DaoProposal, DaoProposalBulla, D
 use darkfi_money_contract::{
     client::{MoneyNote, OwnCoin},
     model::{
-        CoinAttributes, Input, MoneyFeeParamsV1, MoneyGenesisMintParamsV1, Nullifier, Output,
-        TokenAttributes, TokenId,
+        CoinAttributes, Input, MoneyBurnParamsV1, MoneyFeeParamsV1, MoneyGenesisMintParamsV1,
+        MoneyTransferParamsV1, Nullifier, Output, TokenAttributes, TokenId,
     },
     MoneyFunction,
 };
@@ -57,8 +57,9 @@ use darkfi_sdk::{
         BaseBlind, FuncRef, Keypair, MerkleNode, MerkleTree, ScalarBlind, SecretKey,
     },
     pasta::pallas,
+    ContractCall,
 };
-use darkfi_serial::{serialize, Encodable};
+use darkfi_serial::{deserialize_async, serialize, Encodable};
 use kvdb_overlay::{Database, TempDir};
 use num_bigint::BigUint;
 use parking_lot::Mutex;
@@ -907,6 +908,32 @@ impl TestHarness {
 
         token_attrs.to_token_id()
     }
+
+    /// Extract nullifiers from a Money contract call.
+    pub async fn money_call_nullifiers(&self, call: &ContractCall) -> Result<Vec<Nullifier>> {
+        let mut nullifiers: Vec<Nullifier> = vec![];
+        let data = &call.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::BurnV1 => {
+                let params: MoneyBurnParamsV1 = deserialize_async(&data[1..]).await?;
+                for input in params.inputs {
+                    nullifiers.push(input.nullifier);
+                }
+            }
+            _ => { /* Do nothing */ }
+        }
+        Ok(nullifiers)
+    }
 }
 
 async fn benchmark_wasm_calls(

+ 7 - 19
src/contract/test-harness/src/money_burn.rs

@@ -54,34 +54,22 @@ impl TestHarness {
         params.encode(&mut data)?;
         let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
 
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: secrets.proofs }, vec![])?;
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, secrets.proofs, secrets.signature_secrets),
+            vec![],
+        )?;
 
         // Optional fees, if enabled
         let mut fee_params = None;
-        let mut fee_signature_secrets = None;
         if self.verify_fees {
-            let mut tx = tx_builder.build()?;
-            let sigs = tx.create_sigs(&secrets.signature_secrets)?;
-            tx.signatures = vec![sigs];
-
-            let (fee_call, fee_proofs, fee_secrets, spent_fee_coins, fee_call_params) =
-                self.append_fee_call(holder, tx, block_height, &spent_coins).await?;
-
-            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-            fee_signature_secrets = Some(fee_secrets);
+            let (_fee_call, spent_fee_coins, fee_call_params) =
+                self.append_fee_call(holder, &mut tx_builder, block_height).await?;
             spent_coins.extend_from_slice(&spent_fee_coins);
             fee_params = Some(fee_call_params);
         }
 
         // Now build the actual transaction and sign it with all necessary keys
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&secrets.signature_secrets)?;
-        tx.signatures = vec![sigs];
-        if let Some(fee_signature_secrets) = fee_signature_secrets {
-            let sigs = tx.create_sigs(&fee_signature_secrets)?;
-            tx.signatures.push(sigs);
-        }
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, (params, fee_params), spent_coins))
     }

+ 45 - 28
src/contract/test-harness/src/money_fee.rs

@@ -16,12 +16,12 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{collections::HashSet, hash::RandomState};
+use std::slice;
 
 use darkfi::{
     tx::{ContractCallLeaf, Transaction, TransactionBuilder},
-    zk::{halo2::Field, Proof},
-    Result,
+    zk::halo2::Field,
+    Error, Result,
 };
 use darkfi_money_contract::{
     client::{
@@ -148,11 +148,11 @@ impl TestHarness {
         required_fee.encode(&mut data)?;
         params.encode(&mut data)?;
         let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![proof] }, vec![])?;
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[signature_secret])?;
-        tx.signatures = vec![sigs];
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, vec![proof], vec![signature_secret]),
+            vec![],
+        )?;
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, params))
     }
@@ -172,26 +172,24 @@ impl TestHarness {
         Ok(wallet.process_fee(&Some(params.clone()), holder))
     }
 
-    /// Create and append a `Money::Fee` call to a given [`Transaction`] for
-    /// a given [`Holder`].
-    ///
-    /// Additionally takes a set of spent coins in order not to reuse them here.
+    /// Updates a [`TransactionBuilder`] with fee payment for a given [`Holder`].
     ///
-    /// Returns the `Fee` call, and all necessary data and parameters related.
+    /// Returns the call, spent coins, and fee params.
     pub async fn append_fee_call(
         &mut self,
         holder: &Holder,
-        tx: Transaction,
+        tx_builder: &mut TransactionBuilder,
         block_height: u32,
-        spent_coins: &[OwnCoin],
-    ) -> Result<(ContractCall, Vec<Proof>, Vec<SecretKey>, Vec<OwnCoin>, MoneyFeeParamsV1)> {
+    ) -> Result<(ContractCall, Vec<OwnCoin>, MoneyFeeParamsV1)> {
         // First we verify the fee-less transaction to see how much gas it
         // uses for execution and verification.
         let wallet = self.wallet(holder);
         let validator = wallet.validator.read().await;
+        let tx = tx_builder.build_signed()?;
+
         let gas_used = validator
             .add_test_transactions(
-                &[tx],
+                slice::from_ref(&tx),
                 block_height,
                 validator.consensus.module.target,
                 false,
@@ -203,22 +201,35 @@ impl TestHarness {
         // Compute the required fee
         let required_fee = compute_fee(&(gas_used + FEE_CALL_GAS));
 
-        // Knowing the total gas, we can now find an OwnCoin of enough
-        // value so that we can create a valid Money::Fee call.
-        let spent_coins: HashSet<&OwnCoin, RandomState> = HashSet::from_iter(spent_coins);
-        let mut available_coins = wallet.unspent_money_coins.clone();
-        available_coins
-            .retain(|x| x.note.token_id == *DARK_TOKEN_ID && x.note.value > required_fee);
-        available_coins.retain(|x| !spent_coins.contains(x));
-        assert!(!available_coins.is_empty());
+        // Collect spent nullifiers from money calls
+        let mut spent_nullifiers = vec![];
+        for call in &tx.calls {
+            if call.data.contract_id == *MONEY_CONTRACT_ID {
+                if let Ok(nullifiers) = self.money_call_nullifiers(&call.data).await {
+                    spent_nullifiers.extend(nullifiers);
+                }
+            }
+        }
+
+        // Find a suitable coin from wallet
+        let mut coins = wallet.unspent_money_coins.clone();
+        coins.retain(|x| {
+            x.note.token_id == *DARK_TOKEN_ID &&
+                x.note.value > required_fee &&
+                !spent_nullifiers.contains(&x.nullifier())
+        });
+        if coins.is_empty() {
+            return Err(Error::Custom("Not enough native tokens".to_string()));
+        }
+        let coin = &coins[0];
+        let merkle_tree = &wallet.money_merkle_tree;
 
-        let coin = &available_coins[0];
         let change_value = coin.note.value - required_fee;
 
         // Input and output setup
         let input = FeeCallInput {
             coin: coin.clone(),
-            merkle_path: wallet.money_merkle_tree.witness(coin.leaf_position, 0).unwrap(),
+            merkle_path: merkle_tree.witness(coin.leaf_position, 0).unwrap(),
             user_data_blind: BaseBlind::random(&mut OsRng),
         };
 
@@ -298,6 +309,12 @@ impl TestHarness {
         params.encode(&mut data)?;
         let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
 
-        Ok((call, vec![proof], vec![signature_secret], vec![coin.clone()], params))
+        // Append the fee call to the transaction builder
+        tx_builder.append(
+            ContractCallLeaf::new(call.clone(), vec![proof], vec![signature_secret]),
+            vec![],
+        )?;
+
+        Ok((call, vec![coin.clone()], params))
     }
 }

+ 5 - 5
src/contract/test-harness/src/money_genesis_mint.rs

@@ -66,11 +66,11 @@ impl TestHarness {
         let mut data = vec![MoneyFunction::GenesisMintV1 as u8];
         debris.params.encode(&mut data)?;
         let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[wallet.keypair.secret])?;
-        tx.signatures = vec![sigs];
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, debris.proofs, vec![wallet.keypair.secret]),
+            vec![],
+        )?;
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, debris.params))
     }

+ 7 - 27
src/contract/test-harness/src/money_otc_swap.rs

@@ -16,8 +16,6 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::slice;
-
 use darkfi::{
     tx::{ContractCallLeaf, Transaction, TransactionBuilder},
     zk::halo2::Field,
@@ -132,43 +130,25 @@ impl TestHarness {
         let mut data = vec![MoneyFunction::TransferV1 as u8];
         swap_full_params.encode(&mut data)?;
         let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: swap_full_proofs }, vec![])?;
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, swap_full_proofs, vec![debris1.signature_secret]),
+            vec![],
+        )?;
 
         // If we have tx fees enabled, make an offering
         let mut fee_params = None;
-        let mut fee_signature_secrets = None;
         if self.verify_fees {
-            let mut tx = tx_builder.build()?;
-            let sigs = tx.create_sigs(&[debris1.signature_secret])?;
-            tx.signatures = vec![sigs];
-
-            // First holder gets the partially signed transaction and adds their signature
-            let sigs = tx.create_sigs(&[debris0.signature_secret])?;
-            tx.signatures[0].insert(0, sigs[0]);
-
-            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
-                self.append_fee_call(holder0, tx, block_height, slice::from_ref(owncoin0)).await?;
-
-            // Append the fee call to the transaction
-            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-            fee_signature_secrets = Some(fee_secrets);
+            let (_fee_call, _spent_fee_coins, fee_call_params) =
+                self.append_fee_call(holder0, &mut tx_builder, block_height).await?;
             fee_params = Some(fee_call_params);
         }
 
         // Now build the actual transaction and sign it with necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[debris1.signature_secret])?;
-        tx.signatures = vec![sigs];
+        let mut tx = tx_builder.build_signed()?;
         // First holder gets the partially signed transaction and adds their signature
         let sigs = tx.create_sigs(&[debris0.signature_secret])?;
         tx.signatures[0].insert(0, sigs[0]);
 
-        if let Some(fee_signature_secrets) = fee_signature_secrets {
-            let sigs = tx.create_sigs(&fee_signature_secrets)?;
-            tx.signatures.push(sigs);
-        }
-
         Ok((tx, swap_full_params, fee_params))
     }
 

+ 5 - 5
src/contract/test-harness/src/money_pow_reward.rs

@@ -85,11 +85,11 @@ impl TestHarness {
         let mut data = vec![MoneyFunction::PoWRewardV1 as u8];
         debris.params.encode(&mut data)?;
         let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&[wallet.keypair.secret])?;
-        tx.signatures = vec![sigs];
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, debris.proofs, vec![wallet.keypair.secret]),
+            vec![],
+        )?;
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, debris.params))
     }

+ 9 - 40
src/contract/test-harness/src/money_token.rs

@@ -127,9 +127,9 @@ impl TestHarness {
 
         // Create the TransactionBuilder containing above calls
         let mut tx_builder = TransactionBuilder::new(
-            ContractCallLeaf { call: mint_call, proofs: mint_debris.proofs },
+            ContractCallLeaf::new(mint_call, mint_debris.proofs, vec![]),
             vec![DarkTree::new(
-                ContractCallLeaf { call: auth_call, proofs: auth_debris.proofs },
+                ContractCallLeaf::new(auth_call, auth_debris.proofs, vec![mint_authority.secret]),
                 vec![],
                 None,
                 None,
@@ -138,31 +138,14 @@ impl TestHarness {
 
         // If we have tx fees enabled, make an offering
         let mut fee_params = None;
-        let mut fee_signature_secrets = None;
         if self.verify_fees {
-            let mut tx = tx_builder.build()?;
-            let auth_sigs = tx.create_sigs(&[mint_authority.secret])?;
-            let mint_sigs = tx.create_sigs(&[])?;
-            tx.signatures = vec![auth_sigs, mint_sigs];
-
-            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
-                self.append_fee_call(holder, tx, block_height, &[]).await?;
-
-            // Append the fee call to the transaction
-            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-            fee_signature_secrets = Some(fee_secrets);
+            let (_fee_call, _spent_fee_coins, fee_call_params) =
+                self.append_fee_call(holder, &mut tx_builder, block_height).await?;
             fee_params = Some(fee_call_params);
         }
 
         // Now build the actual transaction and sign it with necessary keys.
-        let mut tx = tx_builder.build()?;
-        let auth_sigs = tx.create_sigs(&[mint_authority.secret])?;
-        let mint_sigs = tx.create_sigs(&[])?;
-        tx.signatures = vec![auth_sigs, mint_sigs];
-        if let Some(fee_signature_secrets) = fee_signature_secrets {
-            let sigs = tx.create_sigs(&fee_signature_secrets)?;
-            tx.signatures.push(sigs);
-        }
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, mint_debris.params, auth_debris.params, fee_params))
     }
@@ -249,35 +232,21 @@ impl TestHarness {
 
         // Create the TransactionBuilder containing the above call
         let mut tx_builder = TransactionBuilder::new(
-            ContractCallLeaf { call: freeze_call, proofs: freeze_debris.proofs },
+            ContractCallLeaf::new(freeze_call, freeze_debris.proofs, vec![mint_authority.secret]),
             vec![],
         )?;
 
         // If we have tx fees enabled, make an offering
         let mut fee_params = None;
-        let mut fee_signature_secrets = None;
         if self.verify_fees {
-            let mut tx = tx_builder.build()?;
-            let freeze_sigs = tx.create_sigs(&[mint_authority.secret])?;
-            tx.signatures = vec![freeze_sigs];
+            let (_fee_call, _spent_fee_coins, fee_call_params) =
+                self.append_fee_call(holder, &mut tx_builder, block_height).await?;
 
-            let (fee_call, fee_proofs, fee_secrets, _spent_fee_coins, fee_call_params) =
-                self.append_fee_call(holder, tx, block_height, &[]).await?;
-
-            // Append the fee call to the transaction
-            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-            fee_signature_secrets = Some(fee_secrets);
             fee_params = Some(fee_call_params);
         }
 
         // Now build the actual transaction and sign it with necessary keys.
-        let mut tx = tx_builder.build()?;
-        let freeze_sigs = tx.create_sigs(&[mint_authority.secret])?;
-        tx.signatures = vec![freeze_sigs];
-        if let Some(fee_signature_secrets) = fee_signature_secrets {
-            let sigs = tx.create_sigs(&fee_signature_secrets)?;
-            tx.signatures.push(sigs);
-        }
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, freeze_debris.params, fee_params))
     }

+ 8 - 28
src/contract/test-harness/src/money_transfer.rs

@@ -73,42 +73,22 @@ impl TestHarness {
         let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
 
         // Create the TransactionBuilder containing the `Transfer` call
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: secrets.proofs }, vec![])?;
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf::new(call, secrets.proofs, secrets.signature_secrets),
+            vec![],
+        )?;
 
-        // If we have tx fees enabled, we first have to execute the fee-less
-        // transaction to gather its used gas, and then we feed it into the
-        // fee-creating function.
-        // We also tell it about any spent coins so we don't accidentally
-        // reuse them in the fee call.
-        // TODO: We have to build a proper coin selection algorithm so that we
-        // can utilize the Money::Transfer to merge any coins which would give
-        // us a coin with enough value for paying the transaction fee.
+        // If we have tx fees enabled, make an offering
         let mut fee_params = None;
-        let mut fee_signature_secrets = None;
         if self.verify_fees {
-            let mut tx = tx_builder.build()?;
-            let sigs = tx.create_sigs(&secrets.signature_secrets)?;
-            tx.signatures = vec![sigs];
-
-            let (fee_call, fee_proofs, fee_secrets, spent_fee_coins, fee_call_params) =
-                self.append_fee_call(holder, tx, block_height, &spent_coins).await?;
-
-            // Append the fee call to the transaction
-            tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
-            fee_signature_secrets = Some(fee_secrets);
+            let (_fee_call, spent_fee_coins, fee_call_params) =
+                self.append_fee_call(holder, &mut tx_builder, block_height).await?;
             spent_coins.extend_from_slice(&spent_fee_coins);
             fee_params = Some(fee_call_params);
         }
 
         // Now build the actual transaction and sign it with all necessary keys.
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&secrets.signature_secrets)?;
-        tx.signatures = vec![sigs];
-        if let Some(fee_signature_secrets) = fee_signature_secrets {
-            let sigs = tx.create_sigs(&fee_signature_secrets)?;
-            tx.signatures.push(sigs);
-        }
+        let tx = tx_builder.build_signed()?;
 
         Ok((tx, (params, fee_params), spent_coins))
     }

+ 16 - 0
src/sdk/src/dark_tree.rs

@@ -84,6 +84,16 @@ impl<T: Clone + Send + Sync> DarkTreeLeaf<T> {
     fn set_children_indexes(&mut self, children_indexes: Vec<usize>) {
         self.info.children_indexes = children_indexes;
     }
+
+    /// Get immutable access to the leaf's data
+    pub fn data(&self) -> &T {
+        &self.info.data
+    }
+
+    /// Get mutable access to the leaf's data
+    pub fn data_mut(&mut self) -> &mut T {
+        &mut self.info.data
+    }
 }
 
 /// This struct represents a DFS post-order traversal Tree.
@@ -711,6 +721,12 @@ impl<T: Clone + Send + Sync> DarkForest<T> {
         len
     }
 
+    /// Return a mutable iterator over all leaves in the forest,
+    /// using DFS post-order traversal on each tree.
+    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut DarkTreeLeaf<T>> {
+        self.trees.iter_mut().flat_map(|tree| tree.iter_mut())
+    }
+
     /// Check if configured min capacity have not been exceeded.
     fn check_min_capacity(&self) -> DarkTreeResult<()> {
         if let Some(min_capacity) = self.min_capacity {

+ 70 - 2
src/tx/mod.rs

@@ -261,14 +261,22 @@ pub const MIN_TX_CALLS: usize = 1;
 // TODO: verify max value
 pub const MAX_TX_CALLS: usize = 20;
 
-/// Auxiliarry structure containing all the information
-/// required to execute a contract call.
+/// Auxiliarry structure containing all the information required to execute a
+/// contract call and generate and sign a valid transaction.
 #[derive(Clone)]
 pub struct ContractCallLeaf {
     /// Call executed
     pub call: ContractCall,
     /// Attached ZK proofs
     pub proofs: Vec<Proof>,
+    /// Secret keys
+    pub secrets: Vec<SecretKey>,
+}
+
+impl ContractCallLeaf {
+    pub fn new(call: ContractCall, proofs: Vec<Proof>, secrets: Vec<SecretKey>) -> Self {
+        Self { call, proofs, secrets }
+    }
 }
 
 /// Auxiliary structure to build a full [`Transaction`] using
@@ -327,4 +335,64 @@ impl TransactionBuilder {
 
         Ok(Transaction { calls, proofs, signatures: vec![] })
     }
+
+    /// Builds the [`Transaction`] and create signatures from secrets.
+    pub fn build_signed(&mut self) -> Result<Transaction> {
+        // Build the leafs vector
+        let leafs = self.calls.build_vec()?;
+
+        // Double check integrity
+        dark_forest_leaf_vec_integrity_check(&leafs, Some(MIN_TX_CALLS), Some(MAX_TX_CALLS))?;
+
+        // Build the corresponding transaction
+        let mut calls = Vec::with_capacity(leafs.len());
+        let mut proofs = Vec::with_capacity(leafs.len());
+        let mut secrets = Vec::with_capacity(leafs.len());
+        for leaf in leafs {
+            let call = DarkLeaf {
+                data: leaf.data.call,
+                parent_index: leaf.parent_index,
+                children_indexes: leaf.children_indexes,
+            };
+            calls.push(call);
+            proofs.push(leaf.data.proofs);
+            secrets.push(leaf.data.secrets);
+        }
+
+        let mut tx = Transaction { calls, proofs, signatures: vec![] };
+
+        // Generate signatures
+        for secret_keys in secrets {
+            let sigs = tx.create_sigs(&secret_keys)?;
+            tx.signatures.push(sigs);
+        }
+
+        Ok(tx)
+    }
+
+    /// Extract all secrets from leaves.
+    ///
+    /// Returns a Vec of Vec<SecretKey> where each inner Vec os a leaf in DFS
+    /// post-order traversal order.
+    pub fn get_secrets(&mut self) -> Result<Vec<Vec<SecretKey>>> {
+        let leafs = self.calls.build_vec().map_err(|e| Error::Custom(e.to_string()))?;
+        Ok(leafs.iter().map(|leaf| leaf.data.secrets.clone()).collect())
+    }
+}
+
+/// Trait for types that can iterate over mutable contract calls
+pub trait ContractCallIter {
+    fn call_iter_mut(&mut self) -> impl Iterator<Item = &mut ContractCall>;
+}
+
+impl ContractCallIter for TransactionBuilder {
+    fn call_iter_mut(&mut self) -> impl Iterator<Item = &mut ContractCall> {
+        self.calls.iter_mut().map(|leaf| &mut leaf.data_mut().call)
+    }
+}
+
+impl ContractCallIter for Transaction {
+    fn call_iter_mut(&mut self) -> impl Iterator<Item = &mut ContractCall> {
+        self.calls.iter_mut().map(|leaf| &mut leaf.data)
+    }
 }