diff --git a/src/bin/cmd/wallet.rs b/src/bin/cmd/wallet.rs index dd96441f3..ac8c1f082 100644 --- a/src/bin/cmd/wallet.rs +++ b/src/bin/cmd/wallet.rs @@ -160,48 +160,85 @@ pub fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) { let dest = send_args .value_of("dest") .expect("Destination wallet address required"); - let mut fluff = false; - if send_args.is_present("fluff") { - fluff = true; - } + let fluff = send_args.is_present("fluff"); let max_outputs = 500; - let result = api.issue_send_tx( - amount, - minimum_confirmations, - dest, - max_outputs, - selection_strategy == "all", - ); - let slate = match result { - Ok(s) => { - info!( - LOGGER, - "Tx created: {} grin to {} (strategy '{}')", - core::amount_to_hr_string(amount), - dest, - selection_strategy, - ); - s + if dest.starts_with("http") { + let result = api.issue_send_tx( + amount, + minimum_confirmations, + dest, + max_outputs, + selection_strategy == "all", + ); + let slate = match result { + Ok(s) => { + info!( + LOGGER, + "Tx created: {} grin to {} (strategy '{}')", + core::amount_to_hr_string(amount), + dest, + selection_strategy, + ); + s + } + Err(e) => { + error!(LOGGER, "Tx not created: {:?}", e); + match e.kind() { + // user errors, don't backtrace + libwallet::ErrorKind::NotEnoughFunds { .. } => {} + libwallet::ErrorKind::FeeDispute { .. } => {} + libwallet::ErrorKind::FeeExceedsAmount { .. } => {} + _ => { + // otherwise give full dump + error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap()); + } + }; + panic!(); + } + }; + let result = api.post_tx(&slate, fluff); + match result { + Ok(_) => { + info!(LOGGER, "Tx sent",); + Ok(()) + } + Err(e) => { + error!(LOGGER, "Tx not sent: {:?}", e); + Err(e) + } } - Err(e) => { - error!(LOGGER, "Tx not created: {:?}", e); - match e.kind() { - // user errors, don't backtrace - libwallet::ErrorKind::NotEnoughFunds { .. } => {} - libwallet::ErrorKind::FeeDispute { .. } => {} - libwallet::ErrorKind::FeeExceedsAmount { .. } => {} - _ => { - // otherwise give full dump - error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap()); - } - }; - panic!(); - } - }; + } else { + api.file_send_tx( + amount, + minimum_confirmations, + dest, + max_outputs, + selection_strategy == "all", + ).expect("Send failed"); + Ok(()) + } + } + ("receive", Some(send_args)) => { + let tx_file = send_args + .value_of("input") + .expect("Transaction file required"); + api.file_receive_tx(tx_file).expect("Receive failed"); + Ok(()) + } + ("finalize", Some(send_args)) => { + let fluff = send_args.is_present("fluff"); + let tx_file = send_args + .value_of("input") + .expect("Receiver's transaction file required"); + let priv_file = send_args + .value_of("private") + .expect("Private transaction file required"); + let slate = api.file_finalize_tx(priv_file, tx_file).expect("Finalize failed"); + let result = api.post_tx(&slate, fluff); match result { Ok(_) => { - info!(LOGGER, "Tx sent",); + info!(LOGGER, "Tx sent"); Ok(()) } Err(e) => { diff --git a/src/bin/grin.rs b/src/bin/grin.rs index f16aca155..f026a6e3e 100644 --- a/src/bin/grin.rs +++ b/src/bin/grin.rs @@ -179,14 +179,6 @@ fn main() { .subcommand(SubCommand::with_name("owner_api") .about("Runs the wallet's local web API.")) - .subcommand(SubCommand::with_name("receive") - .about("Processes a JSON transaction file.") - .arg(Arg::with_name("input") - .help("Partial transaction to process, expects a JSON file.") - .short("i") - .long("input") - .takes_value(true))) - .subcommand(SubCommand::with_name("send") .about("Builds a transaction to send coins and sends it to the specified \ listener directly.") @@ -216,6 +208,31 @@ fn main() { .short("f") .long("fluff"))) + .subcommand(SubCommand::with_name("receive") + .about("Processes a transaction file to accept a transfer from a sender.") + .arg(Arg::with_name("input") + .help("Partial transaction to process, expects the sender's transaction file.") + .short("i") + .long("input") + .takes_value(true))) + + .subcommand(SubCommand::with_name("finalize") + .about("Processes a receiver's transaction file to finalize a transfer.") + .arg(Arg::with_name("input") + .help("Partial transaction to process, expects the receiver's transaction file.") + .short("i") + .long("input") + .takes_value(true)) + .arg(Arg::with_name("private") + .help("Private transaction file previously generated by send.") + .short("p") + .long("private") + .takes_value(true)) + .arg(Arg::with_name("fluff") + .help("Fluff the transaction (ignore Dandelion relay protocol)") + .short("f") + .long("fluff"))) + .subcommand(SubCommand::with_name("burn") .about("** TESTING ONLY ** Burns the provided amount to a known \ key. Similar to send but burns an output to allow single-party \ diff --git a/wallet/src/libtx/slate.rs b/wallet/src/libtx/slate.rs index 10280345f..ff49b6c7f 100644 --- a/wallet/src/libtx/slate.rs +++ b/wallet/src/libtx/slate.rs @@ -14,6 +14,7 @@ //! Functions for building partial transactions to be passed //! around during an interactive wallet exchange + use rand::thread_rng; use uuid::Uuid; diff --git a/wallet/src/libwallet/api.rs b/wallet/src/libwallet/api.rs index b714b5c3b..9248557d2 100644 --- a/wallet/src/libwallet/api.rs +++ b/wallet/src/libwallet/api.rs @@ -17,13 +17,17 @@ //! vs. functions to interact with someone else) //! Still experimental, not sure this is the best way to do this +use std::fs::File; +use std::io::{Read, Write}; use std::marker::PhantomData; use std::sync::{Arc, Mutex}; +use serde_json as json; + use core::ser; use keychain::Keychain; use libtx::slate::Slate; -use libwallet::internal::{tx, updater}; +use libwallet::internal::{tx, updater, selection, sigcontext}; use libwallet::types::{ BlockFees, CbData, OutputData, TxLogEntry, TxWrapper, WalletBackend, WalletClient, WalletInfo, }; @@ -173,6 +177,109 @@ where Ok(slate_out) } + /// Write a transaction to send to file so a user can transmit it to the + /// receiver in whichever way they see fit (aka carrier pigeon mode). + pub fn file_send_tx( + &mut self, + amount: u64, + minimum_confirmations: u64, + dest: &str, + max_outputs: usize, + selection_strategy_is_use_all: bool, + ) -> Result<(), Error> { + let mut w = self.wallet.lock().unwrap(); + w.open_with_credentials()?; + + let (slate, context, lock_fn) = tx::create_send_tx( + &mut **w, + amount, + minimum_confirmations, + max_outputs, + selection_strategy_is_use_all, + )?; + + let mut pub_tx = File::create(dest)?; + pub_tx.write_all(json::to_string(&slate).unwrap().as_bytes())?; + pub_tx.sync_all()?; + let mut priv_tx = File::create(dest.to_owned() + ".private")?; + priv_tx.write_all(json::to_string(&context).unwrap().as_bytes())?; + priv_tx.sync_all()?; + + // lock our inputs + lock_fn(&mut **w)?; + w.close()?; + Ok(()) + } + + /// A sender provided a transaction file with appropriate public keys and + /// metadata. Complete the receivers' end of it to generate another file + /// to send back. + pub fn file_receive_tx( + &mut self, + source: &str, + ) -> Result<(), Error> { + + let mut pub_tx_f = File::open(source)?; + let mut content = String::new(); + pub_tx_f.read_to_string(&mut content)?; + let mut slate: Slate = json::from_str(&content).map_err(|_| ErrorKind::Format)?; + + let mut wallet = self.wallet.lock().unwrap(); + wallet.open_with_credentials()?; + + // create an output using the amount in the slate + let (_, mut context, receiver_create_fn) = + selection::build_recipient_output_with_slate(&mut **wallet, &mut slate)?; + + // fill public keys + let _ = slate.fill_round_1( + wallet.keychain(), + &mut context.sec_key, + &context.sec_nonce, + 1, + )?; + + // perform partial sig + let _ = slate.fill_round_2(wallet.keychain(), &context.sec_key, &context.sec_nonce, 1)?; + + // save to file + let mut pub_tx = File::create(source.to_owned() + ".response")?; + pub_tx.write_all(json::to_string(&slate).unwrap().as_bytes())?; + + // Save output in wallet + let _ = receiver_create_fn(&mut wallet); + Ok(()) + } + + /// Sender finalization of the transaction. Takes the file returned by the + /// sender as well as the private file generate on the first send step. + /// Builds the complete transaction and sends it to a grin node for + /// propagation. + pub fn file_finalize_tx( + &mut self, + private_tx_file: &str, + receiver_file: &str, + ) -> Result { + + let mut pub_tx_f = File::open(receiver_file)?; + let mut content = String::new(); + pub_tx_f.read_to_string(&mut content)?; + let mut slate: Slate = json::from_str(&content).map_err(|_| ErrorKind::Format)?; + + let mut priv_tx_f = File::open(private_tx_file)?; + let mut content = String::new(); + priv_tx_f.read_to_string(&mut content)?; + let context: sigcontext::Context = json::from_str(&content).map_err(|_| ErrorKind::Format)?; + + let mut w = self.wallet.lock().unwrap(); + w.open_with_credentials()?; + + tx::complete_tx(&mut **w, &mut slate, &context)?; + + w.close()?; + Ok(slate) + } + /// Roll back a transaction and all associated outputs with a given /// transaction id This means delete all change outputs, (or recipient /// output if you're recipient), and unlock all locked outputs associated diff --git a/wallet/src/libwallet/error.rs b/wallet/src/libwallet/error.rs index 3c9efc205..877813b70 100644 --- a/wallet/src/libwallet/error.rs +++ b/wallet/src/libwallet/error.rs @@ -13,12 +13,15 @@ // limitations under the License. //! Error types for libwallet -use keychain; -use libtx; + use std::fmt::{self, Display}; +use std::io; + +use failure::{Backtrace, Context, Fail}; use core::core::transaction; -use failure::{Backtrace, Context, Fail}; +use keychain; +use libtx; /// Error definition #[derive(Debug, Fail)] @@ -184,6 +187,14 @@ impl From> for Error { } } +impl From for Error { + fn from(_error: io::Error) -> Error { + Error { + inner: Context::new(ErrorKind::IO), + } + } +} + impl From for Error { fn from(error: keychain::Error) -> Error { Error { diff --git a/wallet/src/libwallet/internal/sigcontext.rs b/wallet/src/libwallet/internal/sigcontext.rs index a294b09d7..2ee07f04d 100644 --- a/wallet/src/libwallet/internal/sigcontext.rs +++ b/wallet/src/libwallet/internal/sigcontext.rs @@ -17,7 +17,7 @@ use libtx::aggsig; use util::secp::key::{PublicKey, SecretKey}; use util::secp::{self, Secp256k1}; -#[derive(Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] /// Holds the context for a single aggsig transaction pub struct Context { /// Secret key (of which public is shared)