فهرست منبع

contract/money: WIP swap client and integration test.

parazyd 3 سال پیش
والد
کامیت
6f69003bf4

+ 1 - 1
src/consensus/state.rs

@@ -22,7 +22,7 @@ use chrono::{NaiveDateTime, Utc};
 use darkfi_sdk::crypto::{constants::MERKLE_DEPTH, MerkleNode};
 use darkfi_serial::{SerialDecodable, SerialEncodable};
 use incrementalmerkletree::bridgetree::BridgeTree;
-use log::{debug, info};
+use log::info;
 use pasta_curves::{group::ff::PrimeField, pallas};
 use rand::{thread_rng, Rng};
 

+ 177 - 1
src/contract/money/src/client.rs

@@ -50,7 +50,7 @@ use darkfi_sdk::{
         pallas,
     },
 };
-use darkfi_serial::{Decodable, Encodable, SerialDecodable, SerialEncodable};
+use darkfi_serial::{serialize, Decodable, Encodable, SerialDecodable, SerialEncodable};
 use halo2_proofs::{arithmetic::Field, circuit::Value};
 use log::{debug, error, info};
 use rand::rngs::OsRng;
@@ -472,13 +472,189 @@ fn create_transfer_burn_proof(
     Ok((proof, revealed))
 }
 
