Przeglądaj źródła

darkotc: Refactor, partially document, and implement join tx.

parazyd 4 lat temu
rodzic
commit
548052c6be
5 zmienionych plików z 423 dodań i 217 usunięć
  1. 0 1
      Cargo.lock
  2. 1 2
      bin/darkotc/Cargo.toml
  3. 17 2
      bin/darkotc/src/cli_util.rs
  4. 220 212
      bin/darkotc/src/main.rs
  5. 185 0
      bin/darkotc/src/rpc.rs

+ 0 - 1
Cargo.lock

@@ -1301,7 +1301,6 @@ dependencies = [
  "halo2_proofs",
  "halo2_proofs",
  "rand",
  "rand",
  "serde_json",
  "serde_json",
- "termion",
  "url",
  "url",
 ]
 ]
 
 

+ 1 - 2
bin/darkotc/Cargo.toml

@@ -12,10 +12,9 @@ edition = "2021"
 async-std = {version = "1.12.0", features = ["attributes"]}
 async-std = {version = "1.12.0", features = ["attributes"]}
 bs58 = "0.4.0"
 bs58 = "0.4.0"
 clap = {version = "3.2.16", features = ["derive"]}
 clap = {version = "3.2.16", features = ["derive"]}
-darkfi = {path = "../../", features = ["crypto", "rpc", "util"]}
+darkfi = {path = "../../", features = ["crypto", "rpc", "util", "tx"]}
 halo2_proofs = "0.2.0"
 halo2_proofs = "0.2.0"
 halo2_gadgets = "0.2.0"
 halo2_gadgets = "0.2.0"
 rand = "0.8.5"
 rand = "0.8.5"
 serde_json = "1.0.83"
 serde_json = "1.0.83"
-termion = "1.5.6"
 url = "2.2.2"
 url = "2.2.2"

+ 17 - 2
bin/darkotc/src/cli_util.rs

@@ -1,6 +1,8 @@
 use std::process::exit;
 use std::process::exit;
 
 
-use darkfi::{util::decode_base10, Result};
+use halo2_proofs::pasta::group::ff::PrimeField;
+
+use darkfi::{crypto::types::DrkTokenId, util::decode_base10, Result};
 
 
 pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
 pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> {
     let v: Vec<&str> = s.split(':').collect();
     let v: Vec<&str> = s.split(':').collect();
@@ -29,7 +31,6 @@ pub fn parse_token_pair(s: &str) -> Result<(String, String)> {
         exit(1);
         exit(1);
     }
     }
 
 
-    // TODO: Check if valid Fp
     let tok0 = bs58::decode(v[0]).into_vec();
     let tok0 = bs58::decode(v[0]).into_vec();
     let tok1 = bs58::decode(v[1]).into_vec();
     let tok1 = bs58::decode(v[1]).into_vec();
 
 
@@ -39,5 +40,19 @@ pub fn parse_token_pair(s: &str) -> Result<(String, String)> {
         exit(1);
         exit(1);
     }
     }
 
 
+    if tok0.as_ref().unwrap().len() != 32 ||
+        DrkTokenId::from_repr(tok0.unwrap().try_into().unwrap()).is_some().unwrap_u8() == 0
+    {
+        eprintln!("Error: {} is not a valid token ID", v[0]);
+        exit(1);
+    }
+
+    if tok1.as_ref().unwrap().len() != 32 ||
+        DrkTokenId::from_repr(tok1.unwrap().try_into().unwrap()).is_some().unwrap_u8() == 0
+    {
+        eprintln!("Error: {} is not a valid token ID", v[1]);
+        exit(1);
+    }
+
     Ok((v[0].to_string(), v[1].to_string()))
     Ok((v[0].to_string(), v[1].to_string()))
 }
 }

+ 220 - 212
bin/darkotc/src/main.rs

@@ -1,36 +1,37 @@
 use std::{
 use std::{
     io::{stdin, Read},
     io::{stdin, Read},
     process::exit,
     process::exit,
-    str::FromStr,
 };
 };
 
 
 use clap::{Parser, Subcommand};
 use clap::{Parser, Subcommand};
