From 85285473bdf88fabfb5cc9cfe0a4be15474565b2 Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Mon, 21 May 2018 16:28:11 +0100 Subject: [PATCH] [WIP] Wallet refactor - part 3 (#1072) * Beginning to rework aggsig library workflow * more refactoring of transaction api * whoever does round 1 first creates offset * slate finalisation now context-free, so anyone can do it * remove concept of transaction phase * remove slate phase enum * update actual send/receive code with new transaction lib workflow --- Cargo.lock | 6 +- core/src/core/transaction.rs | 92 ++-- servers/tests/wallet.rs | 6 +- util/Cargo.toml | 2 +- wallet/src/client.rs | 54 +- wallet/src/grinwallet/keys.rs | 22 +- wallet/src/grinwallet/selection.rs | 153 +++++- wallet/src/libwallet/aggsig.rs | 138 ++++- wallet/src/libwallet/transaction.rs | 797 +++++++++++++--------------- wallet/src/receiver.rs | 176 ++---- wallet/src/sender.rs | 183 +++---- wallet/src/types.rs | 136 +---- wallet/tests/common/mod.rs | 19 +- wallet/tests/libwallet.rs | 28 +- wallet/tests/transaction.rs | 191 +++---- 15 files changed, 901 insertions(+), 1102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f3ad63f8..ea4d2a453 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -778,7 +778,7 @@ dependencies = [ "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", - "secp256k1zkp 0.7.1 (git+https://github.com/mimblewimble/rust-secp256k1-zkp?tag=grin_integration_16)", + "secp256k1zkp 0.7.1 (git+https://github.com/mimblewimble/rust-secp256k1-zkp?tag=grin_integration_19)", "serde 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", "slog 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1584,7 +1584,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "secp256k1zkp" version = "0.7.1" -source = "git+https://github.com/mimblewimble/rust-secp256k1-zkp?tag=grin_integration_16#792660cb47acc1dafd509ff068275f86b57564d6" +source = "git+https://github.com/mimblewimble/rust-secp256k1-zkp?tag=grin_integration_19#800e9b3ea4a8b2df7b999980ae78b224a6ad07ce" dependencies = [ "arrayvec 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)", "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2472,7 +2472,7 @@ dependencies = [ "checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637" "checksum scoped-tls 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8674d439c964889e2476f474a3bf198cc9e199e77499960893bac5de7e9218a4" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" -"checksum secp256k1zkp 0.7.1 (git+https://github.com/mimblewimble/rust-secp256k1-zkp?tag=grin_integration_16)" = "" +"checksum secp256k1zkp 0.7.1 (git+https://github.com/mimblewimble/rust-secp256k1-zkp?tag=grin_integration_19)" = "" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum sequence_trie 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c915714ca833b1d4d6b8f6a9d72a3ff632fe45b40a8d184ef79c81bec6327eed" diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index 05b39fea3..479f6ac97 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -13,20 +13,20 @@ // limitations under the License. //! Transactions +use std::cmp::Ordering; +use std::cmp::max; +use std::collections::HashSet; +use std::io::Cursor; +use std::{error, fmt}; +use util::secp::pedersen::{Commitment, ProofMessage, RangeProof}; use util::secp::{self, Message, Signature}; use util::{kernel_sig_msg, static_secp_instance}; -use util::secp::pedersen::{Commitment, ProofMessage, RangeProof}; -use std::collections::HashSet; -use std::cmp::max; -use std::cmp::Ordering; -use std::{error, fmt}; -use std::io::Cursor; use consensus; use consensus::VerifySortOrder; +use core::BlockHeader; use core::Committed; use core::global; -use core::BlockHeader; use core::hash::{Hash, Hashed, ZERO_HASH}; use core::pmmr::MerkleProof; use keychain; @@ -188,15 +188,8 @@ impl TxKernel { let secp = secp.lock().unwrap(); let sig = &self.excess_sig; // Verify aggsig directly in libsecp - let pubkeys = &self.excess.to_two_pubkeys(&secp); - let mut valid = false; - for i in 0..pubkeys.len() { - valid = secp::aggsig::verify_single(&secp, &sig, &msg, None, &pubkeys[i], false); - if valid { - break; - } - } - if !valid { + let pubkey = &self.excess.to_pubkey(&secp)?; + if !secp::aggsig::verify_single(&secp, &sig, &msg, None, &pubkey, false) { return Err(secp::Error::IncorrectSignature); } Ok(()) @@ -400,8 +393,9 @@ impl Transaction { /// To verify transaction kernels we check that - /// * all kernels have an even fee - /// * sum of input/output commitments matches sum of kernel commitments after applying offset - /// * each kernel sig is valid (i.e. tx commitments sum to zero, given above is true) + /// * sum of input/output commitments matches sum of kernel commitments + /// after applying offset * each kernel sig is valid (i.e. tx commitments + /// sum to zero, given above is true) fn verify_kernels(&self) -> Result<(), Error> { // Verify all the output rangeproofs. // Note: this is expensive. @@ -480,10 +474,11 @@ impl Transaction { } /// We can verify the Merkle proof (for coinbase inputs) here in isolation. - /// But we cannot check the following as we need data from the index and the PMMR. - /// So we must be sure to check these at the appropriate point during block validation. - /// * node is in the correct pos in the PMMR - /// * block is the correct one (based on output_root from block_header via the index) + /// But we cannot check the following as we need data from the index and + /// the PMMR. So we must be sure to check these at the appropriate point + /// during block validation. * node is in the correct pos in the PMMR + /// * block is the correct one (based on output_root from block_header + /// via the index) fn verify_inputs(&self) -> Result<(), Error> { let coinbase_inputs = self.inputs .iter() @@ -704,7 +699,8 @@ pub struct Input { /// Currently we only care about this for coinbase outputs. pub block_hash: Option, /// The Merkle Proof that shows the output being spent by this input - /// existed and was unspent at the time of this block (proof of inclusion in output_root) + /// existed and was unspent at the time of this block (proof of inclusion + /// in output_root) pub merkle_proof: Option, } @@ -761,9 +757,9 @@ impl Readable for Input { } /// The input for a transaction, which spends a pre-existing unspent output. -/// The input commitment is a reproduction of the commitment of the output being spent. -/// Input must also provide the original output features and the hash of the block -/// the output originated from. +/// The input commitment is a reproduction of the commitment of the output +/// being spent. Input must also provide the original output features and the +/// hash of the block the output originated from. impl Input { /// Build a new input from the data required to identify and verify an /// output being spent. @@ -781,9 +777,10 @@ impl Input { } } - /// The input commitment which _partially_ identifies the output being spent. - /// In the presence of a fork we need additional info to uniquely identify the output. - /// Specifically the block hash (to correctly calculate lock_height for coinbase outputs). + /// The input commitment which _partially_ identifies the output being + /// spent. In the presence of a fork we need additional info to uniquely + /// identify the output. Specifically the block hash (to correctly + /// calculate lock_height for coinbase outputs). pub fn commitment(&self) -> Commitment { self.commit.clone() } @@ -795,29 +792,33 @@ impl Input { block_hash.unwrap_or(Hash::default()) } - /// Convenience function to return the (optional) merkle_proof for this input. - /// Will return the "empty" Merkle proof if we do not have one. - /// We currently only care about the Merkle proof for inputs spending coinbase outputs. + /// Convenience function to return the (optional) merkle_proof for this + /// input. Will return the "empty" Merkle proof if we do not have one. + /// We currently only care about the Merkle proof for inputs spending + /// coinbase outputs. pub fn merkle_proof(&self) -> MerkleProof { let merkle_proof = self.merkle_proof.clone(); merkle_proof.unwrap_or(MerkleProof::empty()) } /// Verify the maturity of an output being spent by an input. - /// Only relevant for spending coinbase outputs currently (locked for 1,000 confirmations). + /// Only relevant for spending coinbase outputs currently (locked for 1,000 + /// confirmations). /// - /// The proof associates the output with the root by its hash (and pos) in the MMR. - /// The proof shows the output existed and was unspent at the time the output_root was built. - /// The root associates the proof with a specific block header with that output_root. - /// So the proof shows the output was unspent at the time of the block - /// and is at least as old as that block (may be older). + /// The proof associates the output with the root by its hash (and pos) in + /// the MMR. The proof shows the output existed and was unspent at the + /// time the output_root was built. The root associates the proof with a + /// specific block header with that output_root. So the proof shows the + /// output was unspent at the time of the block and is at least as old as + /// that block (may be older). /// /// We can verify maturity of the output being spent by - /// - /// * verifying the Merkle Proof produces the correct root for the given hash (from MMR) - /// * verifying the root matches the output_root in the block_header - /// * verifying the hash matches the node hash in the Merkle Proof - /// * finally verify maturity rules based on height of the block header + /// * verifying the Merkle Proof produces the correct root for the given + /// hash (from MMR) * verifying the root matches the output_root in the + /// block_header * verifying the hash matches the node hash in the Merkle + /// Proof * finally verify maturity rules based on height of the block + /// header /// pub fn verify_maturity( &self, @@ -962,7 +963,8 @@ impl Output { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct OutputIdentifier { /// Output features (coinbase vs. regular transaction output) - /// We need to include this when hashing to ensure coinbase maturity can be enforced. + /// We need to include this when hashing to ensure coinbase maturity can be + /// enforced. pub features: OutputFeatures, /// Output commitment pub commit: Commitment, @@ -1121,8 +1123,8 @@ impl ProofMessageElements { true } - /// Whether our remainder is zero (as it should be if the BF and nonce used to unwind - /// are correct + /// Whether our remainder is zero (as it should be if the BF and nonce used + /// to unwind are correct pub fn zeroes_correct(&self) -> bool { for i in 0..self.zeroes.len() { if self.zeroes[i] != 0 { diff --git a/servers/tests/wallet.rs b/servers/tests/wallet.rs index 561d77ece..c2cabba26 100644 --- a/servers/tests/wallet.rs +++ b/servers/tests/wallet.rs @@ -27,15 +27,15 @@ extern crate grin_wallet as wallet; mod framework; -use std::{thread, time}; -use std::sync::{Arc, Mutex}; use framework::{LocalServerContainer, LocalServerContainerConfig}; +use std::sync::{Arc, Mutex}; +use std::{thread, time}; use util::LOGGER; /// Start 1 node mining and two wallets, then send a few /// transactions from one to the other -//#[test] +#[test] fn basic_wallet_transactions() { let test_name_dir = "test_servers"; core::global::set_mining_mode(core::global::ChainTypes::AutomatedTesting); diff --git a/util/Cargo.toml b/util/Cargo.toml index bcfba3bd0..893504c3f 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -20,6 +20,6 @@ zip = "0.2" [dependencies.secp256k1zkp] git = "https://github.com/mimblewimble/rust-secp256k1-zkp" -tag = "grin_integration_16" +tag = "grin_integration_19" #path = "../../rust-secp256k1-zkp" features = ["bullet-proof-sizing"] diff --git a/wallet/src/client.rs b/wallet/src/client.rs index 084070111..c28f943c6 100644 --- a/wallet/src/client.rs +++ b/wallet/src/client.rs @@ -12,18 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -use futures::{Future, Stream}; use failure::ResultExt; +use futures::{Future, Stream}; use hyper; -use hyper::{Method, Request}; use hyper::header::ContentType; -use tokio_core::reactor; +use hyper::{Method, Request}; +use libwallet::transaction::Slate; use serde_json; +use tokio_core::reactor; -use types::*; -use core::core::Transaction; -use util::LOGGER; use std::io; +use types::*; +use util::LOGGER; /// Call the wallet API to create a coinbase output for the given block_fees. /// Will retry based on default "retry forever with backoff" behavior. @@ -40,7 +40,7 @@ pub fn create_coinbase(url: &str, block_fees: &BlockFees) -> Result Result { +pub fn send_slate(url: &str, slate: &Slate, fluff: bool) -> Result { let mut core = reactor::Core::new().context(ErrorKind::Hyper)?; let client = hyper::Client::new(&core.handle()); @@ -55,54 +55,20 @@ pub fn send_partial_tx(url: &str, partial_tx: &PartialTx, fluff: bool) -> Result url_pool.parse::().context(ErrorKind::Hyper)?, ); req.headers_mut().set(ContentType::json()); - let json = serde_json::to_string(&partial_tx).context(ErrorKind::Hyper)?; + let json = serde_json::to_string(&slate).context(ErrorKind::Hyper)?; req.set_body(json); let work = client.request(req).and_then(|res| { res.body().concat2().and_then(move |body| { - let tx: PartialTx = + let slate: Slate = serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - Ok(tx) + Ok(slate) }) }); let res = core.run(work).context(ErrorKind::Hyper)?; Ok(res) } -///TODO: Factor this out with above later, this version just returns a Transaction instead -///of a partial TX -pub fn send_partial_tx_final( - url: &str, - partial_tx: &PartialTx, - fluff: bool, -) -> Result { - let mut core = reactor::Core::new().context(ErrorKind::Hyper)?; - let client = hyper::Client::new(&core.handle()); - - // In case we want to do an express send - let mut url_pool = url.to_owned(); - if fluff { - url_pool = format!("{}{}", url, "?fluff"); - } - - let mut req = Request::new( - Method::Post, - url_pool.parse::().context(ErrorKind::Hyper)?, - ); - req.headers_mut().set(ContentType::json()); - let json = serde_json::to_string(&partial_tx).context(ErrorKind::Hyper)?; - req.set_body(json); - - let work = client.request(req).and_then(|res| { - res.body().concat2().and_then(move |body| { - let tx: Transaction = - serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - Ok(tx) - }) - }); - let res = core.run(work).context(ErrorKind::Hyper)?; - Ok(res) -} /// Makes a single request to the wallet API to create a new coinbase output. fn single_create_coinbase(url: &str, block_fees: &BlockFees) -> Result { let mut core = diff --git a/wallet/src/grinwallet/keys.rs b/wallet/src/grinwallet/keys.rs index 40ed5a98d..d20c1cb03 100644 --- a/wallet/src/grinwallet/keys.rs +++ b/wallet/src/grinwallet/keys.rs @@ -17,13 +17,23 @@ use rand::thread_rng; use uuid::Uuid; use core::core::{amount_to_hr_string, Committed, Transaction}; -use libwallet::{aggsig, build}; -use keychain::{BlindSum, BlindingFactor, Identifier, Keychain}; -use types::*; -use util::{secp, LOGGER}; -use util::secp::key::{PublicKey, SecretKey}; -use util::secp::Signature; use failure::ResultExt; +use keychain::{BlindSum, BlindingFactor, Identifier, Keychain}; +use libwallet::{aggsig, build}; +use types::*; +use util::secp::Signature; +use util::secp::key::{PublicKey, SecretKey}; +use util::{secp, LOGGER}; + +/// Get our next available key +pub fn new_output_key( + config: &WalletConfig, + keychain: &Keychain, +) -> Result<(Identifier, u32), Error> { + WalletData::with_wallet(&config.data_file_dir, |wallet_data| { + next_available_key(&wallet_data, keychain) + }) +} /// Get next available key in the wallet pub fn next_available_key(wallet_data: &WalletData, keychain: &Keychain) -> (Identifier, u32) { diff --git a/wallet/src/grinwallet/selection.rs b/wallet/src/grinwallet/selection.rs index d0b1feacb..9e2f92935 100644 --- a/wallet/src/grinwallet/selection.rs +++ b/wallet/src/grinwallet/selection.rs @@ -14,19 +14,146 @@ //! Selection of inputs for building transactions -use core::core::{amount_to_hr_string, Committed, Transaction}; -use libwallet::{aggsig, build}; -use keychain::{BlindSum, BlindingFactor, Identifier, Keychain}; -use types::*; -use util::{secp, LOGGER}; -use util::secp::key::{PublicKey, SecretKey}; -use util::secp::Signature; use failure::ResultExt; +use grinwallet::keys; +use keychain::{Identifier, Keychain}; +use libwallet::{aggsig, build, transaction}; +use types::*; + +/// Initialise a transaction on the sender side, returns a corresponding +/// libwallet transaction slate with the appropriate inputs selected, +/// and saves the private wallet identifiers of our selected outputs +/// into our transaction context + +pub fn build_send_tx_slate( + config: &WalletConfig, + keychain: &Keychain, + context_manager: &mut aggsig::ContextManager, + num_participants: usize, + amount: u64, + current_height: u64, + minimum_confirmations: u64, + lock_height: u64, + max_outputs: usize, + selection_strategy_is_use_all: bool, +) -> Result<(transaction::Slate, impl FnOnce() -> Result<(), Error>), Error> { + let (elems, inputs, change_id, amount, fee) = select_send_tx( + config, + keychain, + amount, + current_height, + minimum_confirmations, + lock_height, + max_outputs, + selection_strategy_is_use_all, + )?; + + // Create public slate + let mut slate = transaction::Slate::blank(num_participants); + slate.amount = amount; + slate.height = current_height; + slate.lock_height = lock_height; + slate.fee = fee; + + let blinding = slate.add_transaction_elements(keychain, elems)?; + // Create our own private context + let mut context = context_manager.create_context( + keychain.secp(), + &slate.id, + blinding.secret_key(keychain.secp()).unwrap(), + ); + + // Store our private identifiers for each input + for input in inputs { + context.add_input(&input.key_id); + } + + // Store change output + if change_id.is_some() { + context.add_output(&change_id.unwrap()); + } + + let lock_inputs = context.get_inputs().clone(); + let _lock_outputs = context.get_outputs().clone(); + let data_file_dir = config.data_file_dir.clone(); + + // Return a closure to acquire wallet lock and lock the coins being spent + // so we avoid accidental double spend attempt. + let update_sender_wallet_fn = move || { + WalletData::with_wallet(&data_file_dir, |wallet_data| { + for id in lock_inputs { + let coin = wallet_data.get_output(&id).unwrap().clone(); + wallet_data.lock_output(&coin); + } + // probably just want to leave as unconfirmed for now + // or create a new status + /*for id in lock_outputs { + let coin = wallet_data.get_output(&id).unwrap().clone(); + wallet_data.lock_output(&coin); + }*/ }) + }; + + context_manager.save_context(context); + + Ok((slate, update_sender_wallet_fn)) +} + +/// Creates a new output in the wallet for the recipient, +/// returning the key of the fresh output and a closure +/// that actually performs the addition of the output to the +/// wallet +pub fn build_recipient_output_with_slate( + config: &WalletConfig, + keychain: &Keychain, + context_manager: &mut aggsig::ContextManager, + slate: &mut transaction::Slate, +) -> Result<(Identifier, impl FnOnce() -> Result<(), Error>), Error> { + // Create a potential output for this transaction + let (key_id, derivation) = keys::new_output_key(config, keychain)?; + + let data_file_dir = config.data_file_dir.clone(); + let root_key_id = keychain.root_key_id(); + let key_id_inner = key_id.clone(); + let amount = slate.amount; + + let blinding = + slate.add_transaction_elements(keychain, vec![build::output(amount, key_id.clone())])?; + + // Add blinding sum to our context + let mut context = context_manager.create_context( + keychain.secp(), + &slate.id, + blinding.secret_key(keychain.secp()).unwrap(), + ); + + context.add_output(&key_id); + + // Create closure that adds the output to recipient's wallet + // (up to the caller to decide when to do) + let wallet_add_fn = move || { + WalletData::with_wallet(&data_file_dir, |wallet_data| { + wallet_data.add_output(OutputData { + root_key_id: root_key_id, + key_id: key_id_inner, + n_child: derivation, + value: amount, + status: OutputStatus::Unconfirmed, + height: 0, + lock_height: 0, + is_coinbase: false, + block: None, + merkle_proof: None, + }); + }) + }; + context_manager.save_context(context); + Ok((key_id, wallet_add_fn)) +} /// Builds a transaction to send to someone from the HD seed associated with the /// wallet and the amount to send. Handles reading through the wallet data file, /// selecting outputs to spend and building the change. -pub fn build_send_tx( +pub fn select_send_tx( config: &WalletConfig, keychain: &Keychain, amount: u64, @@ -37,11 +164,11 @@ pub fn build_send_tx( selection_strategy_is_use_all: bool, ) -> Result< ( - Transaction, - BlindingFactor, + Vec>, Vec, Option, - u64, + u64, // amount + u64, // fee ), Error, > { @@ -121,9 +248,7 @@ pub fn build_send_tx( // on tx being sent (based on current chain height via api). parts.push(build::with_lock_height(lock_height)); - let (tx, blind) = build::partial_transaction(parts, &keychain).context(ErrorKind::Keychain)?; - - Ok((tx, blind, coins, change_key, amount_with_fee)) + Ok((parts, coins, change_key, amount, fee)) } /// coins proof count diff --git a/wallet/src/libwallet/aggsig.rs b/wallet/src/libwallet/aggsig.rs index 56ec311b8..f5353b5d6 100644 --- a/wallet/src/libwallet/aggsig.rs +++ b/wallet/src/libwallet/aggsig.rs @@ -12,18 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. /// Aggsig library definitions - use std::collections::HashMap; -use util::secp::key::{PublicKey, SecretKey}; -use util::secp::{self, aggsig, Message, Secp256k1, Signature}; -use util::secp::pedersen::Commitment; -use util::kernel_sig_msg; -use uuid::Uuid; use keychain::Keychain; -use keychain::extkey::Identifier; use keychain::blind::BlindingFactor; +use keychain::extkey::Identifier; use libwallet::error::Error; +use util::kernel_sig_msg; +use util::secp::key::{PublicKey, SecretKey}; +use util::secp::pedersen::Commitment; +use util::secp::{self, aggsig, Message, Secp256k1, Signature}; +use uuid::Uuid; #[derive(Clone, Debug)] /// Holds the context for a single aggsig transaction @@ -36,16 +35,37 @@ pub struct Context { /// (basically a SecretKey) pub sec_nonce: SecretKey, /// If I'm the sender, store change key + /// TODO: remove in favor of outputs below pub change_key: Option, /// store my outputs between invocations pub output_ids: Vec, + /// store my inputs + pub input_ids: Vec, /// store the calculated fee pub fee: u64, } +/*impl Context { + /// Create a new context with defaults + pub fn new( + secp: &secp::Secp256k1, + sec_key: SecretKey, + ) -> Context { + Context { + sec_key: sec_key, + sec_nonce: aggsig::export_secnonce_single(secp).unwrap(), + change_key: None, + input_ids: vec![], + output_ids: vec![], + fee: 0, + }, +}*/ + #[derive(Clone, Debug)] -/// Holds many contexts, to support multiple transactions hitting a wallet receiver -/// at once +/// Holds many contexts, to support multiple transactions hitting a wallet +/// receiver at once +/// TODO: Remove context manager in favour of context.. keeping multiple +/// transactions separate is a wallet-specific concern pub struct ContextManager { contexts: HashMap, } @@ -74,6 +94,7 @@ impl ContextManager { transaction_id: transaction_id.clone(), sec_nonce: aggsig::export_secnonce_single(secp).unwrap(), change_key: None, + input_ids: vec![], output_ids: vec![], fee: 0, }, @@ -105,6 +126,17 @@ impl Context { self.output_ids.clone() } + /// Tracks IDs of my inputs into the transaction + /// be kept between invocations + pub fn add_input(&mut self, input_id: &Identifier) { + self.input_ids.push(input_id.clone()); + } + + /// Returns all stored input identifiers + pub fn get_inputs(&self) -> Vec { + self.input_ids.clone() + } + /// Returns private key, private nonce pub fn get_private_keys(&self) -> (SecretKey, SecretKey) { (self.sec_key.clone(), self.sec_nonce.clone()) @@ -118,9 +150,10 @@ impl Context { ) } - /// 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 + /// 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 sign_single( &self, secp: &Secp256k1, @@ -164,6 +197,7 @@ impl Context { verify_single(secp, sig, &msg, Some(&nonce_sum), pubkey, true) } + ///TODO: Remove when below is integrated pub fn calculate_partial_sig( &self, secp: &Secp256k1, @@ -187,19 +221,36 @@ impl Context { ) } + pub fn calculate_partial_sig_with_nonce_sum( + &self, + secp: &Secp256k1, + nonce_sum: &PublicKey, + fee: u64, + lock_height: u64, + ) -> Result { + // Add public nonces kR*G + kS*G + let (_, sec_nonce) = self.get_private_keys(); + 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.sign_single( + secp, + &msg, + Some(&sec_nonce), + Some(&nonce_sum), + Some(&nonce_sum), + ) + } + /// Helper function to calculate final signature pub fn calculate_final_sig( &self, secp: &Secp256k1, - their_sig: &Signature, - our_sig: &Signature, - their_pub_nonce: &PublicKey, + part_sigs: Vec<&Signature>, + nonce_sum: &PublicKey, ) -> Result { // Add public nonces kR*G + kS*G - let (_, sec_nonce) = self.get_private_keys(); - let mut nonce_sum = their_pub_nonce.clone(); - let _ = nonce_sum.add_exp_assign(secp, &sec_nonce); - let sig = aggsig::add_signatures_single(&secp, their_sig, our_sig, &nonce_sum)?; + let sig = aggsig::add_signatures_single(&secp, part_sigs, &nonce_sum)?; Ok(sig) } @@ -218,6 +269,19 @@ impl Context { // Contextless functions +/// Verifies a partial sig given all public nonces used in the round +pub fn verify_partial_sig( + secp: &Secp256k1, + sig: &Signature, + pub_nonce_sum: &PublicKey, + pubkey: &PublicKey, + fee: u64, + lock_height: u64, +) -> bool { + let msg = secp::Message::from_slice(&kernel_sig_msg(fee, lock_height)).unwrap(); + verify_single(secp, sig, &msg, Some(&pub_nonce_sum), pubkey, true) +} + /// Just a simple sig, creates its own nonce, etc pub fn sign_from_key_id( secp: &Secp256k1, @@ -238,16 +302,21 @@ pub fn verify_single_from_commit( commit: &Commitment, ) -> bool { // Extract the pubkey, unfortunately we need this hack for now, (we just hope - // one is valid) TODO: Create better secp256k1 API to do this - let pubkeys = commit.to_two_pubkeys(secp); - let mut valid = false; - for i in 0..pubkeys.len() { - valid = aggsig::verify_single(secp, &sig, &msg, None, &pubkeys[i], false); - if valid { - break; - } - } - valid + // one is valid) + let pubkey = commit.to_pubkey(secp).unwrap(); + aggsig::verify_single(secp, &sig, &msg, None, &pubkey, false) +} + +/// Verify a sig, with built message +pub fn verify_sig_build_msg( + secp: &Secp256k1, + sig: &Signature, + pubkey: &PublicKey, + fee: u64, + lock_height: u64, +) -> bool { + let msg = secp::Message::from_slice(&kernel_sig_msg(fee, lock_height)).unwrap(); + verify_single(secp, sig, &msg, None, pubkey, true) } //Verifies an aggsig signature @@ -262,6 +331,17 @@ pub fn verify_single( aggsig::verify_single(secp, sig, msg, pubnonce, pubkey, is_partial) } +/// Adds signatures +pub fn add_signatures( + secp: &Secp256k1, + part_sigs: Vec<&Signature>, + nonce_sum: &PublicKey, +) -> Result { + // Add public nonces kR*G + kS*G + let sig = aggsig::add_signatures_single(&secp, part_sigs, &nonce_sum)?; + Ok(sig) +} + /// Just a simple sig, creates its own nonce, etc pub fn sign_with_blinding( secp: &Secp256k1, diff --git a/wallet/src/libwallet/transaction.rs b/wallet/src/libwallet/transaction.rs index 985ad8762..94175565a 100644 --- a/wallet/src/libwallet/transaction.rs +++ b/wallet/src/libwallet/transaction.rs @@ -18,455 +18,384 @@ use rand::thread_rng; use uuid::Uuid; use core::core::{amount_to_hr_string, Committed, Transaction}; +use keychain::{BlindSum, BlindingFactor, Keychain}; use libwallet::{aggsig, build}; -use keychain::{BlindSum, BlindingFactor, Identifier, Keychain}; -use types::*; // TODO: Remove this? -use util::{secp, LOGGER}; -use util::secp::key::{PublicKey, SecretKey}; +//TODO: Remove these from here, replace with libwallet error +use types::{tx_fee, Error, ErrorKind}; + use util::secp::Signature; +use util::secp::key::{PublicKey, SecretKey}; +use util::{secp, LOGGER}; + use failure::ResultExt; -// TODO: None of these functions should care about the wallet implementation, +/// Public data for each participant in the slate -/// Initiate a transaction for the aggsig exchange -/// with the given transaction data -pub fn sender_initiation( - keychain: &Keychain, - tx_id: &Uuid, - context_manager: &mut aggsig::ContextManager, - current_height: u64, - //TODO: Make this nicer, remove wallet-specific OutputData type - tx_data: ( - Transaction, - BlindingFactor, - Vec, - Option, - u64, - ), -) -> Result { - let lock_height = current_height; - - let (tx, blind, coins, _change_key, amount_with_fee) = tx_data; - - // TODO - wrap this up in build_send_tx or even the build() call? - // Generate a random kernel offset here - // and subtract it from the blind_sum so we create - // the aggsig context with the "split" key - let kernel_offset = - BlindingFactor::from_secret_key(SecretKey::new(&keychain.secp(), &mut thread_rng())); - - let blind_offset = keychain - .blind_sum(&BlindSum::new() - .add_blinding_factor(blind) - .sub_blinding_factor(kernel_offset)) - .unwrap(); - - // - // -Sender picks random blinding factors for all outputs it participates in, - // computes total blinding excess xS -Sender picks random nonce kS - // -Sender posts inputs, outputs, Message M=fee, xS * G and kS * G to Receiver - // - let skey = blind_offset - .secret_key(&keychain.secp()) - .context(ErrorKind::Keychain)?; - - // Create a new aggsig context - let mut context = context_manager.create_context(keychain.secp(), &tx_id, skey); - for coin in coins { - context.add_output(&coin.key_id); - } - let partial_tx = build_partial_tx( - &context, - keychain, - amount_with_fee, - lock_height, - kernel_offset, - None, - tx, - ); - context_manager.save_context(context); - Ok(partial_tx) +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ParticipantData { + /// Id of participant in the transaction. (For now, 0=sender, 1=rec) + pub id: u64, + /// Public key corresponding to private blinding factor + pub public_blind_excess: PublicKey, + /// Public key corresponding to private nonce + pub public_nonce: PublicKey, + /// Public partial signature + pub part_sig: Option, } -/// Receive Part 1 of interactive transactions from sender, Sender Initiation -/// Return result of part 2, Recipient Initation, to sender -/// -Receiver receives inputs, outputs xS * G and kS * G -/// -Receiver picks random blinding factors for all outputs being received, -/// computes total blinding -/// excess xR -/// -Receiver picks random nonce kR -/// -Receiver computes Schnorr challenge e = H(M | kR * G + kS * G) -/// -Receiver computes their part of signature, sR = kR + e * xR -/// -Receiver responds with sR, blinding excess xR * G, public nonce kR * G +impl ParticipantData { + /// A helper to return whether this paricipant + /// has completed round 1 and round 2; + /// Round 1 has to be completed before instantiation of this struct + /// anyhow, and for each participant consists of: + /// -Inputs added to transaction + /// -Outputs added to transaction + /// -Public signature nonce chosen and added + /// -Public contribution to blinding factor chosen and added + /// Round 2 can only be completed after all participants have + /// performed round 1, and adds: + /// -Part sig is filled out + pub fn is_complete(&self) -> bool { + self.part_sig.is_some() + } +} -pub fn recipient_initiation( - keychain: &Keychain, - context_manager: &mut aggsig::ContextManager, - partial_tx: &PartialTx, - output_key_id: &Identifier, -) -> Result { - let (amount, _lock_height, _sender_pub_blinding, sender_pub_nonce, kernel_offset, _sig, tx) = - read_partial_tx(keychain, partial_tx)?; +/// A 'Slate' is passed around to all parties to build up all of the public +/// tranaction data needed to create a finalised tranaction. Callers can pass +/// the slate around by whatever means they choose, (but we can provide some +/// binary or JSON serialisation helpers here). - // double check the fee amount included in the partial tx - // we don't necessarily want to just trust the sender - // we could just overwrite the fee here (but we won't) due to the sig - let fee = tx_fee( - tx.inputs.len(), - tx.outputs.len() + 1, - tx.input_proofs_count(), - None, - ); - if fee > tx.fee() { - return Err(ErrorKind::FeeDispute { - sender_fee: tx.fee(), - recipient_fee: fee, - })?; +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Slate { + /// The number of participants intended to take part in this transaction + pub num_participants: usize, + /// Unique transaction ID, selected by sender + pub id: Uuid, + /// The core transaction data: + /// inputs, outputs, kernels, kernel offset + pub tx: Transaction, + /// base amount (excluding fee) + pub amount: u64, + /// fee amount + pub fee: u64, + /// Block height for the transaction + pub height: u64, + /// Lock height + pub lock_height: u64, + /// Participant data, each participant in the transaction will + /// insert their public data here. For now, 0 is sender and 1 + /// is receiver, though this will change for multi-party + pub participant_data: Vec, +} + +impl Slate { + /// Create a new slate + pub fn blank(num_participants: usize) -> Slate { + Slate { + num_participants: num_participants, + id: Uuid::new_v4(), + tx: Transaction::empty(), + amount: 0, + fee: 0, + height: 0, + lock_height: 0, + participant_data: vec![], + } } - if fee > amount { - info!( - LOGGER, - "Rejected the transfer because transaction fee ({}) exceeds received amount ({}).", - amount_to_hr_string(fee), - amount_to_hr_string(amount) + /// Adds selected inputs and outputs to the slate's transaction + /// Returns blinding factor + pub fn add_transaction_elements( + &mut self, + keychain: &Keychain, + mut elems: Vec>, + ) -> Result { + // Append to the exiting transaction + if self.tx.kernels.len() != 0 { + elems.insert(0, build::initial_tx(self.tx.clone())); + } + let (tx, blind) = + build::partial_transaction(elems, &keychain).context(ErrorKind::Keychain)?; + self.tx = tx; + Ok(blind) + } + + /// Completes callers part of round 1, adding public key info + /// to the slate + pub fn fill_round_1( + &mut self, + keychain: &Keychain, + context_manager: &mut aggsig::ContextManager, + participant_id: usize, + ) -> Result<(), Error> { + // Whoever does this first generates the offset + if self.tx.offset == BlindingFactor::zero() { + self.generate_offset(keychain, context_manager)?; + } + self.add_participant_info(keychain, context_manager, participant_id, None)?; + Ok(()) + } + + /// Completes caller's part of round 2, completing signatures + pub fn fill_round_2( + &mut self, + keychain: &Keychain, + context_manager: &mut aggsig::ContextManager, + participant_id: usize, + ) -> Result<(), Error> { + self.check_fees()?; + self.verify_part_sigs(keychain.secp())?; + let context = context_manager.get_context(&self.id); + let sig_part = context + .calculate_partial_sig_with_nonce_sum( + keychain.secp(), + &self.pub_nonce_sum(keychain.secp()), + self.fee, + self.lock_height, + ) + .unwrap(); + self.participant_data[participant_id].part_sig = Some(sig_part); + Ok(()) + } + + /// Creates the final signature, callable by either the sender or recipient + /// (after phase 3: sender confirmation) + /// TODO: Only callable by receiver at the moment + pub fn finalize(&mut self, keychain: &Keychain) -> Result<(), Error> { + let final_sig = self.finalize_signature(keychain)?; + self.finalize_transaction(keychain, &final_sig) + } + + /// Return the sum of public nonces + fn pub_nonce_sum(&self, secp: &secp::Secp256k1) -> PublicKey { + let pub_nonces = self.participant_data + .iter() + .map(|p| &p.public_nonce) + .collect(); + PublicKey::from_combination(secp, pub_nonces).unwrap() + } + + /// Return the sum of public blinding factors + fn pub_blind_sum(&self, secp: &secp::Secp256k1) -> PublicKey { + let pub_blinds = self.participant_data + .iter() + .map(|p| &p.public_blind_excess) + .collect(); + PublicKey::from_combination(secp, pub_blinds).unwrap() + } + + /// Return vector of all partial sigs + fn part_sigs(&self) -> Vec<&Signature> { + self.participant_data + .iter() + .map(|p| p.part_sig.as_ref().unwrap()) + .collect() + } + + /// Adds participants public keys to the slate data + /// and saves participant's transaction context + /// sec_key can be overriden to replace the blinding + /// factor (by whoever split the offset) + fn add_participant_info( + &mut self, + keychain: &Keychain, + context_manager: &aggsig::ContextManager, + id: usize, + part_sig: Option, + ) -> Result<(), Error> { + let context = context_manager.get_context(&self.id); + + // Add our public key and nonce to the slate + let (pub_key, pub_nonce) = context.get_public_keys(keychain.secp()); + self.participant_data.push(ParticipantData { + id: id as u64, + public_blind_excess: pub_key, + public_nonce: pub_nonce, + part_sig: part_sig, + }); + + Ok(()) + } + + /// Somebody involved needs to generate an offset with their private key + /// For now, we'll have the transaction initiator be responsible for it + /// Return offset private key + fn generate_offset( + &mut self, + keychain: &Keychain, + context_manager: &mut aggsig::ContextManager, + ) -> Result<(), Error> { + // Generate a random kernel offset here + // and subtract it from the blind_sum so we create + // the aggsig context with the "split" key + let mut context = context_manager.get_context(&self.id); + self.tx.offset = + BlindingFactor::from_secret_key(SecretKey::new(&keychain.secp(), &mut thread_rng())); + let blind_offset = keychain + .blind_sum(&BlindSum::new() + .add_blinding_factor(BlindingFactor::from_secret_key(context.sec_key)) + .sub_blinding_factor(self.tx.offset)) + .unwrap(); + context.sec_key = blind_offset + .secret_key(&keychain.secp()) + .context(ErrorKind::Keychain)?; + context_manager.save_context(context); + Ok(()) + } + + /// Checks the fees in the transaction in the given slate are valid + fn check_fees(&self) -> Result<(), Error> { + // double check the fee amount included in the partial tx + // we don't necessarily want to just trust the sender + // we could just overwrite the fee here (but we won't) due to the sig + let fee = tx_fee( + self.tx.inputs.len(), + self.tx.outputs.len(), + self.tx.input_proofs_count(), + None, ); - return Err(ErrorKind::FeeExceedsAmount { - sender_amount: amount, - recipient_fee: fee, - })?; - } - - let out_amount = amount - tx.fee(); - - // First step is just to get the excess sum of the outputs we're participating - // in Output and key needs to be stored until transaction finalisation time, - // somehow - // Still handy for getting the blinding sum - let (_, blind_sum) = build::partial_transaction( - vec![build::output(out_amount, output_key_id.clone())], - keychain, - ).context(ErrorKind::Keychain)?; - - // Create a new aggsig context - // this will create a new blinding sum and nonce, and store them - let blind = blind_sum - .secret_key(&keychain.secp()) - .context(ErrorKind::Keychain)?; - debug!(LOGGER, "Creating new aggsig context"); - let mut context = context_manager.create_context(keychain.secp(), &partial_tx.id, blind); - context.add_output(output_key_id); - context.fee = tx.fee(); - - let sig_part = context - .calculate_partial_sig( - keychain.secp(), - &sender_pub_nonce, - tx.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( - &context, - keychain, - amount, - partial_tx.lock_height, - kernel_offset, - Some(sig_part), - tx, - ); - partial_tx.phase = PartialTxPhase::ReceiverInitiation; - - context_manager.save_context(context); - - Ok(partial_tx) -} - -/// -Sender receives xR * G, kR * G, sR -/// -Sender computes Schnorr challenge e = H(M | kR * G + kS * G) -/// -Sender verifies receivers sig, by verifying that kR * G + e * xR * G = -/// sR * G· -/// -Sender computes their part of signature, sS = kS + e * xS - -pub fn sender_confirmation( - keychain: &Keychain, - context_manager: &mut aggsig::ContextManager, - partial_tx: PartialTx, -) -> Result { - let context = context_manager.get_context(&partial_tx.id); - - let (amount, lock_height, recp_pub_blinding, recp_pub_nonce, kernel_offset, sig, tx) = - read_partial_tx(keychain, &partial_tx)?; - - let res = context.verify_partial_sig( - &keychain.secp(), - &sig.unwrap(), - &recp_pub_nonce, - &recp_pub_blinding, - tx.fee(), - lock_height, - ); - if !res { - error!(LOGGER, "Partial Sig from recipient invalid."); - return Err(ErrorKind::Signature("Partial Sig from recipient invalid."))?; - } - - let sig_part = context - .calculate_partial_sig( - &keychain.secp(), - &recp_pub_nonce, - tx.fee(), - tx.lock_height(), - ) - .unwrap(); - - // Build the next stage, containing sS (and our pubkeys again, for the - // recipient's convenience) offset has not been modified during tx building, - // so pass it back in - let mut partial_tx = build_partial_tx( - &context, - keychain, - amount, - lock_height, - kernel_offset, - Some(sig_part), - tx, - ); - partial_tx.phase = PartialTxPhase::SenderConfirmation; - context_manager.save_context(context); - Ok(partial_tx) -} - -/// Creates the final signature, callable by either the sender or recipient -/// (after phase 3: sender confirmation) -/// -/// TODO: takes a partial Tx that just contains the other party's public -/// info at present, but this should be changed to something more appropriate -pub fn finalize_transaction( - keychain: &Keychain, - context_manager: &mut aggsig::ContextManager, - partial_tx: &PartialTx, - other_partial_tx: &PartialTx, - output_key_id: &Identifier, - output_key_derivation: u32, -) -> Result { - let ( - _amount, - _lock_height, - other_pub_blinding, - other_pub_nonce, - kernel_offset, - other_sig_part, - tx, - ) = read_partial_tx(keychain, other_partial_tx)?; - let final_sig = create_final_signature( - keychain, - context_manager, - partial_tx, - &other_pub_blinding, - &other_pub_nonce, - &other_sig_part.unwrap(), - )?; - - build_final_transaction( - keychain, - partial_tx.amount, - kernel_offset, - &final_sig, - tx.clone(), - output_key_id, - output_key_derivation, - ) -} - -/// This should be callable by either the sender or receiver -/// once phase 3 is done -/// -/// Receive Part 3 of interactive transactions from sender, Sender Confirmation -/// Return Ok/Error -/// -Receiver receives sS -/// -Receiver verifies sender's sig, by verifying that -/// kS * G + e *xS * G = sS* G -/// -Receiver calculates final sig as s=(sS+sR, kS * G+kR * G) -/// -Receiver puts into TX kernel: -/// -/// Signature S -/// pubkey xR * G+xS * G -/// fee (= M) -/// -/// Returns completed transaction ready for posting to the chain - -fn create_final_signature( - keychain: &Keychain, - context_manager: &mut aggsig::ContextManager, - partial_tx: &PartialTx, - other_pub_blinding: &PublicKey, - other_pub_nonce: &PublicKey, - other_sig_part: &Signature, -) -> Result { - let (_amount, _lock_height, _, _, _kernel_offset, _, tx) = - read_partial_tx(keychain, partial_tx)?; - let context = context_manager.get_context(&partial_tx.id); - let res = context.verify_partial_sig( - &keychain.secp(), - &other_sig_part, - &other_pub_nonce, - &other_pub_blinding, - tx.fee(), - tx.lock_height(), - ); - - if !res { - error!(LOGGER, "Partial Sig from other party invalid."); - return Err(ErrorKind::Signature( - "Partial Sig from other party invalid.", - ))?; - } - - // Just calculate our sig part again instead of storing - let our_sig_part = context - .calculate_partial_sig( - &keychain.secp(), - &other_pub_nonce, - tx.fee(), - tx.lock_height(), - ) - .unwrap(); - - // And the final signature - let final_sig = context - .calculate_final_sig( - &keychain.secp(), - &other_sig_part, - &our_sig_part, - &other_pub_nonce, - ) - .unwrap(); - - // Calculate the final public key (for our own sanity check) - let final_pubkey = context - .calculate_final_pubkey(&keychain.secp(), &other_pub_blinding) - .unwrap(); - - // Check our final sig verifies - let res = context.verify_final_sig_build_msg( - &keychain.secp(), - &final_sig, - &final_pubkey, - tx.fee(), - tx.lock_height(), - ); - - if !res { - error!(LOGGER, "Final aggregated signature invalid."); - return Err(ErrorKind::Signature("Final aggregated signature invalid."))?; - } - - Ok(final_sig) -} - -/// builds a final transaction after the aggregated sig exchange -fn build_final_transaction( - keychain: &Keychain, - amount: u64, - kernel_offset: BlindingFactor, - excess_sig: &secp::Signature, - tx: Transaction, - output_key_id: &Identifier, - output_key_derivation: u32, -) -> Result { - let root_key_id = keychain.root_key_id(); - - // double check the fee amount included in the partial tx - // we don't necessarily want to just trust the sender - // we could just overwrite the fee here (but we won't) due to the ecdsa sig - let fee = tx_fee( - tx.inputs.len(), - tx.outputs.len() + 1, - tx.input_proofs_count(), - None, - ); - if fee > tx.fee() { - return Err(ErrorKind::FeeDispute { - sender_fee: tx.fee(), - recipient_fee: fee, - })?; - } - - if fee > amount { - info!( - LOGGER, - "Rejected the transfer because transaction fee ({}) exceeds received amount ({}).", - amount_to_hr_string(fee), - amount_to_hr_string(amount) - ); - return Err(ErrorKind::FeeExceedsAmount { - sender_amount: amount, - recipient_fee: fee, - })?; - } - - let out_amount = amount - tx.fee(); - - // Build final transaction, the sum of which should - // be the same as the exchanged excess values - let mut final_tx = build::transaction( - vec![ - build::initial_tx(tx), - build::output(out_amount, output_key_id.clone()), - build::with_offset(kernel_offset), - ], - keychain, - ).context(ErrorKind::Keychain)?; - - // build the final excess based on final tx and offset - let final_excess = { - // TODO - do we need to verify rangeproofs here? - for x in &final_tx.outputs { - x.verify_proof().context(ErrorKind::Transaction)?; + if fee > self.tx.fee() { + return Err(ErrorKind::FeeDispute { + sender_fee: self.tx.fee(), + recipient_fee: fee, + })?; } - // sum the input/output commitments on the final tx - let overage = final_tx.fee() as i64; - let tx_excess = final_tx - .sum_commitments(overage, None) + if fee > self.amount + self.fee { + info!( + LOGGER, + "Rejected the transfer because transaction fee ({}) exceeds received amount ({}).", + amount_to_hr_string(fee), + amount_to_hr_string(self.amount + self.fee) + ); + return Err(ErrorKind::FeeExceedsAmount { + sender_amount: self.amount + self.fee, + recipient_fee: fee, + })?; + } + + Ok(()) + } + + /// Verifies all of the partial signatures in the Slate are valid + fn verify_part_sigs(&self, secp: &secp::Secp256k1) -> Result<(), Error> { + // collect public nonces + for p in self.participant_data.iter() { + if p.is_complete() { + if aggsig::verify_partial_sig( + secp, + p.part_sig.as_ref().unwrap(), + &self.pub_nonce_sum(secp), + &p.public_blind_excess, + self.fee, + self.lock_height, + ) == false + { + error!(LOGGER, "Partial Sig invalid."); + return Err(ErrorKind::Signature("Partial Sig invalid."))?; + } + } + } + Ok(()) + } + + /// This should be callable by either the sender or receiver + /// once phase 3 is done + /// + /// Receive Part 3 of interactive transactions from sender, Sender + /// Confirmation Return Ok/Error + /// -Receiver receives sS + /// -Receiver verifies sender's sig, by verifying that + /// kS * G + e *xS * G = sS* G + /// -Receiver calculates final sig as s=(sS+sR, kS * G+kR * G) + /// -Receiver puts into TX kernel: + /// + /// Signature S + /// pubkey xR * G+xS * G + /// fee (= M) + /// + /// Returns completed transaction ready for posting to the chain + + fn finalize_signature(&mut self, keychain: &Keychain) -> Result { + self.verify_part_sigs(keychain.secp())?; + + let part_sigs = self.part_sigs(); + let pub_nonce_sum = self.pub_nonce_sum(keychain.secp()); + let final_pubkey = self.pub_blind_sum(keychain.secp()); + // get the final signature + let final_sig = + aggsig::add_signatures(&keychain.secp(), part_sigs, &pub_nonce_sum).unwrap(); + + // Calculate the final public key (for our own sanity check) + + // Check our final sig verifies + let res = aggsig::verify_sig_build_msg( + &keychain.secp(), + &final_sig, + &final_pubkey, + self.fee, + self.lock_height, + ); + + if !res { + error!(LOGGER, "Final aggregated signature invalid."); + return Err(ErrorKind::Signature("Final aggregated signature invalid."))?; + } + + Ok(final_sig) + } + + /// builds a final transaction after the aggregated sig exchange + fn finalize_transaction( + &mut self, + keychain: &Keychain, + final_sig: &secp::Signature, + ) -> Result<(), Error> { + let kernel_offset = self.tx.offset; + + self.check_fees()?; + + let mut final_tx = self.tx.clone(); + + // build the final excess based on final tx and offset + let final_excess = { + // TODO - do we need to verify rangeproofs here? + for x in &final_tx.outputs { + x.verify_proof().context(ErrorKind::Transaction)?; + } + + // sum the input/output commitments on the final tx + let overage = final_tx.fee() as i64; + let tx_excess = final_tx + .sum_commitments(overage, None) + .context(ErrorKind::Transaction)?; + + // subtract the kernel_excess (built from kernel_offset) + let offset_excess = keychain + .secp() + .commit(0, kernel_offset.secret_key(&keychain.secp()).unwrap()) + .unwrap(); + keychain + .secp() + .commit_sum(vec![tx_excess], vec![offset_excess]) + .context(ErrorKind::Transaction)? + }; + + // update the tx kernel to reflect the offset excess and sig + assert_eq!(final_tx.kernels.len(), 1); + final_tx.kernels[0].excess = final_excess.clone(); + final_tx.kernels[0].excess_sig = final_sig.clone(); + + // confirm the kernel verifies successfully before proceeding + debug!(LOGGER, "Validating final transaction"); + final_tx.kernels[0] + .verify() .context(ErrorKind::Transaction)?; - // subtract the kernel_excess (built from kernel_offset) - let offset_excess = keychain - .secp() - .commit(0, kernel_offset.secret_key(&keychain.secp()).unwrap()) - .unwrap(); - keychain - .secp() - .commit_sum(vec![tx_excess], vec![offset_excess]) - .context(ErrorKind::Transaction)? - }; + // confirm the overall transaction is valid (including the updated kernel) + let _ = final_tx.validate().context(ErrorKind::Transaction)?; - // update the tx kernel to reflect the offset excess and sig - assert_eq!(final_tx.kernels.len(), 1); - final_tx.kernels[0].excess = final_excess.clone(); - final_tx.kernels[0].excess_sig = excess_sig.clone(); - - // confirm the kernel verifies successfully before proceeding - debug!(LOGGER, "Validating final transaction"); - final_tx.kernels[0] - .verify() - .context(ErrorKind::Transaction)?; - - // confirm the overall transaction is valid (including the updated kernel) - let _ = final_tx.validate().context(ErrorKind::Transaction)?; - - debug!( - LOGGER, - "Finalized transaction and built output - {:?}, {:?}, {}", - root_key_id.clone(), - output_key_id.clone(), - output_key_derivation, - ); - - Ok(final_tx) + self.tx = final_tx; + Ok(()) + } } diff --git a/wallet/src/receiver.rs b/wallet/src/receiver.rs index a73501699..cec358e6e 100644 --- a/wallet/src/receiver.rs +++ b/wallet/src/receiver.rs @@ -25,15 +25,14 @@ use std::sync::{Arc, RwLock}; use api; use core::consensus::reward; -use core::core::{Output, Transaction, TxKernel}; -use libwallet::{aggsig, reward, transaction}; -use grinwallet::keys; -use core::{global, ser}; -use failure::{Fail, ResultExt}; +use core::core::{Output, TxKernel}; +use core::global; +use failure::Fail; +use grinwallet::{keys, selection}; use keychain::Keychain; +use libwallet::{aggsig, reward, transaction}; use types::*; -use urlencoded::UrlEncodedQuery; -use util::{to_hex, LOGGER}; +use util::LOGGER; /// Dummy wrapper for the hex-encoded serialized transaction. #[derive(Serialize, Deserialize)] @@ -47,99 +46,27 @@ lazy_static! { = Arc::new(RwLock::new(aggsig::ContextManager::new())); } -fn handle_sender_initiation( +fn handle_send( config: &WalletConfig, - context_manager: &mut aggsig::ContextManager, keychain: &Keychain, - partial_tx: &PartialTx, -) -> Result { - // Create a potential output for this transaction - let (key_id, derivation) = WalletData::with_wallet(&config.data_file_dir, |wallet_data| { - keys::next_available_key(&wallet_data, keychain) - })?; - - let partial_tx = - transaction::recipient_initiation(keychain, context_manager, partial_tx, &key_id)?; - let mut context = context_manager.get_context(&partial_tx.id); - context.add_output(&key_id); - - // Add the output to our wallet - let _ = WalletData::with_wallet(&config.data_file_dir, |wallet_data| { - wallet_data.add_output(OutputData { - root_key_id: keychain.root_key_id(), - key_id: key_id.clone(), - n_child: derivation, - value: partial_tx.amount - context.fee, - status: OutputStatus::Unconfirmed, - height: 0, - lock_height: 0, - is_coinbase: false, - block: None, - merkle_proof: None, - }); - })?; - - context_manager.save_context(context); - Ok(partial_tx) -} - -fn handle_sender_confirmation( - config: &WalletConfig, context_manager: &mut aggsig::ContextManager, - keychain: &Keychain, - partial_tx: &PartialTx, - fluff: bool, -) -> Result { - let context = context_manager.get_context(&partial_tx.id); - // Get output we created in earlier step - // TODO: will just be one for now, support multiple later - let output_vec = context.get_outputs(); + slate: &mut transaction::Slate, +) -> Result<(), Error> { + // create an output using the amount in the slate + let (_, receiver_create_fn) = + selection::build_recipient_output_with_slate(config, keychain, context_manager, slate) + .unwrap(); - let root_key_id = keychain.root_key_id(); - // operate within a lock on wallet data - let (key_id, derivation) = WalletData::with_wallet(&config.data_file_dir, |wallet_data| { - let (key_id, derivation) = keys::retrieve_existing_key(&wallet_data, output_vec[0].clone()); + // fill public keys + let _ = slate.fill_round_1(&keychain, context_manager, 1)?; - wallet_data.add_output(OutputData { - root_key_id: root_key_id.clone(), - key_id: key_id.clone(), - n_child: derivation, - value: partial_tx.amount - context.fee, - status: OutputStatus::Unconfirmed, - height: 0, - lock_height: 0, - is_coinbase: false, - block: None, - merkle_proof: None, - }); + // perform partial sig + let _ = slate.fill_round_2(&keychain, context_manager, 1)?; - (key_id, derivation) - })?; + // Save output in wallet + let _ = receiver_create_fn(); - // In this case partial_tx contains other party's pubkey info - let final_tx = transaction::finalize_transaction( - keychain, - context_manager, - partial_tx, - partial_tx, - &key_id, - derivation, - )?; - - let tx_hex = to_hex(ser::ser_vec(&final_tx).unwrap()); - - let url; - if fluff { - url = format!( - "{}/v1/pool/push?fluff", - config.check_node_api_http_addr.as_str() - ); - } else { - url = format!("{}/v1/pool/push", config.check_node_api_http_addr.as_str()); - } - api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex }).context(ErrorKind::Node)?; - - Ok(final_tx) + Ok(()) } /// Component used to receive coins, implements all the receiving end of the @@ -152,56 +79,23 @@ pub struct WalletReceiver { impl Handler for WalletReceiver { fn handle(&self, req: &mut Request) -> IronResult { - let struct_body = req.get::>(); + let struct_body = req.get::>(); - let mut fluff = false; - if let Ok(params) = req.get_ref::() { - if let Some(_) = params.get("fluff") { - fluff = true; - } - } - - if let Ok(Some(partial_tx)) = struct_body { + if let Ok(Some(mut slate)) = struct_body { let mut acm = AGGSIG_CONTEXT_MANAGER.write().unwrap(); - match partial_tx.phase { - PartialTxPhase::SenderInitiation => { - let resp_tx = handle_sender_initiation( - &self.config, - &mut acm, - &self.keychain, - &partial_tx, - ).map_err(|e| { - error!(LOGGER, "Phase 1 Sender Initiation -> Problematic partial tx, looks like this: {:?}", partial_tx); - e.context(api::ErrorKind::Internal( - "Error processing partial transaction".to_owned(), - )) - }) - .unwrap(); - let json = serde_json::to_string(&resp_tx).unwrap(); - Ok(Response::with((status::Ok, json))) - } - PartialTxPhase::SenderConfirmation => { - let resp_tx = handle_sender_confirmation( - &self.config, - &mut acm, - &self.keychain, - &partial_tx, - fluff, - ).map_err(|e| { - error!(LOGGER, "Phase 3 Sender Confirmation -> Problematic partial tx, looks like this: {:?}", partial_tx); - e.context(api::ErrorKind::Internal( - "Error processing partial transaction".to_owned(), - )) - }) - .unwrap(); - let json = serde_json::to_string(&resp_tx).unwrap(); - Ok(Response::with((status::Ok, json))) - } - _ => { - error!(LOGGER, "Unhandled Phase: {:?}", partial_tx); - Ok(Response::with((status::BadRequest, "Unhandled Phase"))) - } - } + let _ = handle_send(&self.config, &self.keychain, &mut acm, &mut slate) + .map_err(|e| { + error!( + LOGGER, + "Handling send -> Problematic slate, looks like this: {:?}", slate + ); + e.context(api::ErrorKind::Internal( + "Error processing partial transaction".to_owned(), + )) + }) + .unwrap(); + let json = serde_json::to_string(&slate).unwrap(); + Ok(Response::with((status::Ok, json))) } else { Ok(Response::with((status::BadRequest, ""))) } diff --git a/wallet/src/sender.rs b/wallet/src/sender.rs index b3dff1055..e37f47234 100644 --- a/wallet/src/sender.rs +++ b/wallet/src/sender.rs @@ -12,26 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -use uuid::Uuid; - use api; -use client; use checker; +use client; use core::core::amount_to_hr_string; -use libwallet::{aggsig, build, transaction}; -use grinwallet::selection; use core::ser; +use failure::ResultExt; +use grinwallet::selection; use keychain::{Identifier, Keychain}; +use libwallet::{aggsig, build}; use receiver::TxWrapper; use types::*; -use util::LOGGER; use util; -use failure::ResultExt; +use util::LOGGER; /// Issue a new transaction to the provided sender by spending some of our /// wallet -/// Outputs. The destination can be "stdout" (for command line) (currently disabled) or a URL to the -/// recipients wallet receiver (to be implemented). +/// Outputs. The destination can be "stdout" (for command line) (currently +/// disabled) or a URL to the recipients wallet receiver (to be implemented). pub fn issue_send_tx( config: &WalletConfig, keychain: &Keychain, @@ -42,11 +40,18 @@ pub fn issue_send_tx( selection_strategy_is_use_all: bool, fluff: bool, ) -> Result<(), Error> { + // TODO: Stdout option, probably in a separate implementation + if &dest[..4] != "http" { + panic!( + "dest formatted as {} but send -d expected stdout or http://IP:port", + dest + ); + } + checker::refresh_outputs(config, keychain)?; // Create a new aggsig context let mut context_manager = aggsig::ContextManager::new(); - let tx_id = Uuid::new_v4(); // Get lock height let chain_tip = checker::get_tip_from_node(config)?; @@ -56,125 +61,77 @@ pub fn issue_send_tx( let lock_height = current_height; - let tx_data = selection::build_send_tx( + // Sender selects outputs into a new slate and save our corresponding IDs in + // their transaction context. The secret key in our transaction context will be + // randomly selected. This returns the public slate, and a closure that locks + // our inputs and outputs once we're convinced the transaction exchange went + // according to plan + // This function is just a big helper to do all of that, in theory + // this process can be split up in any way + let (mut slate, sender_lock_fn) = selection::build_send_tx_slate( config, keychain, + &mut context_manager, + 2, amount, current_height, minimum_confirmations, lock_height, max_outputs, selection_strategy_is_use_all, - )?; + ).unwrap(); - let partial_tx = transaction::sender_initiation( - keychain, - &tx_id, - &mut context_manager, - current_height, - tx_data, - )?; - - let context = context_manager.get_context(&tx_id); - - // Closure to acquire wallet lock and lock the coins being spent - // so we avoid accidental double spend attempt. - let update_wallet = || { - WalletData::with_wallet(&config.data_file_dir, |wallet_data| { - for id in context.get_outputs().clone() { - let coin = wallet_data.get_output(&id).unwrap().clone(); - wallet_data.lock_output(&coin); - } - }) - }; - - // Closure to acquire wallet lock and delete the change output in case of tx - // failure. - let rollback_wallet = || { - WalletData::with_wallet(&config.data_file_dir, |wallet_data| { - match context.change_key.clone() { - Some(change) => { - info!(LOGGER, "cleaning up unused change output from wallet"); - wallet_data.delete_output(&change); - } - None => info!(LOGGER, "No change output to clean from wallet"), - } - }) - }; - - // TODO: stdout option removed for now, as it won't work very will with this - // version of aggsig exchange - - /*if dest == "stdout" { - let json_tx = serde_json::to_string_pretty(&partial_tx).unwrap(); - update_wallet()?; - println!("{}", json_tx); - } else */ - - if &dest[..4] != "http" { - WalletData::with_wallet(&config.data_file_dir, |wallet_data| { - match context.change_key.clone() { - Some(change) => { - info!(LOGGER, "cleaning up unused change output from wallet"); - wallet_data.delete_output(&change); - } - None => info!(LOGGER, "No change output to clean from wallet"), - } - }).unwrap(); - panic!( - "dest formatted as {} but send -d expected stdout or http://IP:port", - dest - ); - } + // Generate a kernel offset and subtract from our context's secret key. Store + // the offset in the slate's transaction kernel, and adds our public key + // information to the slate + let _ = slate + .fill_round_1(keychain, &mut context_manager, 0) + .unwrap(); let url = format!("{}/v1/receive/transaction", &dest); debug!(LOGGER, "Posting partial transaction to {}", url); - let res = client::send_partial_tx(&url, &partial_tx, fluff); - if let Err(e) = res { - match e.kind() { - ErrorKind::FeeExceedsAmount { - sender_amount, - recipient_fee, - } => error!( + let mut slate = match client::send_slate(&url, &slate, fluff) { + Ok(s) => s, + Err(e) => { + match e.kind() { + ErrorKind::FeeExceedsAmount { + sender_amount, + recipient_fee, + } => error!( + LOGGER, + "Recipient rejected the transfer because transaction fee ({}) exceeded amount ({}).", + amount_to_hr_string(recipient_fee), + amount_to_hr_string(sender_amount) + ), + _ => error!( LOGGER, - "Recipient rejected the transfer because transaction fee ({}) exceeded amount ({}).", - amount_to_hr_string(recipient_fee), - amount_to_hr_string(sender_amount) + "Communication with receiver failed on SenderInitiation send. Aborting transaction" ), - _ => error!( - LOGGER, - "Communication with receiver failed on SenderInitiation send. Aborting transaction" - ), + } + return Err(e); } - rollback_wallet()?; - return Err(e); + }; + + let _ = slate.fill_round_2(keychain, &mut context_manager, 0)?; + + // Final transaction can be built by anyone at this stage + slate.finalize(keychain)?; + + // So let's post it + let tx_hex = util::to_hex(ser::ser_vec(&slate.tx).unwrap()); + let url; + if fluff { + url = format!( + "{}/v1/pool/push?fluff", + config.check_node_api_http_addr.as_str() + ); + } else { + url = format!("{}/v1/pool/push", config.check_node_api_http_addr.as_str()); } + api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex }).context(ErrorKind::Node)?; - let partial_tx = - transaction::sender_confirmation(keychain, &mut context_manager, res.unwrap())?; - - // And send again, expecting completed transaction as result this time - let res = client::send_partial_tx_final(&url, &partial_tx, fluff); - if let Err(e) = res { - match e.kind() { - ErrorKind::FeeExceedsAmount {sender_amount, recipient_fee} => - error!( - LOGGER, - "Recipient rejected the transfer because transaction fee ({}) exceeded amount ({}).", - amount_to_hr_string(recipient_fee), - amount_to_hr_string(sender_amount) - ), - _ => error!(LOGGER, "Communication with receiver failed on SenderConfirmation send. Aborting transaction"), - } - rollback_wallet()?; - return Err(e); - } - - // Not really necessary here - context_manager.save_context(context.clone()); - - // All good so - update_wallet()?; + // All good so, lock our outputs + sender_lock_fn()?; Ok(()) } @@ -227,8 +184,8 @@ pub fn issue_burn_tx( #[cfg(test)] mod test { - use libwallet::build; use keychain::Keychain; + use libwallet::build; #[test] // demonstrate that input.commitment == referenced output.commitment diff --git a/wallet/src/types.rs b/wallet/src/types.rs index f0e0cb034..89a50bd0b 100644 --- a/wallet/src/types.rs +++ b/wallet/src/types.rs @@ -14,17 +14,15 @@ use blake2; use rand::{thread_rng, Rng}; +use std::cmp::min; +use std::collections::HashMap; +use std::convert::From; use std::fmt; use std::fmt::Display; -use uuid::Uuid; -use std::convert::From; use std::fs::{self, File}; use std::io::{Read, Write}; -use std::path::Path; use std::path::MAIN_SEPARATOR; -use std::collections::HashMap; -use std::cmp::min; -use libwallet::aggsig; +use std::path::Path; use serde; use serde_json; @@ -38,13 +36,8 @@ use core::consensus; use core::core::Transaction; use core::core::hash::Hash; use core::core::pmmr::MerkleProof; -use core::ser; use keychain; -use keychain::BlindingFactor; use util; -use util::secp; -use util::secp::Signature; -use util::secp::key::PublicKey; use util::LOGGER; const DAT_FILE: &'static str = "wallet.dat"; @@ -644,9 +637,10 @@ impl WalletData { } /// Select spendable coins from the wallet. - /// Default strategy is to spend the maximum number of outputs (up to max_outputs). - /// Alternative strategy is to spend smallest outputs first but only as many as necessary. - /// When we introduce additional strategies we should pass something other than a bool in. + /// Default strategy is to spend the maximum number of outputs (up to + /// max_outputs). Alternative strategy is to spend smallest outputs first + /// but only as many as necessary. When we introduce additional strategies + /// we should pass something other than a bool in. pub fn select_coins( &self, root_key_id: keychain::Identifier, @@ -751,120 +745,6 @@ impl WalletData { } } -/// Define the stages of a transaction -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum PartialTxPhase { - SenderInitiation, - ReceiverInitiation, - SenderConfirmation, - ReceiverConfirmation, -} - -/// Helper in serializing the information required during an interactive aggsig -/// transaction -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct PartialTx { - pub phase: PartialTxPhase, - pub id: Uuid, - pub amount: u64, - pub lock_height: u64, - pub public_blind_excess: String, - pub public_nonce: String, - pub kernel_offset: String, - pub part_sig: String, - pub tx: String, -} - -/// Builds a 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( - context: &aggsig::Context, - keychain: &keychain::Keychain, - receive_amount: u64, - lock_height: u64, - kernel_offset: BlindingFactor, - part_sig: Option, - tx: Transaction, -) -> PartialTx { - let (pub_excess, pub_nonce) = context.get_public_keys(keychain.secp()); - 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(); - - let mut pub_nonce = pub_nonce.serialize_vec(keychain.secp(), true); - let len = pub_nonce.clone().len(); - let pub_nonce: Vec<_> = pub_nonce.drain(0..len).collect(); - - PartialTx { - phase: PartialTxPhase::SenderInitiation, - id: context.transaction_id, - amount: receive_amount, - lock_height: lock_height, - public_blind_excess: util::to_hex(pub_excess), - public_nonce: util::to_hex(pub_nonce), - kernel_offset: kernel_offset.to_hex(), - part_sig: match part_sig { - None => String::from("00"), - Some(p) => util::to_hex(p.serialize_der(&keychain.secp())), - }, - tx: util::to_hex(ser::ser_vec(&tx).unwrap()), - } -} - -/// Reads a partial transaction into the amount, sum of blinding -/// factors and the transaction itself. -pub fn read_partial_tx( - keychain: &keychain::Keychain, - partial_tx: &PartialTx, -) -> Result< - ( - u64, - u64, - PublicKey, - PublicKey, - BlindingFactor, - Option, - Transaction, - ), - Error, -> { - let blind_bin = util::from_hex(partial_tx.public_blind_excess.clone()) - .context(ErrorKind::GenericError("Could not decode HEX"))?; - let blinding = PublicKey::from_slice(keychain.secp(), &blind_bin[..]) - .context(ErrorKind::GenericError("Could not construct public key"))?; - - let nonce_bin = util::from_hex(partial_tx.public_nonce.clone()) - .context(ErrorKind::GenericError("Could not decode HEX"))?; - let nonce = PublicKey::from_slice(keychain.secp(), &nonce_bin[..]) - .context(ErrorKind::GenericError("Could not construct public key"))?; - - let kernel_offset = BlindingFactor::from_hex(&partial_tx.kernel_offset.clone()) - .context(ErrorKind::GenericError("Could not decode HEX"))?; - - let sig_bin = util::from_hex(partial_tx.part_sig.clone()) - .context(ErrorKind::GenericError("Could not decode HEX"))?; - let sig = match sig_bin.len() { - 1 => None, - _ => Some(Signature::from_der(keychain.secp(), &sig_bin[..]) - .context(ErrorKind::GenericError("Could not create signature"))?), - }; - let tx_bin = util::from_hex(partial_tx.tx.clone()) - .context(ErrorKind::GenericError("Could not decode HEX"))?; - let tx = ser::deserialize(&mut &tx_bin[..]).context(ErrorKind::GenericError( - "Could not deserialize transaction, invalid format.", - ))?; - Ok(( - partial_tx.amount, - partial_tx.lock_height, - blinding, - nonce, - kernel_offset, - sig, - tx, - )) -} - /// Amount in request to build a coinbase output. #[derive(Serialize, Deserialize, Debug, Clone)] pub enum WalletReceiveRequest { diff --git a/wallet/tests/common/mod.rs b/wallet/tests/common/mod.rs index 991ab165f..a79d86b4c 100644 --- a/wallet/tests/common/mod.rs +++ b/wallet/tests/common/mod.rs @@ -13,8 +13,8 @@ // limitations under the License. //! Common functions to facilitate wallet, walletlib and transaction testing -use std::collections::hash_map::Entry; use std::collections::HashMap; +use std::collections::hash_map::Entry; extern crate grin_api as api; extern crate grin_chain as chain; @@ -24,18 +24,18 @@ extern crate grin_wallet as wallet; extern crate time; use chain::Chain; -use core::core::{Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel}; use core::core::hash::Hashed; +use core::core::{Output, OutputFeatures, OutputIdentifier, Transaction, TxKernel}; use core::{consensus, global, pow}; +use keychain::Keychain; use wallet::types::{BlockIdentifier, Error, ErrorKind, MerkleProofWrapper, OutputStatus, WalletConfig, WalletData}; use wallet::{checker, BlockFees}; -use keychain::Keychain; use util::secp::pedersen; -/// Mostly for testing, refreshes output state against a local chain instance instead of -/// via an http API call +/// Mostly for testing, refreshes output state against a local chain instance +/// instead of via an http API call pub fn refresh_output_state_local( config: &WalletConfig, keychain: &Keychain, @@ -58,8 +58,9 @@ pub fn refresh_output_state_local( } /// Return the spendable wallet balance from the local chain -/// (0:total, 1:amount_awaiting_confirmation, 2:confirmed but locked, 3:currently_spendable, -/// 4:locked total) TODO: Should be a wallet lib function with nicer return values +/// (0:total, 1:amount_awaiting_confirmation, 2:confirmed but locked, +/// 3:currently_spendable, 4:locked total) TODO: Should be a wallet lib +/// function with nicer return values pub fn get_wallet_balances( config: &WalletConfig, keychain: &Keychain, @@ -135,8 +136,8 @@ pub fn add_block_with_reward(chain: &Chain, txs: Vec<&Transaction>, reward: (Out chain.validate(false).unwrap(); } -/// adds a reward output to a wallet, includes that reward in a block, mines the block -/// and adds it to the chain, with option transactions included. +/// adds a reward output to a wallet, includes that reward in a block, mines +/// the block and adds it to the chain, with option transactions included. /// Helpful for building up precise wallet balances for testing. pub fn award_block_to_wallet( chain: &Chain, diff --git a/wallet/tests/libwallet.rs b/wallet/tests/libwallet.rs index 92a5c6443..48bc7b9b8 100644 --- a/wallet/tests/libwallet.rs +++ b/wallet/tests/libwallet.rs @@ -20,11 +20,11 @@ extern crate grin_wallet as wallet; extern crate rand; extern crate uuid; -use uuid::Uuid; -use util::{kernel_sig_msg, secp}; -use util::secp::key::SecretKey; -use util::secp::pedersen::ProofMessage; use keychain::{BlindSum, BlindingFactor, Keychain}; +use util::secp::key::{PublicKey, SecretKey}; +use util::secp::pedersen::ProofMessage; +use util::{kernel_sig_msg, secp}; +use uuid::Uuid; use wallet::libwallet::{aggsig, proof}; use rand::thread_rng; @@ -151,12 +151,16 @@ fn aggsig_sender_receiver_interaction() { let our_sig_part = cx.calculate_partial_sig(&keychain.secp(), &sender_pub_nonce, 0, 0) .unwrap(); + let combined_nonces = PublicKey::from_combination( + keychain.secp(), + vec![&sender_pub_nonce, &cx.get_public_keys(keychain.secp()).1], + ).unwrap(); + // Receiver now generates final signature from the two parts let final_sig = cx.calculate_final_sig( &keychain.secp(), - &sender_sig_part, - &our_sig_part, - &sender_pub_nonce, + vec![&sender_sig_part, &our_sig_part], + &combined_nonces, ).unwrap(); // Receiver calculates the final public key (to verify sig later) @@ -322,12 +326,16 @@ fn aggsig_sender_receiver_interaction_offset() { let our_sig_part = cx.calculate_partial_sig(&keychain.secp(), &sender_pub_nonce, 0, 0) .unwrap(); + let combined_nonces = PublicKey::from_combination( + keychain.secp(), + vec![&sender_pub_nonce, &cx.get_public_keys(keychain.secp()).1], + ).unwrap(); + // Receiver now generates final signature from the two parts let final_sig = cx.calculate_final_sig( &keychain.secp(), - &sender_sig_part, - &our_sig_part, - &sender_pub_nonce, + vec![&sender_sig_part, &our_sig_part], + &combined_nonces, ).unwrap(); // Receiver calculates the final public key (to verify sig later) diff --git a/wallet/tests/transaction.rs b/wallet/tests/transaction.rs index a908c4e4e..202570852 100644 --- a/wallet/tests/transaction.rs +++ b/wallet/tests/transaction.rs @@ -20,6 +20,7 @@ extern crate grin_wallet as wallet; extern crate rand; #[macro_use] extern crate slog; +extern crate serde; extern crate time; extern crate uuid; @@ -28,16 +29,13 @@ mod common; use std::fs; use std::sync::Arc; -use uuid::Uuid; - use chain::Chain; use chain::types::*; -use core::{global, pow}; use core::global::ChainTypes; -use wallet::libwallet::{aggsig, transaction}; -use wallet::grinwallet::{keys, selection}; -use wallet::types::{OutputData, OutputStatus, WalletData}; +use core::{global, pow}; use util::LOGGER; +use wallet::grinwallet::selection; +use wallet::libwallet::aggsig; fn clean_output_dir(test_dir: &str) { let _ = fs::remove_dir_all(test_dir); @@ -57,32 +55,42 @@ fn setup(test_dir: &str, chain_dir: &str) -> Chain { ).unwrap() } -/// Build a transaction between 2 parties -#[cfg(test)] +/// Build and test new version of sending API #[test] -fn build_transaction() { - let chain = setup("test_output", "build_transaction/.grin"); - let wallet1 = common::create_wallet("test_output/build_transaction/wallet1"); - let wallet2 = common::create_wallet("test_output/build_transaction/wallet2"); +fn build_transaction_2() { + let chain = setup("test_output", "build_transaction_2/.grin"); + let wallet1 = common::create_wallet("test_output/build_transaction_2/wallet1"); + let wallet2 = common::create_wallet("test_output/build_transaction_2/wallet2"); common::award_blocks_to_wallet(&chain, &wallet1, 10); // Wallet 1 has 600 Grins, wallet 2 has 0. Create a transaction that sends // 300 Grins from wallet 1 to wallet 2, using libwallet - // Sender creates a new aggsig context - // SENDER (create sender initiation) - let mut sender_context_manager = aggsig::ContextManager::new(); - let tx_id = Uuid::new_v4(); // Get lock height let chain_tip = chain.head().unwrap(); + let amount = 300_000_000_000; // ensure outputs we're selecting are up to date let res = common::refresh_output_state_local(&wallet1.0, &wallet1.1, &chain); - let amount = 300_000_000_000; - // Select our outputs - let tx_data = selection::build_send_tx( + if let Err(e) = res { + panic!("Unable to refresh sender wallet outputs: {}", e); + } + + // TRANSACTION WORKFLOW STARTS HERE + // Sender creates a new aggsig context + let mut sender_context_manager = aggsig::ContextManager::new(); + // Sender selects outputs into a new slate and save our corresponding IDs in + // their transaction context. The secret key in our transaction context will be + // randomly selected. This returns the public slate, and a closure that locks + // our inputs and outputs once we're convinced the transaction exchange went + // according to plan + // This function is just a big helper to do all of that, in theory + // this process can be split up in any way + let (mut slate, sender_lock_fn) = selection::build_send_tx_slate( &wallet1.0, &wallet1.1, + &mut sender_context_manager, + 2, amount, chain_tip.height, 3, @@ -91,135 +99,74 @@ fn build_transaction() { true, ).unwrap(); - if let Err(e) = res { - panic!("Unable to refresh sender wallet outputs: {}", e); - } + // Generate a kernel offset and subtract from our context's secret key. Store + // the offset in the slate's transaction kernel, and adds our public key + // information to the slate + let _ = slate + .fill_round_1(&wallet1.1, &mut sender_context_manager, 0) + .unwrap(); - let partial_tx = transaction::sender_initiation( - &wallet1.1, - &tx_id, - &mut sender_context_manager, - chain_tip.height, - tx_data, - ).unwrap(); - - let sender_context = sender_context_manager.get_context(&tx_id); - - // TODO: Might make more sense to do this before the transaction - // building call - // Closure to acquire wallet lock and lock the coins being spent - // so we avoid accidental double spend attempt. - let update_sender_wallet = || { - WalletData::with_wallet(&wallet1.0.data_file_dir, |wallet_data| { - for id in sender_context.get_outputs().clone() { - let coin = wallet_data.get_output(&id).unwrap().clone(); - wallet_data.lock_output(&coin); - } - }) - }; - debug!(LOGGER, "PartialTx after step 1: sender initiation"); + debug!(LOGGER, "Transaction Slate after step 1: sender initiation"); debug!(LOGGER, "-----------------------------------------"); - debug!(LOGGER, "{:?}", partial_tx); + debug!(LOGGER, "{:?}", slate); // RECIPIENT (Handle sender initiation) let mut recipient_context_manager = aggsig::ContextManager::new(); - // Create a potential output for this transaction - let (key_id, derivation) = WalletData::with_wallet(&wallet2.0.data_file_dir, |wallet_data| { - keys::next_available_key(&wallet_data, &wallet2.1) - }).unwrap(); - - let partial_tx = transaction::recipient_initiation( + // Now, just like the sender did, recipient is going to select a target output, + // add it to the transaction, and keep track of the corresponding wallet + // Identifier Again, this is a helper to do that, which returns a closure that + // creates the output when we're satisified the process was successful + let (_, receiver_create_fn) = selection::build_recipient_output_with_slate( + &wallet2.0, &wallet2.1, &mut recipient_context_manager, - &partial_tx, - &key_id, + &mut slate, ).unwrap(); - let mut context = recipient_context_manager.get_context(&partial_tx.id); - // Add the output to recipient's wallet - let _ = WalletData::with_wallet(&wallet2.0.data_file_dir, |wallet_data| { - wallet_data.add_output(OutputData { - root_key_id: wallet2.1.root_key_id(), - key_id: key_id.clone(), - n_child: derivation, - value: partial_tx.amount - context.fee, - status: OutputStatus::Unconfirmed, - height: 0, - lock_height: 0, - is_coinbase: false, - block: None, - merkle_proof: None, - }); - }).unwrap(); - context.add_output(&key_id); - recipient_context_manager.save_context(context); + let _ = slate + .fill_round_1(&wallet2.1, &mut recipient_context_manager, 1) + .unwrap(); - debug!(LOGGER, "PartialTx after step 2: recipient initiation"); - debug!(LOGGER, "--------------------------------------------"); - debug!(LOGGER, "{:?}", partial_tx); + // recipient can proceed to round 2 now + let _ = receiver_create_fn(); - // TODO: We want to allow the sender to be able to calculate this, but also need - // the recipient's output information available, and the recipient needs to know - // whether to finalize the output in their wallet - let _tx_with_recipients_pubkeys = partial_tx.clone(); + let _ = slate + .fill_round_2(&wallet2.1, &mut recipient_context_manager, 1) + .unwrap(); + + debug!( + LOGGER, + "Transaction Slate after step 2: receiver initiation" + ); + debug!(LOGGER, "-----------------------------------------"); + debug!(LOGGER, "{:?}", slate); // SENDER Part 3: Sender confirmation - let partial_tx = - transaction::sender_confirmation(&wallet1.1, &mut sender_context_manager, partial_tx) - .unwrap(); + let _ = slate + .fill_round_2(&wallet1.1, &mut sender_context_manager, 0) + .unwrap(); debug!(LOGGER, "PartialTx after step 3: sender confirmation"); debug!(LOGGER, "--------------------------------------------"); - debug!(LOGGER, "{:?}", partial_tx); + debug!(LOGGER, "{:?}", slate); - // RECIPIENT Part 4: Recipient confirmation - // Get output we created in earlier step - let context = recipient_context_manager.get_context(&partial_tx.id); - let output_vec = context.get_outputs(); - let root_key_id = &wallet2.1.root_key_id(); + // Final transaction can be built by anyone at this stage + let res = slate.finalize(&wallet1.1); - // operate within a lock on wallet data - let (key_id, derivation) = WalletData::with_wallet(&wallet2.0.data_file_dir, |wallet_data| { - let (key_id, derivation) = keys::retrieve_existing_key(&wallet_data, output_vec[0].clone()); - - wallet_data.add_output(OutputData { - root_key_id: root_key_id.clone(), - key_id: key_id.clone(), - n_child: derivation, - value: partial_tx.amount - context.fee, - status: OutputStatus::Unconfirmed, - height: 0, - lock_height: 0, - is_coinbase: false, - block: None, - merkle_proof: None, - }); - - (key_id, derivation) - }).unwrap(); - - let final_tx_recipient = transaction::finalize_transaction( - &wallet2.1, - &mut recipient_context_manager, - &partial_tx, - &partial_tx, - &key_id, - derivation, - ); - - if let Err(e) = final_tx_recipient { + if let Err(e) = res { panic!("Error creating final tx: {:?}", e); } - debug!(LOGGER, "Recipient calculates final transaction as:"); + debug!(LOGGER, "Final transaction is:"); debug!(LOGGER, "--------------------------------------------"); - debug!(LOGGER, "{:?}", final_tx_recipient); + debug!(LOGGER, "{:?}", slate.tx); - let _ = update_sender_wallet(); + // All okay, lock sender's outputs + let _ = sender_lock_fn(); // Insert this transaction into a new block, then mine till confirmation - common::award_block_to_wallet(&chain, vec![&final_tx_recipient.unwrap()], &wallet1); + common::award_block_to_wallet(&chain, vec![&slate.tx], &wallet1); common::award_blocks_to_wallet(&chain, &wallet1, 3); // Refresh wallets