+/// Build half of the money contract OTC swap transaction parameters with the given data:
+/// * `value_send` - Amount to send
+/// * `token_id_send` - Token ID to send
+/// * `value_recv` - Amount to receive
+/// * `token_id_recv` - Token ID to receive
+/// * `value_blinds` - Value blinds used to calculate remainder blind
+/// * `coins` - Set of coins we're able to spend
+/// * `tree` - Current Merkle tree of coins
+/// * `mint_zkbin` - ZkBinary of the mint circuit
+/// * `mint_pk` - Proving key for the ZK mint proof
+/// * `burn_zkbin` - ZkBinary of the burn circuit
+/// * `burn_pk` - Proving key for the ZK burn proof
+pub fn build_half_swap_tx(
+    pubkey: &PublicKey,
+    value_send: u64,
+    token_id_send: TokenId,
+    value_recv: u64,
+    token_id_recv: TokenId,
+    value_blinds: &[ValueBlind],
+    coins: &[OwnCoin],
+    tree: &BridgeTree<MerkleNode, MERKLE_DEPTH>,
+    mint_zkbin: &ZkBinary,
+    mint_pk: &ProvingKey,
+    burn_zkbin: &ZkBinary,
+    burn_pk: &ProvingKey,
+) -> Result<(MoneyTransferParams, Vec<Proof>, Vec<SecretKey>, Vec<OwnCoin>, Vec<ValueBlind>)> {
+    debug!("Building OTC swap transaction half");
+    assert!(value_send != 0);
+    assert!(value_recv != 0);
+    assert!(!coins.is_empty());
+
+    debug!("Money::build_half_swap_tx(): Building anonymous inputs");
+    // We'll take any coin that has correct value
+    let Some(coin) = coins.iter().find(|x| x.note.value == value_send && x.note.token_id == token_id_send) else {
+        error!("Money::build_half_swap_tx(): Did not find a coin with enough value to swap");
+        return Err(ClientFailed::NotEnoughValue(value_send).into())
+    };
+
+    let leaf_position = coin.leaf_position;
+    let root = tree.root(0).unwrap();
+    let merkle_path = tree.authentication_path(leaf_position, &root).unwrap();
+
+    let input = TransactionBuilderInputInfo {
+        leaf_position,
+        merkle_path,
+        secret: coin.secret,
+        note: coin.note.clone(),
+    };
+
+    let mut spent_coins = vec![];
+    spent_coins.push(coin.clone());
+
+    let output = TransactionBuilderOutputInfo {
+        value: value_recv,
+        token_id: token_id_recv,
+        public_key: *pubkey,
+    };
+
+    // We now fill this with necessary stuff
+    let mut params = MoneyTransferParams { clear_inputs: vec![], inputs: vec![], outputs: vec![] };
+
+    let mut ret_blinds = vec![];
+
+    let value_send_blind = ValueBlind::random(&mut OsRng);
+
+    // If we got a non-empty value_blinds passed into this function, we're making the last
+    // output so we use those blinds to calculate the remainder. The slice should have two
+    // elements, 0 being the input blind, and 1 being the output blind.
+    // BUG: This doesn't work properly, and needs to be fixed.
+    let value_recv_blind = if value_blinds.is_empty() {
+        ValueBlind::random(&mut OsRng)
+    } else {
+        compute_remainder_blind(&[], &[], &[value_blinds[0]])
+    };
+    ret_blinds.push(value_recv_blind);
+    ret_blinds.push(value_send_blind);
+    debug!("RET BLINDS: {:?}", ret_blinds);
+
+    let token_send_blind = ValueBlind::random(&mut OsRng);
+    let token_recv_blind = ValueBlind::random(&mut OsRng);
+
+    let signature_secret = SecretKey::random(&mut OsRng);
+
+    // Disable composability for this old obsolete API
+    let spend_hook = pallas::Base::zero();
+    let user_data = pallas::Base::zero();
+    let user_data_blind = pallas::Base::random(&mut OsRng);
+
+    let mut zk_proofs = vec![];
+
+    info!("Creating swap burn proof for input 0");
+    let (proof, revealed) = create_transfer_burn_proof(
+        burn_zkbin,
+        burn_pk,
+        input.note.value,
+        input.note.token_id,
+        value_send_blind,
+        token_send_blind,
+        input.note.serial,
+        spend_hook,
+        user_data,
+        user_data_blind,
+        input.note.coin_blind,
+        input.secret,
+        input.leaf_position,
+        input.merkle_path.clone(),
+        signature_secret,
+    )?;
+
+    params.inputs.push(Input {
+        value_commit: revealed.value_commit,
+        token_commit: revealed.token_commit,
+        nullifier: revealed.nullifier,
+        merkle_root: revealed.merkle_root,
+        spend_hook: revealed.spend_hook,
+        user_data_enc: revealed.user_data_enc,
+        signature_public: revealed.signature_public,
+    });
+
+    zk_proofs.push(proof);
+
+    let serial = pallas::Base::random(&mut OsRng);
+    let coin_blind = pallas::Base::random(&mut OsRng);
+
+    // Disable composability for this old obsolete API
+    let spend_hook = pallas::Base::zero();
+    let user_data = pallas::Base::zero();
+
+    info!("Creating swap mint proof for output 0");
+    let (proof, revealed) = create_transfer_mint_proof(
+        mint_zkbin,
+        mint_pk,
+        output.value,
+        output.token_id,
+        value_recv_blind,
+        token_recv_blind,
+        serial,
+        spend_hook,
+        user_data,
+        coin_blind,
+        output.public_key,
+    )?;
+
+    zk_proofs.push(proof);
+
+    // Encrypted note
+    let note = Note {
+        serial,
+        value: output.value,
+        token_id: output.token_id,
+        coin_blind,
+        value_blind: value_recv_blind,
+        token_blind: token_recv_blind,
+        // Here we store our secret key we use for signing
+        memo: serialize(&signature_secret),
+    };
+
+    let encrypted_note = note.encrypt(&output.public_key)?;
+
+    params.outputs.push(Output {
+        value_commit: revealed.value_commit,
+        token_commit: revealed.token_commit,
+        coin: revealed.coin.inner(),
+        ciphertext: encrypted_note.ciphertext,
+        ephem_public: encrypted_note.ephem_public,
+    });
+
+    // Now we should have all the params, zk proofs, and signature secrets.
+    // We return it all and let the caller deal with it.
+
+    Ok((params, zk_proofs, vec![signature_secret], spent_coins, ret_blinds))
+}
+
 /// Build money contract transfer transaction parameters with the given data:
 /// * `keypair` - Caller's keypair
 /// * `pubkey` - Public key of the recipient
 /// * `value` - Value of the transfer
