diff --git a/chain/tests/mine_simple_chain.rs b/chain/tests/mine_simple_chain.rs index 448bc6c49..eeab5771e 100644 --- a/chain/tests/mine_simple_chain.rs +++ b/chain/tests/mine_simple_chain.rs @@ -335,7 +335,10 @@ fn prepare_fork_block_tx(kc: &Keychain, prev: &BlockHeader, chain: &Chain, diff: fn prepare_block_nosum(kc: &Keychain, prev: &BlockHeader, diff: u64, txs: Vec<&Transaction>) -> Block { let key_id = kc.derive_key_id(diff as u32).unwrap(); - let mut b = core::core::Block::new(prev, txs, kc, &key_id).unwrap(); + let mut b = match core::core::Block::new(prev, txs, kc, &key_id) { + Err(e) => panic!("{:?}",e), + Ok(b) => b + }; b.header.timestamp = prev.timestamp + time::Duration::seconds(60); b.header.total_difficulty = Difficulty::from_num(diff); b diff --git a/core/src/core/block.rs b/core/src/core/block.rs index 66f1dee5c..4d89a39c7 100644 --- a/core/src/core/block.rs +++ b/core/src/core/block.rs @@ -341,7 +341,6 @@ impl Block { kernels.sort(); // calculate the overall Merkle tree and fees (todo?) - Ok( Block { header: BlockHeader { @@ -585,7 +584,7 @@ impl Block { let excess = secp.commit_sum(vec![out_commit], vec![over_commit])?; let msg = util::secp::Message::from_slice(&[0; secp::constants::MESSAGE_SIZE])?; - let sig = keychain.sign(&msg, &key_id)?; + let sig = keychain.aggsig_sign_from_key_id(&msg, &key_id).unwrap(); let excess_sig = sig.serialize_der(&secp); diff --git a/core/src/core/build.rs b/core/src/core/build.rs index 90044d79c..f691c1b60 100644 --- a/core/src/core/build.rs +++ b/core/src/core/build.rs @@ -25,10 +25,9 @@ //! build::transaction(vec![input_rand(75), output_rand(42), output_rand(32), //! with_fee(1)]) -use util::{secp, static_secp_instance}; +use util::{secp, static_secp_instance, kernel_sig_msg}; use core::{Input, Output, SwitchCommitHash, Transaction, DEFAULT_OUTPUT}; -use core::transaction::kernel_sig_msg; use util::LOGGER; use keychain; use keychain::{BlindSum, BlindingFactor, Identifier, Keychain}; @@ -138,7 +137,7 @@ pub fn transaction( ); let blind_sum = ctx.keychain.blind_sum(&sum)?; let msg = secp::Message::from_slice(&kernel_sig_msg(tx.fee, tx.lock_height))?; - let sig = ctx.keychain.sign_with_blinding(&msg, &blind_sum)?; + let sig = Keychain::aggsig_sign_with_blinding(&keychain.secp(), &msg, &blind_sum)?; let secp = static_secp_instance(); let secp = secp.lock().unwrap(); diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index dcc69ec54..d680c6667 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -13,11 +13,9 @@ // limitations under the License. //! Transactions - -use byteorder::{BigEndian, ByteOrder}; use blake2::blake2b::blake2b; use util::secp::{self, Message, Signature}; -use util::static_secp_instance; +use util::{static_secp_instance, kernel_sig_msg}; use util::secp::pedersen::{Commitment, RangeProof}; use std::cmp::Ordering; use std::ops; @@ -92,14 +90,6 @@ impl From for Error { } } -/// Construct msg bytes from tx fee and lock_height -pub fn kernel_sig_msg(fee: u64, lock_height: u64) -> [u8; 32] { - let mut bytes = [0; 32]; - BigEndian::write_u64(&mut bytes[16..24], fee); - BigEndian::write_u64(&mut bytes[24..], lock_height); - bytes -} - /// A proof that a transaction sums to zero. Includes both the transaction's /// Pedersen commitment and the signature, that guarantees that the commitments /// amount to zero. @@ -166,7 +156,11 @@ impl TxKernel { let secp = static_secp_instance(); let secp = secp.lock().unwrap(); let sig = try!(Signature::from_der(&secp, &self.excess_sig)); - secp.verify_from_commit(&msg, &sig, &self.excess) + let valid = Keychain::aggsig_verify_single_from_commit(&secp, &sig, &msg, &self.excess); + if !valid{ + return Err(secp::Error::IncorrectSignature); + } + Ok(()) } } @@ -330,16 +324,12 @@ impl Transaction { let secp = static_secp_instance(); let secp = secp.lock().unwrap(); let sig = Signature::from_der(&secp, &self.excess_sig)?; - // pretend the sum is a public key (which it is, being of the form r.G) and // verify the transaction sig with it - // - // we originally converted the commitment to a key_id here (commitment to zero) - // and then passed the key_id to secp.verify() - // the secp api no longer allows us to do this so we have wrapped the complexity - // of generating a public key from a commitment behind verify_from_commit - secp.verify_from_commit(&msg, &sig, &rsum)?; - + let valid = Keychain::aggsig_verify_single_from_commit(&secp, &sig, &msg, &rsum); + if !valid{ + return Err(secp::Error::IncorrectSignature); + } Ok(rsum) } diff --git a/grin/Cargo.toml b/grin/Cargo.toml index f6bfc512d..4a8b1a108 100644 --- a/grin/Cargo.toml +++ b/grin/Cargo.toml @@ -32,3 +32,4 @@ itertools = "~0.6.0" [dev_dependencies] blake2-rfc = "~0.2.17" +grin_config = { path = "../config" } diff --git a/grin/src/lib.rs b/grin/src/lib.rs index d5c5df06a..4a7c67c2f 100644 --- a/grin/src/lib.rs +++ b/grin/src/lib.rs @@ -38,7 +38,6 @@ extern crate tokio_timer; extern crate grin_api as api; extern crate grin_chain as chain; -#[macro_use] extern crate grin_core as core; extern crate grin_keychain as keychain; extern crate grin_p2p as p2p; diff --git a/grin/src/server.rs b/grin/src/server.rs index 2021a2e6b..27ca2fd6f 100644 --- a/grin/src/server.rs +++ b/grin/src/server.rs @@ -161,10 +161,16 @@ impl Server { _ => {} } + let skip_sync_wait = match config.skip_sync_wait { + None => false, + Some(b) => b, + }; + sync::run_sync( currently_syncing.clone(), p2p_server.peers.clone(), shared_chain.clone(), + skip_sync_wait, ); evt_handle.spawn(p2p_server.start(evt_handle.clone()).map_err(|_| ())); diff --git a/grin/src/sync.rs b/grin/src/sync.rs index ce0a1b400..9a300da04 100644 --- a/grin/src/sync.rs +++ b/grin/src/sync.rs @@ -30,6 +30,7 @@ pub fn run_sync( currently_syncing: Arc, peers: p2p::Peers, chain: Arc, + skip_sync_wait: bool, ) { let chain = chain.clone(); @@ -40,7 +41,9 @@ pub fn run_sync( let mut prev_header_sync = prev_body_sync.clone(); // initial sleep to give us time to peer with some nodes - thread::sleep(Duration::from_secs(30)); + if !skip_sync_wait { + thread::sleep(Duration::from_secs(30)); + } loop { let syncing = needs_syncing( diff --git a/grin/src/types.rs b/grin/src/types.rs index 04d232d28..c5a44ac4c 100644 --- a/grin/src/types.rs +++ b/grin/src/types.rs @@ -136,6 +136,10 @@ pub struct ServerConfig { /// Transaction pool configuration #[serde(default)] pub pool_config: pool::PoolConfig, + + /// Whether to skip the sync timeout on startup + /// (To assist testing on solo chains) + pub skip_sync_wait: Option, } impl Default for ServerConfig { @@ -150,6 +154,7 @@ impl Default for ServerConfig { mining_config: Some(pow::types::MinerConfig::default()), chain_type: ChainTypes::default(), pool_config: pool::PoolConfig::default(), + skip_sync_wait: Some(true), } } } diff --git a/grin/tests/framework/mod.rs b/grin/tests/framework/mod.rs index 9f5dca196..9931eb59c 100644 --- a/grin/tests/framework/mod.rs +++ b/grin/tests/framework/mod.rs @@ -34,12 +34,9 @@ use std::default::Default; use std::fs; use std::sync::{Arc, Mutex}; -use tokio_core::reactor; -use tokio_timer::Timer; +use self::tokio_core::reactor; +use self::tokio_timer::Timer; -use util::secp::Secp256k1; -// TODO - why does this need self here? Missing something somewhere. -use self::keychain::Keychain; use wallet::WalletConfig; /// Just removes all results from previous runs @@ -158,7 +155,10 @@ pub struct LocalServerContainer { pub peer_list: Vec, // base directory for the server instance - working_dir: String, + pub working_dir: String, + + // Wallet configuration + pub wallet_config:WalletConfig, } impl LocalServerContainer { @@ -168,6 +168,11 @@ impl LocalServerContainer { pub fn new(config: LocalServerContainerConfig) -> Result { let working_dir = format!("target/test_servers/{}", config.name); + let mut wallet_config = WalletConfig::default(); + + wallet_config.api_listen_port = format!("{}", config.wallet_port); + wallet_config.check_node_api_http_addr = config.wallet_validating_node_url.clone(); + wallet_config.data_file_dir = working_dir.clone(); Ok( (LocalServerContainer { config: config, @@ -178,6 +183,7 @@ impl LocalServerContainer { wallet_is_running: false, working_dir: working_dir, peer_list: Vec::new(), + wallet_config:wallet_config, }), ) } @@ -206,6 +212,7 @@ impl LocalServerContainer { seeds: Some(seeds), seeding_type: seeding_type, chain_type: core::global::ChainTypes::AutomatedTesting, + skip_sync_wait:Some(true), ..Default::default() }, &event_loop.handle(), @@ -260,52 +267,93 @@ impl LocalServerContainer { /// Starts a wallet daemon to receive and returns the /// listening server url - pub fn run_wallet(&mut self, _duration_in_seconds: u64) { + pub fn run_wallet(&mut self, _duration_in_mills: u64) { // URL on which to start the wallet listener (i.e. api server) - let url = format!("{}:{}", self.config.base_addr, self.config.wallet_port); + let _url = format!("{}:{}", self.config.base_addr, self.config.wallet_port); // Just use the name of the server for a seed for now let seed = format!("{}", self.config.name); - let seed = blake2::blake2b::blake2b(32, &[], seed.as_bytes()); + let _seed = blake2::blake2b::blake2b(32, &[], seed.as_bytes()); - // TODO - just use from_random_seed here? - let keychain = - Keychain::from_seed(seed.as_bytes()).expect("Error initializing keychain from seed"); println!( "Starting the Grin wallet receiving daemon on {} ", self.config.wallet_port ); - let mut wallet_config = WalletConfig::default(); + self.wallet_config = WalletConfig::default(); - wallet_config.api_listen_port = format!("{}", self.config.wallet_port); - wallet_config.check_node_api_http_addr = self.config.wallet_validating_node_url.clone(); - wallet_config.data_file_dir = self.working_dir.clone(); + self.wallet_config.api_listen_port = format!("{}", self.config.wallet_port); + self.wallet_config.check_node_api_http_addr = self.config.wallet_validating_node_url.clone(); + self.wallet_config.data_file_dir = self.working_dir.clone(); - let receive_tx_handler = wallet::WalletReceiver { - config: wallet_config.clone(), - keychain: keychain.clone(), - }; - let router = router!( - receive_tx: get "/receive/transaction" => receive_tx_handler, + let _=fs::create_dir_all(self.wallet_config.clone().data_file_dir); + wallet::WalletSeed::init_file(&self.wallet_config); + + let wallet_seed = + wallet::WalletSeed::from_file(&self.wallet_config).expect("Failed to read wallet seed file."); + + let keychain = wallet_seed.derive_keychain("grin_test").expect( + "Failed to derive keychain from seed file and passphrase.", ); - let mut api_server = api::ApiServer::new("/v1".to_string()); - api_server.register_handler(router); - api_server.start(url).unwrap_or_else(|e| { - println!("Failed to start Grin wallet receiver: {}.", e); - }); - - self.api_server = Some(api_server); + wallet::server::start_rest_apis(self.wallet_config.clone(), keychain); self.wallet_is_running = true; } - /// Stops the running wallet server + pub fn send_amount_to(config: &WalletConfig, + amount:&str, + minimum_confirmations: u64, + selection_strategy:&str, + dest: &str){ + + let amount = core::core::amount_from_hr_string(amount).expect( + "Could not parse amount as a number with optional decimal point.", + ); + + let wallet_seed = + wallet::WalletSeed::from_file(config).expect("Failed to read wallet seed file."); + + let mut keychain = wallet_seed.derive_keychain("grin_test").expect( + "Failed to derive keychain from seed file and passphrase.", + ); + let max_outputs = 500; + let result = wallet::issue_send_tx( + config, + &mut keychain, + amount, + minimum_confirmations, + dest.to_string(), + max_outputs, + (selection_strategy == "all"), + ); + match result { + Ok(_) => { + println!( + "Tx sent: {} grin to {} (strategy '{}')", + core::core::amount_to_hr_string(amount), + dest, + selection_strategy, + ) + } + Err(wallet::Error::NotEnoughFunds(available)) => { + println!( + "Tx not sent: insufficient funds (max: {})", + core::core::amount_to_hr_string(available), + ); + } + Err(e) => { + println!("Tx not sent to {}: {:?}", dest, e); + } + }; + } + + /// Stops the running wallet server pub fn stop_wallet(&mut self) { - let mut api_server = self.api_server.as_mut().unwrap(); + println!("Stop wallet!"); + let api_server = self.api_server.as_mut().unwrap(); api_server.stop(); } @@ -497,8 +545,8 @@ impl LocalServerContainerPool { } pub fn connect_all_peers(&mut self) { - /// just pull out all currently active servers, build a list, - /// and feed into all servers + // just pull out all currently active servers, build a list, + // and feed into all servers let mut server_addresses: Vec = Vec::new(); for s in &self.server_containers { let server_address = format!("{}:{}", s.config.base_addr, s.config.p2p_server_port); diff --git a/grin/tests/simulnet.rs b/grin/tests/simulnet.rs index 662759fdd..87c93def6 100644 --- a/grin/tests/simulnet.rs +++ b/grin/tests/simulnet.rs @@ -12,9 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[macro_use] -extern crate router; - extern crate grin_api as api; extern crate grin_chain as chain; extern crate grin_core as core; @@ -37,20 +34,18 @@ use std::default::Default; use futures::{Async, Future, Poll}; use futures::task::current; use tokio_core::reactor; -use tokio_timer::Timer; -use core::consensus; use core::global; use core::global::ChainTypes; -use wallet::WalletConfig; -use framework::{LocalServerContainer, LocalServerContainerConfig, LocalServerContainerPool, +use framework::{LocalServerContainerConfig, LocalServerContainerPool, LocalServerContainerPoolConfig}; /// Testing the frameworks by starting a fresh server, creating a genesis /// Block and mining into a wallet for a bit #[test] fn basic_genesis_mine() { + util::init_test_logger(); global::set_mining_mode(ChainTypes::AutomatedTesting); let test_name_dir = "genesis_mine"; @@ -70,7 +65,8 @@ fn basic_genesis_mine() { // Create a server to add into the pool let mut server_config = LocalServerContainerConfig::default(); server_config.start_miner = true; - server_config.start_wallet = true; + server_config.start_wallet = false; + server_config.burn_mining_rewards = true; pool.create_server(&mut server_config); pool.run_all_servers(); @@ -100,7 +96,8 @@ fn simulate_seeding() { // Create a first seed server to add into the pool let mut server_config = LocalServerContainerConfig::default(); // server_config.start_miner = true; - server_config.start_wallet = true; + server_config.start_wallet = false; + server_config.burn_mining_rewards = true; server_config.is_seeding = true; pool.create_server(&mut server_config); @@ -279,7 +276,7 @@ fn simulate_full_sync() { // instantiates 2 servers on different ports let mut servers = vec![]; for n in 0..2 { - let mut config = grin::ServerConfig { + let config = grin::ServerConfig { api_http_addr: format!("127.0.0.1:{}", 19000 + n), db_root: format!("target/{}/grin-sync-{}", test_name_dir, n), p2p_config: Some(p2p::P2PConfig { diff --git a/grin/tests/wallet.rs b/grin/tests/wallet.rs new file mode 100644 index 000000000..095ae247f --- /dev/null +++ b/grin/tests/wallet.rs @@ -0,0 +1,113 @@ +// Copyright 2017 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[macro_use] +extern crate router; +#[macro_use] +extern crate slog; + +extern crate grin_api as api; +extern crate grin_chain as chain; +extern crate grin_core as core; +extern crate grin_grin as grin; +extern crate grin_p2p as p2p; +extern crate grin_pow as pow; +extern crate grin_util as util; +extern crate grin_wallet as wallet; +extern crate grin_config as config; + +mod framework; + +use std::{thread, time}; +use std::sync::{Arc, Mutex}; +use framework::{LocalServerContainer, + LocalServerContainerConfig, + LocalServerContainerPoolConfig}; + +use util::{init_logger, LOGGER}; + +/// Start 1 node mining and two wallets, then send a few +/// transactions from one to the other +#[test] +fn basic_wallet_transactions() { + let test_name_dir = "test_servers"; + core::global::set_mining_mode(core::global::ChainTypes::AutomatedTesting); + framework::clean_all_output(test_name_dir); + let mut log_config = util::LoggingConfig::default(); + //log_config.stdout_log_level = util::LogLevel::Trace; + log_config.stdout_log_level = util::LogLevel::Info; + //init_logger(Some(log_config)); + util::init_test_logger(); + + // Run a separate coinbase wallet for coinbase transactions + let mut coinbase_config = LocalServerContainerConfig::default(); + coinbase_config.name = String::from("coinbase_wallet"); + coinbase_config.wallet_validating_node_url=String::from("http://127.0.0.1:30001"); + coinbase_config.wallet_port = 10002; + let coinbase_wallet = Arc::new(Mutex::new(LocalServerContainer::new(coinbase_config).unwrap())); + let coinbase_wallet_config = { + coinbase_wallet.lock().unwrap().wallet_config.clone() + }; + + let _ = thread::spawn(move || { + let mut w = coinbase_wallet.lock().unwrap(); + w.run_wallet(0); + }); + + let mut recp_config = LocalServerContainerConfig::default(); + recp_config.name = String::from("target_wallet"); + recp_config.wallet_validating_node_url=String::from("http://127.0.0.1:30001"); + recp_config.wallet_port = 20002; + let target_wallet = Arc::new(Mutex::new(LocalServerContainer::new(recp_config).unwrap())); + let target_wallet_cloned = target_wallet.clone(); + let recp_wallet_config = { + target_wallet.lock().unwrap().wallet_config.clone() + }; + + //Start up a second wallet, to receive + let _ = thread::spawn(move || { + let mut w = target_wallet_cloned.lock().unwrap(); + w.run_wallet(0); + }); + + // Spawn server and let it run for a bit + let _ = thread::spawn(move || { + let mut server_config = LocalServerContainerConfig::default(); + server_config.name = String::from("server_one"); + server_config.p2p_server_port = 30000; + server_config.api_server_port = 30001; + server_config.start_miner = true; + server_config.start_wallet = false; + server_config.coinbase_wallet_address = String::from(format!( + "http://{}:{}", + server_config.base_addr, + 10002 + )); + let mut server_one = LocalServerContainer::new(server_config).unwrap(); + server_one.run_server(120); + }); + + //Wait for chain to build + thread::sleep(time::Duration::from_millis(5000)); + 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"); + + //let some more mining happen, make sure nothing pukes + thread::sleep(time::Duration::from_millis(5000)); + + //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)); +} + diff --git a/keychain/src/keychain.rs b/keychain/src/keychain.rs index 678e6f931..007248fa5 100644 --- a/keychain/src/keychain.rs +++ b/keychain/src/keychain.rs @@ -18,14 +18,15 @@ use std::sync::{Arc, RwLock}; use util::secp; use util::secp::{Message, Secp256k1, Signature}; -use util::secp::key::SecretKey; +use util::secp::key::{SecretKey, PublicKey}; use util::secp::pedersen::{Commitment, ProofMessage, ProofInfo, RangeProof}; +use util::secp::aggsig; use util::logger::LOGGER; +use util::kernel_sig_msg; use blake2; use blind::{BlindSum, BlindingFactor}; use extkey::{self, Identifier}; - #[derive(PartialEq, Eq, Clone, Debug)] pub enum Error { ExtendedKey(extkey::Error), @@ -45,10 +46,23 @@ impl From for Error { } } +/// Holds internal information about an aggsig operation +#[derive(Clone, Debug)] +pub struct AggSigTxContext { + // Secret key (of which public is shared) + pub sec_key: SecretKey, + // Secret nonce (of which public is shared) + // (basically a SecretKey) + pub sec_nonce: SecretKey, + // If I'm the recipient, store my outputs between invocations (that I need to sum) + pub output_ids: Vec, +} + #[derive(Clone, Debug)] pub struct Keychain { secp: Secp256k1, extkey: extkey::ExtendedKey, + pub aggsig_context: Arc>>, key_overrides: HashMap, key_derivation_cache: Arc>>, } @@ -77,6 +91,7 @@ impl Keychain { let keychain = Keychain { secp: secp, extkey: extkey, + aggsig_context: Arc::new(RwLock::new(None)), key_overrides: HashMap::new(), key_derivation_cache: Arc::new(RwLock::new(HashMap::new())), }; @@ -232,6 +247,138 @@ 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{ + sec_key: sec_key, + sec_nonce: aggsig::export_secnonce_single(&self.secp).unwrap(), + output_ids: vec![], + }); + } + + /// 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()); + } + + /// 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() + } + + /// 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()) + } + + /// 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()) + } + + /// 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)?; + 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 { + 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 { + 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(); + 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(); + + 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{ + // Add public nonces kR*G + kS*G + let (_, sec_nonce) = self.aggsig_get_private_keys(); + 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)) + } + + /// Helper function to calculate final singature + pub fn aggsig_calculate_final_sig(&self, 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 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)?; + Ok(sig) + } + + /// Helper function to calculate final public key + pub fn aggsig_calculate_final_pubkey(&self, their_public_key: &PublicKey) -> Result { + let (our_sec_key, _) = self.aggsig_get_private_keys(); + let mut pk_sum = their_public_key.clone(); + let _ = pk_sum.add_exp_assign(&self.secp, &our_sec_key); + Ok(pk_sum) + } + + /// Just a simple sig, creates its own nonce, etc + pub fn aggsig_sign_from_key_id(&self, msg: &Message, key_id: &Identifier) -> Result { + let skey = self.derived_key(key_id)?; + let sig = aggsig::sign_single(&self.secp, &msg, &skey, None, None, None)?; + Ok(sig) + } + + /// Verifies a sig given a commitment + pub fn aggsig_verify_single_from_commit(secp:&Secp256k1, sig: &Signature, msg: &Message, 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 + } + + /// Just a simple sig, creates its own nonce, etc + pub fn aggsig_sign_with_blinding(secp:&Secp256k1, msg: &Message, blinding:&BlindingFactor) -> Result { + let sig = aggsig::sign_single(secp, &msg, &blinding.secret_key(), None, None, None)?; + Ok(sig) + } + pub fn sign(&self, msg: &Message, key_id: &Identifier) -> Result { let skey = self.derived_key(key_id)?; let sig = self.secp.sign(msg, &skey)?; diff --git a/keychain/src/lib.rs b/keychain/src/lib.rs index 9e19d2a4c..989c941bd 100644 --- a/keychain/src/lib.rs +++ b/keychain/src/lib.rs @@ -31,4 +31,4 @@ mod extkey; pub use blind::{BlindSum, BlindingFactor}; pub use extkey::{ExtendedKey, Identifier, IDENTIFIER_SIZE}; pub mod keychain; -pub use keychain::{Error, Keychain}; +pub use keychain::{Error, Keychain, AggSigTxContext}; diff --git a/pool/src/pool.rs b/pool/src/pool.rs index 059724177..08d6edab7 100644 --- a/pool/src/pool.rs +++ b/pool/src/pool.rs @@ -737,7 +737,7 @@ mod tests { // a valid transaction. let valid_transaction = test_transaction(vec![5, 6], vec![9]); - match write_pool.add_to_memory_pool(test_source(), valid_transaction) { + match write_pool.add_to_memory_pool(test_source(), valid_transaction.clone()) { Ok(_) => {} Err(x) => panic!("Unexpected error while adding a valid transaction: {:?}", x), }; @@ -764,9 +764,15 @@ mod tests { } }; - let already_in_pool = test_transaction(vec![5, 6], vec![9]); + // Note, this used to work as expected, but after aggsig implementation + // creating another transaction with the same inputs/outputs doesn't create + // the same hash ID due to the random nonces in an aggsig. This + // will instead throw a (correct as well) Already spent error. An AlreadyInPool + // error can only come up in the case of the exact same transaction being + // added + //let already_in_pool = test_transaction(vec![5, 6], vec![9]); - match write_pool.add_to_memory_pool(test_source(), already_in_pool) { + match write_pool.add_to_memory_pool(test_source(), valid_transaction) { Ok(_) => panic!("Expected error when adding already in pool, got Ok"), Err(x) => { match x { diff --git a/src/bin/grin.rs b/src/bin/grin.rs index 8fd8c783d..df67be01e 100644 --- a/src/bin/grin.rs +++ b/src/bin/grin.rs @@ -33,8 +33,6 @@ extern crate grin_wallet as wallet; mod client; use std::thread; -use std::io::Read; -use std::fs::File; use std::time::Duration; use std::env::current_dir; @@ -211,10 +209,8 @@ fn main() { .takes_value(true))) .subcommand(SubCommand::with_name("send") - .about("Builds a transaction to send someone some coins. By default, \ - the transaction will just be printed to stdout. If a destination is \ - provided, the command will attempt to contact the receiver at that \ - address and send the transaction directly.") + .about("Builds a transaction to send coins and sends it to the specified \ + listener directly.") .arg(Arg::with_name("amount") .help("Number of coins to send with optional fraction, e.g. 12.423") .index(1)) @@ -417,7 +413,7 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) { let passphrase = wallet_args.value_of("pass").expect( "Failed to read passphrase.", ); - let keychain = wallet_seed.derive_keychain(&passphrase).expect( + let mut keychain = wallet_seed.derive_keychain(&passphrase).expect( "Failed to derive keychain from seed file and passphrase.", ); @@ -428,7 +424,9 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) { } wallet::server::start_rest_apis(wallet_config, keychain); } - ("receive", Some(receive_args)) => { + // The following is gone for now, as a result of aggsig transactions + // being implemented + /*("receive", Some(receive_args)) => { let input = receive_args.value_of("input").expect("Input file required"); let mut file = File::open(input).expect("Unable to open transaction file."); let mut contents = String::new(); @@ -444,7 +442,7 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) { println!(" * your node isn't running or can't be reached"); println!("\nDetailed error: {:?}", e); } - } + }*/ ("send", Some(send_args)) => { let amount = send_args.value_of("amount").expect( "Amount to send required", @@ -461,14 +459,13 @@ fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) { let selection_strategy = send_args.value_of("selection_strategy").expect( "Selection strategy required", ); - let mut dest = "stdout"; - if let Some(d) = send_args.value_of("dest") { - dest = d; - } + let dest = send_args.value_of("dest").expect( + "Destination wallet address required", + ); let max_outputs = 500; let result = wallet::issue_send_tx( &wallet_config, - &keychain, + &mut keychain, amount, minimum_confirmations, dest.to_string(), diff --git a/util/Cargo.toml b/util/Cargo.toml index f8409b4bb..8d6ef53da 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -10,8 +10,10 @@ slog = { version = "^2.0.12", features = ["max_level_trace", "release_max_level_ slog-term = "^2.2.0" slog-async = "^2.1.0" lazy_static = "~0.2.8" +byteorder = "^0.5" rand = "0.3" serde = "~1.0.8" serde_derive = "~1.0.8" -secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp", tag="grin_integration_2" } +secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp", tag="grin_integration_6" } +#secp256k1zkp = { path = "../../rust-secp256k1-zkp" } diff --git a/util/src/lib.rs b/util/src/lib.rs index 65180c414..30b921b69 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -25,7 +25,7 @@ extern crate slog; extern crate slog_async; extern crate slog_term; - +extern crate byteorder; extern crate rand; #[macro_use] @@ -48,12 +48,13 @@ pub mod secp_static; pub use secp_static::static_secp_instance; pub mod types; -pub use types::LoggingConfig; +pub use types::{LoggingConfig, LogLevel}; // other utils use std::cell::{Ref, RefCell}; #[allow(unused_imports)] use std::ops::Deref; +use byteorder::{BigEndian, ByteOrder}; mod hex; pub use hex::*; @@ -98,3 +99,11 @@ impl OneTime { Ref::map(self.inner.borrow(), |o| o.as_ref().unwrap()) } } + +/// Construct msg bytes from tx fee and lock_height +pub fn kernel_sig_msg(fee: u64, lock_height: u64) -> [u8; 32] { + let mut bytes = [0; 32]; + BigEndian::write_u64(&mut bytes[16..24], fee); + BigEndian::write_u64(&mut bytes[24..], lock_height); + bytes +} diff --git a/wallet/src/client.rs b/wallet/src/client.rs index 4976a1f40..b60e23257 100644 --- a/wallet/src/client.rs +++ b/wallet/src/client.rs @@ -56,11 +56,11 @@ where Ok(res) } -pub fn send_partial_tx(url: &str, partial_tx: &PartialTx) -> Result<(), Error> { +pub fn send_partial_tx(url: &str, partial_tx: &PartialTx) -> Result { single_send_partial_tx(url, partial_tx) } -fn single_send_partial_tx(url: &str, partial_tx: &PartialTx) -> Result<(), Error> { +fn single_send_partial_tx(url: &str, partial_tx: &PartialTx) -> Result { let mut core = reactor::Core::new()?; let client = hyper::Client::new(&core.handle()); @@ -69,21 +69,15 @@ fn single_send_partial_tx(url: &str, partial_tx: &PartialTx) -> Result<(), Error let json = serde_json::to_string(&partial_tx)?; req.set_body(json); - let work = client.request(req); - let _ = core.run(work).and_then(|res| { - if res.status() == hyper::StatusCode::Ok { - info!(LOGGER, "Transaction sent successfully"); - } else { - error!( - LOGGER, - "Error sending transaction - status: {}", - res.status() - ); - return Err(hyper::Error::Status); - } - Ok(()) - })?; - Ok(()) + let work = client.request(req).and_then(|res| { + res.body().concat2().and_then(move |body| { + let partial_tx: PartialTx = + serde_json::from_slice(&body).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + Ok(partial_tx) + }) + }); + let res = core.run(work)?; + Ok(res) } /// Makes a single request to the wallet API to create a new coinbase output. diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 3ae486d2f..6054c77eb 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -54,7 +54,7 @@ pub mod server; pub use outputs::show_outputs; pub use info::show_info; -pub use receiver::{receive_json_tx, receive_json_tx_str, WalletReceiver}; +pub use receiver::{WalletReceiver}; pub use sender::{issue_burn_tx, issue_send_tx}; pub use types::{BlockFees, CbData, Error, WalletConfig, WalletReceiveRequest, WalletSeed}; pub use restore::restore; diff --git a/wallet/src/receiver.rs b/wallet/src/receiver.rs index 76e64b542..18a5d5c0b 100644 --- a/wallet/src/receiver.rs +++ b/wallet/src/receiver.rs @@ -26,10 +26,9 @@ use api; use core::consensus::reward; use core::core::{build, Block, Output, Transaction, TxKernel}; use core::ser; -use keychain::{BlindingFactor, Identifier, Keychain}; +use keychain::{Identifier, Keychain}; use types::*; -use util; -use util::LOGGER; +use util::{LOGGER, to_hex, secp}; /// Dummy wrapper for the hex-encoded serialized transaction. #[derive(Serialize, Deserialize)] @@ -37,41 +36,136 @@ pub struct TxWrapper { pub tx_hex: String, } -pub fn receive_json_tx_str( +/// 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 + +fn handle_sender_initiation( config: &WalletConfig, keychain: &Keychain, - json_tx: &str, -) -> Result<(), Error> { - let partial_tx = serde_json::from_str(json_tx).unwrap(); - receive_json_tx(config, keychain, &partial_tx) + partial_tx: &PartialTx +) -> Result { + let (amount, _sender_pub_blinding, sender_pub_nonce, _sig, tx) = read_partial_tx(keychain, partial_tx)?; + + 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, None); + if fee != tx.fee { + return Err(Error::FeeDispute { + sender_fee: tx.fee, + recipient_fee: fee, + }); + } + + let out_amount = amount - 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 + + let key_id = WalletData::with_wallet(&config.data_file_dir, |wallet_data| { + let (key_id, derivation) = next_available_key(&wallet_data, keychain); + + wallet_data.add_output(OutputData { + root_key_id: root_key_id.clone(), + key_id: key_id.clone(), + n_child: derivation, + value: out_amount, + status: OutputStatus::Unconfirmed, + height: 0, + lock_height: 0, + is_coinbase: false, + }); + + key_id + })?; + + // Still handy for getting the blinding sum + let (_, blind_sum) = build::transaction( + vec![ + build::output(out_amount, key_id.clone()), + ], + keychain, + )?; + + 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 sig_part=keychain.aggsig_calculate_partial_sig(&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); + partial_tx.phase = PartialTxPhase::ReceiverInitiation; + + Ok(partial_tx) } -/// Receive an already well formed JSON transaction issuance and finalize the -/// transaction, adding our receiving output, to broadcast to the rest of the -/// network. -pub fn receive_json_tx( +/// 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) +/// -Receiver sends completed TX to mempool. responds OK to sender + +fn handle_sender_confirmation( config: &WalletConfig, keychain: &Keychain, - partial_tx: &PartialTx, -) -> Result<(), Error> { + partial_tx: &PartialTx +) -> 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); - // reading the partial transaction and finalizing it, adding our output - let (amount, blinding, tx) = read_partial_tx(keychain, partial_tx)?; - let (final_tx, key_id) = receive_transaction(config, keychain, amount, blinding, tx)?; - let tx_hex = util::to_hex(ser::ser_vec(&final_tx).unwrap()); - - // pushing to the transaction pool, in case of error the output that was - // reserved needs to be removed - let url = format!("{}/v1/pool/push", config.check_node_api_http_addr.as_str()); - let res = api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex }); - if let Err(e) = res { - WalletData::with_wallet(&config.data_file_dir, |wallet_data| { - wallet_data.delete_output(&key_id); - })?; - Err(Error::Node(e)) - } else { - Ok(()) + if !res { + error!(LOGGER, "Partial Sig from sender invalid."); + return Err(Error::Signature(String::from("Partial Sig from sender invalid."))); } + + //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(); + + // And the final signature + let final_sig=keychain.aggsig_calculate_final_sig(&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(); + + //Check our final sig verifies + let res = keychain.aggsig_verify_final_sig_build_msg(&final_sig, &final_pubkey, tx.fee, tx.lock_height); + + if !res { + error!(LOGGER, "Final aggregated signature invalid."); + return Err(Error::Signature(String::from("Final aggregated signature invalid."))); + } + + + let final_tx = build_final_transaction(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()); + api::client::post(url.as_str(), &TxWrapper { tx_hex: tx_hex }) + .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); + partial_tx.phase = PartialTxPhase::ReceiverConfirmation; + Ok(partial_tx) } /// Component used to receive coins, implements all the receiving end of the @@ -87,14 +181,34 @@ impl Handler for WalletReceiver { let struct_body = req.get::>(); if let Ok(Some(partial_tx)) = struct_body { - receive_json_tx(&self.config, &self.keychain, &partial_tx) - .map_err(|e| { - error!(LOGGER, "Problematic partial tx, looks like this: {:?}", partial_tx); - api::Error::Internal( - format!("Error processing partial transaction: {:?}", e), - )}) - .unwrap(); - Ok(Response::with(status::Ok)) + match partial_tx.phase { + PartialTxPhase::SenderInitiation => { + let resp_tx=handle_sender_initiation(&self.config, &self.keychain, &partial_tx) + .map_err(|e| { + error!(LOGGER, "Phase 1 Sender Initiation -> Problematic partial tx, looks like this: {:?}", partial_tx); + api::Error::Internal( + format!("Error processing partial transaction: {:?}", e), + )}) + .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, &self.keychain, &partial_tx) + .map_err(|e| { + error!(LOGGER, "Phase 3 Sender Confirmation -> Problematic partial tx, looks like this: {:?}", partial_tx); + api::Error::Internal( + format!("Error processing partial transaction: {:?}", e), + )}) + .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"))) + } + } } else { Ok(Response::with((status::BadRequest, ""))) } @@ -174,33 +288,37 @@ pub fn receive_coinbase( Ok((out, kern, block_fees)) } -/// Builds a full transaction from the partial one sent to us for transfer -fn receive_transaction( +/// builds a final transaction after the aggregated sig exchange +fn build_final_transaction( config: &WalletConfig, keychain: &Keychain, amount: u64, - blinding: BlindingFactor, - partial: Transaction, -) -> Result<(Transaction, Identifier), Error> { + excess_sig: &secp::Signature, + tx: Transaction, +) -> 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(partial.inputs.len(), partial.outputs.len() + 1, None); - if fee != partial.fee { + let fee = tx_fee(tx.inputs.len(), tx.outputs.len() + 1, None); + if fee != tx.fee { return Err(Error::FeeDispute { - sender_fee: partial.fee, + sender_fee: tx.fee, recipient_fee: fee, }); } let out_amount = amount - fee; + // Get output we created in earlier step + // TODO: will just be one for now, support multiple later + let output_vec = keychain.aggsig_get_outputs(); + // operate within a lock on wallet data let (key_id, derivation) = WalletData::with_wallet(&config.data_file_dir, |wallet_data| { - let (key_id, derivation) = next_available_key(&wallet_data, keychain); + let (key_id, derivation) = retrieve_existing_key(&wallet_data, output_vec[0].clone()); wallet_data.add_output(OutputData { root_key_id: root_key_id.clone(), @@ -216,27 +334,29 @@ fn receive_transaction( (key_id, derivation) })?; - let (tx_final, _) = build::transaction( + // 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(partial), - build::with_excess(blinding), + build::initial_tx(tx), build::output(out_amount, key_id.clone()), - // build::with_fee(fee_amount), ], keychain, )?; + final_tx.excess_sig = excess_sig.serialize_der(&keychain.secp()); + // make sure the resulting transaction is valid (could have been lied to on // excess). - tx_final.validate()?; + let _ = final_tx.validate()?; debug!( LOGGER, - "Received txn and built output - {:?}, {:?}, {}", + "Finalized transaction and built output - {:?}, {:?}, {}", root_key_id.clone(), key_id.clone(), derivation, ); - Ok((tx_final, key_id)) + Ok(final_tx) } diff --git a/wallet/src/sender.rs b/wallet/src/sender.rs index eef99577c..7e683fd4c 100644 --- a/wallet/src/sender.rs +++ b/wallet/src/sender.rs @@ -1,4 +1,4 @@ -// Copyright 2016 The Grin Developers +// Copyright 2018 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use serde_json; - use api; use client; use checker; @@ -27,7 +25,7 @@ use util; /// Issue a new transaction to the provided sender by spending some of our /// wallet -/// UTXOs. The destination can be "stdout" (for command line) or a URL to the +/// UTXOs. 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( @@ -57,8 +55,16 @@ pub fn issue_send_tx( max_outputs, selection_strategy, )?; + /* + * -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 partial_tx = build_partial_tx(amount, blind_sum, tx); +// Create a new aggsig context + keychain.aggsig_create_context(blind_sum.secret_key()); + + let partial_tx = build_partial_tx(keychain, amount, None, tx); // Closure to acquire wallet lock and lock the coins being spent // so we avoid accidental double spend attempt. @@ -74,27 +80,56 @@ pub fn issue_send_tx( wallet_data.delete_output(&change_key); }); - if dest == "stdout" { + // 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" { - let url = format!("{}/v1/receive/transaction", &dest); - debug!(LOGGER, "Posting partial transaction to {}", url); - let res = client::send_partial_tx(&url, &partial_tx); - match res { - Err(_) => { - error!(LOGGER, "Communication with receiver failed. Aborting transaction"); - rollback_wallet()?; - return res; - } - Ok(_) => { - update_wallet()?; - } - } - } else { + } else */ + + if &dest[..4] != "http" { panic!("dest formatted as {} but send -d expected stdout or http://IP:port", dest); } + + let url = format!("{}/v1/receive/transaction", &dest); + debug!(LOGGER, "Posting partial transaction to {}", url); + let res = client::send_partial_tx(&url, &partial_tx); + if let Err(e) = res { + error!(LOGGER, "Communication with receiver failed on SenderInitiation send. Aborting transaction"); + rollback_wallet()?; + return Err(e); + } + + /* -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 + * -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); + 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(); + + // 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); + partial_tx.phase = PartialTxPhase::SenderConfirmation; + + // And send again + let res = client::send_partial_tx(&url, &partial_tx); + if let Err(e) = res { + error!(LOGGER, "Communication with receiver failed on SenderConfirmation send. Aborting transaction"); + rollback_wallet()?; + return Err(e); + } + //All good so + update_wallet()?; Ok(()) } @@ -199,15 +234,15 @@ fn inputs_and_change( } // sender is responsible for setting the fee on the partial tx - // recipient should double check the fee calculation and not blindly trust the - // sender + // recipient should double check the fee calculation and not blindly trust the + // sender let fee = tx_fee(coins.len(), 2, None); parts.push(build::with_fee(fee)); // if we are spending 10,000 coins to send 1,000 then our change will be 9,000 - // the fee will come out of the amount itself - // if the fee is 80 then the recipient will only receive 920 - // but our change will still be 9,000 + // the fee will come out of the amount itself + // if the fee is 80 then the recipient will only receive 920 + // but our change will still be 9,000 let change = total - amount; // build inputs using the appropriate derived key_ids diff --git a/wallet/src/types.rs b/wallet/src/types.rs index 67cabef8e..fc6e77dd7 100644 --- a/wallet/src/types.rs +++ b/wallet/src/types.rs @@ -25,7 +25,6 @@ use std::cmp::min; use hyper; use serde_json; -use util::secp; use tokio_core::reactor; use tokio_retry::Retry; use tokio_retry::strategy::FibonacciBackoff; @@ -37,6 +36,9 @@ use core::core::{transaction, Transaction}; use core::ser; use keychain; use util; +use util::secp; +use util::secp::Signature; +use util::secp::key::PublicKey; use util::LOGGER; const DAT_FILE: &'static str = "wallet.dat"; @@ -78,6 +80,8 @@ pub enum Error { Hyper(hyper::Error), /// Error originating from hyper uri parsing. Uri(hyper::error::UriError), + /// Error with signatures during exchange + Signature(String), GenericError(String,) } @@ -601,24 +605,55 @@ impl WalletData { } } -/// Helper in serializing the information a receiver requires to build a -/// transaction. +/// Define the stages of a transaction #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct PartialTx { - amount: u64, - blind_sum: String, - tx: String, +pub enum PartialTxPhase { + SenderInitiation, + ReceiverInitiation, + SenderConfirmation, + ReceiverConfirmation } -/// Builds a PartialTx from data sent by a sender (not yet completed by the receiver). +/// Helper in serializing the information required during an interactive aggsig +/// transaction +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PartialTx { + pub phase: PartialTxPhase, + pub amount: u64, + pub public_blind_excess: String, + pub public_nonce: 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( + keychain: &keychain::Keychain, receive_amount: u64, - blind_sum: keychain::BlindingFactor, + part_sig: Option, tx: Transaction, ) -> PartialTx { + + let (pub_excess, pub_nonce) = keychain.aggsig_get_public_keys(); + 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, amount: receive_amount, - blind_sum: util::to_hex(blind_sum.secret_key().as_ref().to_vec()), + public_blind_excess: util::to_hex(pub_excess), + public_nonce: util::to_hex(pub_nonce), + 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()), } } @@ -628,14 +663,21 @@ pub fn build_partial_tx( pub fn read_partial_tx( keychain: &keychain::Keychain, partial_tx: &PartialTx, -) -> Result<(u64, keychain::BlindingFactor, Transaction), Error> { - let blind_bin = util::from_hex(partial_tx.blind_sum.clone())?; - let blinding = keychain::BlindingFactor::from_slice(keychain.secp(), &blind_bin[..])?; +) -> Result<(u64, PublicKey, PublicKey, Option, Transaction), Error> { + let blind_bin = util::from_hex(partial_tx.public_blind_excess.clone())?; + let blinding = PublicKey::from_slice(keychain.secp(), &blind_bin[..])?; + let nonce_bin = util::from_hex(partial_tx.public_nonce.clone())?; + let nonce = PublicKey::from_slice(keychain.secp(), &nonce_bin[..])?; + let sig_bin = util::from_hex(partial_tx.part_sig.clone())?; + let sig = match sig_bin.len() { + 1 => None, + _ => Some(Signature::from_der(keychain.secp(), &sig_bin[..])?), + }; let tx_bin = util::from_hex(partial_tx.tx.clone())?; let tx = ser::deserialize(&mut &tx_bin[..]).map_err(|_| { Error::Format("Could not deserialize transaction, invalid format.".to_string()) })?; - Ok((partial_tx.amount, blinding, tx)) + Ok((partial_tx.amount, blinding, nonce, sig, tx)) } /// Amount in request to build a coinbase output.