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

Merge branch 'master' of github.com:darkrenaissance/darkfi

lunar-mining 4 лет назад
Родитель
Сommit
4ff6a3c73d

+ 4 - 1
Cargo.lock

@@ -1157,8 +1157,12 @@ dependencies = [
  "darkfi",
  "easy-parallel",
  "futures",
+ "halo2_gadgets",
+ "incrementalmerkletree",
  "log",
  "num_cpus",
+ "pasta_curves",
+ "rand",
  "serde_json",
  "simplelog",
  "smol",
@@ -1297,7 +1301,6 @@ dependencies = [
  "halo2_proofs",
  "rand",
  "serde_json",
- "termion",
  "url",
 ]
 

+ 1 - 0
Cargo.toml

@@ -177,6 +177,7 @@ util = [
 	"fxhash",
 	"chrono",
 	"indicatif",
+	"termion",
 
     "async-net",
     "async-runtime",

+ 7 - 1
bin/daod/Cargo.toml

@@ -5,7 +5,7 @@ edition = "2021"
 
 [dependencies.darkfi]
 path = "../../"
-features = ["rpc"]
+features = ["rpc", "crypto", "tx", "node"]
 
 [dependencies]
 # Async
@@ -23,5 +23,11 @@ num_cpus = "1.13.1"
 simplelog = "0.12.0"
 url = "2.2.2"
 
+# Crypto
+incrementalmerkletree = "0.3.0"
+pasta_curves = "0.4.0"
+halo2_gadgets = "0.2.0"
+rand = "0.8.5"
+
 # Encoding and parsing
 serde_json = "1.0.83"

+ 302 - 57
bin/daod/src/demo.rs

@@ -1,43 +1,61 @@
-use incrementalmerkletree::{bridgetree::BridgeTree, Frontier, Tree};
+use halo2_gadgets::poseidon::primitives as poseidon;
+use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
+use log::debug;
+use pasta_curves::{
+    arithmetic::CurveAffine,
+    group::{ff::Field, Curve},
+    pallas,
+};
 use rand::rngs::OsRng;
+use std::time::Instant;
 
 use darkfi::{
     crypto::{
-        coin::Coin,
+        constants::MERKLE_DEPTH,
         keypair::{Keypair, PublicKey, SecretKey},
         merkle_node::MerkleNode,
         note::{EncryptedNote, Note},
         nullifier::Nullifier,
         proof::{ProvingKey, VerifyingKey},
-        token_id::generate_id2,
+        token_id::generate_id,
+        OwnCoin, OwnCoins,
     },
     node::state::{state_transition, ProgramState, StateUpdate},
-    tx,
+    tx::builder::{
+        TransactionBuilder, TransactionBuilderClearInputInfo, TransactionBuilderInputInfo,
+        TransactionBuilderOutputInfo,
+    },
     util::NetworkName,
-    zk::circuit::{mint_contract::MintContract, spend_contract::SpendContract},
-    Result,
+    zk::circuit::{BurnContract, MintContract},
 };
 
+/// The state machine, held in memory.
 struct MemoryState {
-    // The entire merkle tree state
-    tree: BridgeTree<MerkleNode, 32>,
-    // List of all previous and the current merkle roots
-    // This is the hashed value of all the children.
+    /// The entire Merkle tree state
+    tree: BridgeTree<MerkleNode, MERKLE_DEPTH>,
+    /// List of all previous and the current Merkle roots.
+    /// This is the hashed value of all the children.
     merkle_roots: Vec<MerkleNode>,
-    // Nullifiers prevent double spending
+    /// Nullifiers prevent double spending
     nullifiers: Vec<Nullifier>,
-    // All received coins
-    // NOTE: we need maybe a flag to keep track of which ones are spent
-    // Maybe the spend field links to a tx hash:input index
-    // We should also keep track of the tx hash:output index where this
-    // coin was received
-    own_coins: Vec<(Coin, Note)>,
+    /// All received coins
+    // NOTE: We need maybe a flag to keep track of which ones are
+    // spent. Maybe the spend field links to a tx hash:input index.
+    // We should also keep track of the tx hash:output index where
+    // this coin was received.
+    own_coins: OwnCoins,
+    /// Verifying key for the mint zk circuit.
     mint_vk: VerifyingKey,
-    spend_vk: VerifyingKey,
+    /// Verifying key for the burn zk circuit.
+    burn_vk: VerifyingKey,
 
-    // Public key of the cashier
+    /// Public key of the cashier
     cashier_signature_public: PublicKey,
-    // List of all our secret keys
+
+    /// Public key of the faucet
+    faucet_signature_public: PublicKey,
+
+    /// List of all our secret keys
     secrets: Vec<SecretKey>,
 }
 
@@ -46,6 +64,10 @@ impl ProgramState for MemoryState {
         public == &self.cashier_signature_public
     }
 
+    fn is_valid_faucet_public_key(&self, public: &PublicKey) -> bool {
+        public == &self.faucet_signature_public
+    }
+
     fn is_valid_merkle(&self, merkle_root: &MerkleNode) -> bool {
         self.merkle_roots.iter().any(|m| m == merkle_root)
     }
@@ -58,8 +80,8 @@ impl ProgramState for MemoryState {
         &self.mint_vk
     }
 
-    fn spend_vk(&self) -> &VerifyingKey {
-        &self.spend_vk
+    fn burn_vk(&self) -> &VerifyingKey {
+        &self.burn_vk
     }
 }
 
@@ -70,16 +92,19 @@ impl MemoryState {
 
         // Update merkle tree and witnesses
         for (coin, enc_note) in update.coins.into_iter().zip(update.enc_notes.into_iter()) {
-            // Add the new coins to the merkle tree
+            // Add the new coins to the Merkle tree
             let node = MerkleNode(coin.0);
             self.tree.append(&node);
 
-            // Keep track of all merkle roots that have existed
-            self.merkle_roots.push(self.tree.root());
+            // Keep track of all Merkle roots that have existed
+            self.merkle_roots.push(self.tree.root(0).unwrap());
 
-            if let Some((note, _secret)) = self.try_decrypt_note(enc_note) {
-                self.own_coins.push((coin, note));
-                self.tree.witness();
+            // If it's our own coin, witness it and append to the vector.
+            if let Some((note, secret)) = self.try_decrypt_note(enc_note) {
+                let leaf_position = self.tree.witness().unwrap();
+                let nullifier = Nullifier::new(secret, note.serial);
+                let own_coin = OwnCoin { coin, note, secret, nullifier, leaf_position };
+                self.own_coins.push(own_coin);
             }
         }
     }
@@ -87,48 +112,268 @@ impl MemoryState {
     fn try_decrypt_note(&self, ciphertext: EncryptedNote) -> Option<(Note, SecretKey)> {
         // Loop through all our secret keys...
         for secret in &self.secrets {
-            // ... attempt to decrypt the note ...
+            // .. attempt to decrypt the note ...
             if let Ok(note) = ciphertext.decrypt(secret) {
                 // ... and return the decrypted note for this coin.
                 return Some((note, *secret))
             }
         }
+
         // We weren't able to decrypt the note with any of our keys.
         None
     }
 }
+type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
 
-pub fn demo() -> Result<()> {
-    // Create the treasury token: xDRK
-    //   - mint a new token supply using clear inputs
-    // Create the governance token: gDRK
-    //   - mint a new token supply using clear inputs
-    // Create the DAO instance
-    //   - create proposal auth keypair
-    //   - mint a new bulla:
-    //
-    //       DAO {
-    //           proposal_auth_key
-    //           gov_token_id
-    //           treasury_token_id
-    //       }
-    //
-    // Receive payment to DAO treasury
-    //   - send token to a coin that has:
-    //     - parent set to DAO bulla
-    //     - owner set to contract:function unique address (checked by consensus)
-    // Create a proposal
-    // Proposal is signed
-    // Successful voting
-    // Proposal is executed
-    //   - burn conditions are met
-    //     - DAO bulla matches parent field in coins being spent
-    //     - correct contract:function fields are set
-    //     - burn the coins, but not the DAO
-    //   - main dao execute: voting threshold and outcome
+mod DaoContract {
+    use pasta_curves::pallas;
+
+    pub struct DaoBulla(pub pallas::Base);
+
+    /// This DAO state is for all DAOs on the network. There should only be a single instance.
+    pub struct State {
+        dao_bullas: Vec<DaoBulla>,
+    }
+
+    impl State {
+        pub fn new() -> Self {
+            Self { dao_bullas: Vec::new() }
+        }
+    }
+
+    /// This is an anonymous contract function that mutates the internal DAO state.
+    ///
+    /// Corresponds to `mint(proposer_limit, quorum, approval_ratio, dao_pubkey, dao_blind)`
+    ///
+    /// The prover creates a `Builder`, which then constructs the `Tx` that the verifier can
+    /// check using `state_transition()`.
+    ///
+    /// # Arguments
+    ///
+    /// * `proposer_limit` - Number of governance tokens that holder must possess in order to
+    ///   propose a new vote.
+    /// * `quorum` - Number of minimum votes that must be met for a proposal to pass.
+    /// * `approval_ratio` - Ratio of winning to total votes for a proposal to pass.
+    /// * `dao_pubkey` - Public key of the DAO for permissioned access. This can also be
+    ///   shared publicly if you want a full decentralized DAO.
+    /// * `dao_blind` - Blinding factor for the DAO bulla.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// let dao_proposer_limit = 110;
+    /// let dao_quorum = 110;
+    /// let dao_approval_ratio = 2;
+    ///
+    /// let builder = DaoContract::Mint::Builder(
+    ///     dao_proposer_limit,
+    ///     dao_quorum,
+    ///     dao_approval_ratio,
+    ///     gov_token_id,
+    ///     dao_pubkey,
+    ///     dao_blind
+    /// );
+    /// let tx = builder.build();
+    /// ```
+    pub mod Mint {
+        use darkfi::crypto::keypair::PublicKey;
+        use pasta_curves::pallas;
+
+        pub struct Builder {
+            dao_proposer_limit: u64,
+            dao_quorum: u64,
+            dao_approval_ratio: u64,
+            gov_token_id: pallas::Base,
+            dao_pubkey: PublicKey,
+            dao_bulla_blind: pallas::Base,
+        }
+
+        impl Builder {
+            pub fn new(
+                dao_proposer_limit: u64,
+                dao_quorum: u64,
+                dao_approval_ratio: u64,
+                gov_token_id: pallas::Base,
+                dao_pubkey: PublicKey,
+                dao_bulla_blind: pallas::Base,
+            ) -> Self {
+                Self {
+                    dao_proposer_limit,
+                    dao_quorum,
+                    dao_approval_ratio,
+                    gov_token_id,
+                    dao_pubkey,
+                    dao_bulla_blind,
+                }
+            }
+
+            /// Consumes self, and produces the actual Tx
+            pub fn build(self) -> Tx {
+                Tx {}
+            }
+        }
 
+        pub struct Tx {}
+
+        impl Tx {}
+    }
+}
+
+pub async fn demo() -> Result<()> {
+    // Money parameters
     let xdrk_supply = 1_000_000;
+    let xdrk_token_id = pallas::Base::random(&mut OsRng);
+
+    // Governance token parameters
     let gdrk_supply = 1_000_000;
+    let gdrk_token_id = pallas::Base::random(&mut OsRng);
+
+    // DAO parameters
+    let dao_proposer_limit = 110;
+    let dao_quorum = 110;
+    let dao_approval_ratio = 2;
+
+    /////////////////////////////////////////////////
+
+    // State for money contracts
+    let cashier_signature_secret = SecretKey::random(&mut OsRng);
+    let cashier_signature_public = PublicKey::from_secret(cashier_signature_secret);
+    let faucet_signature_secret = SecretKey::random(&mut OsRng);
+    let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret);
+
+    let start = Instant::now();
+    let mint_vk = VerifyingKey::build(11, &MintContract::default());
+    debug!("Mint VK: [{:?}]", start.elapsed());
+    let start = Instant::now();
+    let burn_vk = VerifyingKey::build(11, &BurnContract::default());
+    debug!("Burn VK: [{:?}]", start.elapsed());
+
+    // TODO: this should not be here.
+    // We should separate wallet functionality from the State completely
+    let keypair = Keypair::random(&mut OsRng);
+
+    let mut money_state = MemoryState {
+        tree: BridgeTree::<MerkleNode, MERKLE_DEPTH>::new(100),
+        merkle_roots: vec![],
+        nullifiers: vec![],
+        own_coins: vec![],
+        mint_vk,
+        burn_vk,
+        cashier_signature_public,
+        faucet_signature_public,
+        secrets: vec![keypair.secret],
+    };
+
+    /////////////////////////////////////////////////
+
+    //
+    let dao_state = DaoContract::State::new();
+
+    // For this demo lets create 10 random preexisting DAO bullas
+    for _ in 0..10 {
+        let messages = [pallas::Base::random(&mut OsRng)];
+        let coin =
+            poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<1>, 3, 2>::init()
+                .hash(messages);
+    }
+
+    /////////////////////////////////////////////////
+    // Create the DAO bulla
+    /////////////////////////////////////////////////
+
+    // Setup the DAO
+    let dao_keypair = Keypair::random(&mut OsRng);
+    let dao_bulla_blind = pallas::Base::random(&mut OsRng);
+
+    //let dao_proposer_limit = pallas::Base::from(110);
+    //let dao_quorum = pallas::Base::from(110);
+    //let dao_approval_ratio = pallas::Base::from(2);
+    //
+    //let dao_pubkey_coords = dao_keypair.public.0.to_affine().coordinates().unwrap();
+    //let messages = [
+    //    dao_proposer_limit,
+    //    dao_quorum,
+    //    dao_approval_ratio,
+    //    gdrk_token_id,
+    //    *dao_pubkey_coords.x(),
+    //    *dao_pubkey_coords.y(),
+    //    dao_bulla_blind,
+    //];
+    //let dao_bulla =
+    //    poseidon::Hash::<_, poseidon::P128Pow5T3, poseidon::ConstantLength<7>, 3, 2>::init()
+    //        .hash(messages);
+    //let dao_bulla = DaoContract::DaoBulla(dao_bulla);
+
+    // Create DAO mint tx
+    let builder = DaoContract::Mint::Builder::new(
+        dao_proposer_limit,
+        dao_quorum,
+        dao_approval_ratio,
+        gdrk_token_id,
+        dao_keypair.public,
+        dao_bulla_blind,
+    );
+    let tx = builder.build();
+
+    /////////////////////////////////////////////////
+
+    let token_id = pallas::Base::random(&mut OsRng);
+
+    let builder = TransactionBuilder {
+        clear_inputs: vec![TransactionBuilderClearInputInfo {
+            value: 110,
+            token_id,
+            signature_secret: cashier_signature_secret,
+        }],
+        inputs: vec![],
+        outputs: vec![TransactionBuilderOutputInfo {
+            value: 110,
+            token_id,
+            public: keypair.public,
+        }],
+    };
+
+    let start = Instant::now();
+    let mint_pk = ProvingKey::build(11, &MintContract::default());
+    debug!("Mint PK: [{:?}]", start.elapsed());
+    let start = Instant::now();
+    let burn_pk = ProvingKey::build(11, &BurnContract::default());
+    debug!("Burn PK: [{:?}]", start.elapsed());
+    let tx = builder.build(&mint_pk, &burn_pk)?;
+
+    tx.verify(&money_state.mint_vk, &money_state.burn_vk)?;
+
+    let _note = tx.outputs[0].enc_note.decrypt(&keypair.secret)?;
+
+    let update = state_transition(&money_state, tx)?;
+    money_state.apply(update);
+
+    // Now spend
+    let owncoin = &money_state.own_coins[0];
+    let note = &owncoin.note;
+    let leaf_position = owncoin.leaf_position;
+    let root = money_state.tree.root(0).unwrap();
+    let merkle_path = money_state.tree.authentication_path(leaf_position, &root).unwrap();
+
+    let builder = TransactionBuilder {
+        clear_inputs: vec![],
+        inputs: vec![TransactionBuilderInputInfo {
+            leaf_position,
+            merkle_path,
+            secret: keypair.secret,
+            note: note.clone(),
+        }],
+        outputs: vec![TransactionBuilderOutputInfo {
+            value: 110,
+            token_id,
+            public: keypair.public,
+        }],
+    };
+
+    let tx = builder.build(&mint_pk, &burn_pk)?;
+
+    let update = state_transition(&money_state, tx)?;
+    money_state.apply(update);
 
     Ok(())
 }

+ 5 - 1
bin/daod/src/main.rs

@@ -14,6 +14,9 @@ use darkfi::{
     Result,
 };
 
+mod demo;
+use crate::demo::demo;
+
 async fn start() -> Result<()> {
     let rpc_addr = Url::parse("tcp://127.0.0.1:7777")?;
     let rpc_interface = Arc::new(JsonRpcInterface {});
@@ -57,6 +60,7 @@ async fn main() -> Result<()> {
         ColorChoice::Auto,
     )?;
 
-    start().await?;
+    //start().await?;
+    demo().await;
     Ok(())
 }

+ 2 - 0
bin/darkfid/src/error.rs

@@ -17,6 +17,7 @@ pub enum RpcError {
     NotYetSynced = -32112,
     InvalidAddressParam = -32113,
     InvalidAmountParam = -32114,
+    DecryptionFailed = -32115,
 }
 
 fn to_tuple(e: RpcError) -> (i64, String) {
@@ -35,6 +36,7 @@ fn to_tuple(e: RpcError) -> (i64, String) {
         RpcError::NotYetSynced => "Blockchain not yet synced",
         RpcError::InvalidAddressParam => "Invalid address parameter",
         RpcError::InvalidAmountParam => "invalid amount parameter",
+        RpcError::DecryptionFailed => "Decryption failed",
     };
 
     (e as i64, msg.to_string())

+ 1 - 0
bin/darkfid/src/main.rs

@@ -185,6 +185,7 @@ impl RequestHandler for Darkfid {
             Some("wallet.get_balances") => return self.get_balances(req.id, params).await,
             Some("wallet.get_coins_valtok") => return self.get_coins_valtok(req.id, params).await,
             Some("wallet.get_merkle_path") => return self.get_merkle_path(req.id, params).await,
+            Some("wallet.decrypt_note") => return self.decrypt_note(req.id, params).await,
             Some(_) | None => return JsonError::new(MethodNotFound, None, req.id).into(),
         }
     }

+ 46 - 1
bin/darkfid/src/rpc_wallet.rs

@@ -10,11 +10,12 @@ use darkfi::{
         keypair::{Keypair, PublicKey, SecretKey},
         token_id,
     },
+    node::State,
     rpc::jsonrpc::{
         ErrorCode::{InternalError, InvalidParams, ParseError},
         JsonError, JsonResponse, JsonResult,
     },
-    util::serial::serialize,
+    util::serial::{deserialize, serialize},
 };
 
 use super::Darkfid;
@@ -283,4 +284,48 @@ impl Darkfid {
             merkle_path.iter().map(|x| bs58::encode(serialize(x)).into_string()).collect();
         JsonResponse::new(json!(ret), id).into()
     }
+
+    // RPCAPI:
+    // Try to decrypt a given encrypted note with the secret keys
+    // found in the wallet.
+    // --> {"jsonrpc": "2.0", "method": "wallet.decrypt_note", params": [ciphertext], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": "base58_encoded_plain_note", "id": 1}
+    pub async fn decrypt_note(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 || !params[0].is_string() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let bytes = match bs58::decode(params[0].as_str().unwrap()).into_vec() {
+            Ok(v) => v,
+            Err(e) => {
+                error!("decrypt_note(): Failed decoding base58 string: {}", e);
+                return JsonError::new(ParseError, None, id).into()
+            }
+        };
+
+        let enc_note = match deserialize(&bytes) {
+            Ok(v) => v,
+            Err(e) => {
+                error!("decrypt_note(): Failed deserializing bytes into EncryptedNote: {}", e);
+                return JsonError::new(InternalError, None, id).into()
+            }
+        };
+
+        let keypairs = match self.client.get_keypairs().await {
+            Ok(v) => v,
+            Err(e) => {
+                error!("decrypt_note(): Failed fetching keypairs: {}", e);
+                return JsonError::new(InternalError, None, id).into()
+            }
+        };
+
+        for kp in keypairs {
+            if let Some(note) = State::try_decrypt_note(&enc_note, kp.secret) {
+                let s = bs58::encode(&serialize(&note)).into_string();
+                return JsonResponse::new(json!(s), id).into()
+            }
+        }
+
+        return server_error(RpcError::DecryptionFailed, id)
+    }
 }

+ 1 - 2
bin/darkotc/Cargo.toml

@@ -12,10 +12,9 @@ edition = "2021"
 async-std = {version = "1.12.0", features = ["attributes"]}
 bs58 = "0.4.0"
 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_gadgets = "0.2.0"
 rand = "0.8.5"
 serde_json = "1.0.83"
-termion = "1.5.6"
 url = "2.2.2"

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

@@ -1,6 +1,8 @@
 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)> {
     let v: Vec<&str> = s.split(':').collect();
@@ -29,7 +31,6 @@ pub fn parse_token_pair(s: &str) -> Result<(String, String)> {
         exit(1);
     }
 
-    // TODO: Check if valid Fp
     let tok0 = bs58::decode(v[0]).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);
     }
 
