From a015a2ff5da96ddc8038cd1728199408cc6d372e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Fri, 17 Nov 2023 14:35:46 +0200 Subject: [PATCH 01/14] feat: add `--offline` for `sign-with-ledger` option --- .../sign_with_ledger/mod.rs | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/src/transaction_signature_options/sign_with_ledger/mod.rs b/src/transaction_signature_options/sign_with_ledger/mod.rs index e381f99c..9411f275 100644 --- a/src/transaction_signature_options/sign_with_ledger/mod.rs +++ b/src/transaction_signature_options/sign_with_ledger/mod.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use color_eyre::eyre::WrapErr; +use color_eyre::eyre::{ContextCompat, WrapErr}; use inquire::{CustomType, Text}; use near_primitives::borsh::BorshSerialize; @@ -27,7 +27,7 @@ pub struct SignLedger { #[interactive_clap(long)] #[interactive_clap(skip_default_from_cli_arg)] #[interactive_clap(skip_default_input_arg)] - block_hash: Option, + pub block_hash: Option, #[interactive_clap(subcommand)] submit: super::Submit, } @@ -52,25 +52,39 @@ impl SignLedgerContext { let seed_phrase_hd_path: slip10::BIP32Path = scope.seed_phrase_hd_path.clone().into(); let public_key: near_crypto::PublicKey = scope.signer_public_key.clone().into(); - let rpc_query_response = network_config - .json_rpc_client() - .blocking_call_view_access_key( - &previous_context.prepopulated_transaction.signer_id, - &public_key, - near_primitives::types::BlockReference::latest() + let (nonce, block_hash) = if previous_context.global_context.offline { + ( + scope + .nonce + .wrap_err("Nonce is required to sign a transaction in offline mode")?, + scope + .block_hash + .wrap_err("Block Hash is required to sign a transaction in offline mode")? + .0, ) - .wrap_err( - "Cannot sign a transaction due to an error while fetching the most recent nonce value", - )?; - let current_nonce = rpc_query_response - .access_key_view() - .wrap_err("Error current_nonce")? - .nonce; + } else { + let rpc_query_response = network_config + .json_rpc_client() + .blocking_call_view_access_key( + &previous_context.prepopulated_transaction.signer_id, + &public_key, + near_primitives::types::BlockReference::latest() + ) + .wrap_err( + "Cannot sign a transaction due to an error while fetching the most recent nonce value", + )?; + let current_nonce = rpc_query_response + .access_key_view() + .wrap_err("Error current_nonce")? + .nonce; + + (current_nonce + 1, rpc_query_response.block_hash) + }; let mut unsigned_transaction = near_primitives::transaction::Transaction { public_key: scope.signer_public_key.clone().into(), - block_hash: rpc_query_response.block_hash, - nonce: current_nonce + 1, + block_hash, + nonce, signer_id: previous_context.prepopulated_transaction.signer_id, receiver_id: previous_context.prepopulated_transaction.receiver_id, actions: previous_context.prepopulated_transaction.actions, @@ -254,9 +268,14 @@ impl SignLedger { fn input_block_hash( context: &crate::commands::TransactionContext, - ) -> color_eyre::eyre::Result> { + ) -> color_eyre::eyre::Result> { if context.global_context.offline { - return Ok(Some(Text::new("Enter recent block hash:").prompt()?)); + return Ok(Some( + CustomType::::new( + "Enter recent block hash:", + ) + .prompt()?, + )); } Ok(None) } From 093543a8cee883a569cc9121c03d989873fe6a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Thu, 16 Nov 2023 20:55:07 +0200 Subject: [PATCH 02/14] chore: change `near-ledger-rs` dep to feature branch --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6e662995..971d6610 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,8 @@ thiserror = "1" bytesize = "1.1.0" prettytable = "0.10.0" -near-ledger = { version = "0.2.0", optional = true } +# near-ledger = { version = "0.2.0", optional = true } +near-ledger = { version = "0.3.0", git = "https://github.com/dj8yfo/near-ledger-rs.git", branch = "blind_signing", optional = true } near-crypto = "0.17.0" near-primitives = "0.17.0" From b9aa795b3f893376cb3ab341c9ff8a8eb8215417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Sat, 18 Nov 2023 00:26:23 +0200 Subject: [PATCH 03/14] feat: implement blind sign subflow --- Cargo.lock | 8 ++- .../sign_with_ledger/mod.rs | 65 ++++++++++++++++++- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b8e4da2..ddd2ba1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2305,14 +2305,16 @@ dependencies = [ [[package]] name = "near-ledger" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edfdfadf4d772ea67c11d69cdbc8b8f0e0742d88c3baac64bdcf21482f7c950" +version = "0.3.0" +source = "git+https://github.com/dj8yfo/near-ledger-rs.git?branch=blind_signing#4e9d0d44a7e7c51db1632e22439e865e6d42a47c" dependencies = [ "ed25519-dalek", + "hex 0.4.3", "ledger-apdu", "ledger-transport", "ledger-transport-hid", + "log", + "sha2 0.10.8", "slip10", ] diff --git a/src/transaction_signature_options/sign_with_ledger/mod.rs b/src/transaction_signature_options/sign_with_ledger/mod.rs index 9411f275..fdd23f2c 100644 --- a/src/transaction_signature_options/sign_with_ledger/mod.rs +++ b/src/transaction_signature_options/sign_with_ledger/mod.rs @@ -1,9 +1,11 @@ use std::str::FromStr; use color_eyre::eyre::{ContextCompat, WrapErr}; -use inquire::{CustomType, Text}; +use inquire::{CustomType, Select, Text}; +use near_ledger::OnlyBlindSigning; use near_primitives::borsh::BorshSerialize; +use slip10::BIP32Path; use crate::common::JsonRpcClientExt; use crate::common::RpcQueryResponseExt; @@ -44,6 +46,62 @@ pub struct SignLedgerContext { } impl SignLedgerContext { + fn input_blind_agree() -> color_eyre::eyre::Result { + let options: Vec<&str> = vec!["Yes", "No"]; + + return Ok( + Select::new("Do you agree to continue with blind signature? ", options) + .prompt() + .map(|selected| selected == "Yes")?, + ); + } + + fn blind_sign_subflow( + hash: OnlyBlindSigning, + hd_path: BIP32Path, + unsigned_transaction: near_primitives::transaction::Transaction, + ) -> color_eyre::eyre::Result { + eprintln!("\n\nBuffer overflow on Ledger device occured. Transaction is too large for normal signature."); + eprintln!("\nThe following is SHA256 of unsigned transaction:"); + eprintln!("{}", hex::encode(&hash.0)); + + eprintln!( + "\nUnsigned transaction (serialized as base64):\n{}\n", + crate::types::transaction::TransactionAsBase64::from(unsigned_transaction) + ); + eprintln!("Before proceeding with blind signature,"); + eprintln!("you have ability to verify unsigned transaction's details and exact SHA256 correspondence,"); + eprintln!("if you have concerns with trust to current device,"); + eprintln!("on another device(s) with the following helper command on near CLI:"); + eprintln!( + "$ {} transaction print-unsigned-transaction\n\n", + crate::common::get_near_exec_path() + ); + + eprintln!("Make sure to enable blind sign in Near app's settings on Ledger device\n"); + let agree = Self::input_blind_agree()?; + if agree { + eprintln!( + "Confirm transaction blind signing on your Ledger device (HD Path: {})", + hd_path, + ); + let result = near_ledger::blind_sign_transaction(hash, hd_path); + let signature = result.map_err(|err| { + color_eyre::Report::msg(format!( + "Error occurred while signing the transaction: {:?}", + err + )) + })?; + let signature = + near_crypto::Signature::from_parts(near_crypto::KeyType::ED25519, &signature) + .expect("Signature is not expected to fail on deserialization"); + + Ok(signature) + } else { + Err(color_eyre::Report::msg("signing with ledger aborted")) + } + } + pub fn from_previous_context( previous_context: crate::commands::TransactionContext, scope: &::InteractiveClapContextScope, @@ -101,12 +159,15 @@ impl SignLedgerContext { unsigned_transaction .try_to_vec() .expect("Transaction is not expected to fail on serialization"), - seed_phrase_hd_path, + seed_phrase_hd_path.clone(), ) { Ok(signature) => { near_crypto::Signature::from_parts(near_crypto::KeyType::ED25519, &signature) .expect("Signature is not expected to fail on deserialization") } + Err(near_ledger::NEARLedgerError::BufferOverflow(hash)) => { + Self::blind_sign_subflow(hash, seed_phrase_hd_path, unsigned_transaction.clone())? + } Err(near_ledger_error) => { return Err(color_eyre::Report::msg(format!( "Error occurred while signing the transaction: {:?}", From 9e5f31922f1a398db895df2393c6529bb7e12a07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 20 Nov 2023 21:07:57 +0200 Subject: [PATCH 04/14] feat: add `print-transaction` command --- Cargo.lock | 1 + Cargo.toml | 1 + src/commands/mod.rs | 10 ++++++ src/commands/transaction/mod.rs | 6 ++++ .../transaction/print_transaction/mod.rs | 28 +++++++++++++++ .../print_transaction/signed/mod.rs | 26 ++++++++++++++ .../print_transaction/unsigned/mod.rs | 26 ++++++++++++++ .../transaction/sign_transaction/mod.rs | 8 ++--- src/common.rs | 36 +++++++++++++++++++ .../sign_with_ledger/mod.rs | 2 +- src/types/signed_transaction.rs | 6 ++++ 11 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 src/commands/transaction/print_transaction/mod.rs create mode 100644 src/commands/transaction/print_transaction/signed/mod.rs create mode 100644 src/commands/transaction/print_transaction/unsigned/mod.rs diff --git a/Cargo.lock b/Cargo.lock index ddd2ba1c..7f05e9de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2197,6 +2197,7 @@ dependencies = [ "semver", "serde", "serde_json", + "sha2 0.10.8", "shell-words", "shellexpand", "slip10", diff --git a/Cargo.toml b/Cargo.toml index 971d6610..c651bd1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ bip39 = { version = "2.0.0", features = ["rand"] } bs58 = "0.5" ed25519-dalek = { version = "1" } hex = "0.4.2" +sha2 = "0.10.8" linked-hash-map = { version = "0.5", features = ["serde_impl"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.57" diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 374d3188..cbb1b732 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -68,6 +68,16 @@ pub struct PrepopulatedTransaction { pub actions: Vec, } +impl From for PrepopulatedTransaction { + fn from(value: near_primitives::transaction::Transaction) -> Self { + Self { + signer_id: value.signer_id, + receiver_id: value.receiver_id, + actions: value.actions, + } + } +} + #[derive(Clone)] pub struct ActionContext { pub global_context: crate::GlobalContext, diff --git a/src/commands/transaction/mod.rs b/src/commands/transaction/mod.rs index 0284ed87..ffe5eed3 100644 --- a/src/commands/transaction/mod.rs +++ b/src/commands/transaction/mod.rs @@ -2,6 +2,7 @@ use strum::{EnumDiscriminants, EnumIter, EnumMessage}; mod construct_transaction; +mod print_transaction; mod send_meta_transaction; mod send_signed_transaction; mod sign_transaction; @@ -35,6 +36,11 @@ pub enum TransactionActions { ))] /// Sign previously prepared unsigned transaction SignTransaction(self::sign_transaction::SignTransaction), + #[strum_discriminants(strum( + message = "print-transaction - Print all fields of previously prepared transaction without modification" + ))] + /// Print previously prepared unsigned transaction without modification + PrintTransaction(self::print_transaction::PrintTransactionCommands), #[strum_discriminants(strum( message = "send-signed-transaction - Send a signed transaction" ))] diff --git a/src/commands/transaction/print_transaction/mod.rs b/src/commands/transaction/print_transaction/mod.rs new file mode 100644 index 00000000..0f6b3785 --- /dev/null +++ b/src/commands/transaction/print_transaction/mod.rs @@ -0,0 +1,28 @@ +#![allow(clippy::enum_variant_names, clippy::large_enum_variant)] +use strum::{EnumDiscriminants, EnumIter, EnumMessage}; + +mod signed; +mod unsigned; + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(context = crate::GlobalContext)] +pub struct PrintTransactionCommands { + #[interactive_clap(subcommand)] + show_transaction_actions: PrintTransactionActions, +} + +#[derive(Debug, EnumDiscriminants, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(context = crate::GlobalContext)] +#[strum_discriminants(derive(EnumMessage, EnumIter))] +pub enum PrintTransactionActions { + #[strum_discriminants(strum( + message = "unsigned - Print all fields of previously prepared unsigned transaction without modification" + ))] + /// Print previously prepared unsigned transaction without modification + Unsigned(self::unsigned::PrintTransaction), + #[strum_discriminants(strum( + message = "signed - Print all fields of previously prepared signed transaction without modification" + ))] + /// Send a signed transaction + Signed(self::signed::PrintTransaction), +} diff --git a/src/commands/transaction/print_transaction/signed/mod.rs b/src/commands/transaction/print_transaction/signed/mod.rs new file mode 100644 index 00000000..7bd26512 --- /dev/null +++ b/src/commands/transaction/print_transaction/signed/mod.rs @@ -0,0 +1,26 @@ +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(input_context = crate::GlobalContext)] +#[interactive_clap(output_context = PrintContext)] +pub struct PrintTransaction { + /// Enter the signed transaction encoded in base64: + signed_transaction: crate::types::signed_transaction::SignedTransactionAsBase64, +} + +#[derive(Debug, Clone)] +pub struct PrintContext; + +impl PrintContext { + pub fn from_previous_context( + _previous_context: crate::GlobalContext, + scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + let signed_transaction: near_primitives::transaction::SignedTransaction = + scope.signed_transaction.clone().into(); + + eprintln!("\nSigned transaction (full):\n"); + crate::common::print_full_signed_transaction(signed_transaction); + eprintln!(); + + Ok(Self) + } +} diff --git a/src/commands/transaction/print_transaction/unsigned/mod.rs b/src/commands/transaction/print_transaction/unsigned/mod.rs new file mode 100644 index 00000000..b31673c9 --- /dev/null +++ b/src/commands/transaction/print_transaction/unsigned/mod.rs @@ -0,0 +1,26 @@ +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(input_context = crate::GlobalContext)] +#[interactive_clap(output_context = PrintContext)] +pub struct PrintTransaction { + /// Enter the unsigned transaction encoded in base64: + unsigned_transaction: crate::types::transaction::TransactionAsBase64, +} + +#[derive(Debug, Clone)] +pub struct PrintContext; + +impl PrintContext { + pub fn from_previous_context( + _previous_context: crate::GlobalContext, + scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + let unsigned_transaction: near_primitives::transaction::Transaction = + scope.unsigned_transaction.clone().into(); + + eprintln!("\nUnsigned transaction (full):\n"); + crate::common::print_full_unsigned_transaction(unsigned_transaction); + eprintln!(); + + Ok(Self) + } +} diff --git a/src/commands/transaction/sign_transaction/mod.rs b/src/commands/transaction/sign_transaction/mod.rs index 5feba731..a838d7bb 100644 --- a/src/commands/transaction/sign_transaction/mod.rs +++ b/src/commands/transaction/sign_transaction/mod.rs @@ -23,11 +23,9 @@ impl SignTransactionContext { scope.unsigned_transaction.clone().into(); move |_network_config| { - Ok(crate::commands::PrepopulatedTransaction { - signer_id: unsigned_transaction.signer_id.clone(), - receiver_id: unsigned_transaction.receiver_id.clone(), - actions: unsigned_transaction.actions.clone(), - }) + Ok(crate::commands::PrepopulatedTransaction::from( + unsigned_transaction.clone(), + )) } }); diff --git a/src/common.rs b/src/common.rs index a8638f20..3611b8f5 100644 --- a/src/common.rs +++ b/src/common.rs @@ -5,6 +5,7 @@ use std::str::FromStr; use color_eyre::eyre::WrapErr; use futures::{StreamExt, TryStreamExt}; +use near_primitives::borsh::BorshSerialize; use prettytable::Table; use near_primitives::{hash::CryptoHash, types::BlockReference, views::AccessKeyPermissionView}; @@ -12,6 +13,7 @@ use near_primitives::{hash::CryptoHash, types::BlockReference, views::AccessKeyP pub type CliResult = color_eyre::eyre::Result<()>; use inquire::{Select, Text}; +use sha2::{Digest, Sha256}; use strum::IntoEnumIterator; pub fn get_near_exec_path() -> String { @@ -517,6 +519,40 @@ pub fn generate_keypair() -> color_eyre::eyre::Result { Ok(key_pair_properties) } +fn compute_hash(bytes: &[u8]) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(&bytes); + + let result = hasher.finalize(); + let mut hash = [0u8; 32]; + hash.copy_from_slice(&result[..]); + + hash +} + +pub fn print_full_signed_transaction(transaction: near_primitives::transaction::SignedTransaction) { + eprintln!("{:<55} {}", "signature:", transaction.signature); + crate::common::print_full_unsigned_transaction(transaction.transaction); +} + +pub fn print_full_unsigned_transaction(transaction: near_primitives::transaction::Transaction) { + let bytes = transaction + .try_to_vec() + .expect("Transaction is not expected to fail on serialization"); + eprintln!( + "{:<55} {}\n\n", + "transaction payload (without signature) sha256 hash:", + hex::encode(&compute_hash(&bytes)) + ); + + eprintln!("{:<13} {}", "public_key:", &transaction.public_key); + eprintln!("{:<13} {}", "nonce:", &transaction.nonce); + eprintln!("{:<13} {}", "block_hash:", &transaction.block_hash); + + let prepopulated = crate::commands::PrepopulatedTransaction::from(transaction); + print_unsigned_transaction(&prepopulated); +} + pub fn print_unsigned_transaction(transaction: &crate::commands::PrepopulatedTransaction) { eprintln!("{:<13} {}", "signer_id:", &transaction.signer_id); eprintln!("{:<13} {}", "receiver_id:", &transaction.receiver_id); diff --git a/src/transaction_signature_options/sign_with_ledger/mod.rs b/src/transaction_signature_options/sign_with_ledger/mod.rs index fdd23f2c..b5d07e48 100644 --- a/src/transaction_signature_options/sign_with_ledger/mod.rs +++ b/src/transaction_signature_options/sign_with_ledger/mod.rs @@ -74,7 +74,7 @@ impl SignLedgerContext { eprintln!("if you have concerns with trust to current device,"); eprintln!("on another device(s) with the following helper command on near CLI:"); eprintln!( - "$ {} transaction print-unsigned-transaction\n\n", + "$ {} transaction print-transaction unsigned\n\n", crate::common::get_near_exec_path() ); diff --git a/src/types/signed_transaction.rs b/src/types/signed_transaction.rs index 2685da60..18fa7cee 100644 --- a/src/types/signed_transaction.rs +++ b/src/types/signed_transaction.rs @@ -5,6 +5,12 @@ pub struct SignedTransactionAsBase64 { pub inner: near_primitives::transaction::SignedTransaction, } +impl From for near_primitives::transaction::SignedTransaction { + fn from(transaction: SignedTransactionAsBase64) -> Self { + transaction.inner + } +} + impl std::str::FromStr for SignedTransactionAsBase64 { type Err = String; fn from_str(s: &str) -> Result { From b34c0c7048237cd1b36b1cfd3e88800c0baeb721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 21 Nov 2023 15:25:31 +0200 Subject: [PATCH 05/14] chore: error msg-s in known cases --- .../sign_with_ledger/mod.rs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/transaction_signature_options/sign_with_ledger/mod.rs b/src/transaction_signature_options/sign_with_ledger/mod.rs index b5d07e48..8830f833 100644 --- a/src/transaction_signature_options/sign_with_ledger/mod.rs +++ b/src/transaction_signature_options/sign_with_ledger/mod.rs @@ -87,10 +87,24 @@ impl SignLedgerContext { ); let result = near_ledger::blind_sign_transaction(hash, hd_path); let signature = result.map_err(|err| { - color_eyre::Report::msg(format!( - "Error occurred while signing the transaction: {:?}", - err - )) + match err { + near_ledger::NEARLedgerError::BlindSignatureDisabled => { + color_eyre::Report::msg(format!( + "Blind signature is disabled in NEAR app's settings on Ledger device", + )) + }, + near_ledger::NEARLedgerError::BlindSignatureNotSupported => { + color_eyre::Report::msg(format!( + "Blind signature is not supported by the version of NEAR app installed on Ledger device", + )) + }, + err @ _ => { + color_eyre::Report::msg(format!( + "Error occurred while signing the transaction: {:?}", + err + )) + } + } })?; let signature = near_crypto::Signature::from_parts(near_crypto::KeyType::ED25519, &signature) From 3aa61cf82b97a2a3b3fce6e17e06ffa1ac0a505b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 21 Nov 2023 15:38:56 +0200 Subject: [PATCH 06/14] chore: less confusing message about device(s) --- src/transaction_signature_options/sign_with_ledger/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/transaction_signature_options/sign_with_ledger/mod.rs b/src/transaction_signature_options/sign_with_ledger/mod.rs index 8830f833..9d9120d4 100644 --- a/src/transaction_signature_options/sign_with_ledger/mod.rs +++ b/src/transaction_signature_options/sign_with_ledger/mod.rs @@ -71,8 +71,8 @@ impl SignLedgerContext { ); eprintln!("Before proceeding with blind signature,"); eprintln!("you have ability to verify unsigned transaction's details and exact SHA256 correspondence,"); - eprintln!("if you have concerns with trust to current device,"); - eprintln!("on another device(s) with the following helper command on near CLI:"); + eprintln!("if you have concerns with trust to current computer, where near CLI command is being run,"); + eprintln!("on another computer(s) with the following helper command on near CLI:"); eprintln!( "$ {} transaction print-transaction unsigned\n\n", crate::common::get_near_exec_path() From 2cccc8787671c7a112efdc64f34efca395d92b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Tue, 21 Nov 2023 16:16:15 +0200 Subject: [PATCH 07/14] chore: print tx hash both in hex and base58 --- src/common.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/common.rs b/src/common.rs index 3611b8f5..e3488ce8 100644 --- a/src/common.rs +++ b/src/common.rs @@ -531,7 +531,7 @@ fn compute_hash(bytes: &[u8]) -> [u8; 32] { } pub fn print_full_signed_transaction(transaction: near_primitives::transaction::SignedTransaction) { - eprintln!("{:<55} {}", "signature:", transaction.signature); + eprintln!("{:<25} {}\n", "signature:", transaction.signature); crate::common::print_full_unsigned_transaction(transaction.transaction); } @@ -539,12 +539,19 @@ pub fn print_full_unsigned_transaction(transaction: near_primitives::transaction let bytes = transaction .try_to_vec() .expect("Transaction is not expected to fail on serialization"); + eprintln!("transaction payload (without signature) hash:"); eprintln!( - "{:<55} {}\n\n", - "transaction payload (without signature) sha256 hash:", + "{:<25} {}", + "SHA256 hash(hex):", hex::encode(&compute_hash(&bytes)) ); + eprintln!( + "{:<25} {}\n\n", + "SHA256 hash(base58):", + CryptoHash::hash_bytes(&bytes) + ); + eprintln!("{:<13} {}", "public_key:", &transaction.public_key); eprintln!("{:<13} {}", "nonce:", &transaction.nonce); eprintln!("{:<13} {}", "block_hash:", &transaction.block_hash); From 1c9f116bef866d0e11347125e19225be84bae3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Fri, 24 Nov 2023 22:47:41 +0200 Subject: [PATCH 08/14] chore: update `near-ledger` dep from fork --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b791d55e..2a165353 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2307,7 +2307,7 @@ dependencies = [ [[package]] name = "near-ledger" version = "0.3.0" -source = "git+https://github.com/dj8yfo/near-ledger-rs.git?branch=blind_signing#4e9d0d44a7e7c51db1632e22439e865e6d42a47c" +source = "git+https://github.com/dj8yfo/near-ledger-rs.git?branch=blind_signing#f7cbaae45216150e3a888e46dc28ef89d1826967" dependencies = [ "ed25519-dalek", "hex 0.4.3", @@ -2315,7 +2315,7 @@ dependencies = [ "ledger-transport", "ledger-transport-hid", "log", - "sha2 0.10.8", + "near-primitives-core", "slip10", ] From 293dc8b830da7e09a4d56388b7e065383c951af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Fri, 24 Nov 2023 23:06:15 +0200 Subject: [PATCH 09/14] chore: [address 2 review comments] related to `CryptoHash` --- Cargo.lock | 1 - Cargo.toml | 1 - src/common.rs | 23 ++----------------- .../sign_with_ledger/mod.rs | 4 ++-- 4 files changed, 4 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a165353..6ab8ba80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2197,7 +2197,6 @@ dependencies = [ "semver", "serde", "serde_json", - "sha2 0.10.8", "shell-words", "shellexpand", "slip10", diff --git a/Cargo.toml b/Cargo.toml index 558e75a6..42c93612 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,6 @@ bip39 = { version = "2.0.0", features = ["rand"] } bs58 = "0.5" ed25519-dalek = { version = "1" } hex = "0.4.2" -sha2 = "0.10.8" linked-hash-map = { version = "0.5", features = ["serde_impl"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.57" diff --git a/src/common.rs b/src/common.rs index 96f8daac..b97da3a4 100644 --- a/src/common.rs +++ b/src/common.rs @@ -13,7 +13,6 @@ use near_primitives::{hash::CryptoHash, types::BlockReference, views::AccessKeyP pub type CliResult = color_eyre::eyre::Result<()>; use inquire::{Select, Text}; -use sha2::{Digest, Sha256}; use strum::IntoEnumIterator; pub fn get_near_exec_path() -> String { @@ -519,17 +518,6 @@ pub fn generate_keypair() -> color_eyre::eyre::Result { Ok(key_pair_properties) } -fn compute_hash(bytes: &[u8]) -> [u8; 32] { - let mut hasher = Sha256::new(); - hasher.update(&bytes); - - let result = hasher.finalize(); - let mut hash = [0u8; 32]; - hash.copy_from_slice(&result[..]); - - hash -} - pub fn print_full_signed_transaction(transaction: near_primitives::transaction::SignedTransaction) { eprintln!("{:<25} {}\n", "signature:", transaction.signature); crate::common::print_full_unsigned_transaction(transaction.transaction); @@ -539,16 +527,9 @@ pub fn print_full_unsigned_transaction(transaction: near_primitives::transaction let bytes = transaction .try_to_vec() .expect("Transaction is not expected to fail on serialization"); - eprintln!("transaction payload (without signature) hash:"); - eprintln!( - "{:<25} {}", - "SHA256 hash(hex):", - hex::encode(&compute_hash(&bytes)) - ); - eprintln!( - "{:<25} {}\n\n", - "SHA256 hash(base58):", + "{} {}\n\n", + "Unsigned transaction hash (Base58-encoded SHA-256 hash):", CryptoHash::hash_bytes(&bytes) ); diff --git a/src/transaction_signature_options/sign_with_ledger/mod.rs b/src/transaction_signature_options/sign_with_ledger/mod.rs index dd81a014..81c92d65 100644 --- a/src/transaction_signature_options/sign_with_ledger/mod.rs +++ b/src/transaction_signature_options/sign_with_ledger/mod.rs @@ -62,8 +62,8 @@ impl SignLedgerContext { unsigned_transaction: near_primitives::transaction::Transaction, ) -> color_eyre::eyre::Result { eprintln!("\n\nBuffer overflow on Ledger device occured. Transaction is too large for normal signature."); - eprintln!("\nThe following is SHA256 of unsigned transaction:"); - eprintln!("{}", hex::encode(&hash.0)); + eprintln!("\nThe following is Base58-encoded SHA-256 hash of unsigned transaction:"); + eprintln!("{}", hash.0); eprintln!( "\nUnsigned transaction (serialized as base64):\n{}\n", From 18305518ec27ba484eed3787bd9140442dc0bf6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 27 Nov 2023 18:32:01 +0200 Subject: [PATCH 10/14] build: fix clippy warns --- src/common.rs | 3 +-- .../sign_with_ledger/mod.rs | 14 +++++--------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/common.rs b/src/common.rs index b97da3a4..3a1b8d3f 100644 --- a/src/common.rs +++ b/src/common.rs @@ -528,8 +528,7 @@ pub fn print_full_unsigned_transaction(transaction: near_primitives::transaction .try_to_vec() .expect("Transaction is not expected to fail on serialization"); eprintln!( - "{} {}\n\n", - "Unsigned transaction hash (Base58-encoded SHA-256 hash):", + "Unsigned transaction hash (Base58-encoded SHA-256 hash): {}\n\n", CryptoHash::hash_bytes(&bytes) ); diff --git a/src/transaction_signature_options/sign_with_ledger/mod.rs b/src/transaction_signature_options/sign_with_ledger/mod.rs index 81c92d65..d64a110b 100644 --- a/src/transaction_signature_options/sign_with_ledger/mod.rs +++ b/src/transaction_signature_options/sign_with_ledger/mod.rs @@ -49,11 +49,11 @@ impl SignLedgerContext { fn input_blind_agree() -> color_eyre::eyre::Result { let options: Vec<&str> = vec!["Yes", "No"]; - return Ok( + Ok( Select::new("Do you agree to continue with blind signature? ", options) .prompt() .map(|selected| selected == "Yes")?, - ); + ) } fn blind_sign_subflow( @@ -89,16 +89,12 @@ impl SignLedgerContext { let signature = result.map_err(|err| { match err { near_ledger::NEARLedgerError::BlindSignatureDisabled => { - color_eyre::Report::msg(format!( - "Blind signature is disabled in NEAR app's settings on Ledger device", - )) + color_eyre::Report::msg("Blind signature is disabled in NEAR app's settings on Ledger device".to_string()) }, near_ledger::NEARLedgerError::BlindSignatureNotSupported => { - color_eyre::Report::msg(format!( - "Blind signature is not supported by the version of NEAR app installed on Ledger device", - )) + color_eyre::Report::msg("Blind signature is not supported by the version of NEAR app installed on Ledger device".to_string()) }, - err @ _ => { + err => { color_eyre::Report::msg(format!( "Error occurred while signing the transaction: {:?}", err From 18af9bbbdceaaa241873205b9f91a8c517de6d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Mon, 27 Nov 2023 18:47:49 +0200 Subject: [PATCH 11/14] chore: [address review comment] change text before blind sign prompt --- .../sign_with_ledger/mod.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/transaction_signature_options/sign_with_ledger/mod.rs b/src/transaction_signature_options/sign_with_ledger/mod.rs index d64a110b..4949522e 100644 --- a/src/transaction_signature_options/sign_with_ledger/mod.rs +++ b/src/transaction_signature_options/sign_with_ledger/mod.rs @@ -44,6 +44,11 @@ pub struct SignLedgerContext { on_after_sending_transaction_callback: crate::transaction_signature_options::OnAfterSendingTransactionCallback, } +const BLIND_SIGN_MEMO: &str = "Blind signature means that transaction is prepared by CLI, but cannot be reviewed on the Ledger device. \ + In order to be absolutely sure that the transaction you are signing is not forged, take the constructed transaction, \ + verify its content using NEAR CLI on another host or use any other tool capable of displaying unsigned NEAR transactions, \ + and confirm that the SHA256 hash matches the one displayed above and another identical one, that will be displayed on your Ledger device after confirming the prompt. \ + Following helper command on NEAR CLI can be used:"; impl SignLedgerContext { fn input_blind_agree() -> color_eyre::eyre::Result { @@ -69,16 +74,13 @@ impl SignLedgerContext { "\nUnsigned transaction (serialized as base64):\n{}\n", crate::types::transaction::TransactionAsBase64::from(unsigned_transaction) ); - eprintln!("Before proceeding with blind signature,"); - eprintln!("you have ability to verify unsigned transaction's details and exact SHA256 correspondence,"); - eprintln!("if you have concerns with trust to current computer, where near CLI command is being run,"); - eprintln!("on another computer(s) with the following helper command on near CLI:"); + eprintln!("{}", BLIND_SIGN_MEMO); eprintln!( "$ {} transaction print-transaction unsigned\n\n", crate::common::get_near_exec_path() ); - eprintln!("Make sure to enable blind sign in Near app's settings on Ledger device\n"); + eprintln!("Make sure to enable blind sign in NEAR app's settings on Ledger device\n"); let agree = Self::input_blind_agree()?; if agree { eprintln!( From 1585d27fe907bb2dc1341f470ead8cec77d9f64d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Wed, 29 Nov 2023 15:01:19 +0200 Subject: [PATCH 12/14] chore: update `near-ledger` dep --- Cargo.lock | 2 +- .../sign_with_ledger/mod.rs | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ab8ba80..ea3f5368 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2306,7 +2306,7 @@ dependencies = [ [[package]] name = "near-ledger" version = "0.3.0" -source = "git+https://github.com/dj8yfo/near-ledger-rs.git?branch=blind_signing#f7cbaae45216150e3a888e46dc28ef89d1826967" +source = "git+https://github.com/dj8yfo/near-ledger-rs.git?branch=blind_signing#7729ef8bc0782688d2604f54f68109cfd0eb1865" dependencies = [ "ed25519-dalek", "hex 0.4.3", diff --git a/src/transaction_signature_options/sign_with_ledger/mod.rs b/src/transaction_signature_options/sign_with_ledger/mod.rs index 4949522e..c4a99750 100644 --- a/src/transaction_signature_options/sign_with_ledger/mod.rs +++ b/src/transaction_signature_options/sign_with_ledger/mod.rs @@ -3,7 +3,6 @@ use std::str::FromStr; use color_eyre::eyre::{ContextCompat, WrapErr}; use inquire::{CustomType, Select, Text}; -use near_ledger::OnlyBlindSigning; use near_primitives::borsh::BorshSerialize; use slip10::BIP32Path; @@ -62,13 +61,13 @@ impl SignLedgerContext { } fn blind_sign_subflow( - hash: OnlyBlindSigning, + hash: near_primitives::hash::CryptoHash, hd_path: BIP32Path, unsigned_transaction: near_primitives::transaction::Transaction, ) -> color_eyre::eyre::Result { eprintln!("\n\nBuffer overflow on Ledger device occured. Transaction is too large for normal signature."); eprintln!("\nThe following is Base58-encoded SHA-256 hash of unsigned transaction:"); - eprintln!("{}", hash.0); + eprintln!("{}", hash); eprintln!( "\nUnsigned transaction (serialized as base64):\n{}\n", @@ -177,8 +176,12 @@ impl SignLedgerContext { near_crypto::Signature::from_parts(near_crypto::KeyType::ED25519, &signature) .expect("Signature is not expected to fail on deserialization") } - Err(near_ledger::NEARLedgerError::BufferOverflow(hash)) => { - Self::blind_sign_subflow(hash, seed_phrase_hd_path, unsigned_transaction.clone())? + Err(near_ledger::NEARLedgerError::BufferOverflow { transaction_hash }) => { + Self::blind_sign_subflow( + transaction_hash, + seed_phrase_hd_path, + unsigned_transaction.clone(), + )? } Err(near_ledger_error) => { return Err(color_eyre::Report::msg(format!( From 60a04911ef1bbe2ccd4ea199cae16e22aaeea284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Thu, 30 Nov 2023 10:55:07 +0200 Subject: [PATCH 13/14] chore: add msg about ledger pr with app-near version tracked --- src/transaction_signature_options/sign_with_ledger/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/transaction_signature_options/sign_with_ledger/mod.rs b/src/transaction_signature_options/sign_with_ledger/mod.rs index c4a99750..cc8bc6ef 100644 --- a/src/transaction_signature_options/sign_with_ledger/mod.rs +++ b/src/transaction_signature_options/sign_with_ledger/mod.rs @@ -93,7 +93,8 @@ impl SignLedgerContext { color_eyre::Report::msg("Blind signature is disabled in NEAR app's settings on Ledger device".to_string()) }, near_ledger::NEARLedgerError::BlindSignatureNotSupported => { - color_eyre::Report::msg("Blind signature is not supported by the version of NEAR app installed on Ledger device".to_string()) + color_eyre::Report::msg("Blind signature is not supported by the version of NEAR app installed on Ledger device. \ + Version of the app with the feature available is tracked in https://github.com/LedgerHQ/app-near/pull/32".to_string()) }, err => { color_eyre::Report::msg(format!( From d4a085af0454106313f075dbe129a34e7f637944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Fri, 1 Dec 2023 22:23:10 +0200 Subject: [PATCH 14/14] chore: update `near-ledger` dep from `crates.io` --- Cargo.lock | 3 ++- Cargo.toml | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea3f5368..d5ea0a77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2306,7 +2306,8 @@ dependencies = [ [[package]] name = "near-ledger" version = "0.3.0" -source = "git+https://github.com/dj8yfo/near-ledger-rs.git?branch=blind_signing#7729ef8bc0782688d2604f54f68109cfd0eb1865" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f92dbf396469644ca92d9565b4e935d0cb66115af30181fef5fe44a6e50d4e0" dependencies = [ "ed25519-dalek", "hex 0.4.3", diff --git a/Cargo.toml b/Cargo.toml index 42c93612..5b373032 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,8 +61,7 @@ thiserror = "1" bytesize = "1.1.0" prettytable = "0.10.0" -# near-ledger = { version = "0.2.0", optional = true } -near-ledger = { version = "0.3.0", git = "https://github.com/dj8yfo/near-ledger-rs.git", branch = "blind_signing", optional = true } +near-ledger = { version = "0.3.0", optional = true } near-crypto = "0.17.0" near-primitives = "0.17.0"