-use darkfi::crypto::proof::VerifyingKey;
 use halo2_proofs::{arithmetic::Field, pasta::group::ff::PrimeField};
 use halo2_proofs::{arithmetic::Field, pasta::group::ff::PrimeField};
 use rand::rngs::OsRng;
 use rand::rngs::OsRng;
-use serde_json::json;
-use termion::color;
 use url::Url;
 use url::Url;
 
 
 use darkfi::{
 use darkfi::{
     cli_desc,
     cli_desc,
     crypto::{
     crypto::{
-        address::Address,
         burn_proof::{create_burn_proof, verify_burn_proof},
         burn_proof::{create_burn_proof, verify_burn_proof},
         keypair::{PublicKey, SecretKey},
         keypair::{PublicKey, SecretKey},
-        merkle_node::MerkleNode,
         mint_proof::{create_mint_proof, verify_mint_proof},
         mint_proof::{create_mint_proof, verify_mint_proof},
-        proof::ProvingKey,
+        note::{EncryptedNote, Note},
+        proof::{ProvingKey, VerifyingKey},
+        schnorr,
+        schnorr::SchnorrSecret,
         token_id,
         token_id,
         types::{DrkCoinBlind, DrkSerial, DrkTokenId, DrkValueBlind},
         types::{DrkCoinBlind, DrkSerial, DrkTokenId, DrkValueBlind},
         util::{pedersen_commitment_base, pedersen_commitment_u64},
         util::{pedersen_commitment_base, pedersen_commitment_u64},
-        BurnRevealedValues, MintRevealedValues, OwnCoin, Proof,
+        BurnRevealedValues, MintRevealedValues, Proof,
+    },
+    rpc::client::RpcClient,
+    tx::{
+        partial::{PartialTransaction, PartialTransactionInput},
+        Transaction, TransactionInput, TransactionOutput,
     },
     },
-    rpc::{client::RpcClient, jsonrpc::JsonRequest},
     util::{
     util::{
-        cli::progress_bar,
+        cli::{fg_green, fg_red, progress_bar},
         encode_base10,
         encode_base10,
-        serial::{deserialize, serialize, SerialDecodable, SerialEncodable},
+        serial::{deserialize, serialize, Encodable, SerialDecodable, SerialEncodable},
     },
     },
     zk::circuit::{BurnContract, MintContract},
     zk::circuit::{BurnContract, MintContract},
     Result,
     Result,
@@ -38,6 +39,8 @@ use darkfi::{
 
 
 mod cli_util;
 mod cli_util;
 use cli_util::{parse_token_pair, parse_value_pair};
 use cli_util::{parse_token_pair, parse_value_pair};
+mod rpc;
+use rpc::Rpc;
 
 
 #[derive(Parser)]
 #[derive(Parser)]
 #[clap(name = "darkotc", about = cli_desc!(), version)]
 #[clap(name = "darkotc", about = cli_desc!(), version)]
@@ -60,187 +63,104 @@ enum Subcmd {
     /// Initialize an atomic swap
     /// Initialize an atomic swap
     Init {
     Init {
         #[clap(short, long)]
         #[clap(short, long)]
-        /// Pair of token IDs to swap: e.g. token_to_send:token_to_recv
+        /// Pair of token IDs to swap: token_to_send:token_to_recv
         token_pair: String,
         token_pair: String,
 
 
         #[clap(short, long)]
         #[clap(short, long)]
-        /// Pair of values to swap: e.g. value_to_send:value_to_recv
+        /// Pair of values to swap: value_to_send:value_to_recv
         value_pair: String,
         value_pair: String,
     },
     },
 
 
-    /// Inspect swap data from stdin or file.
+    /// Inspect partial swap data from stdin.
     Inspect,
     Inspect,
-}
 
 
-struct Rpc {
-    pub rpc_client: RpcClient,
+    /// Join two partial swap data files and build a tx
+    Join { data0: String, data1: String },
 }
 }
 
 
-impl Rpc {
-    async fn balance_of(&self, token_id: &str) -> 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 i == &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)
-    }
-
-    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);
-        }
-
-        Address::from_str(rep[0].as_str().unwrap())
-    }
-
-    async fn get_coins_valtok(&self, value: u64, token_id: &str) -> Result<Vec<OwnCoin>> {
-        let req = JsonRequest::new("wallet.get_coins_valtok", json!([value, 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 mut ret = vec![];
-        let rep = rep.as_array().unwrap();
-
-        for i in rep {
-            if !i.is_string() {
-                eprintln!("Error: Invalid base58 data for OwnCoin");
-                exit(1);
-            }
-
-            let data = match bs58::decode(i.as_str().unwrap()).into_vec() {
-                Ok(v) => v,
-                Err(e) => {
-                    eprintln!("Error: Failed decoding base58 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)
-    }
-
-    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 mut ret = vec![];
-        let rep = rep.as_array().unwrap();
-
-        for i in rep {
-            if !i.is_string() {
-                eprintln!("Error: Invalid base58 data for MerkleNode");
-                exit(1);
-            }
-
-            let n = i.as_str().unwrap();
-            let n = match bs58::decode(n).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_some().unwrap_u8() == 0 {
-                eprintln!("Error: Noncanonical bytes of MerkleNode");
-                exit(1);
-            }
-
-            ret.push(n.unwrap());
-        }
+#[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: DrkTokenId,
+    /// 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: DrkTokenId,
+    /// 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,
+}
 
 
-        Ok(ret)
-    }
+#[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(
 async fn init_swap(
     endpoint: Url,
     endpoint: Url,
     token_pair: (String, String),
     token_pair: (String, String),
     value_pair: (u64, u64),
     value_pair: (u64, u64),
-) -> Result<()> {
+) -> Result<PartialSwapData> {
     let rpc_client = RpcClient::new(endpoint).await?;
     let rpc_client = RpcClient::new(endpoint).await?;
     let rpc = Rpc { rpc_client };
     let rpc = Rpc { rpc_client };
 
 
-    // TODO: Think about decimals, there has to be some metadata to keep track.
+    // TODO: Implement metadata for decimals, don't hardcode.
     let tp = (token_id::parse_b58(&token_pair.0)?, token_id::parse_b58(&token_pair.1)?);
     let tp = (token_id::parse_b58(&token_pair.0)?, token_id::parse_b58(&token_pair.1)?);
-    let vp: (u64, u64) =
-        (value_pair.0.clone().try_into().unwrap(), value_pair.1.clone().try_into().unwrap());
+    let vp = value_pair;
 
 
     // Connect to darkfid and see if there's available funds.
     // Connect to darkfid and see if there's available funds.
     let balance = rpc.balance_of(&token_pair.0).await?;
     let balance = rpc.balance_of(&token_pair.0).await?;
     if balance < vp.0 {
     if balance < vp.0 {
         eprintln!(
         eprintln!(
-            "Error: There is not enough balance for token \"{}\" in your wallet.",
+            "Error: There's not enough balance for token \"{}\" in your wallet.",
             token_pair.0
             token_pair.0
         );
         );
         eprintln!("Available balance is {} ({})", encode_base10(balance, 8), balance);
         eprintln!("Available balance is {} ({})", encode_base10(balance, 8), balance);
         exit(1);
         exit(1);
     }
     }
 
 
-    // If 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.
+    // 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: Implement ^
     // TODO: Maybe this should be done by the user beforehand?
     // TODO: Maybe this should be done by the user beforehand?
 
 
-    // Find a coin to spend
+    // 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?;
     let coins = rpc.get_coins_valtok(vp.0, &token_pair.0).await?;
     if coins.is_empty() {
     if coins.is_empty() {
-        eprintln!("Error: Did not manage to find a coin with enough value to spend");
+        eprintln!("Error: Did not manage to find a coin with enough value to spend.");
         exit(1);
         exit(1);
     }
     }
 
 
     eprintln!("Initializing swap data for:");
     eprintln!("Initializing swap data for:");
-    eprintln!("Send: {} {} tokens", encode_base10(value_pair.0, 8), token_pair.0);
-    eprintln!("Recv: {} {} tokens", encode_base10(value_pair.1, 8), token_pair.1);
+    eprintln!("Send: {} {} tokens", encode_base10(vp.0, 8), token_pair.0);
+    eprintln!("Recv: {} {} tokens", encode_base10(vp.1, 8), token_pair.1);
 
 
     // Fetch our default address
     // Fetch our default address
-    let our_address = rpc.wallet_address().await?;
-    let our_publickey = match PublicKey::try_from(our_address) {
+    let our_addr = rpc.wallet_address().await?;
+    let our_pubk = match PublicKey::try_from(our_addr) {
         Ok(v) => v,
         Ok(v) => v,
         Err(e) => {
         Err(e) => {
             eprintln!("Error converting our address into PublicKey: {}", e);
             eprintln!("Error converting our address into PublicKey: {}", e);
@@ -248,22 +168,22 @@ async fn init_swap(
         }
         }
     };
     };
 
 
-    // Build proving keys
-    let pb = progress_bar("Building proving key for the mint contract");
-    let mint_pk = ProvingKey::build(8, &MintContract::default());
+    // 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();
     pb.finish();
 
 
-    let pb = progress_bar("Building proving key for the burn contract");
+    let pb = progress_bar("Building proving key for the Burn contract");
     let burn_pk = ProvingKey::build(11, &BurnContract::default());
     let burn_pk = ProvingKey::build(11, &BurnContract::default());
     pb.finish();
     pb.finish();
 
 
-    // The coin we want to receive.
+    // The coin we want to receive
     let recv_value_blind = DrkValueBlind::random(&mut OsRng);
     let recv_value_blind = DrkValueBlind::random(&mut OsRng);
     let recv_token_blind = DrkValueBlind::random(&mut OsRng);
     let recv_token_blind = DrkValueBlind::random(&mut OsRng);
     let recv_coin_blind = DrkCoinBlind::random(&mut OsRng);
     let recv_coin_blind = DrkCoinBlind::random(&mut OsRng);
     let recv_serial = DrkSerial::random(&mut OsRng);
     let recv_serial = DrkSerial::random(&mut OsRng);
 
 
-    let pb = progress_bar("Building mint proof for receiving coin");
+    let pb = progress_bar("Building Mint proof for the receiving coin");
     let (mint_proof, mint_revealed) = create_mint_proof(
     let (mint_proof, mint_revealed) = create_mint_proof(
         &mint_pk,
         &mint_pk,
         vp.1,
         vp.1,
@@ -272,20 +192,19 @@ async fn init_swap(
         recv_token_blind,
         recv_token_blind,
         recv_serial,
         recv_serial,
         recv_coin_blind,
         recv_coin_blind,
-        our_publickey,
+        our_pubk,
     )?;
     )?;
     pb.finish();
     pb.finish();
 
 
     // The coin we are spending.
     // The coin we are spending.
-    // We'll spend the first one we've found.
-    let coin = coins[0];
+    let coin = coins[0].clone();
 
 
-    let pb = progress_bar("Building burn proof for spending coin");
+    let pb = progress_bar("Building Burn proof for the spending coin");
     let signature_secret = SecretKey::random(&mut OsRng);
     let signature_secret = SecretKey::random(&mut OsRng);
     let merkle_path = match rpc.get_merkle_path(usize::from(coin.leaf_position)).await {
     let merkle_path = match rpc.get_merkle_path(usize::from(coin.leaf_position)).await {
         Ok(v) => v,
         Ok(v) => v,
         Err(e) => {
         Err(e) => {
-            eprintln!("Failed to get merkle path for our coin from darkfid RPC: {}", e);
+            eprintln!("Failed to get Merkle path for our coin from darkfid RPC: {}", e);
             exit(1);
             exit(1);
         }
         }
     };
     };
@@ -305,9 +224,22 @@ async fn init_swap(
     )?;
     )?;
     pb.finish();
     pb.finish();
 
 
+    // Create encrypted note
+    let note = Note {
+        serial: recv_serial,
+        value: vp.1,
+        token_id: tp.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: signature_secret.to_bytes().to_vec(),
+    };
+    let encrypted_note = note.encrypt(&our_pubk)?;
+
     // Pack proofs together with pedersen commitment openings so
     // Pack proofs together with pedersen commitment openings so
     // counterparty can verify correctness.
     // counterparty can verify correctness.
-    let swap_data = SwapData {
+    let partial_swap_data = PartialSwapData {
         mint_proof,
         mint_proof,
         mint_revealed,
         mint_revealed,
         mint_value: vp.1,
         mint_value: vp.1,
@@ -320,12 +252,10 @@ async fn init_swap(
         burn_revealed,
         burn_revealed,
         burn_value_blind: coin.note.value_blind,
         burn_value_blind: coin.note.value_blind,
         burn_token_blind: coin.note.token_blind,
         burn_token_blind: coin.note.token_blind,
+        encrypted_note,
     };
     };
 
 
-    // Print encoded data.
-    println!("{}", bs58::encode(serialize(&swap_data)).into_string());
-
-    Ok(())
+    Ok(partial_swap_data)
 }
 }
 
 
 fn inspect(data: &str) -> Result<()> {
 fn inspect(data: &str) -> Result<()> {
@@ -344,38 +274,38 @@ fn inspect(data: &str) -> Result<()> {
         }
         }
     };
     };
 
 
-    let sd: SwapData = match deserialize(&bytes) {
+    let sd: PartialSwapData = match deserialize(&bytes) {
         Ok(v) => v,
         Ok(v) => v,
         Err(e) => {
         Err(e) => {
-            eprintln!("Error: Failed to deserialize swap data into struct: {}", e);
+            eprintln!("Error deserializing partial swap data into struct: {}", e);
             exit(1);
             exit(1);
         }
         }
     };
     };
 
 
-    eprintln!("Successfully decoded data into SwapData struct");
+    eprintln!("Successfully decoded partial swap data");
 
 
-    // Build verifying keys
-    let pb = progress_bar("Building verifying key for the mint contract");
-    let mint_vk = VerifyingKey::build(8, &MintContract::default());
+    // 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();
     pb.finish();
 
 
-    let pb = progress_bar("Building verifying key for the burn contract");
+    let pb = progress_bar("Building verifying key for the Burn contract");
     let burn_vk = VerifyingKey::build(11, &BurnContract::default());
     let burn_vk = VerifyingKey::build(11, &BurnContract::default());
     pb.finish();
     pb.finish();
 
 
-    let pb = progress_bar("Verifying burn proof");
+    let pb = progress_bar("Verifying Burn proof");
     if verify_burn_proof(&burn_vk, &sd.burn_proof, &sd.burn_revealed).is_ok() {
     if verify_burn_proof(&burn_vk, &sd.burn_proof, &sd.burn_revealed).is_ok() {
         burn_valid = true;
         burn_valid = true;
     }
     }
     pb.finish();
     pb.finish();
 
 
-    let pb = progress_bar("Verifying mint proof");
+    let pb = progress_bar("Verifying Mint proof");
     if verify_mint_proof(&mint_vk, &sd.mint_proof, &sd.mint_revealed).is_ok() {
     if verify_mint_proof(&mint_vk, &sd.mint_proof, &sd.mint_revealed).is_ok() {
         mint_valid = true;
         mint_valid = true;
     }
     }
     pb.finish();
     pb.finish();
 
 
-    eprintln!("  Verifying pedersen commitments");
+    eprintln!("  Verifying Pedersen commitments");
 
 
     if pedersen_commitment_u64(sd.burn_value, sd.burn_value_blind) == sd.burn_revealed.value_commit
     if pedersen_commitment_u64(sd.burn_value, sd.burn_value_blind) == sd.burn_revealed.value_commit
     {
     {
@@ -402,49 +332,49 @@ fn inspect(data: &str) -> Result<()> {
 
 
     eprint!("  Burn proof: ");
     eprint!("  Burn proof: ");
     if burn_valid {
     if burn_valid {
-        eprintln!("{}VALID{}", color::Fg(color::Green), color::Fg(color::Reset));
+        eprintln!("{}", fg_green("VALID"));
     } else {
     } else {
-        eprintln!("{}INVALID{}", color::Fg(color::Red), color::Fg(color::Reset));
+        eprintln!("{}", fg_red("INVALID"));
         valid = false;
         valid = false;
     }
     }
 
 
     eprint!("  Burn proof value commitment: ");
     eprint!("  Burn proof value commitment: ");
     if burn_value_valid {
     if burn_value_valid {
-        eprintln!("{}VALID{}", color::Fg(color::Green), color::Fg(color::Reset));
+        eprintln!("{}", fg_green("VALID"));
     } else {
     } else {
-        eprintln!("{}INVALID{}", color::Fg(color::Red), color::Fg(color::Reset));
+        eprintln!("{}", fg_red("INVALID"));
         valid = false;
         valid = false;
     }
     }
 
 
     eprint!("  Burn proof token commitment: ");
     eprint!("  Burn proof token commitment: ");
     if burn_token_valid {
     if burn_token_valid {
-        eprintln!("{}VALID{}", color::Fg(color::Green), color::Fg(color::Reset));
+        eprintln!("{}", fg_green("VALID"));
     } else {
     } else {
-        eprintln!("{}INVALID{}", color::Fg(color::Red), color::Fg(color::Reset));
+        eprintln!("{}", fg_red("INVALID"));
         valid = false;
         valid = false;
     }
     }
 
 
     eprint!("  Mint proof: ");
     eprint!("  Mint proof: ");
     if mint_valid {
     if mint_valid {
-        eprintln!("{}VALID{}", color::Fg(color::Green), color::Fg(color::Reset));
+        eprintln!("{}", fg_green("VALID"));
     } else {
     } else {
-        eprintln!("{}INVALID{}", color::Fg(color::Red), color::Fg(color::Reset));
+        eprintln!("{}", fg_red("INVALID"));
         valid = false;
         valid = false;
     }
     }
 
 
     eprint!("  Mint proof value commitment: ");
     eprint!("  Mint proof value commitment: ");
     if mint_value_valid {
     if mint_value_valid {
-        eprintln!("{}VALID{}", color::Fg(color::Green), color::Fg(color::Reset));
+        eprintln!("{}", fg_green("VALID"));
     } else {
     } else {
-        eprintln!("{}INVALID{}", color::Fg(color::Red), color::Fg(color::Reset));
+        eprintln!("{}", fg_red("INVALID"));
         valid = false;
         valid = false;
     }
     }
 
 
     eprint!("  Mint proof token commitment: ");
     eprint!("  Mint proof token commitment: ");
     if mint_token_valid {
     if mint_token_valid {
-        eprintln!("{}VALID{}", color::Fg(color::Green), color::Fg(color::Reset));
+        eprintln!("{}", fg_green("VALID"));
     } else {
     } else {
-        eprintln!("{}INVALID{}", color::Fg(color::Red), color::Fg(color::Reset));
+        eprintln!("{}", fg_red("INVALID"));
         valid = false;
         valid = false;
     }
     }
 
 
@@ -461,38 +391,105 @@ fn inspect(data: &str) -> Result<()> {
         bs58::encode(sd.burn_token.to_repr()).into_string()
         bs58::encode(sd.burn_token.to_repr()).into_string()
     );
     );
 
 
+    eprint!("\nThe ZK proofs and commitments inspected are ");
     if !valid {
     if !valid {
-        eprintln!(
-            "\nThe ZK proofs and commitments inspected are {}NOT VALID{}",
-            color::Fg(color::Red),
-            color::Fg(color::Reset)
-        );
+        println!("{}", fg_red("NOT VALID"));
         exit(1);
         exit(1);
     } else {
     } else {
-        eprintln!(
-            "\nThe ZK proofs and commitments inspected are {}VALID{}",
-            color::Fg(color::Green),
-            color::Fg(color::Reset)
-        );
+        eprintln!("{}", fg_green("VALID"));
     }
     }
 
 
     Ok(())
     Ok(())
 }
 }
 
 
-#[derive(SerialEncodable, SerialDecodable)]
-struct SwapData {
-    mint_proof: Proof,
-    mint_revealed: MintRevealedValues,
-    mint_value: u64,
-    mint_token: DrkTokenId,
-    mint_value_blind: DrkValueBlind,
-    mint_token_blind: DrkValueBlind,
-    burn_proof: Proof,
-    burn_revealed: BurnRevealedValues,
-    burn_value: u64,
-    burn_token: DrkTokenId,
-    burn_value_blind: DrkValueBlind,
-    burn_token_blind: DrkValueBlind,
+async fn join(endpoint: Url, d0: PartialSwapData, d1: PartialSwapData) -> Result<Transaction> {
+    let rpc_client = RpcClient::new(endpoint).await?;
+    let rpc = Rpc { rpc_client };
+
+    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 mut unsigned_tx_data = vec![];
+    partial_tx.encode(&mut unsigned_tx_data)?;
+
+    let mut inputs = vec![];
+    let mut signed: bool;
+
+    eprint!("Trying to decrypt the note of the first half... ");
+    if let Some(note) = rpc.decrypt_note(&d0.encrypted_note).await? {
+        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.
+    if !signed {
+        eprint!("Trying to decrypt the note of the second half... ");
+        if let Some(note) = rpc.decrypt_note(&d1.encrypted_note).await? {
+            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);
+            signed = false;
+        }
+    }
+
+    if !signed {
+        eprintln!("Error: Failed to sign transaction!");
+        exit(1);
+    }
+
+    let tx = Transaction { clear_inputs: vec![], inputs, outputs: partial_tx.outputs };
+    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 coerce the bytes into SecretKey: {}", e);
+            exit(1);
+        }
+    };
+
+    eprintln!("Signing transaction...");
+    let signature = secret.sign(tx_data);
+    Ok(signature)
 }
 }
 
 
 #[async_std::main]
 #[async_std::main]
@@ -503,12 +500,23 @@ async fn main() -> Result<()> {
         Subcmd::Init { token_pair, value_pair } => {
         Subcmd::Init { token_pair, value_pair } => {
             let token_pair = parse_token_pair(&token_pair)?;
             let token_pair = parse_token_pair(&token_pair)?;
             let value_pair = parse_value_pair(&value_pair)?;
             let value_pair = parse_value_pair(&value_pair)?;
-            init_swap(args.endpoint, token_pair, value_pair).await
+            let swap_data = init_swap(args.endpoint, token_pair, value_pair).await?;
+            println!("{}", bs58::encode(serialize(&swap_data)).into_string());
+            Ok(())
         }
         }
         Subcmd::Inspect => {
         Subcmd::Inspect => {
             let mut buf = String::new();
             let mut buf = String::new();
             stdin().read_to_string(&mut buf)?;
             stdin().read_to_string(&mut buf)?;
             inspect(&buf.trim())
             inspect(&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).into_vec()?)?;
+            let d1 = deserialize(&bs58::decode(&d1).into_vec()?)?;
+            let tx = join(args.endpoint, d0, d1).await?;
+            println!("{}", bs58::encode(&serialize(&tx)).into_string());
+            Ok(())
+        }
     }
     }
 }
 }

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

@@ -0,0 +1,185 @@
+use std::{process::exit, str::FromStr};
+
+use serde_json::json;
+
+use darkfi::{
+    crypto::{
+        address::Address,
+        merkle_node::MerkleNode,
+        note::{EncryptedNote, Note},
+        OwnCoin,
+    },
+    rpc::{client::RpcClient, jsonrpc::JsonRequest},
+    util::serial::{deserialize, serialize},
+    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: &str) -> 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 i == 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: &str) -> Result<Vec<OwnCoin>> {
+        let req = JsonRequest::new("wallet.get_coins_valtok", json!([value, 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_some().unwrap_u8() == 0 {
+                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.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))
+    }
+}