From 9fd1d49ddab910ca60e6844ee13a1cfee116ae41 Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Thu, 28 Nov 2019 15:13:52 +0000 Subject: [PATCH] Proof of Payment Command Line (#260) * refactor address generation code into libwallet, bool to flag whether to include proof, add sender address in init_send_tx * rustfmt * require payment proof addr as part of init_tx * rustfmt * store payment proof on sender transaction side * rustfmt * change sig to ed25519 sig * rustfmt * add message creation and signature * rustfmt * add payment proof verification function * rustfmt * validate proof on sender side, store proof * rustfmt * fix json tests * fixes and updates to tests * added API functions for converting and retrieving proof addresses * rustfmt * add payment proof to init_send_tx example * rustfmt * incorrect comment * add commands for requesting payment proofs * rustfmt * wire up payment proofs into command line * rustfmt * add address command * rustfmt * added tor sending from owner api * rustfmt --- api/src/owner.rs | 21 +++++++- api/src/owner_rpc_s.rs | 38 ++++++++++++++ controller/src/command.rs | 53 ++++++++++++++++++- controller/src/controller.rs | 15 ++++-- controller/src/display.rs | 80 ++++++++++++++++++++++++++++- libwallet/src/address.rs | 15 ++++++ libwallet/src/internal/selection.rs | 25 ++++++--- libwallet/src/internal/tx.rs | 12 ++--- libwallet/src/types.rs | 3 ++ src/bin/grin-wallet.yml | 11 ++++ src/cmd/wallet_args.rs | 34 +++++++++--- 11 files changed, 279 insertions(+), 28 deletions(-) diff --git a/api/src/owner.rs b/api/src/owner.rs index 87794f90..3b3fbec3 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -73,6 +73,9 @@ where /// Holds all update and status messages returned by the /// updater process updater_messages: Arc>>, + /// Optional TOR configuration, holding address of sender and + /// data directory + tor_config: Mutex>, } impl Owner @@ -176,9 +179,23 @@ where updater_running, status_tx: Mutex::new(Some(tx)), updater_messages, + tor_config: Mutex::new(None), } } + /// Set the TOR configuration for this instance of the OwnerAPI, used during + /// `init_send_tx` when send args are present and a TOR address is specified + /// + /// # Arguments + /// * `tor_config` - The optional [TorConfig](#) to use + /// # Returns + /// * Nothing + + pub fn set_tor_config(&self, tor_config: Option) { + let mut lock = self.tor_config.lock(); + *lock = tor_config; + } + /// Returns a list of accounts stored in the wallet (i.e. mappings between /// user-specified labels and BIP32 derivation paths. /// # Arguments @@ -636,8 +653,8 @@ where .into()); } }; - //TODO: no TOR just now via this method, to keep compatibility for now - let comm_adapter = create_sender(&sa.method, &sa.dest, None) + let tor_config_lock = self.tor_config.lock(); + let comm_adapter = create_sender(&sa.method, &sa.dest, tor_config_lock.clone()) .map_err(|e| ErrorKind::GenericError(format!("{}", e)))?; slate = comm_adapter.send_tx(&slate)?; self.tx_lock_outputs(keychain_mask, &slate, 0)?; diff --git a/api/src/owner_rpc_s.rs b/api/src/owner_rpc_s.rs index f127a8dd..a96f523d 100644 --- a/api/src/owner_rpc_s.rs +++ b/api/src/owner_rpc_s.rs @@ -1849,6 +1849,39 @@ pub trait OwnerRpcS { */ fn proof_address_from_onion_v3(&self, address_v3: String) -> Result; + + /** + Networked version of [Owner::set_tor_config](struct.Owner.html#method.set_tor_config). + ``` + # grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!( + # r#" + { + "jsonrpc": "2.0", + "method": "set_tor_config", + "params": { + "tor_config": { + "use_tor_listener": true, + "socks_proxy_addr": "127.0.0.1:59050", + "send_config_dir": "." + } + }, + "id": 1 + } + # "# + # , + # r#" + { + "id": 1, + "jsonrpc": "2.0", + "result": { + "Ok": null + } + } + # "# + # , true, 0, false, false, false); + ``` + */ + fn set_tor_config(&self, tor_config: Option) -> Result<(), ErrorKind>; } impl OwnerRpcS for Owner @@ -2173,4 +2206,9 @@ where Owner::proof_address_from_onion_v3(self, &address_v3).map_err(|e| e.kind())?; Ok(PubAddress { address }) } + + fn set_tor_config(&self, tor_config: Option) -> Result<(), ErrorKind> { + Owner::set_tor_config(self, tor_config); + Ok(()) + } } diff --git a/controller/src/command.rs b/controller/src/command.rs index 064e6bbf..ea3322ce 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -21,9 +21,11 @@ use crate::error::{Error, ErrorKind}; use crate::impls::{create_sender, KeybaseAllChannels, SlateGetter as _, SlateReceiver as _}; use crate::impls::{PathToSlate, SlatePutter}; use crate::keychain; -use crate::libwallet::{InitTxArgs, IssueInvoiceTxArgs, NodeClient, WalletInst, WalletLCProvider}; +use crate::libwallet::{ + address, InitTxArgs, IssueInvoiceTxArgs, NodeClient, WalletInst, WalletLCProvider, +}; use crate::util::secp::key::SecretKey; -use crate::util::{Mutex, ZeroingString}; +use crate::util::{to_hex, Mutex, ZeroingString}; use crate::{controller, display}; use serde_json as json; use std::fs::File; @@ -173,6 +175,7 @@ pub fn owner_api( wallet: Arc>>>, keychain_mask: Option, config: &WalletConfig, + tor_config: &TorConfig, g_args: &GlobalArgs, ) -> Result<(), Error> where @@ -190,6 +193,7 @@ where g_args.api_secret.clone(), g_args.tls_conf.clone(), config.owner_api_include_foreign.clone(), + Some(tor_config.clone()), ); if let Err(e) = res { return Err(ErrorKind::LibWallet(e.kind(), e.cause_string()).into()); @@ -254,6 +258,7 @@ pub struct SendArgs { pub fluff: bool, pub max_outputs: usize, pub target_slate_version: Option, + pub payment_proof_address: Option, } pub fn send( @@ -289,6 +294,10 @@ where .collect(); display::estimate(args.amount, strategies, dark_scheme); } else { + let payment_proof_recipient_address = match args.payment_proof_address { + Some(ref p) => Some(address::ed25519_parse_pubkey(p)?), + None => None, + }; let init_args = InitTxArgs { src_acct_name: None, amount: args.amount, @@ -298,6 +307,7 @@ where selection_strategy_is_use_all: args.selection_strategy == "all", message: args.message.clone(), target_slate_version: args.target_slate_version, + payment_proof_recipient_address, send_args: None, ..Default::default() }; @@ -722,6 +732,7 @@ where // should only be one here, but just in case for tx in txs { display::tx_messages(&tx, dark_scheme)?; + display::payment_proof(&tx)?; } } @@ -874,3 +885,41 @@ where })?; Ok(()) } + +/// Payment Proof Address +pub fn address( + wallet: Arc>>>, + g_args: &GlobalArgs, + keychain_mask: Option<&SecretKey>, +) -> Result<(), Error> +where + L: WalletLCProvider<'static, C, K> + 'static, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ + controller::owner_single_use(wallet.clone(), keychain_mask, |api, m| { + // Just address at derivation index 0 for now + let pub_key = api.get_public_proof_address(m, 0)?; + let result = address::onion_v3_from_pubkey(&pub_key); + match result { + Ok(a) => { + println!(); + println!("Public Proof Address for account - {}", g_args.account); + println!("-------------------------------------"); + println!("{}", to_hex(pub_key.as_bytes().to_vec())); + println!(); + println!("TOR Onion V3 Address for account - {}", g_args.account); + println!("-------------------------------------"); + println!("{}", a); + println!(); + Ok(()) + } + Err(e) => { + error!("Addres retrieval failed: {}", e); + error!("Backtrace: {}", e.backtrace().unwrap()); + Err(e) + } + } + })?; + Ok(()) +} diff --git a/controller/src/controller.rs b/controller/src/controller.rs index 1c7632ab..6b82871b 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -15,6 +15,7 @@ //! Controller for wallet.. instantiates and handles listeners (or single-run //! invocations) as needed. use crate::api::{self, ApiServer, BasicAuthMiddleware, ResponseFuture, Router, TLSConfig}; +use crate::config::TorConfig; use crate::keychain::Keychain; use crate::libwallet::{ address, Error, ErrorKind, NodeClient, NodeVersionInfo, Slate, WalletInst, WalletLCProvider, @@ -168,6 +169,7 @@ pub fn owner_listener( api_secret: Option, tls_config: Option, owner_api_include_foreign: Option, + tor_config: Option, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + 'static, @@ -191,8 +193,12 @@ where } let api_handler_v2 = OwnerAPIHandlerV2::new(wallet.clone()); - let api_handler_v3 = - OwnerAPIHandlerV3::new(wallet.clone(), keychain_mask.clone(), running_foreign); + let api_handler_v3 = OwnerAPIHandlerV3::new( + wallet.clone(), + keychain_mask.clone(), + tor_config, + running_foreign, + ); router .add_route("/v2/owner", Arc::new(api_handler_v2)) @@ -593,9 +599,12 @@ where pub fn new( wallet: Arc + 'static>>>, keychain_mask: Arc>>, + tor_config: Option, running_foreign: bool, ) -> OwnerAPIHandlerV3 { - let owner_api = Arc::new(Owner::new(wallet.clone())); + let owner_api = Owner::new(wallet.clone()); + owner_api.set_tor_config(tor_config); + let owner_api = Arc::new(owner_api); OwnerAPIHandlerV3 { wallet, owner_api, diff --git a/controller/src/display.rs b/controller/src/display.rs index 7480a9e5..42342948 100644 --- a/controller/src/display.rs +++ b/controller/src/display.rs @@ -15,7 +15,7 @@ use crate::core::core::{self, amount_to_hr_string}; use crate::core::global; use crate::libwallet::{ - AcctPathMapping, Error, OutputCommitMapping, OutputStatus, TxLogEntry, WalletInfo, + address, AcctPathMapping, Error, OutputCommitMapping, OutputStatus, TxLogEntry, WalletInfo, }; use crate::util; use prettytable; @@ -160,6 +160,7 @@ pub fn txs( bMG->"Amount \nDebited", bMG->"Fee", bMG->"Net \nDifference", + bMG->"Payment \nProof", bMG->"Kernel", bMG->"Tx \nData", ]); @@ -201,6 +202,10 @@ pub fn txs( Some(e) => util::to_hex(e.0.to_vec()), None => "None".to_owned(), }; + let payment_proof = match t.payment_proof { + Some(_) => "Yes".to_owned(), + None => "None".to_owned(), + }; if dark_background_color_scheme { table.add_row(row![ bFC->id, @@ -215,6 +220,7 @@ pub fn txs( bFR->amount_debited_str, bFR->fee, bFY->net_diff, + bfG->payment_proof, bFB->kernel_excess, bFb->tx_data, ]); @@ -233,6 +239,7 @@ pub fn txs( bFD->amount_debited_str, bFD->fee, bFG->net_diff, + bfG->payment_proof, bFB->kernel_excess, bFB->tx_data, ]); @@ -250,6 +257,7 @@ pub fn txs( bFD->amount_debited_str, bFD->fee, bFG->net_diff, + bfG->payment_proof, bFB->kernel_excess, bFB->tx_data, ]); @@ -498,3 +506,73 @@ pub fn tx_messages(tx: &TxLogEntry, dark_background_color_scheme: bool) -> Resul Ok(()) } + +/// Display individual Payment Proof +pub fn payment_proof(tx: &TxLogEntry) -> Result<(), Error> { + let title = format!("Payment Proof - Transaction '{}'", tx.id,); + println!(); + if term::stdout().is_none() { + println!("Could not open terminal"); + return Ok(()); + } + let mut t = term::stdout().unwrap(); + t.fg(term::color::MAGENTA).unwrap(); + writeln!(t, "{}", title).unwrap(); + t.reset().unwrap(); + + let pp = match &tx.payment_proof { + None => { + writeln!(t, "{}", "None").unwrap(); + t.reset().unwrap(); + return Ok(()); + } + Some(p) => p.clone(), + }; + + t.fg(term::color::WHITE).unwrap(); + writeln!(t).unwrap(); + let receiver_address = util::to_hex(pp.receiver_address.to_bytes().to_vec()); + let receiver_onion_address = address::onion_v3_from_pubkey(&pp.receiver_address)?; + let receiver_signature = match pp.receiver_signature { + Some(s) => util::to_hex(s.to_bytes().to_vec()), + None => "None".to_owned(), + }; + let fee = match tx.fee { + Some(f) => f, + None => 0, + }; + let amount = if tx.amount_credited >= tx.amount_debited { + core::amount_to_hr_string(tx.amount_credited - tx.amount_debited, true) + } else { + format!( + "{}", + core::amount_to_hr_string(tx.amount_debited - tx.amount_credited - fee, true) + ) + }; + + let sender_address = util::to_hex(pp.sender_address.to_bytes().to_vec()); + let sender_onion_address = address::onion_v3_from_pubkey(&pp.sender_address)?; + let sender_signature = match pp.sender_signature { + Some(s) => util::to_hex(s.to_bytes().to_vec()), + None => "None".to_owned(), + }; + let kernel_excess = match tx.kernel_excess { + Some(e) => util::to_hex(e.0.to_vec()), + None => "None".to_owned(), + }; + + writeln!(t, "Receiver Address: {}", receiver_address).unwrap(); + writeln!(t, "Receiver Address (Onion V3): {}", receiver_onion_address).unwrap(); + writeln!(t, "Receiver Signature: {}", receiver_signature).unwrap(); + writeln!(t, "Amount: {}", amount).unwrap(); + writeln!(t, "Kernel Excess: {}", kernel_excess).unwrap(); + writeln!(t, "Sender Address: {}", sender_address).unwrap(); + writeln!(t, "Sender Signature: {}", sender_signature).unwrap(); + writeln!(t, "Sender Address (Onion V3): {}", sender_onion_address).unwrap(); + + t.reset().unwrap(); + + println!(); + + Ok(()) +} diff --git a/libwallet/src/address.rs b/libwallet/src/address.rs index 99c4a037..2fabd3c7 100644 --- a/libwallet/src/address.rs +++ b/libwallet/src/address.rs @@ -15,6 +15,7 @@ //! Functions defining wallet 'addresses', i.e. ed2559 keys based on //! a derivation path +use crate::grin_util::from_hex; use crate::grin_util::secp::key::SecretKey; use crate::{Error, ErrorKind}; use grin_wallet_util::grin_keychain::{ChildNumber, Identifier, Keychain, SwitchCommitmentType}; @@ -66,6 +67,20 @@ pub fn ed25519_keypair(sec_key: &SecretKey) -> Result<(DalekSecretKey, DalekPubl Ok((d_skey, d_pub_key)) } +/// Output ed25519 pubkey represented by string +pub fn ed25519_parse_pubkey(pub_key: &str) -> Result { + let bytes = from_hex(pub_key.to_owned()) + .context(ErrorKind::AddressDecoding("Can't parse pubkey".to_owned()))?; + match DalekPublicKey::from_bytes(&bytes) { + Ok(k) => Ok(k), + Err(_) => { + return Err( + ErrorKind::AddressDecoding("Not a valid public key".to_owned()).to_owned(), + )?; + } + } +} + /// Return the ed25519 public key represented in an onion address pub fn pubkey_from_onion_v3(onion_address: &str) -> Result { let mut input = onion_address.to_uppercase(); diff --git a/libwallet/src/internal/selection.rs b/libwallet/src/internal/selection.rs index 30f97f8e..0dbf7321 100644 --- a/libwallet/src/internal/selection.rs +++ b/libwallet/src/internal/selection.rs @@ -14,6 +14,7 @@ //! Selection of inputs for building transactions +use crate::address; use crate::error::{Error, ErrorKind}; use crate::grin_core::core::amount_to_hr_string; use crate::grin_core::libtx::{ @@ -162,17 +163,25 @@ where // store extra payment proof info, if required if let Some(ref p) = slate.payment_proof { + let sender_address_path = match context.payment_proof_derivation_index { + Some(p) => p, + None => { + return Err(ErrorKind::PaymentProof( + "Payment proof derivation index required".to_owned(), + ))?; + } + }; + let sender_key = address::address_from_derivation_path( + &keychain, + &parent_key_id, + sender_address_path, + )?; + let sender_address = address::ed25519_keypair(&sender_key)?.1; t.payment_proof = Some(StoredProofInfo { receiver_address: p.receiver_address.clone(), receiver_signature: p.receiver_signature.clone(), - sender_address_path: match context.payment_proof_derivation_index { - Some(p) => p, - None => { - return Err(ErrorKind::PaymentProof( - "Payment proof derivation index required".to_owned(), - ))?; - } - }, + sender_address, + sender_address_path, sender_signature: None, }); }; diff --git a/libwallet/src/internal/tx.rs b/libwallet/src/internal/tx.rs index 8bb7204e..199c0473 100644 --- a/libwallet/src/internal/tx.rs +++ b/libwallet/src/internal/tx.rs @@ -349,16 +349,16 @@ where let keychain = wallet.keychain(keychain_mask)?; let parent_key_id = wallet.parent_key_id(); let excess = slate.calc_excess(&keychain)?; - let sig = create_payment_proof_signature( - slate.amount, - &excess, - p.sender_address, - address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?, - )?; + let sender_key = + address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?; + let sender_address = address::ed25519_keypair(&sender_key)?.1; + let sig = + create_payment_proof_signature(slate.amount, &excess, p.sender_address, sender_key)?; tx.payment_proof = Some(StoredProofInfo { receiver_address: p.receiver_address, receiver_signature: p.receiver_signature, sender_address_path: derivation_index, + sender_address, sender_signature: Some(sig), }) } diff --git a/libwallet/src/types.rs b/libwallet/src/types.rs index d9a613c9..11582bd6 100644 --- a/libwallet/src/types.rs +++ b/libwallet/src/types.rs @@ -858,6 +858,9 @@ pub struct StoredProofInfo { pub receiver_signature: Option, /// sender address derivation path index pub sender_address_path: u32, + /// sender address + #[serde(with = "dalek_ser::dalek_pubkey_serde")] + pub sender_address: DalekPublicKey, /// sender signature #[serde(with = "dalek_ser::option_dalek_sig_serde")] pub sender_signature: Option, diff --git a/src/bin/grin-wallet.yml b/src/bin/grin-wallet.yml index 085dc9ef..c0e5c767 100644 --- a/src/bin/grin-wallet.yml +++ b/src/bin/grin-wallet.yml @@ -132,6 +132,15 @@ subcommands: short: d long: dest takes_value: true + - request_payment_proof: + help: Request a payment proof from the recipient. If sending to a tor address, the address will be filled automatically. + short: y + long: request_payment_proof + - proof_address: + help: Recipient proof address. If not using TOR, must be provided seprarately by the recipient + short: z + long: proof_address + takes_value: true - fluff: help: Fluff the transaction (ignore Dandelion relay protocol) short: f @@ -345,6 +354,8 @@ subcommands: short: d long: display takes_value: false + - address: + about: Display the wallet's payment proof address - scan: about: Checks a wallet's outputs against a live node, repairing and restoring missing outputs if required args: diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index 4b285198..57e771fc 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -15,7 +15,7 @@ use crate::api::TLSConfig; use crate::config::GRIN_WALLET_DIR; use crate::util::file::get_first_line; -use crate::util::{Mutex, ZeroingString}; +use crate::util::{to_hex, Mutex, ZeroingString}; /// Argument parsing and error handling for wallet commands use clap::ArgMatches; use failure::Fail; @@ -26,7 +26,9 @@ use grin_wallet_impls::tor::config::is_tor_address; use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl}; use grin_wallet_impls::{PathToSlate, SlateGetter as _}; use grin_wallet_libwallet::Slate; -use grin_wallet_libwallet::{IssueInvoiceTxArgs, NodeClient, WalletInst, WalletLCProvider}; +use grin_wallet_libwallet::{ + address, IssueInvoiceTxArgs, NodeClient, WalletInst, WalletLCProvider, +}; use grin_wallet_util::grin_core as core; use grin_wallet_util::grin_core::core::amount_to_hr_string; use grin_wallet_util::grin_core::global; @@ -520,6 +522,20 @@ pub fn parse_send_args(args: &ArgMatches) -> Result { + // if the destination address is a TOR address, we don't need the address + // separately + match address::pubkey_from_onion_v3(&dest) { + Ok(k) => Some(to_hex(k.to_bytes().to_vec())), + Err(_) => Some(parse_required(args, "proof_address")?.to_owned()), + } + } + false => None, + } + }; + Ok(command::SendArgs { amount: amount, message: message, @@ -531,6 +547,7 @@ pub fn parse_send_args(args: &ArgMatches) -> Result { - command::owner_api(wallet, keychain_mask, &wallet_config, &global_wallet_args) + command::owner_api(wallet, keychain_mask, &c, &tor_config, &g) } + ("web", Some(_)) => command::owner_api( + wallet, + keychain_mask, + &wallet_config, + &tor_config, + &global_wallet_args, + ), ("account", Some(args)) => { let a = arg_parse!(parse_account_args(&args)); command::account(wallet, km, a) @@ -1043,6 +1064,7 @@ where let a = arg_parse!(parse_cancel_args(&args)); command::cancel(wallet, km, a) } + ("address", Some(_)) => command::address(wallet, &global_wallet_args, km), ("scan", Some(args)) => { let a = arg_parse!(parse_check_args(&args)); command::scan(wallet, km, a)