From f272771e8a445c49ba788a2a8867998131ed2048 Mon Sep 17 00:00:00 2001 From: bayk Date: Fri, 9 Aug 2024 10:38:40 -0700 Subject: [PATCH] rebase 5.3.X [PATCH 110/142] Show slatepack QR codes (#655) --- Cargo.lock | 7 + controller/Cargo.toml | 1 + controller/src/command.rs | 195 ++++++++++++++++++++++++--- libwallet/src/slatepack/armor.rs | 2 + libwallet/src/slatepack/mod.rs | 2 +- libwallet/src/slatepack/slatepack.rs | 11 ++ src/bin/mwc-wallet.yml | 20 +++ src/cmd/wallet_args.rs | 15 +++ 8 files changed, 235 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e89d28cd..b68e86a0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2307,6 +2307,7 @@ dependencies = [ "libp2p", "log", "prettytable-rs", + "qr_code", "rand 0.7.3", "remove_dir_all", "ring 0.16.20", @@ -4433,6 +4434,12 @@ dependencies = [ "prost", ] +[[package]] +name = "qr_code" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5520fbcd7da152a449261c5a533a1c7fad044e9e8aa9528cfec3f464786c7926" + [[package]] name = "quick-error" version = "1.2.3" diff --git a/controller/Cargo.toml b/controller/Cargo.toml index c8135fbcd..e85857085 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -29,6 +29,7 @@ url = "2.1" chrono = { version = "0.4.11", features = ["serde"] } easy-jsonrpc-mw = "0.5.4" lazy_static = "1.4" +qr_code = "1.1.0" colored = "1.6" x25519-dalek = "0.6" ed25519-dalek = "1.0.0-pre.4" diff --git a/controller/src/command.rs b/controller/src/command.rs index 51ce4f775..8e8081537 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -53,6 +53,7 @@ use grin_wallet_util::grin_p2p::libp2p_connection::ReceivedMessage; use grin_wallet_util::grin_p2p::{libp2p_connection, PeerAddr}; use grin_wallet_util::grin_util::secp::{ContextFlag, Secp256k1}; use grin_wallet_util::grin_util::static_secp_instance; +use qr_code::QrCode; use serde_json as json; use serde_json::json; use serde_json::{Map as JsonMap, Value as JsonValue}; @@ -410,6 +411,7 @@ pub struct SendArgs { pub late_lock: bool, pub min_fee: Option, pub bridge: Option, + pub slatepack_qr: bool, } pub fn send( @@ -578,21 +580,40 @@ where Some((&args.dest).into()) }; + let slatepack_format = args.method == "slatepack"; let slate_str = PathToSlatePutter::build_encrypted( dest, SlatePurpose::SendInitial, slatepack_sender, recipient, - args.method == "slatepack", + slatepack_format, ) .put_tx(&slate, Some(&slatepack_secret), false, &secp) .map_err(|e| { ErrorKind::IO(format!("Unable to store the file at {}, {}", args.dest, e)) })?; api.tx_lock_outputs(m, &slate, Some(String::from("file")), 0)?; - if args.dest.is_empty() { - println!("Slatepack: {}", slate_str); + + if !args.dest.is_empty() { + println!( + "Resulting transaction is successfully stored at : {}", + args.dest + ); + println!(); + } + + if slatepack_format { + show_slatepack( + api, + &slate_str, // encrypted (optionally) slate with a purpose. + &slate.id, + &SlatePurpose::SendInitial, + recipient.is_some(), + false, + args.slatepack_qr, + )?; } + return Ok(()); } "self" => { @@ -662,6 +683,82 @@ where Ok(()) } +/// Show slate pack and save it into the backup for historical purpose +pub fn show_slatepack( + api: &mut Owner, + slate_str: &str, // encrypted (optionally) slate with a purpose. + slate_id: &Uuid, + slate_purpose: &SlatePurpose, + is_encrypted: bool, + show_finalizing_message: bool, + show_qr: bool, +) -> Result<(), Error> +where + L: WalletLCProvider<'static, C, K> + 'static, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ + // Output if it is a slatepack, into stdout and backup file + let tld = api.get_top_level_directory()?; + + // create a directory to which files will be output + let slate_dir = format!("{}/{}", tld, "slatepack"); + let _ = std::fs::create_dir_all(slate_dir.clone()); + let out_file_name = format!( + "{}/{}.{}.slatepack", + slate_dir, + slate_id, + slate_purpose.to_str() + ); + + let mut output = File::create(out_file_name.clone()).map_err(|e| { + ErrorKind::IO(format!( + "Unable to create slate backup file {}, {}", + out_file_name, e + )) + })?; + output.write_all(&slate_str.as_bytes()).map_err(|e| { + ErrorKind::IO(format!( + "Unable to store slate backup data into file {}, {}", + out_file_name, e + )) + })?; + output.sync_all().map_err(|e| { + ErrorKind::IO(format!( + "Unable to sync data for file {}, {}", + out_file_name, e + )) + })?; + + println!(); + if !show_finalizing_message { + println!("Slatepack data follows. Please provide this output to the other party"); + } else { + println!("Slatepack data follows."); + } + println!(); + println!("--- CUT BELOW THIS LINE ---"); + println!(); + println!("{}", slate_str); + println!("--- CUT ABOVE THIS LINE ---"); + println!(); + println!("Slatepack data was also backed up at {}", out_file_name); + println!(); + if show_qr { + if let Ok(qr_string) = QrCode::new(slate_str) { + println!("{}", qr_string.to_string(false, 3)); + println!(); + } + } + if is_encrypted { + println!("The slatepack data is encrypted for the recipient only"); + } else { + println!("The slatepack data is NOT encrypted"); + } + println!(); + Ok(()) +} + /// Receive command argument pub struct ReceiveArgs { pub input_file: Option, @@ -669,6 +766,7 @@ pub struct ReceiveArgs { pub message: Option, pub outfile: Option, pub bridge: Option, + pub slatepack_qr: bool, } pub fn receive( @@ -756,10 +854,22 @@ where .put_tx(&slate, Some(&slatepack_secret), false, &secp)?; if let Some(response_file) = &response_file { - info!("Response file {}.response generated, and can be sent back to the transaction originator.", response_file); - } else { - println!("Response Slate: {}", slatepack_str); + println!("Response file {}.response generated, and can be sent back to the transaction originator.", response_file); + println!(); } + + if slatepack_format { + show_slatepack( + owner_api, + &slatepack_str, // encrypted (optionally) slate with a purpose. + &slate.id, + &SlatePurpose::SendInitial, + sender.is_some(), + false, + args.slatepack_qr, + )?; + } + Ok(()) })?; @@ -855,6 +965,7 @@ pub struct FinalizeArgs { pub fluff: bool, pub nopost: bool, pub dest: Option, + pub slatepack_qr: bool, } pub fn finalize( @@ -1002,7 +1113,7 @@ where }; // save to a destination not as a slatepack - PathToSlatePutter::build_encrypted( + let slatepack_str = PathToSlatePutter::build_encrypted( Some((&args.dest.unwrap()).into()), SlatePurpose::FullSlate, DalekPublicKey::from(&slatepack_secret), @@ -1011,6 +1122,18 @@ where ) .put_tx(&slate, Some(&slatepack_secret), false, &secp)?; + if slatepack_format { + show_slatepack( + api, + &slatepack_str, // encrypted (optionally) slate with a purpose. + &slate.id, + &SlatePurpose::FullSlate, + sender.is_some(), + true, + args.slatepack_qr, + )?; + } + Ok(()) })?; } @@ -1024,6 +1147,8 @@ pub struct IssueInvoiceArgs { pub dest: String, /// issue invoice tx args pub issue_args: IssueInvoiceTxArgs, + /// show slatepack as QR code + pub slatepack_qr: bool, } pub fn issue_invoice_tx( @@ -1054,14 +1179,28 @@ where (slatepack_secret, slatepack_pk, keychain.secp().clone()) }; - PathToSlatePutter::build_encrypted( + let slatepack_format = recipient.is_some(); + let slate_str = PathToSlatePutter::build_encrypted( Some((&args.dest).into()), SlatePurpose::InvoiceInitial, tor_address, recipient, - recipient.is_some(), + slatepack_format, ) .put_tx(&slate, Some(&slatepack_secret), false, &secp)?; + + if slatepack_format { + show_slatepack( + api, + &slate_str, // encrypted (optionally) slate with a purpose. + &slate.id, + &SlatePurpose::InvoiceInitial, + recipient.is_some(), + false, + args.slatepack_qr, + )?; + } + Ok(()) })?; Ok(()) @@ -1079,6 +1218,7 @@ pub struct ProcessInvoiceArgs { pub estimate_selection_strategies: bool, pub ttl_blocks: Option, pub bridge: Option, + pub slatepack_qr: bool, } /// Process invoice @@ -1094,13 +1234,19 @@ where C: NodeClient + 'static, K: keychain::Keychain + 'static, { - let (slatepack_secret, height, secp) = { + let (slatepack_secret, tor_address, height, secp) = { let mut w_lock = owner_api.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; let keychain = w.keychain(keychain_mask)?; let slatepack_secret = proofaddress::payment_proof_address_dalek_secret(&keychain, None)?; + let slatepack_pk = DalekPublicKey::from(&slatepack_secret); let (height, _, _) = w.w2n_client().get_chain_tip()?; - (slatepack_secret, height, keychain.secp().clone()) + ( + slatepack_secret, + slatepack_pk, + height, + keychain.secp().clone(), + ) }; let slate_pkg = PathToSlateGetter::build_form_path((&args.input).into()).get_tx( @@ -1181,13 +1327,28 @@ where match args.method.as_str() { "file" => { // Process invoice slate is not required to send anywhere. Let's write it for our records. - PathToSlatePutter::build_plain(Some((&args.dest).into())).put_tx( - &slate, - Some(&slatepack_secret), - false, - &secp, - )?; + let slatepack_format = sender_pk.is_some(); + let slate_str = PathToSlatePutter::build_encrypted( + Some((&args.dest).into()), + SlatePurpose::InvoiceResponse, + tor_address, + sender_pk, + slatepack_format, + ) + .put_tx(&slate, Some(&slatepack_secret), false, &secp)?; api.tx_lock_outputs(m, &slate, Some(String::from("file")), 1)?; + + if slatepack_format { + show_slatepack( + api, + &slate_str, // encrypted (optionally) slate with a purpose. + &slate.id, + &SlatePurpose::InvoiceResponse, + sender_pk.is_some(), + false, + args.slatepack_qr, + )?; + } } "self" => { api.tx_lock_outputs(m, &slate, Some(String::from("self")), 1)?; diff --git a/libwallet/src/slatepack/armor.rs b/libwallet/src/slatepack/armor.rs index d39366ce5..f5c85501a 100644 --- a/libwallet/src/slatepack/armor.rs +++ b/libwallet/src/slatepack/armor.rs @@ -27,6 +27,8 @@ use sha2::{Digest, Sha256}; use std::str; // Framing and formatting for slate armor. Headers and footers better to be the same size, otherwise formatting makes it ugly + +/// Header used for armored slatepack pub static HEADER_ENC: &str = "BEGINSLATEPACK."; static FOOTER_ENC: &str = ". ENDSLATEPACK."; pub static HEADER_BIN: &str = "BEGINSLATE_BIN."; diff --git a/libwallet/src/slatepack/mod.rs b/libwallet/src/slatepack/mod.rs index 60e606584..9c6be904a 100644 --- a/libwallet/src/slatepack/mod.rs +++ b/libwallet/src/slatepack/mod.rs @@ -18,6 +18,6 @@ mod armor; mod packer; mod slatepack; -pub use self::armor::{generate_check, max_size, min_size, SlatepackArmor}; +pub use self::armor::{generate_check, max_size, min_size, SlatepackArmor, HEADER_ENC}; pub use self::packer::Slatepacker; pub use self::slatepack::{SlatePurpose, Slatepack}; diff --git a/libwallet/src/slatepack/slatepack.rs b/libwallet/src/slatepack/slatepack.rs index d163a1874..85fa54c2b 100644 --- a/libwallet/src/slatepack/slatepack.rs +++ b/libwallet/src/slatepack/slatepack.rs @@ -105,6 +105,17 @@ impl SlatePurpose { SlatePurpose::FullSlate => 4, } } + + /// Show slatepack purpose as string + pub fn to_str(&self) -> &str { + match self { + SlatePurpose::SendInitial => "send_init", + SlatePurpose::SendResponse => "send_response", + SlatePurpose::InvoiceInitial => "invoice_init", + SlatePurpose::InvoiceResponse => "invoice_response", + SlatePurpose::FullSlate => "full", + } + } } const SLATE_PACK_PLAIN_DATA_SIZE: usize = 1 + 32 + 32; diff --git a/src/bin/mwc-wallet.yml b/src/bin/mwc-wallet.yml index 76d5cbed4..90fbb8f42 100644 --- a/src/bin/mwc-wallet.yml +++ b/src/bin/mwc-wallet.yml @@ -235,6 +235,10 @@ subcommands: help: Enable tor bridge relay when sending via Slatepack workflow long: bridge takes_value: true + - slatepack_qr: + help: Show slatepack data as QR code + short: q + long: slatepack_qr - unpack: about: Unpack and display an armored Slatepack Message, decrypting if possible args: @@ -280,6 +284,10 @@ subcommands: help: Enable tor bridge relay when receiving via Slatepack workflow long: bridge takes_value: true + - slatepack_qr: + help: Show slatepack data as QR code + short: q + long: slatepack_qr - finalize: about: Processes a receiver's transaction file to finalize a transfer. args: @@ -306,6 +314,10 @@ subcommands: short: d long: dest takes_value: true + - slatepack_qr: + help: Show slatepack data as QR code + short: q + long: slatepack_qr - invoice: about: Initialize an invoice transaction. args: @@ -352,6 +364,10 @@ subcommands: short: d long: dest takes_value: true + - slatepack_qr: + help: Show slatepack data as QR code + short: q + long: slatepack_qr - pay: about: Spend coins to pay the provided invoice transaction args: @@ -408,6 +424,10 @@ subcommands: help: Enable tor bridge relay when receiving via Slatepack workflow long: bridge takes_value: true + - slatepack_qr: + help: Show slatepack data as QR code + short: q + long: slatepack_qr - outputs: about: Raw wallet output info (list of outputs) - txs: diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index 7671f869b..57b1011a3 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -644,6 +644,8 @@ pub fn parse_send_args(args: &ArgMatches) -> Result None, }; + let slatepack_qr = args.is_present("slatepack_qr"); + if minimum_confirmations_change_outputs_is_present && !exclude_change_outputs { Err(ArgumentError("minimum_confirmations_change_outputs may only be specified if exclude_change_outputs is set".to_string())) } else { @@ -670,6 +672,7 @@ pub fn parse_send_args(args: &ArgMatches) -> Result Result Result None, }; + let slatepack_qr = args.is_present("slatepack_qr"); + Ok(command::FinalizeArgs { input_file, input_slatepack_message: args.value_of("content").map(|s| s.to_string()), fluff: args.is_present("fluff"), nopost: args.is_present("nopost"), dest: args.value_of("dest").map(|s| s.to_string()), + slatepack_qr: slatepack_qr, }) } @@ -772,6 +781,8 @@ pub fn parse_issue_invoice_args( None => None, }; + let slatepack_qr = args.is_present("slatepack_qr"); + // dest (output file) let dest = parse_required(args, "dest")?; Ok(command::IssueInvoiceArgs { @@ -784,6 +795,7 @@ pub fn parse_issue_invoice_args( target_slate_version, slatepack_recipient, }, + slatepack_qr: slatepack_qr, }) } @@ -871,6 +883,8 @@ pub fn parse_process_invoice_args( let bridge = parse_optional(args, "bridge")?; + let slatepack_qr = args.is_present("slatepack_qr"); + Ok(command::ProcessInvoiceArgs { message: message, minimum_confirmations: min_c, @@ -882,6 +896,7 @@ pub fn parse_process_invoice_args( input: tx_file.to_owned(), ttl_blocks, bridge, + slatepack_qr: slatepack_qr, }) }