Ver código fonte

bin: Remove darkotc and merge it with drk

parazyd 3 anos atrás
pai
commit
f722db3131

+ 0 - 17
Cargo.lock

@@ -1342,23 +1342,6 @@ dependencies = [
  "url",
 ]
 
-[[package]]
-name = "darkotc"
-version = "0.3.0"
-dependencies = [
- "async-std",
- "bs58",
- "clap 4.0.29",
- "darkfi",
- "darkfi-sdk",
- "darkfi-serial",
- "halo2_gadgets",
- "halo2_proofs",
- "rand",
- "serde_json",
- "url",
-]
-
 [[package]]
 name = "darkwiki"
 version = "0.4.0"

+ 0 - 1
Cargo.toml

@@ -22,7 +22,6 @@ members = [
     "bin/zkas",
     #"bin/cashierd",
     "bin/darkfid",
-    "bin/darkotc",
     "bin/drk",
     "bin/faucetd",
     "bin/fud/fu",

+ 0 - 22
bin/darkotc/Cargo.toml

@@ -1,22 +0,0 @@
-[package]
-name = "darkotc"
-version = "0.3.0"
-homepage = "https://dark.fi"
-description = "Atomic swap commandline tool"
-authors = ["Dyne.org foundation <foundation@dyne.org>"]
-repository = "https://github.com/darkrenaissance/darkfi"
-license = "AGPL-3.0-only"
-edition = "2021"
-
-[dependencies]
-async-std = {version = "1.12.0", features = ["attributes"]}
-bs58 = "0.4.0"
-clap = {version = "4.0.29", features = ["derive"]}
-darkfi = {path = "../../", features = ["crypto", "rpc", "util", "tx"]}
-darkfi-sdk = {path = "../../src/sdk"}
-darkfi-serial = {path = "../../src/serial"}
-halo2_proofs = "0.2.0"
-halo2_gadgets = "0.2.0"
-rand = "0.8.5"
-serde_json = "1.0.89"
-url = "2.3.1"

+ 0 - 142
bin/darkotc/README.md

@@ -1,142 +0,0 @@
-darkotc
-=======
-
-Commandline tool for atomic swaps.
-
-
-## Usage (localnet)
-
-Prerequisite: Compile all the tools:
-
-```
-% make BINS="darkfid drk faucetd darkotc"
-```
-
-First start the localnet simulation from `contrib/localnet/darkfid`
-called `tmux_sessions.sh` and allow them all to run and sync up.
-
-We will communicate with two darkfid nodes which represent two peers
-in the atomic swap, and with the faucet, so we can get some coins in
-our wallets.
-
-* The first endpoint (Alice) JSON-RPC URI is: `tcp://127.0.0.1:8440`.
-* The second endpoint (Bob) JSON-RPC URI is: `tcp://127.0.0.1:8540`.
-* The faucet JSON-RPC URI is: `tcp://127.0.0.1:8640`.
-
-
-### Airdropping shitcoins
-
-Once the daemons are running, we can interact with them. First we
-will airdrop some coins to both Alice and Bob, so they can swap them
-between each other. We'll also export some variables for easier usage.
-
-```
-% export ALICE_RPC="tcp://127.0.0.1:8440"
-% export BOB_RPC="tcp://127.0.0.1:8540"
-% export FAUCET_RPC="tcp://127.0.0.1:8640"
-% export ALICE_TOKEN="A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd"
-% export BOB_TOKEN="BNBZ9YprWvEGMYHW4dFvbLuLfHnN9Bs64zuTFQAbw9Dy"
-% ./drk -e "${ALICE_RPC}" airdrop --faucet-endpoint "${FAUCET_RPC}" \
-    --token-id "${ALICE_TOKEN}" 113
-% ./drk -e "${BOB_RPC}" airdrop --faucet-endpoint "${FAUCET_RPC}" \
-    --token-id "${BOB_TOKEN}" 14
-```
-
-This will airdrop 113 `ALICE_TOKEN` into Alice's wallet, and 14
-`BOB_TOKEN` into Bob's wallet.
-
-Wait a bit until the transactions settle on the blockchain and they
-appear in your wallet. You can check this with the `drk` utility:
-
-```
-% ./drk -e "${ALICE_RPC}" wallet --balance
-% ./drk -e "${BOB_RPC}" wallet --balance
-```
-
-Once the coins are settled, you should see them in a table after
-running one of the above commands.
-
-### Doing everyone's part
-
-After we have the coins in the wallets, Alice and Bob decide they
-want to swap 113 `ALICE_TOKEN` for 14 `BOB_TOKEN`. Now both of them
-will do a partial transaction which they are going to join if they
-both agree on the parameters, and finally publish it on the network.
-
-So first, let's have Alice do her part:
-
-```
-% ./darkotc -e "${ALICE_RPC}" init -t "${ALICE_TOKEN}:${BOB_TOKEN}" \
-    -v 113:14 > alice_partial_swap
-```
-
-This command will make the `darkotc` tool communicate with
-her `darkfid` instance and gather necessary information to
-create her half of the atomic swap. This includes the ZK mint
-and burn proofs, and all the necessary transaction data (see
-`darkotc/src/main.rs::PartialSwapData`).
-
-The way this works is that Alice _mints_ the tokens she wants to
-receive (in this case, 14 `BOB_TOKEN`, and _burns_ the token she
-wants to send to Bob (in this case 113 `ALICE_TOKEN`). In turn,
-Bob does the same with his tokens.
-
-Once Alice builds her half of the atomic swap, she can send the newly
-created file that we redirected from running the `darkotc` tool to
-Bob over an encrypted channel. When Bob has it, he can inspect if
-everything is in order, beacuse Alice will send all the blinding
-values necessary to convince Bob that the transaction they will make
-is true.  Note however that this cannot be exploited in a way of
-stealing Alice's coins, because Alice does not create a signature
-for the data she is sending, so if Bob published something else,
-it would not be considered valid on the network.
-
-To inspect the `alice_partial_swap` file, Bob runs:
-
-```
-% ./darkotc -e "${BOB_RPC}" inspect-partial < alice_partial_swap
-```
-
-The tool will then verify all pieces of the partial transaction
-and report what is or isn't valid. If Bob is satisfied, then he can
-proceed by creating his half of the atomic swap:
-
-```
-% ./darkotc -e "${BOB_RPC}" init -t "${BOB_TOKEN}:${ALICE_TOKEN}" \
-    -v 14:113 > bob_partial_swap
-```
-
-(Note how the values separated by `:` are swapped in contrast to what
-Alice has done).
-
-Now Bob can send this back to Alice for verification. We'll leave that
-as an excercise to the reader.
-
-Once both parties are satisfied with the contents, they can join the
-two halves into a full transaction, and sign it. First, Bob will join
-both parts and create a signature:
-
-```
-% ./darkotc -e "${BOB_RPC}" join alice_partial_swap bob_partial_swap \
-    | ./darkotc -e "${BOB_RPC}" sign-tx > bob_signed_swap
-```
-
-Now the `bob_signed_swap` file contains a full atomic swap signed
-by Bob. However we're still missing Alice's signature. So Bob sends
-back the signed data to Alice, and she can now sign and broadcast it
-to the network.
-
-```
-% ./darkotc -e "${ALICE_RPC}" sign-tx < bob_signed_swap > signed_swap \
-    | ./drk -e "${ALICE_RPC}" broadcast
-```
-
-On success, Alice should see a transaction ID. Now wait until the
-swap settles on the network, and check the balances:
-
-```
-% ./drk -e "${ALICE_RPC}" wallet --balance
-% ./drk -e "${BOB_RPC}" wallet --balance
-```
-
-Alice and Bob successfully executed an atomic swap!

+ 0 - 612
bin/darkotc/src/main.rs

@@ -1,612 +0,0 @@
-/* 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::{
-    io::{stdin, Read},
-    process::exit,
-};
-
-use clap::{Parser, Subcommand};
-use darkfi_sdk::crypto::{
-    pedersen::{pedersen_commitment_base, pedersen_commitment_u64},
-    schnorr,
-    schnorr::SchnorrSecret,
-    PublicKey, SecretKey, TokenId,
-};
-use darkfi_serial::{deserialize, serialize, SerialDecodable, SerialEncodable};
-use halo2_proofs::arithmetic::Field;
-use rand::rngs::OsRng;
-use url::Url;
-
-use darkfi::{
-    cli_desc,
-    crypto::{
-        burn_proof::{create_burn_proof, verify_burn_proof},
-        mint_proof::{create_mint_proof, verify_mint_proof},
-        note::{EncryptedNote, Note},
-        proof::{ProvingKey, VerifyingKey},
-        types::{
-            DrkCoinBlind, DrkSerial, DrkSpendHook, DrkUserData, DrkUserDataBlind, DrkValueBlind,
-        },
-        BurnRevealedValues, MintRevealedValues, Proof,
-    },
-    rpc::client::RpcClient,
-    tx::{
-        partial::{PartialTransaction, PartialTransactionInput},
-        Transaction, TransactionInput, TransactionOutput,
-    },
-    util::{
-        cli::{fg_green, fg_red, progress_bar},
-        parse::encode_base10,
-    },
-    zk::circuit::{BurnContract, MintContract},
-    Result,
-};
-
-mod cli_util;
-use cli_util::{parse_token_pair, parse_value_pair};
-mod rpc;
-use rpc::Rpc;
-
-#[derive(Parser)]
-#[clap(name = "darkotc", about = cli_desc!(), version)]
-#[clap(arg_required_else_help(true))]
-struct Args {
-    #[clap(short, parse(from_occurrences))]
-    /// Increase verbosity (-vvv supported)
-    verbose: u8,
-
-    #[clap(short, long, default_value = "tcp://127.0.0.1:8340")]
-    /// darkfid JSON-RPC endpoint
-    endpoint: Url,
-
-    #[clap(subcommand)]
-    command: Subcmd,
-}
-
-#[derive(Subcommand)]
-enum Subcmd {
-    /// Initialize an atomic swap
-    Init {
-        #[clap(short, long)]
-        /// Pair of token IDs to swap: token_to_send:token_to_recv
-        token_pair: String,
-
-        #[clap(short, long)]
-        /// Pair of values to swap: value_to_send:value_to_recv
-        value_pair: String,
-    },
-
-    /// Inspect partial swap data from stdin.
-    InspectPartial,
-
-    /// Join two partial swap data files and build a tx
-    Join { data0: String, data1: String },
-
-    /// Sign a transaction given from stdin.
-    SignTx,
-}
-
-#[derive(SerialEncodable, SerialDecodable)]
-/// Half of the swap data, includes the coin that is supposed to be received,
-/// and the coin that is supposed to be sent.
-struct PartialSwapData {
-    /// Mint proof of coin to be received
-    mint_proof: Proof,
-    /// Public values for the mint proof
-    mint_revealed: MintRevealedValues,
-    /// Value of the coin to be received
-    mint_value: u64,
-    /// Token ID of the coin to be received
-    mint_token: TokenId,
-    /// Blinding factor for the minted value pedersen commitment
-    mint_value_blind: DrkValueBlind,
-    /// Blinding factor for the minted token ID pedersen commitment
-    mint_token_blind: DrkValueBlind,
-    /// Burn proof of the coin to be sent
-    burn_proof: Proof,
-    /// Public values for the burn proof
-    burn_revealed: BurnRevealedValues,
-    /// Value of the coin to be sent
-    burn_value: u64,
-    /// Token ID of the coin to be sent
-    burn_token: TokenId,
-    /// Blinding factor for the burned value pedersen commitment
-    burn_value_blind: DrkValueBlind,
-    /// Blinding factor for the burned token ID pedersen commitment
-    burn_token_blind: DrkValueBlind,
-    /// Encrypted note
-    encrypted_note: EncryptedNote,
-}
-
-#[derive(SerialEncodable, SerialDecodable)]
-/// Full swap data, containing two instances of `PartialSwapData`, which
-/// represent an atomic swap.
-struct SwapData {
-    swap0: PartialSwapData,
-    swap1: PartialSwapData,
-}
-
-async fn init_swap(
-    endpoint: Url,
-    token_pair: (TokenId, TokenId),
-    value_pair: (u64, u64),
-) -> Result<PartialSwapData> {
-    let rpc_client = match RpcClient::new(endpoint).await {
-        Ok(v) => v,
-        Err(e) => {
-            eprintln!("Error: Failed connecting to darkfid JSON-RPC endpoint.");
-            return Err(e)
-        }
-    };
-    let rpc = Rpc { rpc_client };
-
-    // TODO: Implement metadata for decimals, don't hardcode.
-    let vp = value_pair;
-
-    // Connect to darkfid and see if there's available funds.
-    let balance = rpc.balance_of(token_pair.0).await?;
-    if balance < vp.0 {
-        eprintln!(
-            "Error: There's not enough balance for token \"{}\" in your wallet.",
-            token_pair.0
-        );
-        eprintln!("Available balance is {} ({})", encode_base10(balance, 8), balance);
-        exit(1);
-    }
-
-    // If there's not enough funds in a single coin, mint a single new coin
-    // with the funds. We do this to minimize the size of the swap transaction.
-    // i.e. 2 inputs and 2 outputs.
-    // TODO: Implement ^
-    // TODO: Maybe this should be done by the user beforehand?
-
-    // Find a coin to spend. We can find multiple, but we'll pick the first one.
-    let coins = rpc.get_coins_valtok(vp.0, token_pair.0).await?;
-    if coins.is_empty() {
-        eprintln!("Error: Did not manage to find a coin with enough value to spend.");
-        exit(1);
-    }
-
-    // Fetch our default address
-    let our_addr = rpc.wallet_address().await?;
-    let our_pubk = match PublicKey::try_from(our_addr) {
-        Ok(v) => v,
-        Err(e) => {
-            eprintln!("Error converting our address into PublicKey: {}", e);
-            exit(1);
-        }
-    };
-
-    // Build ZK proving keys
-    let pb = progress_bar("Building proving key for the Mint contract");
-    let mint_pk = ProvingKey::build(11, &MintContract::default());
-    pb.finish();
-
-    let pb = progress_bar("Building proving key for the Burn contract");
-    let burn_pk = ProvingKey::build(11, &BurnContract::default());
-    pb.finish();
-
-    // The coin we want to receive
-    let recv_value_blind = DrkValueBlind::random(&mut OsRng);
-    let recv_token_blind = DrkValueBlind::random(&mut OsRng);
-    let recv_coin_blind = DrkCoinBlind::random(&mut OsRng);
-    let recv_serial = DrkSerial::random(&mut OsRng);
-
-    // Spend hook and user data disabled
-    let spend_hook = DrkSpendHook::from(0);
-    let user_data = DrkUserData::from(0);
-
-    let pb = progress_bar("Building Mint proof for the receiving coin");
-    let (mint_proof, mint_revealed) = create_mint_proof(
-        &mint_pk,
-        vp.1,
-        token_pair.1,
-        recv_value_blind,
-        recv_token_blind,
-        recv_serial,
-        spend_hook,
-        user_data,
-        recv_coin_blind,
-        our_pubk,
-    )?;
-    pb.finish();
-
-    // The coin we are spending.
-    let coin = coins[0].clone();
-
-    let pb = progress_bar("Building Burn proof for the spending coin");
-    let signature_secret = SecretKey::random(&mut OsRng);
-    let merkle_path = match rpc.get_merkle_path(usize::from(coin.leaf_position)).await {
-        Ok(v) => v,
-        Err(e) => {
-            eprintln!("Failed to get Merkle path for our coin from darkfid RPC: {}", e);
-            exit(1);
-        }
-    };
-
-    // Spend hook and user data disabled
-    let spend_hook = DrkSpendHook::from(0);
-    let user_data = DrkUserData::from(0);
-    let user_data_blind = DrkUserDataBlind::random(&mut OsRng);
-
-    let (burn_proof, burn_revealed) = create_burn_proof(
-        &burn_pk,
-        vp.0,
-        token_pair.0,
-        coin.note.value_blind,
-        coin.note.token_blind,
-        coin.note.serial,
-        spend_hook,
-        user_data,
-        user_data_blind,
-        coin.note.coin_blind,
-        coin.secret,
-        coin.leaf_position,
-        merkle_path,
-        signature_secret,
-    )?;
-    pb.finish();
-
-    // Create encrypted note
-    let note = Note {
-        serial: recv_serial,
-        value: vp.1,
-        token_id: token_pair.1,
-        coin_blind: recv_coin_blind,
-        value_blind: recv_value_blind,
-        token_blind: recv_token_blind,
-        // Here we store our secret key we used for signing
-        memo: serialize(&signature_secret),
-    };
-    let encrypted_note = note.encrypt(&our_pubk)?;
-
-    // Pack proofs together with pedersen commitment openings so
-    // counterparty can verify correctness.
-    let partial_swap_data = PartialSwapData {
-        mint_proof,
-        mint_revealed,
-        mint_value: vp.1,
-        mint_token: token_pair.1,
-        mint_value_blind: recv_value_blind,
-        mint_token_blind: recv_token_blind,
-        burn_proof,
-        burn_value: vp.0,
-        burn_token: token_pair.0,
-        burn_revealed,
-        burn_value_blind: coin.note.value_blind,
-        burn_token_blind: coin.note.token_blind,
-        encrypted_note,
-    };
-
-    Ok(partial_swap_data)
-}
-
-fn inspect_partial(data: &str) -> Result<()> {
-    let bytes = match bs58::decode(data).into_vec() {
-        Ok(v) => v,
-        Err(e) => {
-            eprintln!("Error decoding base58 data from input: {}", e);
-            exit(1);
-        }
-    };
-
-    let sd: PartialSwapData = match deserialize(&bytes) {
-        Ok(v) => v,
-        Err(e) => {
-            eprintln!("Error deserializing partial swap data into struct: {}", e);
-            exit(1);
-        }
-    };
-
-    eprintln!("Successfully decoded partial swap data");
-
-    // Build ZK verifying keys
-    let pb = progress_bar("Building verifying key for the Mint contract");
-    let mint_vk = VerifyingKey::build(11, &MintContract::default());
-    pb.finish();
-
-    let pb = progress_bar("Building verifying key for the Burn contract");
-    let burn_vk = VerifyingKey::build(11, &BurnContract::default());
-    pb.finish();
-
-    let pb = progress_bar("Verifying Burn proof");
-    let burn_valid = verify_burn_proof(&burn_vk, &sd.burn_proof, &sd.burn_revealed).is_ok();
-    pb.finish();
-
-    let pb = progress_bar("Verifying Mint proof");
-    let mint_valid = verify_mint_proof(&mint_vk, &sd.mint_proof, &sd.mint_revealed).is_ok();
-    pb.finish();
-
-    eprintln!("  Verifying Pedersen commitments");
-
-    let burn_value_valid = pedersen_commitment_u64(sd.burn_value, sd.burn_value_blind) ==
-        sd.burn_revealed.value_commit;
-
-    let burn_token_valid = pedersen_commitment_base(sd.burn_token.inner(), sd.burn_token_blind) ==
-        sd.burn_revealed.token_commit;
-
-    let mint_value_valid = pedersen_commitment_u64(sd.mint_value, sd.mint_value_blind) ==
-        sd.mint_revealed.value_commit;
-
-    let mint_token_valid = pedersen_commitment_base(sd.mint_token.inner(), sd.mint_token_blind) ==
-        sd.mint_revealed.token_commit;
-
-    let mut valid = true;
-    eprintln!("Summary:");
-
-    eprint!("  Burn proof: ");
-    if burn_valid {
-        eprintln!("{}", fg_green("VALID"));
-    } else {
-        eprintln!("{}", fg_red("INVALID"));
-        valid = false;
-    }
-
-    eprint!("  Burn proof value commitment: ");
-    if burn_value_valid {
-        eprintln!("{}", fg_green("VALID"));
-    } else {
-        eprintln!("{}", fg_red("INVALID"));
-        valid = false;
-    }
-
-    eprint!("  Burn proof token commitment: ");
-    if burn_token_valid {
-        eprintln!("{}", fg_green("VALID"));
-    } else {
-        eprintln!("{}", fg_red("INVALID"));
-        valid = false;
-    }
-
-    eprint!("  Mint proof: ");
-    if mint_valid {
-        eprintln!("{}", fg_green("VALID"));
-    } else {
-        eprintln!("{}", fg_red("INVALID"));
-        valid = false;
-    }
-
-    eprint!("  Mint proof value commitment: ");
-    if mint_value_valid {
-        eprintln!("{}", fg_green("VALID"));
-    } else {
-        eprintln!("{}", fg_red("INVALID"));
-        valid = false;
-    }
-
-    eprint!("  Mint proof token commitment: ");
-    if mint_token_valid {
-        eprintln!("{}", fg_green("VALID"));
-    } else {
-        eprintln!("{}", fg_red("INVALID"));
-        valid = false;
-    }
-
-    eprintln!("========================================");
-
-    eprintln!("Mint: {} {}", encode_base10(sd.mint_value, 8), sd.mint_token);
-    eprintln!("Burn: {} {}", encode_base10(sd.burn_value, 8), sd.burn_token);
-
-    eprint!("\nThe ZK proofs and commitments inspected are ");
-    if !valid {
-        println!("{}", fg_red("NOT VALID"));
-        exit(1);
-    } else {
-        eprintln!("{}", fg_green("VALID"));
-    }
-
-    Ok(())
-}
-
-async fn join(endpoint: Url, d0: PartialSwapData, d1: PartialSwapData) -> Result<Transaction> {
-    eprintln!("Joining data into a transaction");
-
-    let input0 = PartialTransactionInput { burn_proof: d0.burn_proof, revealed: d0.burn_revealed };
-    let input1 = PartialTransactionInput { burn_proof: d1.burn_proof, revealed: d1.burn_revealed };
-    let inputs = vec![input0, input1];
-
-    let output0 = TransactionOutput {
-        mint_proof: d0.mint_proof,
-        revealed: d0.mint_revealed,
-        enc_note: d0.encrypted_note.clone(),
-    };
-    let output1 = TransactionOutput {
-        mint_proof: d1.mint_proof,
-        revealed: d1.mint_revealed,
-        enc_note: d1.encrypted_note.clone(),
-    };
-    let outputs = vec![output0, output1];
-
-    let partial_tx = PartialTransaction { clear_inputs: vec![], inputs, outputs };
-    let unsigned_tx_data = serialize(&partial_tx);
-
-    let mut inputs = vec![];
-    let mut signed: bool;
-
-    eprint!("Trying to decrypt the note of the first half... ");
-    let rpc_client = RpcClient::new(endpoint.clone()).await?;
-    let rpc = Rpc { rpc_client };
-    let note = match rpc.decrypt_note(&d0.encrypted_note).await {
-        Ok(v) => v,
-        Err(_) => None,
-    };
-
-    if let Some(note) = note {
-        eprintln!("{}", fg_green("Success"));
-        let signature = try_sign_tx(&note, &unsigned_tx_data[..])?;
-        let input = TransactionInput::from_partial(partial_tx.inputs[0].clone(), signature);
-        inputs.push(input);
-        signed = true;
-    } else {
-        eprintln!("{}", fg_red("Failure"));
-        let signature = schnorr::Signature::dummy();
-        let input = TransactionInput::from_partial(partial_tx.inputs[0].clone(), signature);
-        inputs.push(input);
-        signed = false;
-    }
-
-    // If we have signed, we shouldn't have to look in the other one, but we might
-    // be sending to ourself for some reason.
-    eprint!("Trying to decrypt the note of the second half... ");
-    let rpc_client = RpcClient::new(endpoint).await?;
-    let rpc = Rpc { rpc_client };
-    let note = match rpc.decrypt_note(&d1.encrypted_note).await {
-        Ok(v) => v,
-        Err(_) => None,
-    };
-
-    if let Some(note) = note {
-        eprintln!("{}", fg_green("Success"));
-        let signature = try_sign_tx(&note, &unsigned_tx_data[..])?;
-        let input = TransactionInput::from_partial(partial_tx.inputs[1].clone(), signature);
-        inputs.push(input);
-        signed = true;
-    } else {
-        eprintln!("{}", fg_red("Failure"));
-        let signature = schnorr::Signature::dummy();
-        let input = TransactionInput::from_partial(partial_tx.inputs[1].clone(), signature);
-        inputs.push(input);
-        if !signed {
-            eprintln!("Error: Failed to sign transaction!");
-            exit(1);
-        }
-    }
-
-    if !signed {
-        eprintln!("Error: Failed to sign transaction!");
-        exit(1);
-    }
-
-    let tx = Transaction { clear_inputs: vec![], inputs, outputs: partial_tx.outputs };
-    Ok(tx)
-}
-
-async fn sign_tx(endpoint: Url, data: &str) -> Result<Transaction> {
-    eprintln!("Trying to sign transaction");
-    let mut tx: Transaction = deserialize(&bs58::decode(data).into_vec()?)?;
-
-    // We assume our input and our output are in the same index, since this
-    // transaction contains 2 inputs and 2 outputs, and one of each is ours,
-    // and one of each is the other party's. So we go on and sign the input
-    // index of the output index we can decrypt the note for.
-    let mut idx_to_sign = 0;
-    let mut signature = schnorr::Signature::dummy();
-
-    eprintln!("Looking for an encrypted note we can decrypt...");
-    let mut found_secret = false;
-    for (i, output) in tx.outputs.iter().enumerate() {
-        // TODO: FIXME: Consider not closing the RPC on failure.
-        let rpc = Rpc { rpc_client: RpcClient::new(endpoint.clone()).await? };
-
-        let note = match rpc.decrypt_note(&output.enc_note).await {
-            Ok(v) => v,
-            Err(_) => continue,
-        };
-
-        if let Some(note) = note {
-            eprintln!("Successfully decrypted note in output {}", i);
-            eprintln!("Creating signature...");
-            let mut unsigned_tx_data = vec![];
-            let _ = tx.encode_without_signature(&mut unsigned_tx_data)?;
-
-            signature = try_sign_tx(&note, &unsigned_tx_data[..])?;
-            found_secret = true;
-            idx_to_sign = i;
-            break
-        }
-
-        eprintln!("Failed to find a note to decrypt. Signing failed.");
-        exit(1);
-    }
-
-    if !found_secret {
-        eprintln!("Error: Did not manage to sign transaction. Couldn't find any secret keys.");
-        exit(1);
-    }
-
-    tx.inputs[idx_to_sign].signature = signature;
-    Ok(tx)
-}
-
-fn try_sign_tx(note: &Note, tx_data: &[u8]) -> Result<schnorr::Signature> {
-    if note.memo.len() != 32 {
-        eprintln!("Error: The note memo is not 32 bytes");
-        exit(1);
-    }
-
-    let secret = match SecretKey::from_bytes(note.memo.clone().try_into().unwrap()) {
-        Ok(v) => v,
-        Err(e) => {
-            eprintln!("Did not manage to cast bytes into SecretKey: {}", e);
-            exit(1);
-        }
-    };
-
-    eprintln!("Signing transaction...");
-    let signature = secret.sign(&mut OsRng, tx_data);
-    Ok(signature)
-}
-
-#[async_std::main]
-async fn main() -> Result<()> {
-    let args = Args::parse();
-
-    match args.command {
-        Subcmd::Init { token_pair, value_pair } => {
-            let token_pair = parse_token_pair(&token_pair)?;
-            let value_pair = parse_value_pair(&value_pair)?;
-            eprintln!("Creating half of an atomic swap");
-            eprintln!("Send: {} {} tokens.", encode_base10(value_pair.0, 8), token_pair.0);
-            eprintln!("Recv: {} {} tokens.", encode_base10(value_pair.1, 8), token_pair.1);
-            let swap_data = init_swap(args.endpoint, token_pair, value_pair).await?;
-
-            println!("{}", bs58::encode(serialize(&swap_data)).into_string());
-            Ok(())
-        }
-        Subcmd::InspectPartial => {
-            let mut buf = String::new();
-            stdin().read_to_string(&mut buf)?;
-
-            inspect_partial(buf.trim())
-        }
-        Subcmd::Join { data0, data1 } => {
-            let d0 = std::fs::read_to_string(data0)?;
-            let d1 = std::fs::read_to_string(data1)?;
-
-            let d0 = deserialize(&bs58::decode(&d0.trim()).into_vec()?)?;
-            let d1 = deserialize(&bs58::decode(&d1.trim()).into_vec()?)?;
-
-            let tx = join(args.endpoint, d0, d1).await?;
-
-            println!("{}", bs58::encode(&serialize(&tx)).into_string());
-            eprintln!("Successfully signed transaction");
-            Ok(())
-        }
-        Subcmd::SignTx => {
-            let mut buf = String::new();
-            stdin().read_to_string(&mut buf)?;
-
-            let tx = sign_tx(args.endpoint, buf.trim()).await?;
-
-            println!("{}", bs58::encode(&serialize(&tx)).into_string());
-            eprintln!("Successfully signed transaction");
-            Ok(())
-        }
-    }
-}

+ 0 - 205
bin/darkotc/src/rpc.rs

@@ -1,205 +0,0 @@
-/* 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::{process::exit, str::FromStr};
-
-use darkfi_sdk::crypto::{Address, MerkleNode, TokenId};
-use darkfi_serial::{deserialize, serialize};
-use serde_json::json;
-
-use darkfi::{
-    crypto::{
-        coin::OwnCoin,
-        note::{EncryptedNote, Note},
-    },
-    rpc::{client::RpcClient, jsonrpc::JsonRequest},
-    Result,
-};
-
-/// The RPC object with functionality for connecting to darkfid.
-pub struct Rpc {
-    pub rpc_client: RpcClient,
-}
-
-impl Rpc {
-    /// Fetch wallet balance of given token ID and return its u64 representation.
-    pub async fn balance_of(&self, token_id: TokenId) -> Result<u64> {
-        let req = JsonRequest::new("wallet.get_balances", json!([]));
-        let rep = self.rpc_client.request(req).await?;
-
-        if !rep.is_object() {
-            eprintln!("Error: Invalid balance data received from darkfid RPC endpoint.");
-            exit(1);
-        }
-
-        for i in rep.as_object().unwrap().keys() {
-            if TokenId::try_from(i.as_str()).unwrap() == token_id {
-                if let Some(balance) = rep[i].as_u64() {
-                    return Ok(balance)
-                }
-
-                eprintln!("Error: Invalid balance data received from darkfid RPC endpoint.");
-                exit(1);
-            }
-        }
-
-        Ok(0)
-    }
-
-    /// Fetch default wallet address from the darkfid RPC endpoint.
-    pub async fn wallet_address(&self) -> Result<Address> {
-        let req = JsonRequest::new("wallet.get_addrs", json!([0_i64]));
-        let rep = self.rpc_client.request(req).await?;
-
-        if !rep.is_array() || !rep.as_array().unwrap()[0].is_string() {
-            eprintln!("Error: Invalid wallet address received from darkfid RPC endpoint.");
-            exit(1);
-        }
-
-        match Address::from_str(rep[0].as_str().unwrap()) {
-            Ok(v) => Ok(v),
-            Err(e) => {
-                eprintln!(
-                    "Error: Invalid wallet address received from darkfid RPC endpoint: {}",
-                    e
-                );
-                exit(1)
-            }
-        }
-    }
-
-    /// Query wallet for unspent coins in wallet matching value and token_id.
-    pub async fn get_coins_valtok(&self, value: u64, token_id: TokenId) -> Result<Vec<OwnCoin>> {
-        let req = JsonRequest::new(
-            "wallet.get_coins_valtok",
-            json!([value, format!("{}", token_id), true]),
-        );
-        let rep = self.rpc_client.request(req).await?;
-
-        if !rep.is_array() {
-            eprintln!("Error: Invalid coin data received from darkfid RPC endpoint.");
-            exit(1);
-        }
-
-        let rep = rep.as_array().unwrap();
-        let mut ret = vec![];
-
-        for i in rep {
-            if !i.is_string() {
-                eprintln!(
-                    "Error: Invalid base58 data for OwnCoin received from darkfid RPC endpoint."
-                );
-                exit(1);
-            }
-
-            let data = match bs58::decode(i.as_str().unwrap()).into_vec() {
-                Ok(v) => v,
-                Err(e) => {
-                    eprintln!("Error: Failed decoding base58 data for OwnCoin: {}", e);
-                    exit(1);
-                }
-            };
-
-            let oc = match deserialize(&data) {
-                Ok(v) => v,
-                Err(e) => {
-                    eprintln!("Error: Failed deserializing OwnCoin: {}", e);
-                    exit(1);
-                }
-            };
-
-            ret.push(oc);
-        }
-
-        Ok(ret)
-    }
-
-    /// Fetch the merkle path for a given leaf position in the coin tree
-    pub async fn get_merkle_path(&self, leaf_pos: usize) -> Result<Vec<MerkleNode>> {
-        let req = JsonRequest::new("wallet.get_merkle_path", json!([leaf_pos as u64]));
-        let rep = self.rpc_client.request(req).await?;
-
-        if !rep.is_array() {
-            eprintln!("Error: Invalid merkle path data received from darkfid RPC endpoint.");
-            exit(1);
-        }
-
-        let rep = rep.as_array().unwrap();
-        let mut ret = vec![];
-
-        for i in rep {
-            if !i.is_string() {
-                eprintln!("Error: Invalid base58 data received for MerkleNode");
-                exit(1);
-            }
-
-            let n = match bs58::decode(i.as_str().unwrap()).into_vec() {
-                Ok(v) => v,
-                Err(e) => {
-                    eprintln!("Error: Failed decoding base58 for MerkleNode: {}", e);
-                    exit(1);
-                }
-            };
-
-            if n.len() != 32 {
-                eprintln!("error: MerkleNode byte length is not 32");
-                exit(1);
-            }
-
-            let n = MerkleNode::from_bytes(n.try_into().unwrap());
-            if n.is_none() {
-                eprintln!("Error: Noncanonical bytes of MerkleNode");
-                exit(1);
-            }
-
-            ret.push(n.unwrap());
-        }
-
-        Ok(ret)
-    }
-
-    /// Try to decrypt a given `EncryptedNote`
-    pub async fn decrypt_note(&self, enc_note: &EncryptedNote) -> Result<Option<Note>> {
-        let encoded = bs58::encode(&serialize(enc_note)).into_string();
-        let req = JsonRequest::new("wallet.decrypt_note", json!([encoded]));
-        let rep = self.rpc_client.oneshot_request(req).await?;
-
-        if !rep.is_string() {
-            eprintln!("Error: decrypt_note() RPC call returned invalid data");
-            exit(1);
-        }
-
-        let decoded = match bs58::decode(rep.as_str().unwrap()).into_vec() {
-            Ok(v) => v,
-            Err(e) => {
-                eprintln!("Error decoding base58 data received from RPC call: {}", e);
-                exit(1);
-            }
-        };
-
-        let note = match deserialize(&decoded) {
-            Ok(v) => v,
-            Err(e) => {
-                eprintln!("Failed deserializing bytes into Note: {}", e);
-                exit(1);
-            }
-        };
-
-        Ok(Some(note))
-    }
-}

+ 2 - 3
bin/darkotc/src/cli_util.rs → bin/drk/src/cli_util.rs

@@ -15,7 +15,6 @@
  * 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::process::exit;
 
 use darkfi::{util::parse::decode_base10, Result};
@@ -24,7 +23,7 @@ use darkfi_sdk::crypto::TokenId;
 pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
     let v: Vec<&str> = s.split(':').collect();
     if v.len() != 2 {
-        eprintln!("Invalid value pair. Use a pair such as '13.37:11.0'.");
+        eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
         exit(1);
     }
 
@@ -33,7 +32,7 @@ pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
     let val1 = decode_base10(v[1], 8, true);
 
     if val0.is_err() || val1.is_err() {
-        eprintln!("Invalid value pair. Use a pair such as '13.37:11.0'.");
+        eprintln!("Invalid value pair. Use a pair such as 13.37:11.0");
         exit(1);
     }
 

+ 85 - 5
bin/drk/src/main.rs

@@ -48,12 +48,20 @@ mod rpc_airdrop;
 /// Payment methods
 mod rpc_transfer;
 
+/// Swap methods
+mod rpc_swap;
+use rpc_swap::PartialSwapData;
+
 /// Blockchain methods
 mod rpc_blockchain;
 
 /// Wallet operation methods for darkfid's JSON-RPC
 mod rpc_wallet;
 
+/// CLI utility functions
+mod cli_util;
+use cli_util::{parse_token_pair, parse_value_pair};
+
 #[derive(Parser)]
 #[command(about = cli_desc!())]
 struct Args {
@@ -139,6 +147,10 @@ enum Subcmd {
         recipient: String,
     },
 
+    /// OTC atomic swap
+    #[command(subcommand)]
+    Otc(OtcSubcmd),
+
     /// Inspect a transaction from stdin
     Inspect,
 
@@ -160,6 +172,26 @@ enum Subcmd {
     },
 }
 
+#[derive(Subcommand)]
+enum OtcSubcmd {
+    /// Initialize the first half of the atomic swap
+    Init {
+        /// Value pair to send:recv (11.55:99.42)
+        #[clap(short, long)]
+        value_pair: String,
+
+        /// Token pair to send:recv (f00:b4r)
+        #[clap(short, long)]
+        token_pair: String,
+    },
+
+    /// Build entire swap tx given the first half from stdin
+    Join,
+
+    /// Sign a transaction given from stdin as the first-half
+    Sign,
+}
+
 pub struct Drk {
     pub rpc_client: RpcClient,
 }
@@ -267,7 +299,7 @@ async fn main() -> Result<()> {
                 drk.rpc_client.close().await?;
 
                 for i in coins {
-                    print!("{} ", bs58::encode(i.0.coin.inner().to_repr()).into_string());
+                    print!("{:?} ", i.0.coin.inner());
                     if i.1 {
                         println!("(spent)");
                     } else {
@@ -348,15 +380,64 @@ async fn main() -> Result<()> {
             Ok(())
         }
 
+        Subcmd::Otc(cmd) => {
+            let rpc_client = RpcClient::new(args.endpoint)
+                .await
+                .with_context(|| "Could not connect to darkfid RPC endpoint")?;
+
+            let drk = Drk { rpc_client };
+
+            match cmd {
+                OtcSubcmd::Init { value_pair, token_pair } => {
+                    let (vp_send, vp_recv) = parse_value_pair(&value_pair)?;
+                    let (tp_send, tp_recv) = parse_token_pair(&token_pair)?;
+
+                    let half = drk
+                        .init_swap(vp_send, tp_send, vp_recv, tp_recv)
+                        .await
+                        .with_context(|| "Failed to create swap transaction half")?;
+
+                    println!("{}", bs58::encode(&serialize(&half)).into_string());
+                    Ok(())
+                }
+
+                OtcSubcmd::Join => {
+                    let mut buf = String::new();
+                    stdin().read_to_string(&mut buf)?;
+                    let bytes = bs58::decode(&buf.trim()).into_vec()?;
+                    let partial: PartialSwapData = deserialize(&bytes)?;
+
+                    let tx = drk
+                        .join_swap(partial)
+                        .await
+                        .with_context(|| "Failed to create a join swap transaction")?;
+
+                    println!("{}", bs58::encode(&serialize(&tx)).into_string());
+                    Ok(())
+                }
+
+                OtcSubcmd::Sign => {
+                    let mut buf = String::new();
+                    stdin().read_to_string(&mut buf)?;
+                    let bytes = bs58::decode(&buf.trim()).into_vec()?;
+                    let mut tx: Transaction = deserialize(&bytes)?;
+
+                    drk.sign_swap(&mut tx)
+                        .await
+                        .with_context(|| "Failed to sign joined swap transaction")?;
+
+                    println!("{}", bs58::encode(&serialize(&tx)).into_string());
+                    Ok(())
+                }
+            }
+        }
+
         Subcmd::Inspect => {
             let mut buf = String::new();
             stdin().read_to_string(&mut buf)?;
-
             let bytes = bs58::decode(&buf.trim()).into_vec()?;
             let tx: Transaction = deserialize(&bytes)?;
-
             println!("{:#?}", tx);
-
             Ok(())
         }
 
@@ -364,7 +445,6 @@ async fn main() -> Result<()> {
             eprintln!("Reading transaction from stdin...");
             let mut buf = String::new();
             stdin().read_to_string(&mut buf)?;
-
             let bytes = bs58::decode(&buf.trim()).into_vec()?;
             let tx = deserialize(&bytes)?;
 

+ 17 - 5
bin/drk/src/rpc_blockchain.rs

@@ -123,19 +123,28 @@ impl Drk {
 
         let mut outputs: Vec<Output> = vec![];
 
-        let mf = MoneyFunction::Transfer as u8;
-
         // TODO: FIXME: This shouldn't be hardcoded here obviously.
         let contract_id = ContractId::from(pallas::Base::from(u64::MAX - 420));
 
         for (i, tx) in block.txs.iter().enumerate() {
             for (j, call) in tx.calls.iter().enumerate() {
-                if call.contract_id == contract_id && call.data[0] == mf {
-                    eprintln!("Found money transfer in call {} in tx {}", j, i);
+                if call.contract_id == contract_id && call.data[0] == MoneyFunction::Transfer as u8
+                {
+                    eprintln!("Found Money::Transfer in call {} in tx {}", j, i);
+                    let params: MoneyTransferParams = deserialize(&call.data[1..])?;
+                    for output in params.outputs {
+                        outputs.push(output);
+                    }
+                    continue
+                }
+
+                if call.contract_id == contract_id && call.data[0] == MoneyFunction::OtcSwap as u8 {
+                    eprintln!("Found Money::OtcSwap in call {} in tx {}", j, i);
                     let params: MoneyTransferParams = deserialize(&call.data[1..])?;
                     for output in params.outputs {
                         outputs.push(output);
                     }
+                    continue
                 }
             }
         }
@@ -152,9 +161,11 @@ impl Drk {
 
         let mut owncoins = vec![];
 
+        // FIXME: We end up adding duplicate coins that could already be in the tree
         for output in outputs {
-            // Append the new coin to the Merkle tree. Every coin has to be added.
             let coin = output.coin;
+
+            // Append the new coin to the Merkle tree. Every coin has to be added.
             tree.append(&MerkleNode::from(coin));
 
             // Attempt to decrypt the note
@@ -204,6 +215,7 @@ impl Drk {
 
         eprintln!("Found {} OwnCoin(s) in block", owncoins.len());
         for owncoin in owncoins {
+            eprintln!("Owncoin: {:?}", owncoin.coin);
             let params = json!([
                 query,
                 QueryType::Blob as u8,

+ 280 - 0
bin/drk/src/rpc_swap.rs

@@ -0,0 +1,280 @@
+/* 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 anyhow::{anyhow, Result};
+use darkfi::{
+    tx::Transaction,
+    zk::{proof::ProvingKey, vm::ZkCircuit, vm_stack::empty_witnesses, Proof},
+    zkas::ZkBinary,
+};
+use darkfi_money_contract::{
+    client::{build_half_swap_tx, EncryptedNote},
+    state::MoneyTransferParams,
+    MoneyFunction, ZKAS_BURN_NS, ZKAS_MINT_NS,
+};
+use darkfi_sdk::{
+    crypto::{pedersen::ValueBlind, ContractId, SecretKey, TokenId},
+    pasta::pallas,
+    tx::ContractCall,
+};
+use darkfi_serial::{deserialize, Encodable, SerialDecodable, SerialEncodable};
+use rand::rngs::OsRng;
+
+use super::Drk;
+
+#[derive(SerialEncodable, SerialDecodable)]
+/// Half of the swap data, includes the coin that is supposed to be sent,
+/// and the coin that is supposed to be received.
+pub struct PartialSwapData {
+    params: MoneyTransferParams,
+    proofs: Vec<Proof>,
+    value_pair: (u64, u64),
+    token_pair: (TokenId, TokenId),
+    value_blinds: Vec<ValueBlind>,
+    token_blinds: Vec<ValueBlind>,
+}
+
+impl Drk {
+    /// Initialize the first half of an atomic swap
+    pub async fn init_swap(
+        &self,
+        value_send: u64,
+        token_send: TokenId,
+        value_recv: u64,
+        token_recv: TokenId,
+    ) -> Result<PartialSwapData> {
+        // First we'll fetch all of our unspent coins from the wallet.
+        let mut owncoins = self.wallet_coins(false).await?;
+        // Then we see if we have one that we can send.
+        owncoins.retain(|x| (x.0.note.value == value_send && x.0.note.token_id == token_send));
+        if owncoins.is_empty() {
+            return Err(anyhow!(
+                "Did not find any unspent coins of value {} and token_id {}",
+                value_send,
+                token_send
+            ))
+        }
+
+        // If there are any, we'll just spend the first one we see.
+        let burn_coin = owncoins[0].0.clone();
+
+        // Fetch our default address
+        let address = self.wallet_address(0).await?;
+
+        // We'll also need our Merkle tree
+        let tree = self.wallet_tree().await?;
+
+        // TODO: FIXME: Do not hardcode the contract ID
+        let contract_id = ContractId::from(pallas::Base::from(u64::MAX - 420));
+
+        // 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(&contract_id).await?;
+
+        let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == ZKAS_MINT_NS) else {
+            return Err(anyhow!("Mint circuit not found"))
+        };
+
+        let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == ZKAS_BURN_NS) else {
+            return Err(anyhow!("Burn circuit not found"))
+        };
+
+        let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
+        let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
+
+        let k = 13;
+        let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin), mint_zkbin.clone());
+        let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin), burn_zkbin.clone());
+
+        eprintln!("Creating Mint circuit proving key");
+        let mint_pk = ProvingKey::build(k, &mint_circuit);
+        eprintln!("Creating Burn circuit proving key");
+        let burn_pk = ProvingKey::build(k, &burn_circuit);
+
+        // Now we should have everything we need to build the swap half
+        eprintln!("Building first half of the swap transaction");
+        let (half_params, half_proofs, _half_keys, _spent_coins, value_blinds, token_blinds) =
+            build_half_swap_tx(
+                &address,
+                value_send,
+                token_send,
+                value_recv,
+                token_recv,
+                &[],
+                &[],
+                &[burn_coin],
+                &tree,
+                &mint_zkbin,
+                &mint_pk,
+                &burn_zkbin,
+                &burn_pk,
+            )?;
+
+        // Now we have the half, so we can build `PartialSwapData` and return it.
+        let ret = PartialSwapData {
+            params: half_params,
+            proofs: half_proofs,
+            value_pair: (value_send, value_recv),
+            token_pair: (token_send, token_recv),
+            value_blinds,
+            token_blinds,
+        };
+
+        Ok(ret)
+    }
+
+    /// Create a full transaction by inspecting and verifying given partial swap data,
+    /// making the other half, and joining all this into a `Transaction` object.
+    pub async fn join_swap(&self, partial: PartialSwapData) -> Result<Transaction> {
+        // Our side of the tx in the pairs is the second half, so we try to find
+        // an unspent coin like that in our wallet.
+        let mut owncoins = self.wallet_coins(false).await?;
+        owncoins.retain(|x| {
+            x.0.note.value == partial.value_pair.1 && x.0.note.token_id == partial.token_pair.1
+        });
+
+        if owncoins.is_empty() {
+            return Err(anyhow!(
+                "Did not find any unspent coins of value {} and token_id {}",
+                partial.value_pair.1,
+                partial.token_pair.1
+            ))
+        }
+
+        // If there are any, we'll just spend the first one we see.
+        let burn_coin = owncoins[0].0.clone();
+
+        // Fetch our default address
+        let address = self.wallet_address(0).await?;
+
+        // We'll also need our Merkle tree
+        let tree = self.wallet_tree().await?;
+
+        // TODO: FIXME: Do not hardcode the contract ID
+        let contract_id = ContractId::from(pallas::Base::from(u64::MAX - 420));
+
+        // 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(&contract_id).await?;
+
+        let Some(mint_zkbin) = zkas_bins.iter().find(|x| x.0 == ZKAS_MINT_NS) else {
+            return Err(anyhow!("Mint circuit not found"))
+        };
+
+        let Some(burn_zkbin) = zkas_bins.iter().find(|x| x.0 == ZKAS_BURN_NS) else {
+            return Err(anyhow!("Burn circuit not found"))
+        };
+
+        let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
+        let burn_zkbin = ZkBinary::decode(&burn_zkbin.1)?;
+
+        let k = 13;
+        let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin), mint_zkbin.clone());
+        let burn_circuit = ZkCircuit::new(empty_witnesses(&burn_zkbin), burn_zkbin.clone());
+
+        eprintln!("Creating Mint circuit proving key");
+        let mint_pk = ProvingKey::build(k, &mint_circuit);
+        eprintln!("Creating Burn circuit proving key");
+        let burn_pk = ProvingKey::build(k, &burn_circuit);
+
+        // TODO: Maybe some kind of verification at this point
+
+        // Now we should have everything we need to build the swap half
+        eprintln!("Building second half of the swap transaction");
+        let (half_params, half_proofs, half_keys, _spent_coins, _value_blinds, _token_blinds) =
+            build_half_swap_tx(
+                &address,
+                partial.value_pair.1,
+                partial.token_pair.1,
+                partial.value_pair.0,
+                partial.token_pair.0,
+                &partial.value_blinds,
+                &partial.token_blinds,
+                &[burn_coin],
+                &tree,
+                &mint_zkbin,
+                &mint_pk,
+                &burn_zkbin,
+                &burn_pk,
+            )?;
+
+        let full_params = MoneyTransferParams {
+            clear_inputs: vec![],
+            inputs: vec![partial.params.inputs[0].clone(), half_params.inputs[0].clone()],
+            outputs: vec![partial.params.outputs[0].clone(), half_params.outputs[0].clone()],
+        };
+
+        let full_proofs = vec![
+            partial.proofs[0].clone(),
+            half_proofs[0].clone(),
+            partial.proofs[1].clone(),
+            half_proofs[1].clone(),
+        ];
+
+        let mut data = vec![MoneyFunction::OtcSwap as u8];
+        full_params.encode(&mut data)?;
+        let mut tx = Transaction {
+            calls: vec![ContractCall { contract_id, data }],
+            proofs: vec![full_proofs],
+            signatures: vec![],
+        };
+        eprintln!("Signing swap transaction");
+        let sigs = tx.create_sigs(&mut OsRng, &half_keys)?;
+        tx.signatures = vec![sigs];
+
+        Ok(tx)
+    }
+
+    /// Sign a given transaction by retrieving the secret key from the encrypted
+    /// note and prepending it to the transaction's signatures.
+    pub async fn sign_swap(&self, tx: &mut Transaction) -> Result<()> {
+        // We need our secret keys to try and decrypt the note
+        let secret_keys = self.wallet_secrets().await?;
+        let params: MoneyTransferParams = deserialize(&tx.calls[0].data[1..])?;
+
+        // Our output should be outputs[0] so we try to decrypt that.
+        let ciphertext = params.outputs[0].ciphertext.clone();
+        let ephem_public = params.outputs[0].ephem_public;
+        let encrypted_note = EncryptedNote { ciphertext, ephem_public };
+
+        eprintln!("Trying to decrypt note in outputs[0]");
+        let mut skey = None;
+
+        for secret in &secret_keys {
+            if let Ok(note) = encrypted_note.decrypt(secret) {
+                let s: SecretKey = deserialize(&note.memo)?;
+                eprintln!("Successfully decrypted and found an ephemeral secret");
+                skey = Some(s);
+                break
+            }
+        }
+
+        let Some(skey) = skey else {
+            eprintln!("Error: Failed to decrypt note with any of our secret keys");
+            return Err(anyhow!("Failed to decrypt note with any of our secret keys"))
+        };
+
+        eprintln!("Signing swap transaction");
+        let sigs = tx.create_sigs(&mut OsRng, &[skey])?;
+        tx.signatures[0].insert(0, sigs[0]);
+
+        Ok(())
+    }
+}

+ 8 - 1
src/zk/proof.rs

@@ -55,7 +55,7 @@ impl ProvingKey {
     }
 }
 
-#[derive(Clone, Default, Debug, PartialEq, Eq, SerialEncodable, SerialDecodable)]
+#[derive(Clone, Default, PartialEq, Eq, SerialEncodable, SerialDecodable)]
 pub struct Proof(Vec<u8>);
 
 impl AsRef<[u8]> for Proof {
@@ -63,6 +63,13 @@ impl AsRef<[u8]> for Proof {
         &self.0
     }
 }
+
+impl core::fmt::Debug for Proof {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(f, "Proof({:?})", self.0)
+    }
+}
+
 impl Proof {
     pub fn create(
         pk: &ProvingKey,