diff --git a/src/bin/grin.rs b/src/bin/grin.rs index 360edf4f6..330f9a143 100644 --- a/src/bin/grin.rs +++ b/src/bin/grin.rs @@ -96,7 +96,12 @@ fn main() { .help("Wallet passphrase used to generate the private key seed") .takes_value(true)) .subcommand(SubCommand::with_name("receive") - .about("Run the wallet in receiving mode")) + .about("Run the wallet in receiving mode. If an input file is provided, will process it, otherwise runs in server mode waiting for send requests.") + .arg(Arg::with_name("input") + .help("Partial transaction to receive, expects as a JSON file.") + .short("i") + .long("input") + .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.") .arg(Arg::with_name("amount") @@ -195,14 +200,21 @@ fn wallet_command(wallet_args: &ArgMatches) { .expect("Error deriving extended key from seed."); match wallet_args.subcommand() { - ("receive", _) => { - info!("Starting the Grin wallet receiving daemon..."); - let mut apis = api::ApiServer::new("/v1".to_string()); - apis.register_endpoint("/receive_coinbase".to_string(), - wallet::WalletReceiver { key: key }); - apis.start("127.0.0.1:13416").unwrap_or_else(|e| { - error!("Failed to start Grin wallet receiver: {}.", e); - }); + ("receive", Some(receive_args)) => { + if let Some(f) = receive_args.value_of("input") { + let mut file = File::open(f).expect("Unable to open transaction file."); + let mut contents = String::new(); + file.read_to_string(&mut contents).expect("Unable to read transaction file."); + wallet::receive_json_tx(&key, contents.as_str()).unwrap(); + } else { + info!("Starting the Grin wallet receiving daemon..."); + let mut apis = api::ApiServer::new("/v1".to_string()); + apis.register_endpoint("/receive_coinbase".to_string(), + wallet::WalletReceiver { key: key }); + apis.start("127.0.0.1:13416").unwrap_or_else(|e| { + error!("Failed to start Grin wallet receiver: {}.", e); + }); + } } ("send", Some(send_args)) => { let amount = send_args.value_of("amount") diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index c9c56ec9d..59c4110c1 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -36,5 +36,5 @@ mod sender; mod types; pub use extkey::ExtendedKey; -pub use receiver::WalletReceiver; +pub use receiver::{WalletReceiver, receive_json_tx}; pub use sender::issue_send_tx; diff --git a/wallet/src/receiver.rs b/wallet/src/receiver.rs index 0b3a3cea5..2ebf1c9f8 100644 --- a/wallet/src/receiver.rs +++ b/wallet/src/receiver.rs @@ -60,6 +60,17 @@ use extkey::{self, ExtendedKey}; use types::*; use util; +/// 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(ext_key: &ExtendedKey, partial_tx_str: &str) -> Result<(), Error> { + let (amount, blinding, partial_tx) = partial_tx_from_json(partial_tx_str)?; + let final_tx = receive_transaction(ext_key, amount, blinding, partial_tx)?; + // TODO send to a node to broadcast + println!("TX OK!"); + Ok(()) +} + /// Amount in request to build a coinbase output. #[derive(Serialize, Deserialize, Debug, Clone)] pub struct CbAmount { @@ -140,5 +151,37 @@ fn receive_coinbase(ext_key: &ExtendedKey, amount: u64) -> Result<(Output, TxKer info!("Using child {} for a new coinbase output.", coinbase_key.n_child); - Block::reward_output(coinbase_key.key, &secp).map_err(&From::from); + Block::reward_output(coinbase_key.key, &secp).map_err(&From::from) +} + +/// Builds a full transaction from the partial one sent to us for transfer +fn receive_transaction(ext_key: &ExtendedKey, + amount: u64, + blinding: SecretKey, + partial: Transaction) + -> Result { + + let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); + + // derive a new private for the receiving output + let mut wallet_data = WalletData::read_or_create()?; + let next_child = wallet_data.next_child(ext_key.fingerprint); + let out_key = ext_key.derive(&secp, next_child).map_err(|e| Error::Key(e))?; + + let (tx_final, _) = build::transaction(vec![build::initial_tx(partial), + build::with_excess(blinding), + build::output(amount, out_key.key)])?; + + // track the new output and return the finalized transaction to broadcast + wallet_data.append_output(OutputData { + fingerprint: out_key.fingerprint, + n_child: out_key.n_child, + value: amount, + status: OutputStatus::Unconfirmed, + }); + wallet_data.write()?; + + info!("Using child {} for a new coinbase output.", out_key.n_child); + + Ok(tx_final) } diff --git a/wallet/src/sender.rs b/wallet/src/sender.rs index e5e1b3843..6cd77b628 100644 --- a/wallet/src/sender.rs +++ b/wallet/src/sender.rs @@ -21,6 +21,10 @@ use core::core::{Transaction, build}; use extkey::ExtendedKey; use types::*; +/// 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 +/// recipients wallet receiver (to be implemented). pub fn issue_send_tx(ext_key: &ExtendedKey, amount: u64, dest: String) -> Result<(), Error> { checker::refresh_outputs(&WalletConfig::default(), ext_key); @@ -44,7 +48,7 @@ fn build_send_tx(ext_key: &ExtendedKey, amount: u64) -> Result<(Transaction, Sec // second, check from our local wallet data for outputs to spend let mut wallet_data = WalletData::read()?; - let (mut coins, change) = wallet_data.select(ext_key.fingerprint, amount); + let (coins, change) = wallet_data.select(ext_key.fingerprint, amount); if change < 0 { return Err(Error::NotEnoughFunds((-change) as u64)); } diff --git a/wallet/src/types.rs b/wallet/src/types.rs index 45888c585..f5a60b4ff 100644 --- a/wallet/src/types.rs +++ b/wallet/src/types.rs @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::convert::From; use std::fs::File; use std::io::Write; +use std::num; use std::path::Path; -use std::convert::From; -use serde::{Serialize, Deserialize}; use serde_json; -use secp::{self, Secp256k1}; +use secp; use secp::key::SecretKey; use core::core::Transaction; @@ -37,6 +37,8 @@ pub enum Error { Crypto(secp::Error), Key(extkey::Error), WalletData(String), + /// An error in the format of the JSON structures exchanged by the wallet + Format(String), } impl From for Error { @@ -51,6 +53,18 @@ impl From for Error { } } +impl From for Error { + fn from(e: serde_json::Error) -> Error { + Error::Format(e.to_string()) + } +} + +impl From for Error { + fn from(e: num::ParseIntError) -> Error { + Error::Format("Invalid hex".to_string()) + } +} + #[derive(Debug, Clone)] pub struct WalletConfig { pub api_http_addr: String, @@ -191,3 +205,20 @@ pub fn partial_tx_to_json(receive_amount: u64, blind_sum: SecretKey, tx: Transac }; serde_json::to_string_pretty(&partial_tx).unwrap() } + +/// Reads a partial transaction encoded as JSON into the amount, sum of blinding +/// factors and the transaction itself. +pub fn partial_tx_from_json(json_str: &str) -> Result<(u64, SecretKey, Transaction), Error> { + let partial_tx: JSONPartialTx = serde_json::from_str(json_str)?; + + let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); + let blind_bin = util::from_hex(partial_tx.blind_sum)?; + let blinding = SecretKey::from_slice(&secp, &blind_bin[..])?; + let tx_bin = util::from_hex(partial_tx.tx)?; + 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)) +}