+/// * `token_id` - Token ID to transfer
 /// * `coins` - Set of coins we're able to spend
 /// * `tree` - Current Merkle tree of coins
+/// * `mint_zkbin` - ZkBinary of the mint circuit
 /// * `mint_pk` - Proving key for the ZK mint proof
+/// * `burn_zkbin` - ZkBinary of the burn circuit
 /// * `burn_pk` - Proving key for the ZK burn proof
 /// * `clear_input` - Marks if we're creating clear or anonymous inputs
 pub fn build_transfer_tx(

+ 3 - 1
src/contract/money/src/lib.rs

@@ -239,6 +239,7 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
 
     match MoneyFunction::try_from(self_.data[0])? {
         MoneyFunction::Transfer => {
+            msg!("[Transfer] Entered match arm");
             let params: MoneyTransferParams = deserialize(&self_.data[1..])?;
 
             assert!(params.clear_inputs.len() + params.inputs.len() > 0);
@@ -348,16 +349,17 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
         }
 
         MoneyFunction::OtcSwap => {
+            msg!("[OtcSwap] Entered match arm");
             let params: MoneyTransferParams = deserialize(&self_.data[1..])?;
 
             let nullifier_db = db_lookup(cid, NULLIFIERS_TREE)?;
             let coin_roots_db = db_lookup(cid, COIN_ROOTS_TREE)?;
 
             // State transition for OTC swaps
-            assert!(params.clear_inputs.is_empty());
             // For now we enforce 2 inputs and 2 outputs, which means the coins
             // must be available beforehand. We might want to change this and
             // allow transactions including leftover change.
+            assert!(params.clear_inputs.is_empty());
             assert!(params.inputs.len() == 2);
             assert!(params.outputs.len() == 2);
 

+ 5 - 5
src/contract/money/src/state.rs

@@ -26,7 +26,7 @@ use darkfi_sdk::{
 use darkfi_serial::{SerialDecodable, SerialEncodable};
 
 /// Inputs and outputs for a payment
-#[derive(Debug, SerialEncodable, SerialDecodable)]
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct MoneyTransferParams {
     /// Clear inputs
     pub clear_inputs: Vec<ClearInput>,
@@ -37,7 +37,7 @@ pub struct MoneyTransferParams {
 }
 
 /// State update produced by a payment
-#[derive(Debug, SerialEncodable, SerialDecodable)]
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct MoneyTransferUpdate {
     /// Revealed nullifiers
     pub nullifiers: Vec<Nullifier>,
@@ -46,7 +46,7 @@ pub struct MoneyTransferUpdate {
 }
 
 /// A transaction's clear input
-#[derive(Debug, SerialEncodable, SerialDecodable)]
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct ClearInput {
     /// Input's value (amount)
     pub value: u64,
@@ -61,7 +61,7 @@ pub struct ClearInput {
 }
 
 /// A transaction's anonymous input
-#[derive(Debug, SerialEncodable, SerialDecodable)]
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct Input {
     /// Pedersen commitment for the input's value
     pub value_commit: ValueCommit,
@@ -80,7 +80,7 @@ pub struct Input {
 }
 
 /// A transaction's anonymous output
-#[derive(Debug, SerialEncodable, SerialDecodable)]
+#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 pub struct Output {
     /// Pedersen commitment for the output's value
     pub value_commit: ValueCommit,

+ 353 - 0
src/contract/money/tests/otcswap.rs

@@ -0,0 +1,353 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2022 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+use std::collections::HashMap;
+
+use darkfi::{
+    consensus::{
+        constants::{TESTNET_GENESIS_HASH_BYTES, TESTNET_GENESIS_TIMESTAMP},
+        ValidatorState,
+    },
+    tx::Transaction,
+    util::parse::decode_base10,
+    wallet::WalletDb,
+    zk::{proof::ProvingKey, vm::ZkCircuit, vm_stack::empty_witnesses},
+    zkas::ZkBinary,
+    Result,
+};
+use darkfi_sdk::{
+    crypto::{
+        constants::MERKLE_DEPTH, poseidon_hash, ContractId, Keypair, MerkleNode, Nullifier, TokenId,
+    },
+    db::ZKAS_DB_NAME,
+    incrementalmerkletree::{bridgetree::BridgeTree, Tree},
+    pasta::{
+        group::ff::{Field, PrimeField},
+        pallas,
+    },
+    tx::ContractCall,
+};
+use darkfi_serial::{deserialize, serialize, Encodable};
+use log::info;
+use rand::rngs::OsRng;
+
+use darkfi_money_contract::{
+    client::{build_half_swap_tx, build_transfer_tx, Coin, EncryptedNote, OwnCoin},
+    state::MoneyTransferParams,
+    MoneyFunction, ZKAS_BURN_NS, ZKAS_MINT_NS,
+};
+
+#[async_std::test]
+async fn money_contract_swap() -> Result<()> {
+    // Debug log configuration
+    let mut cfg = simplelog::ConfigBuilder::new();
+    cfg.add_filter_ignore("sled".to_string());
+    simplelog::TermLogger::init(
+        simplelog::LevelFilter::Debug,
+        cfg.build(),
+        simplelog::TerminalMode::Mixed,
+        simplelog::ColorChoice::Auto,
+    )?;
+
+    // A keypair we can use for the faucet whitelist
+    let faucet_kp = Keypair::random(&mut OsRng);
+
+    // A keypair we'll use for Alice
+    let alice_kp = Keypair::random(&mut OsRng);
+
+    // A keypair we'll use for Bob
+    let bob_kp = Keypair::random(&mut OsRng);
+
+    // The faucet's pubkey is allowed to make clear inputs
+    let faucet_pubkeys = vec![faucet_kp.public];
+
+    // The wallets are just noops to get around the ValidatorState API
+    let faucet_wallet = WalletDb::new("sqlite::memory:", "foo").await?;
+    let alice_wallet = WalletDb::new("sqlite::memory:", "foo").await?;
+    let bob_wallet = WalletDb::new("sqlite::memory:", "foo").await?;
+
+    // Our main sled database references which live in memory during this test.
+    info!("Initializing ValidatorState");
+    let faucet_sled_db = sled::Config::new().temporary(true).open()?;
+    let alice_sled_db = sled::Config::new().temporary(true).open()?;
+    let bob_sled_db = sled::Config::new().temporary(true).open()?;
+
+    let faucet_state = ValidatorState::new(
+        &faucet_sled_db,
+        *TESTNET_GENESIS_TIMESTAMP,
+        *TESTNET_GENESIS_HASH_BYTES,
+        faucet_wallet,
+        faucet_pubkeys.clone(),
+        false,
+    )
+    .await?;
+
+    let alice_state = ValidatorState::new(
+        &alice_sled_db,
+        *TESTNET_GENESIS_TIMESTAMP,
+        *TESTNET_GENESIS_HASH_BYTES,
+        alice_wallet,
+        faucet_pubkeys.clone(),
+        false,
+    )
+    .await?;
+
+    let bob_state = ValidatorState::new(
+        &bob_sled_db,
+        *TESTNET_GENESIS_TIMESTAMP,
+        *TESTNET_GENESIS_HASH_BYTES,
+        bob_wallet,
+        faucet_pubkeys.clone(),
+        false,
+    )
+    .await?;
+
+    // In a hacky way, we just generate the proving keys for the circuits used.
+    info!("Looking up zkas circuits from DB");
+    let contract_id = ContractId::from(pallas::Base::from(u64::MAX - 420));
+
+    let alice_sled = &alice_state.read().await.blockchain.sled_db;
+    let db_handle = alice_state.read().await.blockchain.contracts.lookup(
+        alice_sled,
+        &contract_id,
+        ZKAS_DB_NAME,
+    )?;
+
+    let mint_zkbin = db_handle.get(&serialize(&ZKAS_MINT_NS))?.unwrap();
+    let burn_zkbin = db_handle.get(&serialize(&ZKAS_BURN_NS))?.unwrap();
+    info!("Decoding bincode");
+    let mint_zkbin = ZkBinary::decode(&mint_zkbin.clone())?;
+    let burn_zkbin = ZkBinary::decode(&burn_zkbin.clone())?;
+    let mint_witnesses = empty_witnesses(&mint_zkbin);
+    let burn_witnesses = empty_witnesses(&burn_zkbin);
+    let mint_circuit = ZkCircuit::new(mint_witnesses, mint_zkbin.clone());
+    let burn_circuit = ZkCircuit::new(burn_witnesses, burn_zkbin.clone());
+
+    info!("Creating zk proving keys");
+    let k = 13;
+    let mut proving_keys = HashMap::<[u8; 32], Vec<(&str, ProvingKey)>>::new();
+    let mint_pk = ProvingKey::build(k, &mint_circuit);
+    let burn_pk = ProvingKey::build(k, &burn_circuit);
+    let pks = vec![(ZKAS_MINT_NS, mint_pk.clone()), (ZKAS_BURN_NS, burn_pk.clone())];
+    proving_keys.insert(contract_id.inner().to_repr(), pks);
+
+    // We also have to initialize the Merkle trees used for coins.
+    info!("Initializing Merkle trees");
+    let mut faucet_merkle_tree = BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(100);
+    let mut alice_merkle_tree = BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(100);
+    let mut bob_merkle_tree = BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(100);
+
+    // The faucet will now mint some tokens for Alice and for Bob
+    let alice_token_id = TokenId::from(pallas::Base::random(&mut OsRng));
+    let bob_token_id = TokenId::from(pallas::Base::random(&mut OsRng));
+    let alice_amount = decode_base10("42.69", 8, true)?;
+    let bob_amount = decode_base10("69.42", 8, true)?;
+
+    info!("Building transfer tx for Alice's airdrop");
+    let (params, proofs, secret_keys, _spent_coins) = build_transfer_tx(
+        &faucet_kp,
+        &alice_kp.public,
+        alice_amount,
+        alice_token_id,
+        &[],
+        &faucet_merkle_tree,
+        &mint_zkbin,
+        &mint_pk,
+        &burn_zkbin,
+        &burn_pk,
+        true,
+    )?;
+
+    // Build transaction
+    let mut data = vec![MoneyFunction::Transfer as u8];
+    params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &secret_keys)?;
+    tx.signatures = vec![sigs];
+
+    // Let's first execute this transaction for the faucet to see if it passes.
+    // Then Alice gets the tx and also executes it.
+    info!("Executing transaction on the faucet's blockchain db");
+    faucet_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+    faucet_merkle_tree.append(&MerkleNode::from(params.outputs[0].coin));
+
+    info!("Executing transaction on Alice's blockchain db");
+    alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+    // TODO: FIXME: Actually have a look at the `merkle_add` calls
+    alice_merkle_tree.append(&MerkleNode::from(params.outputs[0].coin));
+    let leaf_position = alice_merkle_tree.witness().unwrap();
+
+    info!("Executing transaction on Bob's blockchain db");
+    bob_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+    bob_merkle_tree.append(&MerkleNode::from(params.outputs[0].coin));
+
+    let params: MoneyTransferParams = deserialize(&tx.calls[0].data[1..])?;
+    let output = &params.outputs[0];
+    let encrypted_note =
+        EncryptedNote { ciphertext: output.ciphertext.clone(), ephem_public: output.ephem_public };
+    let note = encrypted_note.decrypt(&alice_kp.secret)?;
+
+    let mut alice_owncoins = vec![];
+    let owncoin = OwnCoin {
+        coin: Coin::from(output.coin),
+        note: note.clone(),
+        secret: alice_kp.secret, // <-- What should this be?
+        nullifier: Nullifier::from(poseidon_hash([alice_kp.secret.inner(), note.serial])),
+        leaf_position,
+    };
+    alice_owncoins.push(owncoin);
+
+    info!("Building transfer tx for Bob's airdrop");
+    let (params, proofs, secret_keys, _spent_coins) = build_transfer_tx(
+        &faucet_kp,
+        &bob_kp.public,
+        bob_amount,
+        bob_token_id,
+        &[],
+        &faucet_merkle_tree,
+        &mint_zkbin,
+        &mint_pk,
+        &burn_zkbin,
+        &burn_pk,
+        true,
+    )?;
+
+    // Build transaction
+    let mut data = vec![MoneyFunction::Transfer as u8];
+    params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &secret_keys)?;
+    tx.signatures = vec![sigs];
+
+    // Let's first execute this transaction for the faucet to see if it passes.
+    // Then Alice gets the tx and also executes it.
+    info!("Executing transaction on the faucet's blockchain db");
+    faucet_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+    faucet_merkle_tree.append(&MerkleNode::from(params.outputs[0].coin));
+
+    info!("Executing transaction on Alice's blockchain db");
+    alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+    alice_merkle_tree.append(&MerkleNode::from(params.outputs[0].coin));
+
+    info!("Executing transaction on Bob's blockchain db");
+    bob_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+    bob_merkle_tree.append(&MerkleNode::from(params.outputs[0].coin));
+    let leaf_position = bob_merkle_tree.witness().unwrap();
+
+    let params: MoneyTransferParams = deserialize(&tx.calls[0].data[1..])?;
+    let output = &params.outputs[0];
+    let encrypted_note =
+        EncryptedNote { ciphertext: output.ciphertext.clone(), ephem_public: output.ephem_public };
+    let note = encrypted_note.decrypt(&bob_kp.secret)?;
+
+    let mut bob_owncoins = vec![];
+    let owncoin = OwnCoin {
+        coin: Coin::from(output.coin),
+        note: note.clone(),
+        secret: bob_kp.secret, // <-- What should this be?
+        nullifier: Nullifier::from(poseidon_hash([bob_kp.secret.inner(), note.serial])),
+        leaf_position,
+    };
+    bob_owncoins.push(owncoin);
+
+    // Now Alice and Bob should have their tokens. They can attempt to swap them.
+    // Alice will create a transaction half, and send it to Bob, which he can inspect
+    // and add his half, sign it, and return to Alice. The Alice can do the inspection
+    // and sign with her key, and broadcast the transaction.
+    let (
+        alice_half_params,
+        alice_half_proofs,
+        alice_half_keys,
+        _alice_half_spent_coins,
+        alice_value_blinds,
+    ) = build_half_swap_tx(
+        &alice_kp.public,
+        alice_amount,
+        alice_token_id,
+        bob_amount,
+        bob_token_id,
+        &[],
+        &alice_owncoins,
+        &alice_merkle_tree,
+        &mint_zkbin,
+        &mint_pk,
+        &burn_zkbin,
+        &burn_pk,
+    )?;
+
+    let (bob_half_params, bob_half_proofs, bob_half_keys, _bob_half_spent_coins, _bob_value_blinds) =
+        build_half_swap_tx(
+            &bob_kp.public,
+            bob_amount,
+            bob_token_id,
+            alice_amount,
+            alice_token_id,
+            &alice_value_blinds,
+            &bob_owncoins,
+            &bob_merkle_tree,
+            &mint_zkbin,
+            &mint_pk,
+            &burn_zkbin,
+            &burn_pk,
+        )?;
+
+    let bob_full_params = MoneyTransferParams {
+        clear_inputs: vec![],
+        inputs: vec![alice_half_params.inputs[0].clone(), bob_half_params.inputs[0].clone()],
+        outputs: vec![alice_half_params.outputs[0].clone(), bob_half_params.outputs[0].clone()],
+    };
+
+    assert!(bob_full_params.inputs.len() == 2);
+    assert!(bob_full_params.outputs.len() == 2);
+
+    let mut bob_full_proofs = vec![];
+    bob_full_proofs.extend_from_slice(&alice_half_proofs);
+    bob_full_proofs.extend_from_slice(&bob_half_proofs);
+
+    let mut data = vec![MoneyFunction::OtcSwap as u8];
+    bob_full_params.encode(&mut data)?;
+    let calls = vec![ContractCall { contract_id, data }];
+    let proofs = vec![bob_full_proofs];
+    let mut tx = Transaction { calls, proofs, signatures: vec![] };
+    let sigs = tx.create_sigs(&mut OsRng, &bob_half_keys)?;
+    tx.signatures = vec![sigs];
+
+    // This tx finds its way back to Alice.
+    // She can try broadcasting the tx without signing, but this should fail to verify.
+    info!("[Alice] Verifying half-signed swap transaction (should fail)");
+    assert!(alice_state.read().await.verify_transactions(&[tx.clone()], false).await.is_err());
+
+    // So she signs it.
+    let sigs = tx.create_sigs(&mut OsRng, &alice_half_keys)?;
+    tx.signatures.push(sigs);
+
+    info!("[Alice] Verifying signed swap transaction");
+    // Now the transaction is signed by both parties.
+    // Let's execute it on Alice's chain state.
+    alice_state.read().await.verify_transactions(&[tx.clone()], true).await?;
+    // Alice's received coin is in outputs[0]
+    alice_merkle_tree.append(&MerkleNode::from(params.outputs[0].coin));
+    let leaf_position = alice_merkle_tree.witness().unwrap();
+    // This is Bob's received coin
+    alice_merkle_tree.append(&MerkleNode::from(params.outputs[1].coin));
+
+    Ok(())
+}

+ 4 - 8
src/contract/money/tests/transfer.rs

@@ -47,13 +47,13 @@ use darkfi_sdk::{
     tx::ContractCall,
 };
 use darkfi_serial::{deserialize, serialize, Encodable};
-use log::{debug, info};
+use log::info;
 use rand::rngs::OsRng;
 
 use darkfi_money_contract::{
     client::{build_transfer_tx, Coin, EncryptedNote, OwnCoin},
     state::MoneyTransferParams,
-    MoneyFunction,
+    MoneyFunction, ZKAS_BURN_NS, ZKAS_MINT_NS,
 };
 
 #[async_std::test]
@@ -130,11 +130,10 @@ async fn money_contract_transfer() -> Result<()> {
 
     info!("Creating zk proving keys");
     let k = 13;
-    let mut proving_keys = HashMap::<[u8; 32], Vec<(String, ProvingKey)>>::new();
+    let mut proving_keys = HashMap::<[u8; 32], Vec<(&str, ProvingKey)>>::new();
     let mint_pk = ProvingKey::build(k, &mint_circuit);
     let burn_pk = ProvingKey::build(k, &burn_circuit);
-    let pks =
-        vec![(zkas_mint_ns.clone(), mint_pk.clone()), (zkas_burn_ns.clone(), burn_pk.clone())];
+    let pks = vec![(ZKAS_MINT_NS, mint_pk.clone()), (ZKAS_BURN_NS, burn_pk.clone())];
     proving_keys.insert(contract_id.inner().to_repr(), pks);
 
     // We also have to initialize the Merkle trees used for coins.
@@ -161,9 +160,6 @@ async fn money_contract_transfer() -> Result<()> {
         true,
     )?;
 
-    debug!("PARAMS: {:#?}", params);
-    debug!("PROOFS: {:?}", proofs);
-
     // Build transaction
     let mut data = vec![MoneyFunction::Transfer as u8];
     params.encode(&mut data)?;