+    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()))
 }

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

@@ -1,36 +1,37 @@
 use std::{
     io::{stdin, Read},
     process::exit,
-    str::FromStr,
 };
 
 use clap::{Parser, Subcommand};
-use darkfi::crypto::proof::VerifyingKey;
 use halo2_proofs::{arithmetic::Field, pasta::group::ff::PrimeField};
 use rand::rngs::OsRng;
-use serde_json::json;
-use termion::color;
 use url::Url;
 
 use darkfi::{
     cli_desc,
     crypto::{
-        address::Address,
         burn_proof::{create_burn_proof, verify_burn_proof},
         keypair::{PublicKey, SecretKey},
-        merkle_node::MerkleNode,
         mint_proof::{create_mint_proof, verify_mint_proof},
-        proof::ProvingKey,
+        note::{EncryptedNote, Note},
+        proof::{ProvingKey, VerifyingKey},
+        schnorr,
+        schnorr::SchnorrSecret,
         token_id,
         types::{DrkCoinBlind, DrkSerial, DrkTokenId, DrkValueBlind},
         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::{
-        cli::progress_bar,
+        cli::{fg_green, fg_red, progress_bar},
         encode_base10,
-        serial::{deserialize, serialize, SerialDecodable, SerialEncodable},
+        serial::{deserialize, serialize, Encodable, SerialDecodable, SerialEncodable},
     },
     zk::circuit::{BurnContract, MintContract},
     Result,
@@ -38,6 +39,8 @@ use darkfi::{
 
 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)]
@@ -60,187 +63,104 @@ enum Subcmd {
     /// Initialize an atomic swap
     Init {
         #[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,
 
         #[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,
     },
 
-    /// Inspect swap data from stdin or file.
+    /// Inspect partial swap data from stdin.
     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(
     endpoint: Url,
     token_pair: (String, String),
     value_pair: (u64, u64),
-) -> Result<()> {
+) -> Result<PartialSwapData> {
     let rpc_client = RpcClient::new(endpoint).await?;
     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 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.
     let balance = rpc.balance_of(&token_pair.0).await?;
     if balance < vp.0 {
         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
         );
         eprintln!("Available balance is {} ({})", encode_base10(balance, 8), balance);
         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: 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?;
     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);
     }
 
     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
-    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,
         Err(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();
 
-    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());
     pb.finish();
 
-    // The coin we want to receive.
+    // 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);
 
-    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(
         &mint_pk,
         vp.1,
@@ -272,20 +192,19 @@ async fn init_swap(
         recv_token_blind,
         recv_serial,
         recv_coin_blind,
-        our_publickey,
+        our_pubk,
     )?;
     pb.finish();
 
     // 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 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);
+            eprintln!("Failed to get Merkle path for our coin from darkfid RPC: {}", e);
             exit(1);
         }
     };
