Explorar o código

improve transfer_v1, simplify the default builder, and provide a lower level API for constructing more complex calls

x %!s(int64=2) %!d(string=hai) anos
pai
achega
929b6d0833

+ 1 - 1
src/contract/dao/src/client/exec.rs

@@ -26,7 +26,7 @@ use log::debug;
 use rand::rngs::OsRng;
 
 use darkfi::{
-    zk::{export_witness_json, Proof, ProvingKey, Witness, ZkCircuit},
+    zk::{Proof, ProvingKey, Witness, ZkCircuit},
     zkas::ZkBinary,
     Result,
 };

+ 1 - 1
src/contract/dao/src/entrypoint/exec.rs

@@ -71,8 +71,8 @@ pub(crate) fn dao_exec_get_metadata(
         DAO_CONTRACT_ZKAS_DAO_EXEC_NS.to_string(),
         vec![
             dao_exec_params.proposal.inner(),
-            money_xfer_params.outputs[1].coin.inner(),
             money_xfer_params.outputs[0].coin.inner(),
+            money_xfer_params.outputs[1].coin.inner(),
             *yes_vote_coords.x(),
             *yes_vote_coords.y(),
             *all_vote_coords.x(),

+ 2 - 4
src/contract/dao/tests/integration.rs

@@ -104,8 +104,6 @@ fn integration_test() -> Result<()> {
             &Holder::Dao,
             Some(DAO_CONTRACT_ID.inner()),           // spend_hook
             Some(dao_mint_params.dao_bulla.inner()), // user_data
-            None,
-            None,
         )?;
 
         for holder in &HOLDERS {
@@ -317,8 +315,8 @@ fn integration_test() -> Result<()> {
         th.assert_trees(&HOLDERS);
 
         // Gather the coins
-        th.gather_owncoin(&Holder::Dao, &xfer_params.outputs[0], None)?;
-        th.gather_owncoin(&Holder::Rachel, &xfer_params.outputs[1], None)?;
+        th.gather_owncoin(&Holder::Rachel, &xfer_params.outputs[0], None)?;
+        th.gather_owncoin(&Holder::Dao, &xfer_params.outputs[1], None)?;
 
         let rachel_wallet = th.holders.get(&Holder::Rachel).unwrap();
         assert!(rachel_wallet.unspent_money_coins[0].note.value == PROPOSAL_AMOUNT);

+ 108 - 197
src/contract/money/src/client/transfer_v1.rs

@@ -40,50 +40,37 @@ use crate::{
     model::{ClearInput, Coin, Input, MoneyTransferParamsV1, Output},
 };
 
-// TODO: split this into secret and non-secret squads
-/// Output metadata claimed from building a `Money::Transfer` call
-pub struct TransferCallDebris {
-    /// The parameters for `Money::Transfer` respective to this call
-    pub params: MoneyTransferParamsV1,
-    /// The ZK proofs created in this builder
-    pub proofs: Vec<Proof>,
-    /// The ephemeral secret keys created for signing
-    pub signature_secrets: Vec<SecretKey>,
-    /// The coins that have been spent in this builder
-    // TODO: this is duplicate field, use params.inputs instead
-    pub spent_coins: Vec<OwnCoin>,
-    /// The coins that have been minted in this builder
-    // TODO: this is duplicate field, use params.outputs instead
-    pub minted_coins: Vec<OwnCoin>,
-
-    // TODO: should we maybe pass these into the builder explicitly?
-    /// The value blinds created for the inputs
-    pub input_value_blinds: Vec<pallas::Scalar>,
-    /// The value blinds created for the outputs
-    pub output_value_blinds: Vec<pallas::Scalar>,
-}
-
-impl TransferCallDebris {
-    // TODO: implement these methods
-    // fn spent_coins()
-    // fn minted_coins()
-}
-
-struct TransferCallSecrets {
+pub struct TransferCallSecrets {
     /// The ZK proofs created in this builder
     pub proofs: Vec<Proof>,
     /// The ephemeral secret keys created for signing
     pub signature_secrets: Vec<SecretKey>,
 
+    /// Decrypted notes associated with each output
     pub output_notes: Vec<MoneyNote>,
 
-    // TODO: should we maybe pass these into the builder explicitly?
     /// The value blinds created for the inputs
     pub input_value_blinds: Vec<pallas::Scalar>,
     /// The value blinds created for the outputs
     pub output_value_blinds: Vec<pallas::Scalar>,
 }
 
+impl TransferCallSecrets {
+    pub fn minted_coins(&self, params: &MoneyTransferParamsV1) -> Vec<OwnCoin> {
+        let mut minted_coins = vec![];
+        for (output, output_note) in params.outputs.iter().zip(self.output_notes.iter()) {
+            minted_coins.push(OwnCoin {
+                coin: output.coin,
+                note: output_note.clone(),
+                secret: SecretKey::from(pallas::Base::ZERO),
+                nullifier: Nullifier::from(pallas::Base::ZERO),
+                leaf_position: 0.into(),
+            });
+        }
+        minted_coins
+    }
+}
+
 pub struct TransferMintRevealed {
     pub coin: Coin,
     pub value_commit: pallas::Point,
@@ -131,7 +118,6 @@ impl TransferBurnRevealed {
     }
 }
 
-// TODO: these names are wrong, should be Transfer..., also drop Info suffix
 pub struct TransferCallClearInput {
     pub value: u64,
     pub token_id: TokenId,
@@ -157,7 +143,7 @@ pub struct TransferCallOutput {
 }
 
 /// Struct holding necessary information to build a `Money::TransferV1` contract call.
-pub struct TransferCallBuilder2 {
+pub struct TransferCallBuilder {
     /// Clear inputs
     pub clear_inputs: Vec<TransferCallClearInput>,
     /// Anonymous inputs
@@ -174,7 +160,7 @@ pub struct TransferCallBuilder2 {
     pub burn_pk: ProvingKey,
 }
 
-impl TransferCallBuilder2 {
+impl TransferCallBuilder {
     fn compute_remainder_blind(
         clear_inputs: &[ClearInput],
         input_blinds: &[pallas::Scalar],
@@ -197,7 +183,7 @@ impl TransferCallBuilder2 {
         total
     }
 
-    fn build(self) -> Result<(MoneyTransferParamsV1, TransferCallSecrets)> {
+    pub fn build(self) -> Result<(MoneyTransferParamsV1, TransferCallSecrets)> {
         debug!("Building Money::TransferV1 contract call");
         assert!(self.clear_inputs.len() + self.inputs.len() > 0);
 
@@ -321,52 +307,10 @@ impl TransferCallBuilder2 {
     }
 }
 
-/// Struct holding necessary information to build a `Money::TransferV1` contract call.
-pub struct TransferCallBuilder {
-    /// Caller's keypair
-    pub keypair: Keypair,
-    /// Recipient's public key
-    pub recipient: PublicKey,
-    /// Amount that we want to send to the recipient
-    pub value: u64,
-    /// Token ID that we want to send to the recipient
-    pub token_id: TokenId,
-    /// Spend hook for the recipient's output
-    pub rcpt_spend_hook: pallas::Base,
-    /// User data for the recipient's output
-    pub rcpt_user_data: pallas::Base,
-    /// User data blind for the recipient's output
-    pub rcpt_user_data_blind: pallas::Base,
-    /// Spend hook for the change output
-    pub change_spend_hook: pallas::Base,
-    /// User data for the change output
-    pub change_user_data: pallas::Base,
-    /// User data blind for inputs
-    pub input_user_data_blind: pallas::Base,
-    /// Set of `OwnCoin` we're given to use in this builder
-    pub coins: Vec<OwnCoin>,
-    /// Merkle tree of coins used to create inclusion proofs
-    pub tree: MerkleTree,
-    /// `Mint_V1` zkas circuit ZkBinary
-    pub mint_zkbin: ZkBinary,
-    /// Proving key for the `Mint_V1` zk circuit
-    pub mint_pk: ProvingKey,
-    /// `Burn_V1` zkas circuit ZkBinary
-    pub burn_zkbin: ZkBinary,
-    /// Proving key for the `Burn_V1` zk circuit
-    pub burn_pk: ProvingKey,
-    /// Marks if we want to build clear inputs instead of anonymous inputs
-    pub clear_input: bool,
-}
-
-// cannot use different select_coins() algos
-// low level api mixing concerns - owncoin, select, and build
-// unable to specify exact structure of tx (multiple outputs, clear and anon inputs)
-
 /// Select coins from `coins` of at least `min_value` in total.
 /// Different strategies can be used. This function uses the dumb strategy
 /// of selecting coins until we reach `min_value`.
-pub fn select_coins(coins: Vec<OwnCoin>, min_value: u64) -> Result<Vec<OwnCoin>> {
+pub fn select_coins(coins: Vec<OwnCoin>, min_value: u64) -> Result<(Vec<OwnCoin>, u64)> {
     let mut total_value = 0;
     let mut selected = vec![];
 
@@ -384,117 +328,106 @@ pub fn select_coins(coins: Vec<OwnCoin>, min_value: u64) -> Result<Vec<OwnCoin>>
         return Err(ClientFailed::NotEnoughValue(total_value).into())
     }
 
-    Ok(selected)
+    let change_value = total_value - min_value;
+
+    Ok((selected, change_value))
 }
 
-impl TransferCallBuilder {
-    pub fn build(self) -> Result<TransferCallDebris> {
-        debug!("Building Money::TransferV1 contract call");
-        assert!(self.value != 0);
-        assert!(self.token_id.inner() != pallas::Base::zero());
-        if !self.clear_input {
-            assert!(!self.coins.is_empty());
-        }
+/// Make a simple anonymous transfer call.
+///
+/// * `keypair`: Caller's keypair
+/// * `recipient`: Recipient's public key
+/// * `value`: Amount that we want to send to the recipient
+/// * `token_id`: Token ID that we want to send to the recipient
+/// * `coins`: Set of `OwnCoin` we're given to use in this builder
+/// * `tree`: Merkle tree of coins used to create inclusion proofs
+/// * `mint_zkbin`: `Mint_V1` zkas circuit ZkBinary
+/// * `mint_pk`: Proving key for the `Mint_V1` zk circuit
+/// * `burn_zkbin`: `Burn_V1` zkas circuit ZkBinary
+/// * `burn_pk`: Proving key for the `Burn_V1` zk circuit
+///
+/// Returns a tuple of:
+///
+/// * The actual call data
+/// * Secret values such as blinds
+/// * A list of the spent coins
+pub fn make_transfer_call(
+    keypair: Keypair,
+    recipient: PublicKey,
+    value: u64,
+    token_id: TokenId,
+    coins: Vec<OwnCoin>,
+    tree: MerkleTree,
+    mint_zkbin: ZkBinary,
+    mint_pk: ProvingKey,
+    burn_zkbin: ZkBinary,
+    burn_pk: ProvingKey,
+) -> Result<(MoneyTransferParamsV1, TransferCallSecrets, Vec<OwnCoin>)> {
+    debug!("Building Money::TransferV1 contract call");
+    assert_ne!(value, 0);
+    assert_ne!(token_id.inner(), pallas::Base::ZERO);
+    assert!(!coins.is_empty());
+
+    // Ensure the coins given to us are all of the same token ID.
+    // The money contract base transfer doesn't allow conversions.
+    for coin in &coins {
+        assert_eq!(token_id, coin.note.token_id);
+    }
 
-        // Ensure the coins given to us are all of the same token ID.
-        // The money contract base transfer doesn't allow conversions.
-        for coin in self.coins.iter() {
-            assert_eq!(self.token_id, coin.note.token_id);
-        }
+    let mut inputs = vec![];
+    let mut outputs = vec![];
 
-        let mut clear_inputs = vec![];
-        let mut inputs = vec![];
-        let mut outputs = vec![];
+    let (spent_coins, change_value) = select_coins(coins, value)?;
 
-        //let mut change_outputs = vec![];
-        let mut spent_coins = vec![];
-        let mut minted_coins = vec![];
+    for coin in spent_coins.iter() {
+        let leaf_position = coin.leaf_position;
+        let merkle_path = tree.witness(leaf_position, 0).unwrap();
 
-        if self.clear_input {
-            let input = TransferCallClearInput {
-                value: self.value,
-                token_id: self.token_id,
-                signature_secret: self.keypair.secret,
-            };
+        let input = TransferCallInput {
+            leaf_position,
+            merkle_path,
+            secret: coin.secret,
+            note: coin.note.clone(),
+            user_data_blind: pallas::Base::random(&mut OsRng),
+        };
 
-            clear_inputs.push(input);
-        } else {
-            spent_coins = select_coins(self.coins, self.value)?;
-
-            let mut inputs_value = 0;
-            for coin in spent_coins.iter() {
-                let leaf_position = coin.leaf_position;
-                let merkle_path = self.tree.witness(leaf_position, 0).unwrap();
-                inputs_value += coin.note.value;
-
-                let input = TransferCallInput {
-                    leaf_position,
-                    merkle_path,
-                    secret: coin.secret,
-                    note: coin.note.clone(),
-                    user_data_blind: self.input_user_data_blind,
-                };
-
-                inputs.push(input);
-            }
-
-            if inputs_value > self.value {
-                let return_value = inputs_value - self.value;
-                outputs.push(TransferCallOutput {
-                    value: return_value,
-                    token_id: self.token_id,
-                    public_key: self.keypair.public,
-                    spend_hook: self.change_spend_hook,
-                    user_data: self.change_user_data,
-                });
-            }
-        }
-        debug!("Selected inputs");
+        inputs.push(input);
+    }
+    debug!("Selected inputs");
 
+    outputs.push(TransferCallOutput {
+        value,
+        token_id,
+        public_key: recipient,
+        spend_hook: pallas::Base::ZERO,
+        user_data: pallas::Base::ZERO,
+    });
+
+    if change_value > 0 {
         outputs.push(TransferCallOutput {
-            value: self.value,
-            token_id: self.token_id,
-            public_key: self.recipient,
-            spend_hook: self.rcpt_spend_hook,
-            user_data: self.rcpt_user_data,
+            value: change_value,
+            token_id,
+            public_key: keypair.public,
+            spend_hook: pallas::Base::ZERO,
+            user_data: pallas::Base::ZERO,
         });
+    }
 
-        assert!(clear_inputs.len() + inputs.len() > 0);
+    assert!(!inputs.is_empty());
 
-        let xfer_builder = TransferCallBuilder2 {
-            clear_inputs,
-            inputs,
-            outputs,
-            mint_zkbin: self.mint_zkbin,
-            mint_pk: self.mint_pk,
-            burn_zkbin: self.burn_zkbin,
-            burn_pk: self.burn_pk,
-        };
-        let (params, secrets) = xfer_builder.build()?;
+    let xfer_builder = TransferCallBuilder {
+        clear_inputs: vec![],
+        inputs,
+        outputs,
+        mint_zkbin,
+        mint_pk,
+        burn_zkbin,
+        burn_pk,
+    };
 
-        for (output, output_note) in params.outputs.iter().zip(secrets.output_notes.iter()) {
-            minted_coins.push(OwnCoin {
-                coin: output.coin,
-                note: output_note.clone(),
-                secret: SecretKey::from(pallas::Base::ZERO),
-                nullifier: Nullifier::from(pallas::Base::ZERO),
-                leaf_position: 0.into(),
-            });
-        }
+    let (params, secrets) = xfer_builder.build()?;
 
-        // Now we should have all the params, zk proofs, and signature secrets.
-        // We return it all and let the caller deal with it.
-        let debris = TransferCallDebris {
-            params,
-            proofs: secrets.proofs,
-            signature_secrets: secrets.signature_secrets,
-            spent_coins,
-            minted_coins,
-            input_value_blinds: secrets.input_value_blinds,
-            output_value_blinds: secrets.output_value_blinds,
-        };
-        Ok(debris)
-    }
+    Ok((params, secrets, spent_coins))
 }
 
 pub fn create_transfer_burn_proof(
@@ -622,25 +555,3 @@ pub fn create_transfer_mint_proof(
 
     Ok((proof, public_inputs))
 }
-
-fn compute_remainder_blind(
-    clear_inputs: &[ClearInput],
-    input_blinds: &[pallas::Scalar],
-    output_blinds: &[pallas::Scalar],
-) -> pallas::Scalar {
-    let mut total = pallas::Scalar::zero();
-
-    for input in clear_inputs {
-        total += input.value_blind;
-    }
-
-    for input_blind in input_blinds {
-        total += input_blind;
-    }
-
-    for output_blind in output_blinds {
-        total -= output_blind;
-    }
-
-    total
-}

+ 1 - 1
src/contract/money/tests/integration.rs

@@ -54,7 +54,7 @@ fn money_integration() -> Result<()> {
 
         info!("[Faucet] Building Alice airdrop tx");
         let (airdrop_tx, airdrop_params) =
-            th.airdrop_native(ALICE_NATIVE_AIRDROP, &Holder::Alice, None, None, None, None)?;
+            th.airdrop_native(ALICE_NATIVE_AIRDROP, &Holder::Alice, None, None)?;
 
         for holder in &HOLDERS {
             info!("[{holder:?}] Executing Alice airdrop tx");

+ 1 - 1
src/contract/money/tests/verification_bench.rs

@@ -55,7 +55,7 @@ fn alice2alice_random_amounts() -> Result<()> {
         info!(target: "money", "[Faucet] Building Alice's airdrop");
         info!(target: "money", "[Faucet] ========================");
         let (airdrop_tx, airdrop_params) =
-            th.airdrop_native(ALICE_AIRDROP, &Holder::Alice, None, None, None, None)?;
+            th.airdrop_native(ALICE_AIRDROP, &Holder::Alice, None, None)?;
 
         for holder in &HOLDERS {
             info!(target: "money", "[{holder:?}] ==========================");

+ 48 - 36
src/contract/test-harness/src/dao_exec.rs

@@ -25,9 +25,8 @@ use darkfi_dao_contract::{
     DaoFunction, DAO_CONTRACT_ZKAS_DAO_EXEC_NS,
 };
 use darkfi_money_contract::{
-    client::{transfer_v1::TransferCallBuilder, OwnCoin},
-    model::MoneyTransferParamsV1,
-    MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+    client::transfer_v1 as xfer, model::MoneyTransferParamsV1, MoneyFunction,
+    MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
 };
 use darkfi_sdk::{
     crypto::{
@@ -66,67 +65,80 @@ impl TestHarness {
         let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::DaoExec).unwrap();
         let timer = Instant::now();
 
+        let input_user_data_blind = pallas::Base::random(&mut OsRng);
         // TODO: FIXME: This is not checked anywhere!
         let exec_signature_secret = SecretKey::random(&mut OsRng);
 
-        let rcpt_spend_hook = pallas::Base::ZERO;
-        let rcpt_user_data = pallas::Base::ZERO;
-        let rcpt_user_data_blind = pallas::Base::random(&mut OsRng);
-
-        let change_spend_hook = DAO_CONTRACT_ID.inner();
-        let change_user_data = dao_bulla.inner();
-
-        let input_user_data_blind = pallas::Base::random(&mut OsRng);
-
-        let coins: Vec<OwnCoin> = dao_wallet
+        let coins = dao_wallet
             .unspent_money_coins
             .iter()
             .filter(|x| x.note.token_id == proposal.token_id)
             .cloned()
             .collect();
+        let (spent_coins, change_value) = xfer::select_coins(coins, proposal.amount)?;
         let tree = dao_wallet.money_merkle_tree.clone();
 
-        let xfer_builder = TransferCallBuilder {
-            keypair: dao_wallet.keypair,
-            recipient: proposal.dest,
-            value: proposal.amount,
-            token_id: proposal.token_id,
-            rcpt_spend_hook,
-            rcpt_user_data,
-            rcpt_user_data_blind,
-            change_spend_hook,
-            change_user_data,
-            input_user_data_blind,
-            coins,
-            tree,
+        let mut inputs = vec![];
+        for coin in &spent_coins {
+            let leaf_position = coin.leaf_position;
+            let merkle_path = tree.witness(leaf_position, 0).unwrap();
+
+            inputs.push(xfer::TransferCallInput {
+                leaf_position,
+                merkle_path,
+                secret: coin.secret,
+                note: coin.note.clone(),
+                user_data_blind: input_user_data_blind,
+            });
+        }
+
+        let xfer_builder = xfer::TransferCallBuilder {
+            clear_inputs: vec![],
+            inputs,
+            outputs: vec![
+                xfer::TransferCallOutput {
+                    value: proposal.amount,
+                    token_id: proposal.token_id,
+                    public_key: proposal.dest,
+                    spend_hook: pallas::Base::ZERO,
+                    user_data: pallas::Base::ZERO,
+                },
+                xfer::TransferCallOutput {
+                    value: change_value,
+                    token_id: proposal.token_id,
+                    public_key: dao_wallet.keypair.public,
+                    spend_hook: DAO_CONTRACT_ID.inner(),
+                    user_data: dao_bulla.inner(),
+                },
+            ],
             mint_zkbin: mint_zkbin.clone(),
             mint_pk: mint_pk.clone(),
             burn_zkbin: burn_zkbin.clone(),
             burn_pk: burn_pk.clone(),
-            clear_input: false,
         };
 
-        let xfer_debris = xfer_builder.build()?;
+        let (xfer_params, xfer_secrets) = xfer_builder.build()?;
         let mut data = vec![MoneyFunction::TransferV1 as u8];
-        xfer_debris.params.encode(&mut data)?;
+        xfer_params.encode(&mut data)?;
         let xfer_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
 
         // We need to extract stuff from the inputs and outputs that we'll also
         // use in the DAO::Exec call. This DAO API needs to be better.
         let mut input_value = 0;
         let mut input_value_blind = pallas::Scalar::ZERO;
-        for (input, blind) in xfer_debris.spent_coins.iter().zip(xfer_debris.input_value_blinds) {
+        for (input, blind) in spent_coins.iter().zip(xfer_secrets.input_value_blinds.iter()) {
             input_value += input.note.value;
             input_value_blind += blind;
         }
         assert_eq!(
             pedersen_commitment_u64(input_value, input_value_blind),
-            xfer_debris.params.inputs.iter().map(|input| input.value_commit).sum()
+            xfer_params.inputs.iter().map(|input| input.value_commit).sum()
         );
 
         // First output is change, second output is recipient.
-        let dao_serial = xfer_debris.minted_coins[0].note.serial;
-        let user_serial = xfer_debris.minted_coins[1].note.serial;
+        let minted_coins = xfer_secrets.minted_coins(&xfer_params);
+        let user_serial = minted_coins[0].note.serial;
+        let dao_serial = minted_coins[1].note.serial;
 
         let exec_builder = DaoExecCall {
             proposal: proposal.clone(),
@@ -151,10 +163,10 @@ impl TestHarness {
 
         let mut tx = Transaction {
             calls: vec![xfer_call, exec_call],
-            proofs: vec![xfer_debris.proofs, exec_proofs],
+            proofs: vec![xfer_secrets.proofs, exec_proofs],
             signatures: vec![],
         };
-        let xfer_sigs = tx.create_sigs(&mut OsRng, &xfer_debris.signature_secrets)?;
+        let xfer_sigs = tx.create_sigs(&mut OsRng, &xfer_secrets.signature_secrets)?;
         let exec_sigs = tx.create_sigs(&mut OsRng, &[exec_signature_secret])?;
         tx.signatures = vec![xfer_sigs, exec_sigs];
         tx_action_benchmark.creation_times.push(timer.elapsed());
@@ -167,7 +179,7 @@ impl TestHarness {
         let size = std::mem::size_of_val(&*base58);
         tx_action_benchmark.broadcasted_sizes.push(size);
 
-        Ok((tx, xfer_debris.params, exec_params))
+        Ok((tx, xfer_params, exec_params))
     }
 
     pub async fn execute_dao_exec_tx(

+ 21 - 24
src/contract/test-harness/src/money_airdrop.rs

@@ -20,7 +20,7 @@ use std::time::Instant;
 
 use darkfi::{tx::Transaction, zk::halo2::Field, Result};
 use darkfi_money_contract::{
-    client::{transfer_v1::TransferCallBuilder, OwnCoin},
+    client::{transfer_v1 as xfer, OwnCoin},
     model::MoneyTransferParamsV1,
     MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
 };
@@ -42,8 +42,6 @@ impl TestHarness {
         holder: &Holder,
         rcpt_spend_hook: Option<pallas::Base>,
         rcpt_user_data: Option<pallas::Base>,
-        change_spend_hook: Option<pallas::Base>,
-        change_user_data: Option<pallas::Base>,
     ) -> Result<(Transaction, MoneyTransferParamsV1)> {
         let recipient = self.holders.get(holder).unwrap().keypair.public;
         let faucet = self.holders.get(&Holder::Faucet).unwrap();
@@ -59,34 +57,34 @@ impl TestHarness {
 
         let timer = Instant::now();
 
-        let builder = TransferCallBuilder {
-            keypair: faucet.keypair,
-            recipient,
-            value,
-            token_id: *DARK_TOKEN_ID,
-            rcpt_spend_hook: rcpt_spend_hook.unwrap_or(pallas::Base::ZERO),
-            rcpt_user_data: rcpt_user_data.unwrap_or(pallas::Base::ZERO),
-            rcpt_user_data_blind: pallas::Base::random(&mut OsRng),
-            change_spend_hook: change_spend_hook.unwrap_or(pallas::Base::ZERO),
-            change_user_data: change_user_data.unwrap_or(pallas::Base::ZERO),
-            input_user_data_blind: pallas::Base::random(&mut OsRng),
-            coins: vec![],
-            tree: faucet.money_merkle_tree.clone(),
+        let xfer_builder = xfer::TransferCallBuilder {
+            clear_inputs: vec![xfer::TransferCallClearInput {
+                value,
+                token_id: *DARK_TOKEN_ID,
+                signature_secret: faucet.keypair.secret,
+            }],
+            inputs: vec![],
+            outputs: vec![xfer::TransferCallOutput {
+                value,
+                token_id: *DARK_TOKEN_ID,
+                public_key: recipient,
+                spend_hook: rcpt_spend_hook.unwrap_or(pallas::Base::ZERO),
+                user_data: rcpt_user_data.unwrap_or(pallas::Base::ZERO),
+            }],
             mint_zkbin: mint_zkbin.clone(),
             mint_pk: mint_pk.clone(),
             burn_zkbin: burn_zkbin.clone(),
             burn_pk: burn_pk.clone(),
-            clear_input: true,
         };
 
-        let debris = builder.build()?;
+        let (params, secrets) = xfer_builder.build()?;
 
         let mut data = vec![MoneyFunction::TransferV1 as u8];
-        debris.params.encode(&mut data)?;
+        params.encode(&mut data)?;
         let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }];
-        let proofs = vec![debris.proofs];
+        let proofs = vec![secrets.proofs];
         let mut tx = Transaction { calls, proofs, signatures: vec![] };
-        let sigs = tx.create_sigs(&mut OsRng, &debris.signature_secrets)?;
+        let sigs = tx.create_sigs(&mut OsRng, &secrets.signature_secrets)?;
         tx.signatures = vec![sigs];
         tx_action_benchmark.creation_times.push(timer.elapsed());
 
@@ -98,7 +96,7 @@ impl TestHarness {
         let size = std::mem::size_of_val(&*base58);
         tx_action_benchmark.broadcasted_sizes.push(size);
 
-        Ok((tx, debris.params))
+        Ok((tx, params))
     }
 
     pub async fn execute_airdrop_native_tx(
@@ -131,8 +129,7 @@ impl TestHarness {
         info!(target: "consensus", "[Faucet] ==============================");
         info!(target: "consensus", "[Faucet] Building {holder:?} airdrop tx");
         info!(target: "consensus", "[Faucet] ==============================");
-        let (airdrop_tx, airdrop_params) =
-            self.airdrop_native(value, holder, None, None, None, None)?;
+        let (airdrop_tx, airdrop_params) = self.airdrop_native(value, holder, None, None)?;
 
         for h in holders {
             info!(target: "consensus", "[{h:?}] ===============================");

+ 17 - 37
src/contract/test-harness/src/money_transfer.rs

@@ -18,15 +18,14 @@
 
 use std::time::Instant;
 
-use darkfi::{tx::Transaction, zk::halo2::Field, Result};
+use darkfi::{tx::Transaction, Result};
 use darkfi_money_contract::{
-    client::{transfer_v1::TransferCallBuilder, OwnCoin},
+    client::{transfer_v1::make_transfer_call, OwnCoin},
     model::MoneyTransferParamsV1,
     MoneyFunction, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
 };
 use darkfi_sdk::{
     crypto::{MerkleNode, TokenId, MONEY_CONTRACT_ID},
-    pasta::pallas,
     ContractCall,
 };
 use darkfi_serial::{serialize, Encodable};
@@ -57,44 +56,25 @@ impl TestHarness {
 
         let timer = Instant::now();
 
-        // We're just going to be using a zero spend-hook and user-data
-        let rcpt_spend_hook = pallas::Base::zero();
-        let rcpt_user_data = pallas::Base::zero();
-        let rcpt_user_data_blind = pallas::Base::random(&mut OsRng);
-
-        // TODO: verify this is correct
-        let change_spend_hook = pallas::Base::zero();
-        let change_user_data = pallas::Base::zero();
-        let input_user_data_blind = pallas::Base::random(&mut OsRng);
-
-        let builder = TransferCallBuilder {
-            keypair: wallet.keypair,
-            recipient: rcpt,
-            value: amount,
+        let (params, secrets, spent_coins) = make_transfer_call(
+            wallet.keypair,
+            rcpt,
+            amount,
             token_id,
-            rcpt_spend_hook,
-            rcpt_user_data,
-            rcpt_user_data_blind,
-            change_spend_hook,
-            change_user_data,
-            input_user_data_blind,
-            coins: owncoins.to_owned(),
-            tree: wallet.money_merkle_tree.clone(),
-            mint_zkbin: mint_zkbin.clone(),
-            mint_pk: mint_pk.clone(),
-            burn_zkbin: burn_zkbin.clone(),
-            burn_pk: burn_pk.clone(),
-            clear_input: false,
-        };
-
-        let debris = builder.build()?;
+            owncoins.to_owned(),
+            wallet.money_merkle_tree.clone(),
+            mint_zkbin.clone(),
+            mint_pk.clone(),
+            burn_zkbin.clone(),
+            burn_pk.clone(),
+        )?;
 
         let mut data = vec![MoneyFunction::TransferV1 as u8];
-        debris.params.encode(&mut data)?;
+        params.encode(&mut data)?;
         let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }];
-        let proofs = vec![debris.proofs];
+        let proofs = vec![secrets.proofs];
         let mut tx = Transaction { calls, proofs, signatures: vec![] };
-        let sigs = tx.create_sigs(&mut OsRng, &debris.signature_secrets)?;
+        let sigs = tx.create_sigs(&mut OsRng, &secrets.signature_secrets)?;
         tx.signatures = vec![sigs];
         tx_action_benchmark.creation_times.push(timer.elapsed());
 
@@ -106,7 +86,7 @@ impl TestHarness {
         let size = std::mem::size_of_val(&*base58);
         tx_action_benchmark.broadcasted_sizes.push(size);
 
-        Ok((tx, debris.params, debris.spent_coins))
+        Ok((tx, params, spent_coins))
     }
 
     pub async fn execute_transfer_tx(