diff --git a/grin/tests/framework/mod.rs b/grin/tests/framework/mod.rs index ed840cf19..3755d8ae9 100644 --- a/grin/tests/framework/mod.rs +++ b/grin/tests/framework/mod.rs @@ -288,6 +288,23 @@ impl LocalServerContainer { self.wallet_is_running = true; } + pub fn get_wallet_seed(config: &WalletConfig) -> wallet::WalletSeed { + let _=fs::create_dir_all(config.clone().data_file_dir); + wallet::WalletSeed::init_file(config); + let wallet_seed = + wallet::WalletSeed::from_file(config).expect("Failed to read wallet seed file."); + wallet_seed + } + + pub fn get_wallet_info(config: &WalletConfig, wallet_seed: &wallet::WalletSeed) -> wallet::WalletInfo { + let keychain = wallet_seed.derive_keychain("grin_test").expect( + "Failed to derive keychain from seed file and passphrase.", + ); + + wallet::retrieve_info(config, &keychain) + + } + pub fn send_amount_to(config: &WalletConfig, amount:&str, diff --git a/grin/tests/wallet.rs b/grin/tests/wallet.rs index 4e0435611..f267ca661 100644 --- a/grin/tests/wallet.rs +++ b/grin/tests/wallet.rs @@ -58,6 +58,8 @@ fn basic_wallet_transactions() { coinbase_wallet.lock().unwrap().wallet_config.clone() }; + let coinbase_seed = LocalServerContainer::get_wallet_seed(&coinbase_wallet_config); + let _ = thread::spawn(move || { let mut w = coinbase_wallet.lock().unwrap(); w.run_wallet(0); @@ -73,6 +75,7 @@ fn basic_wallet_transactions() { target_wallet.lock().unwrap().wallet_config.clone() }; + let recp_seed = LocalServerContainer::get_wallet_seed(&recp_wallet_config); //Start up a second wallet, to receive let _ = thread::spawn(move || { let mut w = target_wallet_cloned.lock().unwrap(); @@ -96,15 +99,45 @@ fn basic_wallet_transactions() { server_one.run_server(120); }); - //Wait for chain to build - thread::sleep(time::Duration::from_millis(5000)); + //Wait until we have some funds to send + let mut coinbase_info = LocalServerContainer::get_wallet_info(&coinbase_wallet_config, &coinbase_seed); + let mut slept_time = 0; + while coinbase_info.amount_currently_spendable < 100000000000{ + thread::sleep(time::Duration::from_millis(500)); + slept_time+=500; + if slept_time > 10000 { + panic!("Coinbase not confirming in time"); + } + coinbase_info = LocalServerContainer::get_wallet_info(&coinbase_wallet_config, &coinbase_seed); + } warn!(LOGGER, "Sending 50 Grins to recipient wallet"); - LocalServerContainer::send_amount_to(&coinbase_wallet_config, "50.00", 1, "all", "http://127.0.0.1:20002"); + LocalServerContainer::send_amount_to(&coinbase_wallet_config, "50.00", 1, "not_all", "http://127.0.0.1:20002"); - //let some more mining happen, make sure nothing pukes - thread::sleep(time::Duration::from_millis(5000)); + //Wait for a confirmation + thread::sleep(time::Duration::from_millis(3000)); + let coinbase_info = LocalServerContainer::get_wallet_info(&coinbase_wallet_config, &coinbase_seed); + println!("Coinbase wallet info: {:?}", coinbase_info); + let recipient_info = LocalServerContainer::get_wallet_info(&recp_wallet_config, &recp_seed); + println!("Recipient wallet info: {:?}", recipient_info); + + assert!(recipient_info.data_confirmed && recipient_info.amount_currently_spendable==49992000000); + + warn!(LOGGER, "Sending many small transactions to recipient wallet"); + for _ in 0..10 { + LocalServerContainer::send_amount_to(&coinbase_wallet_config, "1.00", 1, "not_all", "http://127.0.0.1:20002"); + } + + thread::sleep(time::Duration::from_millis(10000)); + let recipient_info = LocalServerContainer::get_wallet_info(&recp_wallet_config, &recp_seed); + println!("Recipient wallet info post little sends: {:?}", recipient_info); + + assert!(recipient_info.data_confirmed && recipient_info.amount_currently_spendable==59912000000); //send some cash right back LocalServerContainer::send_amount_to(&recp_wallet_config, "25.00", 1, "all", "http://127.0.0.1:10002"); + thread::sleep(time::Duration::from_millis(5000)); + + let coinbase_info = LocalServerContainer::get_wallet_info(&coinbase_wallet_config, &coinbase_seed); + println!("Coinbase wallet info final: {:?}", coinbase_info); } diff --git a/keychain/Cargo.toml b/keychain/Cargo.toml index 65af4b1c7..649be6cdb 100644 --- a/keychain/Cargo.toml +++ b/keychain/Cargo.toml @@ -6,6 +6,7 @@ authors = ["Antioch Peverell"] [dependencies] byteorder = "^1.0" blake2-rfc = "~0.2.17" +uuid = { version = "~0.5.1", features = ["serde", "v4"] } rand = "^0.3" slog = { version = "^2.0.12", features = ["max_level_trace", "release_max_level_trace"] } serde = "~1.0.8" diff --git a/keychain/src/keychain.rs b/keychain/src/keychain.rs index d3d4123c1..2b24d62e9 100644 --- a/keychain/src/keychain.rs +++ b/keychain/src/keychain.rs @@ -24,6 +24,7 @@ use util::secp::aggsig; use util::logger::LOGGER; use util::kernel_sig_msg; use blake2; +use uuid::Uuid; use blind::{BlindSum, BlindingFactor}; use extkey::{self, Identifier}; @@ -32,6 +33,7 @@ pub enum Error { ExtendedKey(extkey::Error), Secp(secp::Error), KeyDerivation(String), + Transaction(String), } impl From for Error { @@ -62,7 +64,7 @@ pub struct AggSigTxContext { pub struct Keychain { secp: Secp256k1, extkey: extkey::ExtendedKey, - pub aggsig_context: Arc>>, + pub aggsig_contexts: Arc>>>, key_overrides: HashMap, key_derivation_cache: Arc>>, } @@ -91,7 +93,7 @@ impl Keychain { let keychain = Keychain { secp: secp, extkey: extkey, - aggsig_context: Arc::new(RwLock::new(None)), + aggsig_contexts: Arc::new(RwLock::new(None)), key_overrides: HashMap::new(), key_derivation_cache: Arc::new(RwLock::new(HashMap::new())), }; @@ -269,74 +271,109 @@ impl Keychain { Ok(BlindingFactor::new(blinding)) } - pub fn aggsig_create_context(&self, sec_key:SecretKey) { - let mut context = self.aggsig_context.write().unwrap(); - *context = Some(AggSigTxContext{ + pub fn aggsig_create_context(&self, transaction_id: &Uuid, sec_key:SecretKey) + -> Result<(), Error>{ + let mut contexts = self.aggsig_contexts.write().unwrap(); + if contexts.is_none() { + *contexts = Some(HashMap::new()) + } + if contexts.as_mut().unwrap().contains_key(transaction_id) { + return Err(Error::Transaction(String::from("Duplication transaction id"))); + } + contexts.as_mut().unwrap().insert(transaction_id.clone(), AggSigTxContext{ sec_key: sec_key, sec_nonce: aggsig::export_secnonce_single(&self.secp).unwrap(), output_ids: vec![], }); + Ok(()) } /// Tracks an output contributing to my excess value (if it needs to /// be kept between invocations - pub fn aggsig_add_output(&self, id: &Identifier){ - let mut agg_context=self.aggsig_context.write().unwrap(); - let agg_context_write=agg_context.as_mut().unwrap(); - agg_context_write.output_ids.push(id.clone()); + pub fn aggsig_add_output(&self, transaction_id: &Uuid, output_id:&Identifier){ + let mut agg_contexts = self.aggsig_contexts.write().unwrap(); + let mut agg_contexts_local = agg_contexts.as_mut().unwrap().clone(); + let mut agg_context = agg_contexts_local.get(transaction_id).unwrap().clone(); + agg_context.output_ids.push(output_id.clone()); + agg_contexts_local.insert(transaction_id.clone(), agg_context); + *agg_contexts = Some(agg_contexts_local); } /// Returns all stored outputs - pub fn aggsig_get_outputs(&self) -> Vec { - let context = self.aggsig_context.clone(); - let context_read=context.read().unwrap(); - let agg_context=context_read.as_ref().unwrap(); - agg_context.output_ids.clone() + pub fn aggsig_get_outputs(&self, transaction_id: &Uuid) -> Vec { + let contexts = self.aggsig_contexts.clone(); + let contexts_read = contexts.read().unwrap(); + let agg_context = contexts_read.as_ref().unwrap(); + let agg_context_return = agg_context.get(transaction_id); + agg_context_return.unwrap().output_ids.clone() } /// Returns private key, private nonce - pub fn aggsig_get_private_keys(&self) -> (SecretKey, SecretKey) { - let context = self.aggsig_context.clone(); - let context_read=context.read().unwrap(); - let agg_context=context_read.as_ref().unwrap(); - (agg_context.sec_key.clone(), - agg_context.sec_nonce.clone()) + pub fn aggsig_get_private_keys(&self, transaction_id: &Uuid) -> (SecretKey, SecretKey) { + let contexts = self.aggsig_contexts.clone(); + let contexts_read=contexts.read().unwrap(); + let agg_context = contexts_read.as_ref().unwrap(); + let agg_context_return = agg_context.get(transaction_id); + (agg_context_return.unwrap().sec_key.clone(), + agg_context_return.unwrap().sec_nonce.clone()) } /// Returns public key, public nonce - pub fn aggsig_get_public_keys(&self) -> (PublicKey, PublicKey) { - let context = self.aggsig_context.clone(); - let context_read=context.read().unwrap(); - let agg_context=context_read.as_ref().unwrap(); - (PublicKey::from_secret_key(&self.secp, &agg_context.sec_key).unwrap(), - PublicKey::from_secret_key(&self.secp, &agg_context.sec_nonce).unwrap()) + pub fn aggsig_get_public_keys(&self, transaction_id: &Uuid) -> (PublicKey, PublicKey) { + let contexts = self.aggsig_contexts.clone(); + let contexts_read=contexts.read().unwrap(); + let agg_context = contexts_read.as_ref().unwrap(); + let agg_context_return = agg_context.get(transaction_id); + (PublicKey::from_secret_key(&self.secp, &agg_context_return.unwrap().sec_key).unwrap(), + PublicKey::from_secret_key(&self.secp, &agg_context_return.unwrap().sec_nonce).unwrap()) } /// Note 'secnonce' here is used to perform the signature, while 'pubnonce' just allows you to /// provide a custom public nonce to include while calculating e /// nonce_sum is the sum used to decide whether secnonce should be inverted during sig time - pub fn aggsig_sign_single(&self, msg: &Message, secnonce:Option<&SecretKey>, pubnonce: Option<&PublicKey>, nonce_sum: Option<&PublicKey>) -> Result { - let context = self.aggsig_context.clone(); - let context_read=context.read().unwrap(); - let agg_context=context_read.as_ref().unwrap(); - let sig = aggsig::sign_single(&self.secp, msg, &agg_context.sec_key, secnonce, pubnonce, nonce_sum)?; + pub fn aggsig_sign_single(&self, + transaction_id: &Uuid, + msg: &Message, + secnonce:Option<&SecretKey>, + pubnonce: Option<&PublicKey>, + nonce_sum: Option<&PublicKey>) -> Result { + let contexts = self.aggsig_contexts.clone(); + let contexts_read=contexts.read().unwrap(); + let agg_context = contexts_read.as_ref().unwrap(); + let agg_context_return = agg_context.get(transaction_id); + let sig = aggsig::sign_single(&self.secp, msg, &agg_context_return.unwrap().sec_key, secnonce, pubnonce, nonce_sum)?; Ok(sig) } //Verifies an aggsig signature - pub fn aggsig_verify_single(&self, sig: &Signature, msg: &Message, pubnonce:Option<&PublicKey>, pubkey:&PublicKey, is_partial:bool) -> bool { + pub fn aggsig_verify_single(&self, + sig: &Signature, + msg: &Message, + pubnonce:Option<&PublicKey>, + pubkey:&PublicKey, + is_partial:bool) -> bool { aggsig::verify_single(&self.secp, sig, msg, pubnonce, pubkey, is_partial) } //Verifies other final sig corresponds with what we're expecting - pub fn aggsig_verify_final_sig_build_msg(&self, sig: &Signature, pubkey: &PublicKey, fee: u64, lock_height:u64) -> bool { + pub fn aggsig_verify_final_sig_build_msg(&self, + sig: &Signature, + pubkey: &PublicKey, + fee: u64, + lock_height:u64) -> bool { let msg = secp::Message::from_slice(&kernel_sig_msg(fee, lock_height)).unwrap(); self.aggsig_verify_single(sig, &msg, None, pubkey, true) } //Verifies other party's sig corresponds with what we're expecting - pub fn aggsig_verify_partial_sig(&self, sig: &Signature, other_pub_nonce:&PublicKey, pubkey:&PublicKey, fee: u64, lock_height:u64) -> bool { - let (_, sec_nonce) = self.aggsig_get_private_keys(); + pub fn aggsig_verify_partial_sig(&self, + transaction_id: &Uuid, + sig: &Signature, + other_pub_nonce:&PublicKey, + pubkey:&PublicKey, + fee: u64, + lock_height:u64) -> bool { + let (_, sec_nonce) = self.aggsig_get_private_keys(transaction_id); let mut nonce_sum = other_pub_nonce.clone(); let _ = nonce_sum.add_exp_assign(&self.secp, &sec_nonce); let msg = secp::Message::from_slice(&kernel_sig_msg(fee, lock_height)).unwrap(); @@ -344,21 +381,29 @@ impl Keychain { self.aggsig_verify_single(sig, &msg, Some(&nonce_sum), pubkey, true) } - pub fn aggsig_calculate_partial_sig(&self, other_pub_nonce:&PublicKey, fee:u64, lock_height:u64) -> Result{ + pub fn aggsig_calculate_partial_sig(&self, + transaction_id: &Uuid, + other_pub_nonce:&PublicKey, + fee:u64, + lock_height:u64) -> Result{ // Add public nonces kR*G + kS*G - let (_, sec_nonce) = self.aggsig_get_private_keys(); + let (_, sec_nonce) = self.aggsig_get_private_keys(transaction_id); let mut nonce_sum = other_pub_nonce.clone(); let _ = nonce_sum.add_exp_assign(&self.secp, &sec_nonce); let msg = secp::Message::from_slice(&kernel_sig_msg(fee, lock_height))?; //Now calculate signature using message M=fee, nonce in e=nonce_sum - self.aggsig_sign_single(&msg, Some(&sec_nonce), Some(&nonce_sum), Some(&nonce_sum)) + self.aggsig_sign_single(transaction_id, &msg, Some(&sec_nonce), Some(&nonce_sum), Some(&nonce_sum)) } /// Helper function to calculate final singature - pub fn aggsig_calculate_final_sig(&self, their_sig: &Signature, our_sig: &Signature, their_pub_nonce: &PublicKey) -> Result { + pub fn aggsig_calculate_final_sig(&self, + transaction_id: &Uuid, + their_sig: &Signature, + our_sig: &Signature, + their_pub_nonce: &PublicKey) -> Result { // Add public nonces kR*G + kS*G - let (_, sec_nonce) = self.aggsig_get_private_keys(); + let (_, sec_nonce) = self.aggsig_get_private_keys(transaction_id); let mut nonce_sum = their_pub_nonce.clone(); let _ = nonce_sum.add_exp_assign(&self.secp, &sec_nonce); let sig = aggsig::add_signatures_single(&self.secp, their_sig, our_sig, &nonce_sum)?; @@ -368,9 +413,10 @@ impl Keychain { /// Helper function to calculate final public key pub fn aggsig_calculate_final_pubkey( &self, + transaction_id: &Uuid, their_public_key: &PublicKey, ) -> Result { - let (our_sec_key, _) = self.aggsig_get_private_keys(); + let (our_sec_key, _) = self.aggsig_get_private_keys(transaction_id); let mut pk_sum = their_public_key.clone(); let _ = pk_sum.add_exp_assign(&self.secp, &our_sec_key); Ok(pk_sum) diff --git a/keychain/src/lib.rs b/keychain/src/lib.rs index 989c941bd..0cebd6edc 100644 --- a/keychain/src/lib.rs +++ b/keychain/src/lib.rs @@ -18,6 +18,7 @@ extern crate blake2_rfc as blake2; extern crate byteorder; extern crate grin_util as util; extern crate rand; +extern crate uuid; extern crate serde; #[macro_use] extern crate serde_derive; diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 7cb5c0667..33c886e84 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -25,6 +25,7 @@ tokio-retry="~0.1.0" router = "~0.5.1" prettytable-rs = "^0.6" term = "~0.4.6" +uuid = { version = "~0.5.1", features = ["serde", "v4"] } grin_api = { path = "../api" } grin_core = { path = "../core" } grin_keychain = { path = "../keychain" } diff --git a/wallet/src/info.rs b/wallet/src/info.rs index 99e7877fc..05e966dac 100644 --- a/wallet/src/info.rs +++ b/wallet/src/info.rs @@ -15,13 +15,40 @@ use checker; use keychain::Keychain; use core::core::amount_to_hr_string; -use types::{WalletConfig, WalletData, OutputStatus}; +use types::{WalletConfig, WalletData, OutputStatus, WalletInfo}; use prettytable; pub fn show_info(config: &WalletConfig, keychain: &Keychain) { + let wallet_info = retrieve_info(config, keychain); + println!("\n____ Wallet Summary Info at {} ({}) ____\n", + wallet_info.current_height, + wallet_info.data_confirmed_from); + let mut table = table!( + [bFG->"Total", FG->amount_to_hr_string(wallet_info.total)], + [bFY->"Awaiting Confirmation", FY->amount_to_hr_string(wallet_info.amount_awaiting_confirmation)], + [bFY->"Confirmed but Still Locked", FY->amount_to_hr_string(wallet_info.amount_confirmed_but_locked)], + [bFG->"Currently Spendable", FG->amount_to_hr_string(wallet_info.amount_currently_spendable)], + [Fw->"---------", Fw->"---------"], + [Fr->"(Locked by previous transaction)", Fr->amount_to_hr_string(wallet_info.amount_locked)] + ); + table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); + table.printstd(); + println!(); + + if !wallet_info.data_confirmed { + println!( + "\nWARNING: Failed to verify wallet contents with grin server. \ + Above info is maybe not fully updated or invalid! \ + Check that your `grin server` is OK, or see `wallet help restore`" + ); + } +} + +pub fn retrieve_info(config: &WalletConfig, keychain: &Keychain) + -> WalletInfo { let result = checker::refresh_outputs(&config, &keychain); - let _ = WalletData::read_wallet(&config.data_file_dir, |wallet_data| { + let ret_val = WalletData::read_wallet(&config.data_file_dir, |wallet_data| { let (current_height, from) = match checker::get_tip_from_node(config) { Ok(tip) => (tip.height, "from server node"), Err(_) => match wallet_data.outputs.values().map(|out| out.height).max() { @@ -52,25 +79,20 @@ pub fn show_info(config: &WalletConfig, keychain: &Keychain) { } }; - println!("\n____ Wallet Summary Info at {} ({}) ____\n", current_height, from); - let mut table = table!( - [bFG->"Total", FG->amount_to_hr_string(unspent_total+unconfirmed_total)], - [bFY->"Awaiting Confirmation", FY->amount_to_hr_string(unconfirmed_total)], - [bFY->"Confirmed but Still Locked", FY->amount_to_hr_string(unspent_but_locked_total)], - [bFG->"Currently Spendable", FG->amount_to_hr_string(unspent_total-unspent_but_locked_total)], - [Fw->"---------", Fw->"---------"], - [Fr->"(Locked by previous transaction)", Fr->amount_to_hr_string(locked_total)] - ); - table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); - table.printstd(); - println!(); + let mut data_confirmed = true; + if let Err(_) = result { + data_confirmed = false; + } + WalletInfo { + current_height : current_height, + total: unspent_total+unconfirmed_total, + amount_awaiting_confirmation: unconfirmed_total, + amount_confirmed_but_locked: unspent_but_locked_total, + amount_currently_spendable: unspent_total-unspent_but_locked_total, + amount_locked: locked_total, + data_confirmed: data_confirmed, + data_confirmed_from: String::from(from), + } }); - - if let Err(_) = result { - println!( - "\nWARNING: Failed to verify wallet contents with grin server. \ - Above info is maybe not fully updated or invalid! \ - Check that your `grin server` is OK, or see `wallet help restore`" - ); - } + ret_val.unwrap() } diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 6054c77eb..d280c4e38 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -18,6 +18,7 @@ extern crate blake2_rfc as blake2; extern crate byteorder; extern crate rand; extern crate serde; +extern crate uuid; #[macro_use] extern crate serde_derive; extern crate serde_json; @@ -53,8 +54,8 @@ pub mod client; pub mod server; pub use outputs::show_outputs; -pub use info::show_info; +pub use info::{show_info, retrieve_info}; pub use receiver::{WalletReceiver}; pub use sender::{issue_burn_tx, issue_send_tx}; -pub use types::{BlockFees, CbData, Error, WalletConfig, WalletReceiveRequest, WalletSeed}; +pub use types::{BlockFees, CbData, Error, WalletConfig, WalletReceiveRequest, WalletInfo, WalletSeed}; pub use restore::restore; diff --git a/wallet/src/receiver.rs b/wallet/src/receiver.rs index 51ca27d72..a0430ba98 100644 --- a/wallet/src/receiver.rs +++ b/wallet/src/receiver.rs @@ -21,6 +21,7 @@ use iron::prelude::*; use iron::Handler; use iron::status; use serde_json; +use uuid::Uuid; use api; use core::consensus::reward; @@ -113,13 +114,16 @@ fn handle_sender_initiation( warn!(LOGGER, "Creating new aggsig context"); // Create a new aggsig context // this will create a new blinding sum and nonce, and store them - keychain.aggsig_create_context(blind_sum.secret_key()); - keychain.aggsig_add_output(&key_id); + let result = keychain.aggsig_create_context(&partial_tx.id, blind_sum.secret_key()); + if let Err(_) = result { + return Err(Error::DuplicateTransactionId); + } + keychain.aggsig_add_output(&partial_tx.id, &key_id); - let sig_part=keychain.aggsig_calculate_partial_sig(&sender_pub_nonce, fee, tx.lock_height).unwrap(); + let sig_part=keychain.aggsig_calculate_partial_sig(&partial_tx.id, &sender_pub_nonce, fee, tx.lock_height).unwrap(); // Build the response, which should contain sR, blinding excess xR * G, public nonce kR * G - let mut partial_tx = build_partial_tx(keychain, amount, Some(sig_part), tx); + let mut partial_tx = build_partial_tx(&partial_tx.id, keychain, amount, Some(sig_part), tx); partial_tx.phase = PartialTxPhase::ReceiverInitiation; Ok(partial_tx) @@ -144,7 +148,7 @@ fn handle_sender_confirmation( ) -> Result { let (amount, sender_pub_blinding, sender_pub_nonce, sender_sig_part, tx) = read_partial_tx(keychain, partial_tx)?; let sender_sig_part=sender_sig_part.unwrap(); - let res = keychain.aggsig_verify_partial_sig(&sender_sig_part, &sender_pub_nonce, &sender_pub_blinding, tx.fee, tx.lock_height); + let res = keychain.aggsig_verify_partial_sig(&partial_tx.id, &sender_sig_part, &sender_pub_nonce, &sender_pub_blinding, tx.fee, tx.lock_height); if !res { error!(LOGGER, "Partial Sig from sender invalid."); @@ -152,13 +156,13 @@ fn handle_sender_confirmation( } //Just calculate our sig part again instead of storing - let our_sig_part=keychain.aggsig_calculate_partial_sig(&sender_pub_nonce, tx.fee, tx.lock_height).unwrap(); + let our_sig_part=keychain.aggsig_calculate_partial_sig(&partial_tx.id, &sender_pub_nonce, tx.fee, tx.lock_height).unwrap(); // And the final signature - let final_sig=keychain.aggsig_calculate_final_sig(&sender_sig_part, &our_sig_part, &sender_pub_nonce).unwrap(); + let final_sig=keychain.aggsig_calculate_final_sig(&partial_tx.id, &sender_sig_part, &our_sig_part, &sender_pub_nonce).unwrap(); // Calculate the final public key (for our own sanity check) - let final_pubkey=keychain.aggsig_calculate_final_pubkey(&sender_pub_blinding).unwrap(); + let final_pubkey=keychain.aggsig_calculate_final_pubkey(&partial_tx.id, &sender_pub_blinding).unwrap(); //Check our final sig verifies let res = keychain.aggsig_verify_final_sig_build_msg(&final_sig, &final_pubkey, tx.fee, tx.lock_height); @@ -168,8 +172,7 @@ fn handle_sender_confirmation( return Err(Error::Signature(String::from("Final aggregated signature invalid."))); } - - let final_tx = build_final_transaction(config, keychain, amount, &final_sig, tx.clone())?; + let final_tx = build_final_transaction(&partial_tx.id, config, keychain, amount, &final_sig, tx.clone())?; let tx_hex = to_hex(ser::ser_vec(&final_tx).unwrap()); let url = format!("{}/v1/pool/push", config.check_node_api_http_addr.as_str()); @@ -177,7 +180,7 @@ fn handle_sender_confirmation( .map_err(|e| Error::Node(e))?; // Return what we've actually posted - let mut partial_tx = build_partial_tx(keychain, amount, Some(final_sig), tx); + let mut partial_tx = build_partial_tx(&partial_tx.id, keychain, amount, Some(final_sig), tx); partial_tx.phase = PartialTxPhase::ReceiverConfirmation; Ok(partial_tx) } @@ -310,6 +313,7 @@ pub fn receive_coinbase( /// builds a final transaction after the aggregated sig exchange fn build_final_transaction( + tx_id: &Uuid, config: &WalletConfig, keychain: &Keychain, amount: u64, @@ -347,7 +351,7 @@ fn build_final_transaction( // Get output we created in earlier step // TODO: will just be one for now, support multiple later - let output_vec = keychain.aggsig_get_outputs(); + let output_vec = keychain.aggsig_get_outputs(tx_id); // operate within a lock on wallet data let (key_id, derivation) = WalletData::with_wallet(&config.data_file_dir, |wallet_data| { diff --git a/wallet/src/sender.rs b/wallet/src/sender.rs index 43c3d0b4a..74259ce3a 100644 --- a/wallet/src/sender.rs +++ b/wallet/src/sender.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use uuid::Uuid; use api; use client; use checker; @@ -61,9 +62,9 @@ pub fn issue_send_tx( */ // Create a new aggsig context - keychain.aggsig_create_context(blind_sum.secret_key()); - - let partial_tx = build_partial_tx(keychain, amount, None, tx); + let tx_id = Uuid::new_v4(); + let _ = keychain.aggsig_create_context(&tx_id, blind_sum.secret_key()); + let partial_tx = build_partial_tx(&tx_id, keychain, amount, None, tx); // Closure to acquire wallet lock and lock the coins being spent // so we avoid accidental double spend attempt. @@ -117,16 +118,16 @@ pub fn issue_send_tx( * -Sender posts sS to receiver */ let (_amount, recp_pub_blinding, recp_pub_nonce, sig, tx) = read_partial_tx(keychain, &res.unwrap())?; - let res = keychain.aggsig_verify_partial_sig(&sig.unwrap(), &recp_pub_nonce, &recp_pub_blinding, tx.fee, lock_height); + let res = keychain.aggsig_verify_partial_sig(&tx_id, &sig.unwrap(), &recp_pub_nonce, &recp_pub_blinding, tx.fee, lock_height); if !res { error!(LOGGER, "Partial Sig from recipient invalid."); return Err(Error::Signature(String::from("Partial Sig from recipient invalid."))); } - let sig_part=keychain.aggsig_calculate_partial_sig(&recp_pub_nonce, tx.fee, tx.lock_height).unwrap(); + let sig_part=keychain.aggsig_calculate_partial_sig(&tx_id, &recp_pub_nonce, tx.fee, tx.lock_height).unwrap(); // Build the next stage, containing sS (and our pubkeys again, for the recipient's convenience) - let mut partial_tx = build_partial_tx(keychain, amount, Some(sig_part), tx); + let mut partial_tx = build_partial_tx(&tx_id, keychain, amount, Some(sig_part), tx); partial_tx.phase = PartialTxPhase::SenderConfirmation; // And send again diff --git a/wallet/src/types.rs b/wallet/src/types.rs index 92ea629ea..ff4c027fd 100644 --- a/wallet/src/types.rs +++ b/wallet/src/types.rs @@ -15,6 +15,7 @@ use blake2; use rand::{thread_rng, Rng}; use std::{error, fmt, num}; +use uuid::Uuid; use std::convert::From; use std::fs::{self, File, OpenOptions}; use std::io::{self, Read, Write}; @@ -85,6 +86,11 @@ pub enum Error { Uri(hyper::error::UriError), /// Error with signatures during exchange Signature(String), + /// Attempt to use duplicate transaction id in separate transactions + DuplicateTransactionId, + /// Wallet seed already exists + WalletSeedExists, + /// Other GenericError(String,) } @@ -393,7 +399,7 @@ impl WalletSeed { debug!(LOGGER, "Generating wallet seed file at: {}", seed_file_path,); if Path::new(seed_file_path).exists() { - panic!("wallet seed file already exists"); + Err(Error::WalletSeedExists) } else { let seed = WalletSeed::init_new(); let mut file = File::create(seed_file_path)?; @@ -707,6 +713,7 @@ pub enum PartialTxPhase { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct PartialTx { pub phase: PartialTxPhase, + pub id: Uuid, pub amount: u64, pub public_blind_excess: String, pub public_nonce: String, @@ -718,13 +725,14 @@ pub struct PartialTx { /// aggsig_tx_context should contain the private key/nonce pair /// the resulting partial tx will contain the corresponding public keys pub fn build_partial_tx( + transaction_id : &Uuid, keychain: &keychain::Keychain, receive_amount: u64, part_sig: Option, tx: Transaction, ) -> PartialTx { - let (pub_excess, pub_nonce) = keychain.aggsig_get_public_keys(); + let (pub_excess, pub_nonce) = keychain.aggsig_get_public_keys(transaction_id); let mut pub_excess = pub_excess.serialize_vec(keychain.secp(), true).clone(); let len = pub_excess.clone().len(); let pub_excess: Vec<_> = pub_excess.drain(0..len).collect(); @@ -735,6 +743,7 @@ pub fn build_partial_tx( PartialTx { phase: PartialTxPhase::SenderInitiation, + id : transaction_id.clone(), amount: receive_amount, public_blind_excess: util::to_hex(pub_excess), public_nonce: util::to_hex(pub_nonce), @@ -797,3 +806,25 @@ pub struct CbData { pub kernel: String, pub key_id: String, } + +/// a contained wallet info struct, so automated tests can parse wallet info +/// can add more fields here over time as needed +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct WalletInfo { + // height from which info was taken + pub current_height: u64, + // total amount in the wallet + pub total: u64, + // amount awaiting confirmation + pub amount_awaiting_confirmation: u64, + // confirmed but locked + pub amount_confirmed_but_locked: u64, + // amount currently spendable + pub amount_currently_spendable: u64, + // amount locked by previous transactions + pub amount_locked: u64, + // whether the data was confirmed against a live node + pub data_confirmed: bool, + // node confirming the data + pub data_confirmed_from: String, +}