@@ -305,9 +224,22 @@ async fn init_swap(
     )?;
     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
     // counterparty can verify correctness.
-    let swap_data = SwapData {
+    let partial_swap_data = PartialSwapData {
         mint_proof,
         mint_revealed,
         mint_value: vp.1,
@@ -320,12 +252,10 @@ async fn init_swap(
         burn_revealed,
         burn_value_blind: coin.note.value_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<()> {
@@ -344,38 +274,38 @@ fn inspect(data: &str) -> Result<()> {
         }
     };
 
-    let sd: SwapData = match deserialize(&bytes) {
+    let sd: PartialSwapData = match deserialize(&bytes) {
         Ok(v) => v,
         Err(e) => {
-            eprintln!("Error: Failed to deserialize swap data into struct: {}", e);
+            eprintln!("Error deserializing partial swap data into struct: {}", e);
             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();
 
-    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());
     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() {
         burn_valid = true;
     }
     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() {
         mint_valid = true;
     }
     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
     {
@@ -402,49 +332,49 @@ fn inspect(data: &str) -> Result<()> {
 
     eprint!("  Burn proof: ");
     if burn_valid {
-        eprintln!("{}VALID{}", color::Fg(color::Green), color::Fg(color::Reset));
+        eprintln!("{}", fg_green("VALID"));
     } else {
-        eprintln!("{}INVALID{}", color::Fg(color::Red), color::Fg(color::Reset));
+        eprintln!("{}", fg_red("INVALID"));
         valid = false;
     }
 
     eprint!("  Burn proof value commitment: ");
     if burn_value_valid {
-        eprintln!("{}VALID{}", color::Fg(color::Green), color::Fg(color::Reset));
+        eprintln!("{}", fg_green("VALID"));
     } else {
-        eprintln!("{}INVALID{}", color::Fg(color::Red), color::Fg(color::Reset));
+        eprintln!("{}", fg_red("INVALID"));
         valid = false;
     }
 
     eprint!("  Burn proof token commitment: ");
     if burn_token_valid {
-        eprintln!("{}VALID{}", color::Fg(color::Green), color::Fg(color::Reset));
+        eprintln!("{}", fg_green("VALID"));
     } else {
-        eprintln!("{}INVALID{}", color::Fg(color::Red), color::Fg(color::Reset));
+        eprintln!("{}", fg_red("INVALID"));
         valid = false;
     }
 
     eprint!("  Mint proof: ");
     if mint_valid {
-        eprintln!("{}VALID{}", color::Fg(color::Green), color::Fg(color::Reset));
+        eprintln!("{}", fg_green("VALID"));
     } else {
-        eprintln!("{}INVALID{}", color::Fg(color::Red), color::Fg(color::Reset));
+        eprintln!("{}", fg_red("INVALID"));
         valid = false;
     }
 
     eprint!("  Mint proof value commitment: ");
     if mint_value_valid {
-        eprintln!("{}VALID{}", color::Fg(color::Green), color::Fg(color::Reset));
+        eprintln!("{}", fg_green("VALID"));
     } else {
-        eprintln!("{}INVALID{}", color::Fg(color::Red), color::Fg(color::Reset));
+        eprintln!("{}", fg_red("INVALID"));
         valid = false;
     }
 
     eprint!("  Mint proof token commitment: ");
     if mint_token_valid {
-        eprintln!("{}VALID{}", color::Fg(color::Green), color::Fg(color::Reset));
+        eprintln!("{}", fg_green("VALID"));
     } else {
-        eprintln!("{}INVALID{}", color::Fg(color::Red), color::Fg(color::Reset));
+        eprintln!("{}", fg_red("INVALID"));
         valid = false;
     }
 
@@ -461,38 +391,105 @@ fn inspect(data: &str) -> Result<()> {
         bs58::encode(sd.burn_token.to_repr()).into_string()
     );
 
+    eprint!("\nThe ZK proofs and commitments inspected are ");
     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);
     } else {
-        eprintln!(
-            "\nThe ZK proofs and commitments inspected are {}VALID{}",
-            color::Fg(color::Green),
-            color::Fg(color::Reset)
-        );
+        eprintln!("{}", fg_green("VALID"));
     }
 
     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]
@@ -503,12 +500,23 @@ async fn main() -> Result<()> {
         Subcmd::Init { token_pair, value_pair } => {
             let token_pair = parse_token_pair(&token_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 => {
             let mut buf = String::new();
             stdin().read_to_string(&mut buf)?;
             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))
+    }
+}

+ 0 - 1
bin/dnetview/src/main.rs

@@ -14,7 +14,6 @@ use tui::{
     backend::{Backend, TermionBackend},
     Terminal,
 };
-
 pub mod config;
 pub mod error;
 pub mod model;

+ 2 - 2
example/tx.rs

@@ -170,7 +170,7 @@ fn main() -> Result<()> {
 
     // Now spend
     let owncoin = &state.own_coins[0];
-    let note = owncoin.note;
+    let note = &owncoin.note;
     let leaf_position = owncoin.leaf_position;
     let root = state.tree.root(0).unwrap();
     let merkle_path = state.tree.authentication_path(leaf_position, &root).unwrap();
@@ -181,7 +181,7 @@ fn main() -> Result<()> {
             leaf_position,
             merkle_path,
             secret: keypair.secret,
-            note,
+            note: note.clone(),
         }],
         outputs: vec![TransactionBuilderOutputInfo {
             value: 110,

+ 2 - 1
script/sql/coins.sql

@@ -9,5 +9,6 @@ CREATE TABLE IF NOT EXISTS coins(
 	secret BLOB NOT NULL,
 	is_spent BOOLEAN NOT NULL,
 	nullifier BLOB NOT NULL,
-	leaf_position BLOB NOT NULL
+	leaf_position BLOB NOT NULL,
+	memo BLOB
 );

+ 1 - 1
src/crypto/mod.rs

@@ -32,7 +32,7 @@ use crate::{
 use keypair::SecretKey;
 use std::io;
 
-#[derive(Copy, Clone, Debug, PartialEq, Eq, SerialEncodable, SerialDecodable)]
+#[derive(Clone, Debug, PartialEq, Eq, SerialEncodable, SerialDecodable)]
 pub struct OwnCoin {
     pub coin: coin::Coin,
     pub note: note::Note,

+ 9 - 9
src/crypto/note.rs

@@ -11,12 +11,9 @@ use crate::{
     Error, Result,
 };
 
-/// Plaintext size is serial + value + token_id + coin_blind + value_blind
-pub const NOTE_PLAINTEXT_SIZE: usize = 32 + 8 + 32 + 32 + 32 + 32;
 pub const AEAD_TAG_SIZE: usize = 16;
-pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + AEAD_TAG_SIZE;
 
-#[derive(Copy, Clone, Debug, PartialEq, Eq, SerialEncodable, SerialDecodable)]
+#[derive(Clone, Debug, PartialEq, Eq, SerialEncodable, SerialDecodable)]
 pub struct Note {
     pub serial: DrkSerial,
     pub value: u64,
@@ -24,6 +21,7 @@ pub struct Note {
     pub coin_blind: DrkCoinBlind,
     pub value_blind: DrkValueBlind,
     pub token_blind: DrkValueBlind,
+    pub memo: Vec<u8>,
 }
 
 impl Note {
@@ -36,12 +34,12 @@ impl Note {
         let mut input = Vec::new();
         self.encode(&mut input)?;
 
-        let mut ciphertext = [0u8; ENC_CIPHERTEXT_SIZE];
+        let mut ciphertext = vec![0; input.len() + AEAD_TAG_SIZE];
         assert_eq!(
             ChachaPolyIetf::aead_cipher()
                 .seal_to(&mut ciphertext, &input, &[], key.as_ref(), &[0u8; 12])
                 .unwrap(),
-            ENC_CIPHERTEXT_SIZE
+            input.len() + AEAD_TAG_SIZE
         );
 
         Ok(EncryptedNote { ciphertext, ephem_public })
@@ -50,7 +48,7 @@ impl Note {
 
 #[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
 pub struct EncryptedNote {
-    ciphertext: [u8; ENC_CIPHERTEXT_SIZE],
+    ciphertext: Vec<u8>,
     ephem_public: PublicKey,
 }
 
@@ -59,12 +57,12 @@ impl EncryptedNote {
         let shared_secret = sapling_ka_agree(secret, &self.ephem_public);
         let key = kdf_sapling(&shared_secret, &self.ephem_public);
 
-        let mut plaintext = [0; ENC_CIPHERTEXT_SIZE];
+        let mut plaintext = vec![0; self.ciphertext.len()];
         assert_eq!(
             ChachaPolyIetf::aead_cipher()
                 .open_to(&mut plaintext, &self.ciphertext, &[], key.as_ref(), &[0u8; 12])
                 .map_err(|_| Error::NoteDecryptionFailed)?,
-            NOTE_PLAINTEXT_SIZE
+            self.ciphertext.len() - AEAD_TAG_SIZE
         );
 
         Note::decode(&plaintext[..])
@@ -86,6 +84,7 @@ mod tests {
             coin_blind: DrkCoinBlind::random(&mut OsRng),
             value_blind: DrkValueBlind::random(&mut OsRng),
             token_blind: DrkValueBlind::random(&mut OsRng),
+            memo: vec![32, 223, 231, 3, 1, 1],
         };
 
         let keypair = Keypair::random(&mut OsRng);
@@ -95,5 +94,6 @@ mod tests {
         assert_eq!(note.value, note2.value);
         assert_eq!(note.token_id, note2.token_id);
         assert_eq!(note.token_blind, note2.token_blind);
+        assert_eq!(note.memo, note2.memo);
     }
 }

+ 7 - 0
src/crypto/proof.rs

@@ -15,6 +15,13 @@ use crate::{
     Result,
 };
 
+// TODO: this API needs rework. It's not very good.
+// keygen_pk() takes a VerifyingKey by value,
+// yet ProvingKey also provides get_vk() -> &VerifyingKey
+//
+// Maybe we should just use the native halo2 types instead of wrapping them.
+// We can avoid double creating the vk when we call VerifyingKey::build(), ProvingKey::build()
+
 #[derive(Clone, Debug)]
 pub struct VerifyingKey {
     pub params: Params<vesta::Affine>,

+ 7 - 1
src/crypto/schnorr.rs

@@ -2,7 +2,7 @@ use std::io;
 
 use halo2_gadgets::ecc::chip::FixedPoint;
 use pasta_curves::{
-    group::{ff::Field, GroupEncoding},
+    group::{ff::Field, Group, GroupEncoding},
     pallas,
 };
 use rand::rngs::OsRng;
@@ -23,6 +23,12 @@ pub struct Signature {
     response: pallas::Scalar,
 }
 
+impl Signature {
+    pub fn dummy() -> Self {
+        Self { commit: pallas::Point::identity(), response: pallas::Scalar::zero() }
+    }
+}
+
 pub trait SchnorrSecret {
     fn sign(&self, message: &[u8]) -> Signature;
 }

+ 6 - 0
src/error.rs

@@ -324,6 +324,12 @@ pub enum Error {
 /// Transaction verification errors
 #[derive(Debug, Clone, thiserror::Error)]
 pub enum VerifyFailed {
+    #[error("Transaction has no inputs")]
+    LackingInputs,
+
+    #[error("Transaction has no outputs")]
+    LackingOutputs,
+
     #[error("Invalid cashier/faucet public key for clear input {0}")]
     InvalidCashierOrFaucetKey(usize),
 

+ 1 - 1
src/net/settings.rs

@@ -35,7 +35,7 @@ impl Default for Settings {
             connect_timeout_seconds: 10,
             channel_handshake_seconds: 4,
             channel_heartbeat_seconds: 10,
-            outbound_retry_seconds: 1200,
+            outbound_retry_seconds: 20,
             external_addr: None,
             peers: Vec::new(),
             seeds: Vec::new(),

+ 8 - 4
src/node/client.rs

@@ -2,6 +2,7 @@ use async_std::sync::{Arc, Mutex};
 use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
 use lazy_init::Lazy;
 use log::{debug, error, info};
+use pasta_curves::group::ff::PrimeField;
 
 use super::state::{state_transition, State};
 use crate::{
@@ -101,7 +102,7 @@ impl Client {
                     leaf_position,
                     merkle_path,
                     secret: own_coin.secret,
-                    note: own_coin.note,
+                    note: own_coin.note.clone(),
                 };
 
                 inputs.push(input);
@@ -155,8 +156,11 @@ impl Client {
         clear_input: bool,
         state: Arc<Mutex<State>>,
     ) -> ClientResult<Transaction> {
-        // TODO: Token id debug
-        debug!("send(): Sending {}", amount);
+        debug!(
+            "send(): Sending {} {} tokens",
+            amount,
+            bs58::encode(token_id.to_repr()).into_string()
+        );
 
         if amount == 0 {
             return Err(ClientFailed::InvalidAmount(0))
@@ -234,7 +238,7 @@ impl Client {
 
     fn build_mint_pk() -> ProvingKey {
         debug!("Building proving key for MintContract");
-        ProvingKey::build(8, &MintContract::default())
+        ProvingKey::build(11, &MintContract::default())
     }
 
     fn build_burn_pk() -> ProvingKey {

+ 9 - 4
src/node/state.rs

@@ -164,8 +164,13 @@ impl State {
                     debug!(target: "state_apply", "Received a coin: amount {}", note.value);
                     let leaf_position = self.tree.witness().unwrap();
                     let nullifier = Nullifier::new(*secret, note.serial);
-                    let own_coin =
-                        OwnCoin { coin, note, secret: *secret, nullifier, leaf_position };
+                    let own_coin = OwnCoin {
+                        coin,
+                        note: note.clone(),
+                        secret: *secret,
+                        nullifier,
+                        leaf_position,
+                    };
 
                     // TODO: FIXME: BUG check values inside the note are correct
                     // We need to hash them all and check them against the coin
@@ -190,7 +195,7 @@ impl State {
         Ok(())
     }
 
-    fn try_decrypt_note(ciphertext: &EncryptedNote, secret: SecretKey) -> Option<Note> {
+    pub fn try_decrypt_note(ciphertext: &EncryptedNote, secret: SecretKey) -> Option<Note> {
         match ciphertext.decrypt(&secret) {
             Ok(note) => Some(note),
             Err(_) => None,
@@ -238,7 +243,7 @@ impl ProgramState for State {
 
 fn build_mint_vk() -> VerifyingKey {
     debug!("Building verifying key for MintContract");
-    VerifyingKey::build(8, &MintContract::default())
+    VerifyingKey::build(11, &MintContract::default())
 }
 
 fn build_burn_vk() -> VerifyingKey {

+ 8 - 5
src/tx/builder.rs

@@ -69,6 +69,8 @@ impl TransactionBuilder {
     }
 
     pub fn build(self, mint_pk: &ProvingKey, burn_pk: &ProvingKey) -> Result<Transaction> {
+        assert!(self.clear_inputs.len() + self.inputs.len() > 0);
+
         let mut clear_inputs = vec![];
         let token_blind = DrkValueBlind::random(&mut OsRng);
         for input in &self.clear_inputs {
@@ -89,9 +91,8 @@ impl TransactionBuilder {
         let mut input_blinds = vec![];
         let mut signature_secrets = vec![];
         for input in self.inputs {
-            // FIXME: BUG - looks like we are reusing the value_blind from the output
-            // This must be a completely new random value or the value_commit will be the same.
-            input_blinds.push(input.note.value_blind);
+            let value_blind = DrkValueBlind::random(&mut OsRng);
+            input_blinds.push(value_blind);
 
             let signature_secret = SecretKey::random(&mut OsRng);
 
@@ -99,7 +100,7 @@ impl TransactionBuilder {
                 burn_pk,
                 input.note.value,
                 input.note.token_id,
-                input.note.value_blind,
+                value_blind,
                 token_blind,
                 input.note.serial,
                 input.note.coin_blind,
@@ -118,6 +119,8 @@ impl TransactionBuilder {
 
         let mut outputs = vec![];
         let mut output_blinds = vec![];
+        // This value_blind calc assumes there will always be at least a single output
+        assert!(self.outputs.len() > 0);
 
         for (i, output) in self.outputs.iter().enumerate() {
             let value_blind = if i == self.outputs.len() - 1 {
@@ -142,7 +145,6 @@ impl TransactionBuilder {
             )?;
 
             // Encrypted note
-
             let note = Note {
                 serial,
                 value: output.value,
@@ -150,6 +152,7 @@ impl TransactionBuilder {
                 coin_blind,
                 value_blind,
                 token_blind,
+                memo: vec![],
             };
 
             let encrypted_note = note.encrypt(&output.public)?;

+ 13 - 3
src/tx/mod.rs

@@ -22,7 +22,7 @@ use crate::{
 };
 
 pub mod builder;
-mod partial;
+pub mod partial;
 
 /// A DarkFi transaction
 #[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
@@ -48,7 +48,7 @@ pub struct TransactionClearInput {
     pub token_blind: DrkValueBlind,
     /// Public key for the signature
     pub signature_public: PublicKey,
-    /// Input's signature
+    /// Transaction signature
     pub signature: schnorr::Signature,
 }
 
@@ -77,6 +77,16 @@ pub struct TransactionOutput {
 impl Transaction {
     /// Verify the transaction
     pub fn verify(&self, mint_vk: &VerifyingKey, burn_vk: &VerifyingKey) -> VerifyResult<()> {
+        // Transaction must have minimum 1 clear or anon input, and 1 output
+        if self.clear_inputs.len() + self.inputs.len() == 0 {
+            error!("tx::verify(): Missing inputs");
+            return Err(VerifyFailed::LackingInputs)
+        }
+        if self.outputs.len() == 0 {
+            error!("tx::verify(): Missing outputs");
+            return Err(VerifyFailed::LackingOutputs)
+        }
+
         // Accumulator for the value commitments
         let mut valcom_total = DrkValueCommit::identity();
 
@@ -196,7 +206,7 @@ impl TransactionClearInput {
 }
 
 impl TransactionInput {
-    fn from_partial(
+    pub fn from_partial(
         partial: partial::PartialTransactionInput,
         signature: schnorr::Signature,
     ) -> Self {

+ 3 - 3
src/tx/partial.rs

@@ -12,14 +12,14 @@ use crate::{
     Result,
 };
 
-#[derive(SerialEncodable, SerialDecodable)]
+#[derive(Clone, SerialEncodable, SerialDecodable)]
 pub struct PartialTransaction {
     pub clear_inputs: Vec<PartialTransactionClearInput>,
     pub inputs: Vec<PartialTransactionInput>,
     pub outputs: Vec<TransactionOutput>,
 }
 
-#[derive(SerialEncodable, SerialDecodable)]
+#[derive(Clone, SerialEncodable, SerialDecodable)]
 pub struct PartialTransactionClearInput {
     pub value: u64,
     pub token_id: DrkTokenId,
@@ -28,7 +28,7 @@ pub struct PartialTransactionClearInput {
     pub signature_public: PublicKey,
 }
 
-#[derive(SerialEncodable, SerialDecodable)]
+#[derive(Clone, SerialEncodable, SerialDecodable)]
 pub struct PartialTransactionInput {
     pub burn_proof: Proof,
     pub revealed: BurnRevealedValues,

+ 9 - 0
src/util/cli.rs

@@ -10,6 +10,7 @@ use std::{
 use indicatif::{ProgressBar, ProgressStyle};
 use serde::{de::DeserializeOwned, Serialize};
 use simplelog::ConfigBuilder;
+use termion::color;
 
 use crate::{Error, Result};
 
@@ -208,3 +209,11 @@ pub fn progress_bar(message: &str) -> ProgressBar {
     progress_bar.set_message(message.to_string());
     progress_bar
 }
+
+pub fn fg_red(message: &str) -> String {
+    format!("{}{}{}", color::Fg(color::Red), message, color::Fg(color::Reset))
+}
+
+pub fn fg_green(message: &str) -> String {
+    format!("{}{}{}", color::Fg(color::Green), message, color::Fg(color::Reset))
+}

+ 13 - 8
src/wallet/walletdb.rs

@@ -270,7 +270,8 @@ impl WalletDb {
             let value = deserialize(row.get("value"))?;
             let token_id = deserialize(row.get("token_id"))?;
             let token_blind = deserialize(row.get("token_blind"))?;
-            let note = Note { serial, value, token_id, coin_blind, value_blind, token_blind };
+            let memo = deserialize(row.get("memo"))?;
+            let note = Note { serial, value, token_id, coin_blind, value_blind, token_blind, memo };
 
             let secret = deserialize(row.get("secret"))?;
             let nullifier = deserialize(row.get("nullifier"))?;
@@ -329,7 +330,8 @@ impl WalletDb {
             let value = deserialize(row.get("value"))?;
             let token_id = deserialize(row.get("token_id"))?;
             let token_blind = deserialize(row.get("token_blind"))?;
-            let note = Note { serial, value, token_id, coin_blind, value_blind, token_blind };
+            let memo = deserialize(row.get("memo"))?;
+            let note = Note { serial, value, token_id, coin_blind, value_blind, token_blind, memo };
 
             let secret = deserialize(row.get("secret"))?;
             let nullifier = deserialize(row.get("nullifier"))?;
@@ -356,15 +358,16 @@ impl WalletDb {
         let secret = serialize(&own_coin.secret);
         let nullifier = serialize(&own_coin.nullifier);
         let leaf_position = serialize(&own_coin.leaf_position);
+        let memo = serialize(&own_coin.note.memo);
         let is_spent: u8 = 0;
 
         let mut conn = self.conn.acquire().await?;
         sqlx::query(
             "INSERT OR REPLACE INTO coins
             (coin, serial, coin_blind, valcom_blind, token_blind, value,
-             token_id, secret, is_spent, nullifier, leaf_position)
+             token_id, secret, is_spent, nullifier, leaf_position, memo)
             VALUES
-             (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11);",
+             (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12);",
         )
         .bind(coin)
         .bind(serial)
@@ -377,6 +380,7 @@ impl WalletDb {
         .bind(is_spent)
         .bind(nullifier)
         .bind(leaf_position)
+        .bind(memo)
         .execute(&mut conn)
         .await?;
 
@@ -510,6 +514,7 @@ mod tests {
             coin_blind: DrkCoinBlind::random(&mut OsRng),
             value_blind: DrkValueBlind::random(&mut OsRng),
             token_blind: DrkValueBlind::random(&mut OsRng),
+            memo: vec![],
         };
 
         let coin = Coin(pallas::Base::random(&mut OsRng));
@@ -541,19 +546,19 @@ mod tests {
         let c3 = dummy_coin(&keypair.secret, 11, &token_id);
 
         // put_own_coin()
-        wallet.put_own_coin(c0).await?;
+        wallet.put_own_coin(c0.clone()).await?;
         tree1.append(&MerkleNode::from_coin(&c0.coin));
         tree1.witness();
 
-        wallet.put_own_coin(c1).await?;
+        wallet.put_own_coin(c1.clone()).await?;
         tree1.append(&MerkleNode::from_coin(&c1.coin));
         tree1.witness();
 
-        wallet.put_own_coin(c2).await?;
+        wallet.put_own_coin(c2.clone()).await?;
         tree1.append(&MerkleNode::from_coin(&c2.coin));
         tree1.witness();
 
-        wallet.put_own_coin(c3).await?;
+        wallet.put_own_coin(c3.clone()).await?;
         tree1.append(&MerkleNode::from_coin(&c3.coin));
         tree1.witness();