diff --git a/Cargo.toml b/Cargo.toml index 4ba6bbc..8d51997 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "near-ledger" -version = "0.4.0" +version = "0.5.0" edition = "2018" authors = ["Bohdan Khorolets "] description = "Transport library to integrate with NEAR Ledger app" @@ -12,13 +12,80 @@ keywords = ["ledger", "nearprotocol"] name = "get_version" [[example]] -name = "get_public_key" +name = "get_public_key_display" +path = "examples/get_public_key/display.rs" [[example]] -name = "sign_transaction" +name = "get_public_key_silent" +path = "examples/get_public_key/silent.rs" [[example]] -name = "blind_sign_transaction" +name = "get_wallet_id" +path = "examples/get_wallet_id.rs" + +[[example]] +name = "sign_transfer" +path = "examples/sign_transaction/transfer.rs" + +[[example]] +name = "sign_create_account" +path = "examples/sign_transaction/create_account.rs" + +[[example]] +name = "sign_delete_account_short" +path = "examples/sign_transaction/delete_account_short.rs" + +[[example]] +name = "sign_delete_account_long" +path = "examples/sign_transaction/delete_account_long.rs" + +[[example]] +name = "sign_delete_key_ed25519" +path = "examples/sign_transaction/delete_key_ed25519.rs" + +[[example]] +name = "sign_delete_key_secp256k1" +path = "examples/sign_transaction/delete_key_secp256k1.rs" + +[[example]] +name = "sign_stake" +path = "examples/sign_transaction/stake.rs" + +[[example]] +name = "sign_add_key_fullaccess" +path = "examples/sign_transaction/add_key_fullaccess.rs" + +[[example]] +name = "sign_add_key_functioncall" +path = "examples/sign_transaction/add_key_functioncall.rs" + +[[example]] +name = "sign_deploy_contract" +path = "examples/sign_transaction/deploy_contract.rs" + +[[example]] +name = "sign_functioncall_str" +path = "examples/sign_transaction/functioncall_str.rs" + +[[example]] +name = "sign_functioncall_bin" +path = "examples/sign_transaction/functioncall_bin.rs" + +[[example]] +name = "sign_functioncall_str_parse_err" +path = "examples/sign_transaction/functioncall_str_parse_err.rs" + +[[example]] +name = "sign_batch_all_actions" +path = "examples/sign_transaction/batch_all_actions.rs" + +[[example]] +name = "sign_nep_413_message" +path = "examples/sign_nep_413_message.rs" + +[[example]] +name = "sign_nep_366_delegate_action" +path = "examples/sign_nep_366_delegate_action.rs" [dependencies] ed25519-dalek = { version = "1" } @@ -29,8 +96,10 @@ slip10 = "0.4.3" log = "0.4.20" hex = "0.4.3" near-primitives-core = "0.20.0" +near-primitives = "0.20.0" [dev-dependencies] env_logger = "0.10.0" near-crypto = "0.20.0" near-primitives = "0.20.0" +near-account-id = { version = "1.0.0", features = ["internal_unstable"]} diff --git a/README.md b/README.md index e5bfee3..1b201ef 100644 --- a/README.md +++ b/README.md @@ -75,23 +75,62 @@ let signature = near_crypto::Signature::from_parts(near_crypto::KeyType::ED25519 ### Get version ```bash -RUST_LOG=get_version,near_ledger=info cargo run --example get_version +RUST_LOG=info cargo run --example get_version ``` ### Get PublicKey from Ledger +#### Display + +```bash +RUST_LOG=info cargo run --example get_public_key_display +``` +#### Silent + ```bash -RUST_LOG=get_public_key,near_ledger=info cargo run --example get_public_key +RUST_LOG=info cargo run --example get_public_key_silent ``` +### Get WalletID from Ledger + +```bash +RUST_LOG=info cargo run --example get_wallet_id +``` ### Sign a transaction +#### Transfer + +```bash +RUST_LOG=info cargo run --example sign_transfer +``` + +#### Other + +```bash +export RUST_LOG=info +cargo run --example sign_create_account +cargo run --example sign_delete_account_short +cargo run --example sign_delete_account_long +cargo run --example sign_delete_key_ed25519 +cargo run --example sign_delete_key_secp256k1 +cargo run --example sign_stake +cargo run --example sign_add_key_fullaccess +cargo run --example sign_add_key_functioncall +cargo run --example sign_deploy_contract +cargo run --example sign_functioncall_str +cargo run --example sign_functioncall_bin +cargo run --example sign_functioncall_str_parse_err +cargo run --example sign_batch_all_actions +``` + +### Sign a NEP-413 message + ```bash -RUST_LOG=sign_transaction,near_ledger=info cargo run --example sign_transaction +RUST_LOG=info cargo run --example sign_nep_413_message ``` -### Blind sign a transaction +### Sign a NEP-366 delegate action ```bash -RUST_LOG=blind_sign_transaction,near_ledger=info cargo run --example blind_sign_transaction +RUST_LOG=info cargo run --example sign_nep_366_delegate_action ``` diff --git a/examples/blind_sign_transaction.rs b/examples/blind_sign_transaction.rs deleted file mode 100644 index ad9b42e..0000000 --- a/examples/blind_sign_transaction.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::str::FromStr; - -use near_ledger::NEARLedgerError; - -use near_primitives_core::hash::CryptoHash; -use slip10::BIP32Path; - -#[path = "./common/lib.rs"] -mod common; - -fn long_tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { - let mut tx = common::tx_template(ledger_pub_key); - - const SIZE: usize = 27; - let transfers = (0..SIZE) - .map(|_el| { - near_primitives::transaction::Action::Transfer( - near_primitives::transaction::TransferAction { - deposit: 150000000000000000000000 * _el as u128, - }, - ) - }) - .collect::>(); - tx.actions = transfers; - tx -} - -fn compute_and_display_hash(bytes: &[u8]) -> CryptoHash { - log::info!("---"); - log::info!("SHA-256 hash:"); - let hash = CryptoHash::hash_bytes(&bytes); - log::info!("{:<15} : {}", "hash (hex)", hex::encode(hash.as_ref())); - log::info!("{:<15} : {}", "hash (base58)", hash); - log::info!("---"); - hash -} - -fn main() -> Result<(), NEARLedgerError> { - env_logger::builder().init(); - let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); - let public_key = near_ledger::get_public_key(hd_path.clone())?; - common::display_pub_key(public_key); - - let unsigned_transaction = long_tx(public_key); - - let bytes = common::serialize_and_display_tx(unsigned_transaction); - let payload = compute_and_display_hash(&bytes); - - let signature_bytes = near_ledger::blind_sign_transaction(payload, hd_path)?; - common::display_and_verify_signature(bytes, signature_bytes, public_key); - - Ok(()) -} diff --git a/examples/common/lib.rs b/examples/common/lib.rs index 9727114..17ec8c1 100644 --- a/examples/common/lib.rs +++ b/examples/common/lib.rs @@ -3,7 +3,12 @@ use std::str::FromStr; use ed25519_dalek::Signature; use ed25519_dalek::Verifier; +use near_ledger::NEARLedgerError; use near_primitives_core::{borsh, borsh::BorshSerialize, hash::CryptoHash, types::AccountId}; +use slip10::BIP32Path; + +use near_crypto::SecretKey; +use near_primitives::transaction::{DeployContractAction, FunctionCallAction}; pub fn display_pub_key(public_key: ed25519_dalek::PublicKey) { log::info!("---"); @@ -43,6 +48,172 @@ pub fn tx_template( } } +fn derive_secp256k1_public_key(public_key: &ed25519_dalek::PublicKey) -> near_crypto::PublicKey { + let sk = SecretKey::from_seed( + near_crypto::KeyType::SECP256K1, + &format!("{:?}", public_key), + ); + sk.public_key() +} + +#[allow(deprecated)] +pub fn batch_of_all_types_of_actions( + ledger_pub_key: ed25519_dalek::PublicKey, +) -> Vec { + let create_account = near_primitives::transaction::Action::CreateAccount( + near_primitives::transaction::CreateAccountAction {}, + ); + + let delete_account = near_primitives::transaction::Action::DeleteAccount( + near_primitives::transaction::DeleteAccountAction { + beneficiary_id: AccountId::new_unvalidated( + "dc7e34eecec3096a4a661e10932834f801149c49dba9b93322f6d9de18047f9c1b11b3b31673033936ad07bddc01f9da27d974811e480fb197c799e23480a489".to_string()), + }, + ); + + let delete_key_ed25519 = { + let sk = SecretKey::from_seed( + near_crypto::KeyType::ED25519, + &format!("{:?}", ledger_pub_key), + ); + let public_key_ed = sk.public_key(); + near_primitives::transaction::Action::DeleteKey(Box::new( + near_primitives::transaction::DeleteKeyAction { + public_key: public_key_ed, + }, + )) + }; + + let delete_key_secp256k1 = near_primitives::transaction::Action::DeleteKey(Box::new( + near_primitives::transaction::DeleteKeyAction { + public_key: derive_secp256k1_public_key(&ledger_pub_key), + }, + )); + + let stake = near_primitives::transaction::Action::Stake(Box::new( + near_primitives::transaction::StakeAction { + stake: 1157130000000000000000000, // 1.15713 NEAR, + public_key: derive_secp256k1_public_key(&ledger_pub_key), + }, + )); + + let add_key_fullaccess = near_primitives::transaction::Action::AddKey(Box::new( + near_primitives::transaction::AddKeyAction { + public_key: derive_secp256k1_public_key(&ledger_pub_key), + access_key: near_primitives_core::account::AccessKey { + nonce: 127127127127, + permission: near_primitives_core::account::AccessKeyPermission::FullAccess, + }, + }, + )); + + let add_key_function_call = { + let permission = { + let method_names = vec![ + "first_method", + "saturating_add_signed", + "iterator_chain_to_do_multiple_instances_of_an_operation_that_can_fail", + "from_residual", + "from_output", + "unwrap_err_unchecked", + "try_reserve_exact", + "first_method", + "saturating_add_signed", + "iterator_chain_to_do_multiple_instances_of_an_operation_that_can_fail", + ] + .into_iter() + .map(Into::into) + .collect::>(); + near_primitives_core::account::FunctionCallPermission { + allowance: Some(150000000000000000000), + receiver_id: + "dc7e34eecec3096a4a661e10932834f801149c49dba9b93322f6d9de18047f9c1b11b3b31673033936ad07bddc01f9da27d974811e480fb197c799e23480a489".into(), + method_names, + } + }; + near_primitives::transaction::Action::AddKey(Box::new( + near_primitives::transaction::AddKeyAction { + public_key: derive_secp256k1_public_key(&ledger_pub_key), + access_key: near_primitives_core::account::AccessKey { + nonce: 127127127127, + permission: near_primitives_core::account::AccessKeyPermission::FunctionCall( + permission, + ), + }, + }, + )) + }; + + let transfer = near_primitives::transaction::Action::Transfer( + near_primitives::transaction::TransferAction { + deposit: 150000000000000000000000, // 0.15 NEAR + }, + ); + + let deploy_contract = { + let code = core::iter::repeat(42u8).take(30).collect::>(); + + let code_hash = CryptoHash::hash_bytes(&code); + log::info!("Contract code hash: {:?}", code_hash); + near_primitives::transaction::Action::DeployContract(DeployContractAction { code }) + }; + + let function_call_str_args = { + let args_str = r#"{"previous_vesting_schedule_with_salt":{"vesting_schedule":{"start_timestamp":"1577919600000000000","cliff_timestamp":"1609455600000000000","end_timestamp":"1704150000000000000"},"salt":"7bc709c22801118b743fae3866edb4dea1630a97ab9cd67e993428b94a0f397a"}, "vesting_schedule_with_salt":{"vesting_schedule":{"start_timestamp":"1577919600000000000","cliff_timestamp":"1609455600000000000","end_timestamp":"1704150000000000000"},"salt":"7bc709c22801118b743fae3866edb4dea1630a97ab9cd67e993428b94a0f397aababab"}}"#; + + let f_call = FunctionCallAction { + method_name: "saturating_add_signed".to_string(), + args: args_str.as_bytes().to_vec(), + gas: 127127122121, + deposit: 150000000000000000000000, // 0.15 NEAR, + }; + near_primitives::transaction::Action::FunctionCall(Box::new(f_call)) + }; + + let function_call_binary_args = { + let args_binary = hex::decode("204f6e206f6c646572207465726d696e616c732c2074686520756e64657273636f726520636f646520697320646973706c617965642061732061206c6566740a202020202020206172726f772c2063616c6c6564206261636b6172726f772c2074686520636172657420697320646973706c6179656420617320616e2075702d6172726f770a20202020202020616e642074686520766572746963616c2062617220686173206120686f6c6520696e20746865206d6964646c652e0a0a2020202020202055707065726361736520616e64206c6f77657263617365206368617261637465727320646966666572206279206a757374206f6e652062697420616e64207468650a20202020202020415343494920636861726163746572203220646966666572732066726f6d2074686520646f75626c652071756f7465206279206a757374206f6e65206269742c0a20202020202020746f6f2e202054686174206d616465206974206d7563682065617369657220746f20656e636f64652063686172616374657273206d656368616e6963616c6c790a202020202020206f7220776974682061206e6f6e2d6d6963726f636f6e74726f6c6c65722d626173656420656c656374726f6e6963206b6579626f61726420616e6420746861740a2020202020202070616972696e672077617320666f756e64206f6e206f6c642074656c6574797065732e0a").unwrap(); + + let f_call = FunctionCallAction { + method_name: "saturating_add_signed".to_string(), + args: args_binary, + gas: 127127122121, + deposit: 150000000000000000000000, // 0.15 NEAR, + }; + near_primitives::transaction::Action::FunctionCall(Box::new(f_call)) + }; + + let function_call_binary_args_after_parse_error = { + let mut bytes = vec![]; + bytes.push(123u8); + + bytes.extend((0..255).into_iter().collect::>()); + + let f_call = FunctionCallAction { + method_name: "saturating_add_signed".to_string(), + args: bytes, + gas: 127127122121, + deposit: 150000000000000000000000, // 0.15 NEAR, + }; + + near_primitives::transaction::Action::FunctionCall(Box::new(f_call)) + }; + + vec![ + create_account, + delete_account, + delete_key_ed25519, + delete_key_secp256k1, + stake, + add_key_fullaccess, + add_key_function_call, + transfer, + deploy_contract, + function_call_str_args, + function_call_binary_args, + function_call_binary_args_after_parse_error, + ] +} + pub fn serialize_and_display_tx(transaction: near_primitives::transaction::Transaction) -> Vec { log::info!("---"); log::info!("Transaction:"); @@ -53,12 +224,7 @@ pub fn serialize_and_display_tx(transaction: near_primitives::transaction::Trans log::info!("---"); bytes } - -pub fn display_and_verify_signature( - msg: Vec, - signature_bytes: Vec, - public_key: ed25519_dalek::PublicKey, -) { +pub fn display_signature(signature_bytes: Vec) -> ed25519_dalek::Signature { log::info!("---"); log::info!("Signature:"); let signature = Signature::from_bytes(&signature_bytes).unwrap(); @@ -68,9 +234,37 @@ pub fn display_and_verify_signature( .expect("Signature is not expected to fail on deserialization"); log::info!("{:<20} : {}", "signature (hex)", signature); log::info!("{:<20} : {}", "signature (base58)", signature_near); + signature +} +pub fn display_and_verify_signature( + msg: Vec, + signature_bytes: Vec, + public_key: ed25519_dalek::PublicKey, +) { + let signature = display_signature(signature_bytes); assert!(public_key .verify(&CryptoHash::hash_bytes(&msg).as_ref(), &signature) .is_ok()); log::info!("---"); } + +pub fn get_key_sign_and_verify_flow(f_transaction: F) -> Result<(), NEARLedgerError> +where + F: FnOnce(ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction, +{ + env_logger::builder().init(); + let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); + + let ledger_pub_key = near_ledger::get_public_key_with_display_flag(hd_path.clone(), false)?; + display_pub_key(ledger_pub_key); + + let unsigned_transaction = f_transaction(ledger_pub_key); + + let bytes = serialize_and_display_tx(unsigned_transaction); + let signature_bytes = near_ledger::sign_transaction(bytes.clone(), hd_path)?; + + display_and_verify_signature(bytes, signature_bytes, ledger_pub_key); + + Ok(()) +} diff --git a/examples/get_public_key/display.rs b/examples/get_public_key/display.rs new file mode 100644 index 0000000..ae626f9 --- /dev/null +++ b/examples/get_public_key/display.rs @@ -0,0 +1,18 @@ +use std::str::FromStr; + +use near_ledger::{get_public_key_with_display_flag, NEARLedgerError}; +use slip10::BIP32Path; + +#[path = "../common/lib.rs"] +mod common; + +fn main() -> Result<(), NEARLedgerError> { + env_logger::builder().init(); + let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); + + let public_key = get_public_key_with_display_flag(hd_path, true)?; + + common::display_pub_key(public_key); + + Ok(()) +} diff --git a/examples/get_public_key/silent.rs b/examples/get_public_key/silent.rs new file mode 100644 index 0000000..afd29b8 --- /dev/null +++ b/examples/get_public_key/silent.rs @@ -0,0 +1,18 @@ +use std::str::FromStr; + +use near_ledger::{get_public_key_with_display_flag, NEARLedgerError}; +use slip10::BIP32Path; + +#[path = "../common/lib.rs"] +mod common; + +fn main() -> Result<(), NEARLedgerError> { + env_logger::builder().init(); + let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); + + let public_key = get_public_key_with_display_flag(hd_path, false)?; + + common::display_pub_key(public_key); + + Ok(()) +} diff --git a/examples/get_public_key.rs b/examples/get_wallet_id.rs similarity index 74% rename from examples/get_public_key.rs rename to examples/get_wallet_id.rs index fa1614e..1fead57 100644 --- a/examples/get_public_key.rs +++ b/examples/get_wallet_id.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use near_ledger::{get_public_key, NEARLedgerError}; +use near_ledger::{get_wallet_id, NEARLedgerError}; use slip10::BIP32Path; #[path = "./common/lib.rs"] @@ -10,7 +10,7 @@ fn main() -> Result<(), NEARLedgerError> { env_logger::builder().init(); let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); - let public_key = get_public_key(hd_path)?; + let public_key = get_wallet_id(hd_path)?; common::display_pub_key(public_key); diff --git a/examples/sign_nep_366_delegate_action.rs b/examples/sign_nep_366_delegate_action.rs new file mode 100644 index 0000000..1b1f27a --- /dev/null +++ b/examples/sign_nep_366_delegate_action.rs @@ -0,0 +1,55 @@ +use std::{convert::TryInto, str::FromStr}; + +use near_account_id::AccountId; +use near_crypto::Signature; +use near_ledger::NEARLedgerError; +use near_primitives::action::delegate::{DelegateAction, SignedDelegateAction}; +use slip10::BIP32Path; + +use crate::common::display_pub_key; + +#[path = "./common/lib.rs"] +mod common; + +fn main() -> Result<(), NEARLedgerError> { + env_logger::builder().init(); + + let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); + let ledger_pub_key = near_ledger::get_public_key_with_display_flag(hd_path.clone(), false)?; + display_pub_key(ledger_pub_key); + + let sender_id = AccountId::from_str("bob.near").unwrap(); + + let actions = common::batch_of_all_types_of_actions(ledger_pub_key) + .into_iter() + .map(|action| action.try_into().unwrap()) + .collect::>(); + + let ledger_pub_key = near_crypto::PublicKey::ED25519(near_crypto::ED25519PublicKey::from( + ledger_pub_key.to_bytes(), + )); + + let delegate_action = DelegateAction { + sender_id, + receiver_id: AccountId::from_str("alice.near").unwrap(), + actions, + nonce: 127127122121, + max_block_height: 100500, + public_key: ledger_pub_key, + }; + + let signature_bytes = + near_ledger::sign_message_nep366_delegate_action(&delegate_action, hd_path)?; + + let signature = Signature::from_parts(near_crypto::KeyType::ED25519, &signature_bytes).unwrap(); + + let signed_delegate = SignedDelegateAction { + delegate_action, + signature, + }; + log::info!("{:#?}", signed_delegate); + assert!(signed_delegate.verify()); + + common::display_signature(signature_bytes); + Ok(()) +} diff --git a/examples/sign_nep_413_message.rs b/examples/sign_nep_413_message.rs new file mode 100644 index 0000000..ef4c9ac --- /dev/null +++ b/examples/sign_nep_413_message.rs @@ -0,0 +1,61 @@ +use std::str::FromStr; + +use ed25519_dalek::Signature; +use ed25519_dalek::Verifier; +use near_ledger::{NEARLedgerError, NEP413Payload}; +use near_primitives::signable_message::{MessageDiscriminant, SignableMessage}; +use near_primitives_core::{borsh, hash::CryptoHash}; +use slip10::BIP32Path; + +use crate::common::display_pub_key; + +#[path = "./common/lib.rs"] +mod common; + +pub fn display_and_verify_signature( + msg: &NEP413Payload, + signature_bytes: Vec, + public_key: ed25519_dalek::PublicKey, +) { + log::info!("---"); + log::info!("Signature:"); + let signature = Signature::from_bytes(&signature_bytes).unwrap(); + + let msg_discriminant = MessageDiscriminant::new_off_chain(413).unwrap(); + let signable_message = SignableMessage { + discriminant: msg_discriminant, + msg, + }; + + let hash = CryptoHash::hash_bytes(&borsh::to_vec(&signable_message).unwrap()); + + let signature_near = + near_crypto::Signature::from_parts(near_crypto::KeyType::ED25519, &signature_bytes) + .expect("Signature is not expected to fail on deserialization"); + log::info!("{:<20} : {}", "signature (hex)", signature); + log::info!("{:<20} : {}", "signature (base58)", signature_near); + + assert!(public_key.verify(&hash.as_ref(), &signature).is_ok()); + log::info!("---"); +} + +fn main() -> Result<(), NEARLedgerError> { + env_logger::builder().init(); + + let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); + let public_key = near_ledger::get_public_key_with_display_flag(hd_path.clone(), false)?; + display_pub_key(public_key); + + let msg = NEP413Payload { + messsage: "Makes it possible to authenticate users without having to add new access keys. This will improve UX, save money and will not increase the on-chain storage of the users' accounts./Makes it possible to authenticate users without having to add new access keys. This will improve UX, save money and will not increase the on-chain storage of the users' accounts./Makes it possible to authenticate users without having to add new access keys. This will improve UX, save money and will not increase the on-chain storage of the users' accounts.".to_string(), + nonce: [42; 32], + recipient: "alice.near".to_string(), + callback_url: Some("myapp.com/callback".to_string()) + }; + + let signature_bytes = near_ledger::sign_message_nep413(&msg, hd_path)?; + + display_and_verify_signature(&msg, signature_bytes, public_key); + + Ok(()) +} diff --git a/examples/sign_transaction.rs b/examples/sign_transaction.rs deleted file mode 100644 index 983d6bc..0000000 --- a/examples/sign_transaction.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::str::FromStr; - -use near_ledger::NEARLedgerError; - -use slip10::BIP32Path; - -#[path = "./common/lib.rs"] -mod common; - -fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { - let mut tx = common::tx_template(ledger_pub_key); - tx.actions = vec![near_primitives::transaction::Action::Transfer( - near_primitives::transaction::TransferAction { - deposit: 150000000000000000000000, - }, - )]; - tx -} - -fn main() -> Result<(), NEARLedgerError> { - env_logger::builder().init(); - let hd_path = BIP32Path::from_str("44'/397'/0'/0'/1'").unwrap(); - - let public_key = near_ledger::get_public_key(hd_path.clone())?; - common::display_pub_key(public_key); - - let unsigned_transaction = tx(public_key); - - let bytes = common::serialize_and_display_tx(unsigned_transaction); - let signature_bytes = near_ledger::sign_transaction(bytes.clone(), hd_path)?; - - common::display_and_verify_signature(bytes, signature_bytes, public_key); - - Ok(()) -} diff --git a/examples/sign_transaction/add_key_fullaccess.rs b/examples/sign_transaction/add_key_fullaccess.rs new file mode 100644 index 0000000..a3c5cf4 --- /dev/null +++ b/examples/sign_transaction/add_key_fullaccess.rs @@ -0,0 +1,28 @@ +use near_crypto::SecretKey; +use near_ledger::NEARLedgerError; + +#[path = "../common/lib.rs"] +mod common; + +fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { + let mut tx = common::tx_template(ledger_pub_key.clone()); + let sk = SecretKey::from_seed( + near_crypto::KeyType::SECP256K1, + &format!("{:?}", ledger_pub_key), + ); + let public_key = sk.public_key(); + tx.actions = vec![near_primitives::transaction::Action::AddKey(Box::new( + near_primitives::transaction::AddKeyAction { + public_key, + access_key: near_primitives_core::account::AccessKey { + nonce: 127127127127, + permission: near_primitives_core::account::AccessKeyPermission::FullAccess, + }, + }, + ))]; + tx +} + +fn main() -> Result<(), NEARLedgerError> { + common::get_key_sign_and_verify_flow(tx) +} diff --git a/examples/sign_transaction/add_key_functioncall.rs b/examples/sign_transaction/add_key_functioncall.rs new file mode 100644 index 0000000..18e8aeb --- /dev/null +++ b/examples/sign_transaction/add_key_functioncall.rs @@ -0,0 +1,53 @@ +use near_crypto::SecretKey; +use near_ledger::NEARLedgerError; + +#[path = "../common/lib.rs"] +mod common; + +fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { + let mut tx = common::tx_template(ledger_pub_key.clone()); + let sk = SecretKey::from_seed( + near_crypto::KeyType::SECP256K1, + &format!("{:?}", ledger_pub_key), + ); + let public_key = sk.public_key(); + let method_names = vec![ + "first_method", + "saturating_add_signed", + "iterator_chain_to_do_multiple_instances_of_an_operation_that_can_fail", + "from_residual", + "from_output", + "unwrap_err_unchecked", + "try_reserve_exact", + "first_method", + "saturating_add_signed", + "iterator_chain_to_do_multiple_instances_of_an_operation_that_can_fail", + ] + .into_iter() + .map(Into::into) + .collect::>(); + + let permission = near_primitives_core::account::FunctionCallPermission { + allowance: Some(150000000000000000000), + receiver_id: + "dc7e34eecec3096a4a661e10932834f801149c49dba9b93322f6d9de18047f9c1b11b3b31673033936ad07bddc01f9da27d974811e480fb197c799e23480a489".into(), + method_names, + }; + + tx.actions = vec![near_primitives::transaction::Action::AddKey(Box::new( + near_primitives::transaction::AddKeyAction { + public_key, + access_key: near_primitives_core::account::AccessKey { + nonce: 127127127127, + permission: near_primitives_core::account::AccessKeyPermission::FunctionCall( + permission, + ), + }, + }, + ))]; + tx +} + +fn main() -> Result<(), NEARLedgerError> { + common::get_key_sign_and_verify_flow(tx) +} diff --git a/examples/sign_transaction/batch_all_actions.rs b/examples/sign_transaction/batch_all_actions.rs new file mode 100644 index 0000000..8075b3e --- /dev/null +++ b/examples/sign_transaction/batch_all_actions.rs @@ -0,0 +1,14 @@ +use near_ledger::NEARLedgerError; + +#[path = "../common/lib.rs"] +mod common; + +fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { + let mut tx = common::tx_template(ledger_pub_key.clone()); + tx.actions = common::batch_of_all_types_of_actions(ledger_pub_key); + tx +} + +fn main() -> Result<(), NEARLedgerError> { + common::get_key_sign_and_verify_flow(tx) +} diff --git a/examples/sign_transaction/create_account.rs b/examples/sign_transaction/create_account.rs new file mode 100644 index 0000000..d2cb39c --- /dev/null +++ b/examples/sign_transaction/create_account.rs @@ -0,0 +1,16 @@ +use near_ledger::NEARLedgerError; + +#[path = "../common/lib.rs"] +mod common; + +fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { + let mut tx = common::tx_template(ledger_pub_key); + tx.actions = vec![near_primitives::transaction::Action::CreateAccount( + near_primitives::transaction::CreateAccountAction {}, + )]; + tx +} + +fn main() -> Result<(), NEARLedgerError> { + common::get_key_sign_and_verify_flow(tx) +} diff --git a/examples/sign_transaction/delete_account_long.rs b/examples/sign_transaction/delete_account_long.rs new file mode 100644 index 0000000..4b5e3ff --- /dev/null +++ b/examples/sign_transaction/delete_account_long.rs @@ -0,0 +1,21 @@ +use near_account_id::AccountId; +use near_ledger::NEARLedgerError; + +#[path = "../common/lib.rs"] +mod common; + +#[allow(deprecated)] +fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { + let mut tx = common::tx_template(ledger_pub_key); + tx.actions = vec![near_primitives::transaction::Action::DeleteAccount( + near_primitives::transaction::DeleteAccountAction { + beneficiary_id: AccountId::new_unvalidated( + "dc7e34eecec3096a4a661e10932834f801149c49dba9b93322f6d9de18047f9c1b11b3b31673033936ad07bddc01f9da27d974811e480fb197c799e23480a489".to_string()), + }, + )]; + tx +} + +fn main() -> Result<(), NEARLedgerError> { + common::get_key_sign_and_verify_flow(tx) +} diff --git a/examples/sign_transaction/delete_account_short.rs b/examples/sign_transaction/delete_account_short.rs new file mode 100644 index 0000000..bcd3203 --- /dev/null +++ b/examples/sign_transaction/delete_account_short.rs @@ -0,0 +1,21 @@ +use std::str::FromStr; + +use near_account_id::AccountId; +use near_ledger::NEARLedgerError; + +#[path = "../common/lib.rs"] +mod common; + +fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { + let mut tx = common::tx_template(ledger_pub_key); + tx.actions = vec![near_primitives::transaction::Action::DeleteAccount( + near_primitives::transaction::DeleteAccountAction { + beneficiary_id: AccountId::from_str("bob.near").unwrap(), + }, + )]; + tx +} + +fn main() -> Result<(), NEARLedgerError> { + common::get_key_sign_and_verify_flow(tx) +} diff --git a/examples/sign_transaction/delete_key_ed25519.rs b/examples/sign_transaction/delete_key_ed25519.rs new file mode 100644 index 0000000..8672d0f --- /dev/null +++ b/examples/sign_transaction/delete_key_ed25519.rs @@ -0,0 +1,22 @@ +use near_crypto::SecretKey; +use near_ledger::NEARLedgerError; + +#[path = "../common/lib.rs"] +mod common; + +fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { + let mut tx = common::tx_template(ledger_pub_key.clone()); + let sk = SecretKey::from_seed( + near_crypto::KeyType::ED25519, + &format!("{:?}", ledger_pub_key), + ); + let public_key = sk.public_key(); + tx.actions = vec![near_primitives::transaction::Action::DeleteKey(Box::new( + near_primitives::transaction::DeleteKeyAction { public_key }, + ))]; + tx +} + +fn main() -> Result<(), NEARLedgerError> { + common::get_key_sign_and_verify_flow(tx) +} diff --git a/examples/sign_transaction/delete_key_secp256k1.rs b/examples/sign_transaction/delete_key_secp256k1.rs new file mode 100644 index 0000000..ea7c722 --- /dev/null +++ b/examples/sign_transaction/delete_key_secp256k1.rs @@ -0,0 +1,22 @@ +use near_crypto::SecretKey; +use near_ledger::NEARLedgerError; + +#[path = "../common/lib.rs"] +mod common; + +fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { + let mut tx = common::tx_template(ledger_pub_key.clone()); + let sk = SecretKey::from_seed( + near_crypto::KeyType::SECP256K1, + &format!("{:?}", ledger_pub_key), + ); + let public_key = sk.public_key(); + tx.actions = vec![near_primitives::transaction::Action::DeleteKey(Box::new( + near_primitives::transaction::DeleteKeyAction { public_key }, + ))]; + tx +} + +fn main() -> Result<(), NEARLedgerError> { + common::get_key_sign_and_verify_flow(tx) +} diff --git a/examples/sign_transaction/deploy_contract.rs b/examples/sign_transaction/deploy_contract.rs new file mode 100644 index 0000000..696db06 --- /dev/null +++ b/examples/sign_transaction/deploy_contract.rs @@ -0,0 +1,23 @@ +use near_ledger::NEARLedgerError; +use near_primitives::transaction::DeployContractAction; +use near_primitives_core::hash::CryptoHash; + +#[path = "../common/lib.rs"] +mod common; + +fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { + let mut tx = common::tx_template(ledger_pub_key); + + let code = core::iter::repeat(42u8).take(3000).collect::>(); + + let code_hash = CryptoHash::hash_bytes(&code); + log::info!("Contract code hash: {:?}", code_hash); + tx.actions = vec![near_primitives::transaction::Action::DeployContract( + DeployContractAction { code }, + )]; + tx +} + +fn main() -> Result<(), NEARLedgerError> { + common::get_key_sign_and_verify_flow(tx) +} diff --git a/examples/sign_transaction/functioncall_bin.rs b/examples/sign_transaction/functioncall_bin.rs new file mode 100644 index 0000000..d3a390a --- /dev/null +++ b/examples/sign_transaction/functioncall_bin.rs @@ -0,0 +1,28 @@ +use near_ledger::NEARLedgerError; +use near_primitives::transaction::FunctionCallAction; + +#[path = "../common/lib.rs"] +mod common; + +fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { + let mut tx = common::tx_template(ledger_pub_key.clone()); + + let args = hex::decode("204f6e206f6c646572207465726d696e616c732c2074686520756e64657273636f726520636f646520697320646973706c617965642061732061206c6566740a202020202020206172726f772c2063616c6c6564206261636b6172726f772c2074686520636172657420697320646973706c6179656420617320616e2075702d6172726f770a20202020202020616e642074686520766572746963616c2062617220686173206120686f6c6520696e20746865206d6964646c652e0a0a2020202020202055707065726361736520616e64206c6f77657263617365206368617261637465727320646966666572206279206a757374206f6e652062697420616e64207468650a20202020202020415343494920636861726163746572203220646966666572732066726f6d2074686520646f75626c652071756f7465206279206a757374206f6e65206269742c0a20202020202020746f6f2e202054686174206d616465206974206d7563682065617369657220746f20656e636f64652063686172616374657273206d656368616e6963616c6c790a202020202020206f7220776974682061206e6f6e2d6d6963726f636f6e74726f6c6c65722d626173656420656c656374726f6e6963206b6579626f61726420616e6420746861740a2020202020202070616972696e672077617320666f756e64206f6e206f6c642074656c6574797065732e0a").unwrap(); + + let f_call = FunctionCallAction { + method_name: "saturating_add_signed".to_string(), + args, + gas: 127127122121, + deposit: 150000000000000000000000, // 0.15 NEAR, + }; + + tx.actions = vec![near_primitives::transaction::Action::FunctionCall( + Box::new(f_call), + )]; + tx +} + +fn main() -> Result<(), NEARLedgerError> { + common::get_key_sign_and_verify_flow(tx)?; + Ok(()) +} diff --git a/examples/sign_transaction/functioncall_str.rs b/examples/sign_transaction/functioncall_str.rs new file mode 100644 index 0000000..ada585d --- /dev/null +++ b/examples/sign_transaction/functioncall_str.rs @@ -0,0 +1,28 @@ +use near_ledger::NEARLedgerError; +use near_primitives::transaction::FunctionCallAction; + +#[path = "../common/lib.rs"] +mod common; + +fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { + let mut tx = common::tx_template(ledger_pub_key.clone()); + + let args = r#"{"previous_vesting_schedule_with_salt":{"vesting_schedule":{"start_timestamp":"1577919600000000000","cliff_timestamp":"1609455600000000000","end_timestamp":"1704150000000000000"},"salt":"7bc709c22801118b743fae3866edb4dea1630a97ab9cd67e993428b94a0f397a"}, "vesting_schedule_with_salt":{"vesting_schedule":{"start_timestamp":"1577919600000000000","cliff_timestamp":"1609455600000000000","end_timestamp":"1704150000000000000"},"salt":"7bc709c22801118b743fae3866edb4dea1630a97ab9cd67e993428b94a0f397a"}}"#; + + let f_call = FunctionCallAction { + method_name: "saturating_add_signed".to_string(), + args: args.as_bytes().to_vec(), + gas: 127127122121, + deposit: 150000000000000000000000, // 0.15 NEAR, + }; + + tx.actions = vec![near_primitives::transaction::Action::FunctionCall( + Box::new(f_call), + )]; + tx +} + +fn main() -> Result<(), NEARLedgerError> { + common::get_key_sign_and_verify_flow(tx)?; + Ok(()) +} diff --git a/examples/sign_transaction/functioncall_str_parse_err.rs b/examples/sign_transaction/functioncall_str_parse_err.rs new file mode 100644 index 0000000..ac88cc1 --- /dev/null +++ b/examples/sign_transaction/functioncall_str_parse_err.rs @@ -0,0 +1,31 @@ +use near_ledger::NEARLedgerError; +use near_primitives::transaction::FunctionCallAction; + +#[path = "../common/lib.rs"] +mod common; + +fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { + let mut tx = common::tx_template(ledger_pub_key.clone()); + + let mut bytes = vec![]; + bytes.push(123u8); + + bytes.extend((0..255).into_iter().collect::>()); + + let f_call = FunctionCallAction { + method_name: "saturating_add_signed".to_string(), + args: bytes, + gas: 127127122121, + deposit: 150000000000000000000000, // 0.15 NEAR, + }; + + tx.actions = vec![near_primitives::transaction::Action::FunctionCall( + Box::new(f_call), + )]; + tx +} + +fn main() -> Result<(), NEARLedgerError> { + common::get_key_sign_and_verify_flow(tx)?; + Ok(()) +} diff --git a/examples/sign_transaction/stake.rs b/examples/sign_transaction/stake.rs new file mode 100644 index 0000000..380e775 --- /dev/null +++ b/examples/sign_transaction/stake.rs @@ -0,0 +1,25 @@ +use near_crypto::SecretKey; +use near_ledger::NEARLedgerError; + +#[path = "../common/lib.rs"] +mod common; + +fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { + let mut tx = common::tx_template(ledger_pub_key.clone()); + let sk = SecretKey::from_seed( + near_crypto::KeyType::SECP256K1, + &format!("{:?}", ledger_pub_key), + ); + let public_key = sk.public_key(); + tx.actions = vec![near_primitives::transaction::Action::Stake(Box::new( + near_primitives::transaction::StakeAction { + stake: 1157130000000000000000000, // 1.15713 NEAR, + public_key, + }, + ))]; + tx +} + +fn main() -> Result<(), NEARLedgerError> { + common::get_key_sign_and_verify_flow(tx) +} diff --git a/examples/sign_transaction/transfer.rs b/examples/sign_transaction/transfer.rs new file mode 100644 index 0000000..1c5eada --- /dev/null +++ b/examples/sign_transaction/transfer.rs @@ -0,0 +1,18 @@ +use near_ledger::NEARLedgerError; + +#[path = "../common/lib.rs"] +mod common; + +fn tx(ledger_pub_key: ed25519_dalek::PublicKey) -> near_primitives::transaction::Transaction { + let mut tx = common::tx_template(ledger_pub_key); + tx.actions = vec![near_primitives::transaction::Action::Transfer( + near_primitives::transaction::TransferAction { + deposit: 150000000000000000000000, // 0.15 NEAR + }, + )]; + tx +} + +fn main() -> Result<(), NEARLedgerError> { + common::get_key_sign_and_verify_flow(tx) +} diff --git a/src/lib.rs b/src/lib.rs index 01df9fe..2f92fb6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,30 +8,28 @@ use ledger_transport_hid::{ hidapi::{HidApi, HidError}, LedgerHIDError, TransportNativeHID, }; -use near_primitives_core::hash::CryptoHash; +use near_primitives::action::delegate::DelegateAction; +use near_primitives_core::borsh::{self, BorshSerialize}; const CLA: u8 = 0x80; // Instruction class const INS_GET_PUBLIC_KEY: u8 = 4; // Instruction code to get public key +const INS_GET_WALLET_ID: u8 = 0x05; // Get Wallet ID const INS_GET_VERSION: u8 = 6; // Instruction code to get app version from the Ledger const INS_SIGN_TRANSACTION: u8 = 2; // Instruction code to sign a transaction on the Ledger +const INS_SIGN_NEP413_MESSAGE: u8 = 7; // Instruction code to sign a nep-413 message with Ledger +const INS_SIGN_NEP366_DELEGATE_ACTION: u8 = 8; // Instruction code to sign a nep-413 message with Ledger const NETWORK_ID: u8 = 'W' as u8; // Instruction parameter 2 const RETURN_CODE_OK: u16 = 36864; // APDUAnswer.retcode which means success from Ledger -const CHUNK_SIZE: usize = 128; // Chunk size to be sent to Ledger +const CHUNK_SIZE: usize = 250; // Chunk size to be sent to Ledger /// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with pub type BorshSerializedUnsignedTransaction = Vec; -const SIGN_NORMAL: u8 = 0; -const SIGN_NORMAL_LAST_CHUNK: u8 = 0x80; -const SIGN_BLIND: u8 = 1; +const P1_GET_PUB_DISPLAY: u8 = 0; +const P1_GET_PUB_SILENT: u8 = 1; -// this is value from LedgerHQ/app-near repo -const SW_INCORRECT_P1_P2: u16 = 0x6A86; -// this is value from LedgerHQ/app-near repo -const SW_BUFFER_OVERFLOW: u16 = 0x6990; - -// this is value from LedgerHQ/app-near repo -const SW_SETTING_BLIND_DISABLED: u16 = 0x6192; +const P1_SIGN_NORMAL: u8 = 0; +const P1_SIGN_NORMAL_LAST_CHUNK: u8 = 0x80; /// Alias of `Vec`. The goal is naming to help understand what the bytes to deal with pub type NEARLedgerAppVersion = Vec; @@ -46,14 +44,8 @@ pub enum NEARLedgerError { LedgerHidError(LedgerHIDError), /// Error occurred while exchanging with Ledger device APDUExchangeError(String), - /// Blind signature not supported - BlindSignatureNotSupported, - /// Blind signature disabled in ledger's app settings - BlindSignatureDisabled, /// Error with transport LedgerHIDError(LedgerHIDError), - /// Transaction is too large to be signed - BufferOverflow { transaction_hash: CryptoHash }, } /// Converts BIP32Path into bytes (`Vec`) @@ -67,6 +59,19 @@ fn hd_path_to_bytes(hd_path: &slip10::BIP32Path) -> Vec { .collect::>() } +#[inline(always)] +fn log_command(index: usize, is_last_chunk: bool, command: &APDUCommand>) { + log::info!( + "APDU in{}: {}", + if is_last_chunk { + " (last)".to_string() + } else { + format!(" ({})", index) + }, + hex::encode(&command.serialize()) + ); +} + /// Get the version of NEAR App installed on Ledger /// /// # Returns @@ -151,6 +156,13 @@ pub fn get_version() -> Result { /// ``` pub fn get_public_key( hd_path: slip10::BIP32Path, +) -> Result { + get_public_key_with_display_flag(hd_path, true) +} + +pub fn get_public_key_with_display_flag( + hd_path: slip10::BIP32Path, + display_and_confirm: bool, ) -> Result { // instantiate the connection to Ledger // will return an error if Ledger is not connected @@ -159,9 +171,57 @@ pub fn get_public_key( // hd_path must be converted into bytes to be sent as `data` to the Ledger let hd_path_bytes = hd_path_to_bytes(&hd_path); + let p1 = if display_and_confirm { + P1_GET_PUB_DISPLAY + } else { + P1_GET_PUB_SILENT + }; + let command = APDUCommand { cla: CLA, ins: INS_GET_PUBLIC_KEY, + p1, // Instruction parameter 1 (offset) + p2: NETWORK_ID, + data: hd_path_bytes, + }; + log::info!("APDU in: {}", hex::encode(&command.serialize())); + + match transport.exchange(&command) { + Ok(response) => { + log::info!( + "APDU out: {}\nAPDU ret code: {:x}", + hex::encode(response.apdu_data()), + response.retcode(), + ); + // Ok means we successfully exchanged with the Ledger + // but doesn't mean our request succeeded + // we need to check it based on `response.retcode` + if response.retcode() == RETURN_CODE_OK { + return Ok(ed25519_dalek::PublicKey::from_bytes(&response.data()).unwrap()); + } else { + let retcode = response.retcode(); + + let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); + return Err(NEARLedgerError::APDUExchangeError(error_string)); + } + } + Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), + }; +} + +pub fn get_wallet_id( + hd_path: slip10::BIP32Path, +) -> Result { + // instantiate the connection to Ledger + // will return an error if Ledger is not connected + let transport = get_transport()?; + + // hd_path must be converted into bytes to be sent as `data` to the Ledger + let hd_path_bytes = hd_path_to_bytes(&hd_path); + + let command = APDUCommand { + cla: CLA, + ins: INS_GET_WALLET_ID, p1: 0, // Instruction parameter 1 (offset) p2: NETWORK_ID, data: hd_path_bytes, @@ -257,14 +317,14 @@ pub fn sign_transaction( cla: CLA, ins: INS_SIGN_TRANSACTION, p1: if is_last_chunk { - SIGN_NORMAL_LAST_CHUNK + P1_SIGN_NORMAL_LAST_CHUNK } else { - SIGN_NORMAL + P1_SIGN_NORMAL }, // Instruction parameter 1 (offset) p2: NETWORK_ID, data: chunk.to_vec(), }; - log::info!("APDU in: {}", hex::encode(&command.serialize())); + log_command(i, is_last_chunk, &command); match transport.exchange(&command) { Ok(response) => { log::info!( @@ -282,11 +342,72 @@ pub fn sign_transaction( } else { let retcode = response.retcode(); - if retcode == SW_BUFFER_OVERFLOW { - return Err(NEARLedgerError::BufferOverflow { - transaction_hash: CryptoHash::hash_bytes(&unsigned_tx), - }); + let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); + return Err(NEARLedgerError::APDUExchangeError(error_string)); + } + } + Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), + }; + } + Err(NEARLedgerError::APDUExchangeError( + "Unable to process request".to_owned(), + )) +} + +#[derive(Debug, BorshSerialize)] +#[borsh(crate = "near_primitives_core::borsh")] +pub struct NEP413Payload { + pub messsage: String, + pub nonce: [u8; 32], + pub recipient: String, + pub callback_url: Option, +} + +pub fn sign_message_nep413( + payload: &NEP413Payload, + seed_phrase_hd_path: slip10::BIP32Path, +) -> Result { + let transport = get_transport()?; + // seed_phrase_hd_path must be converted into bytes to be sent as `data` to the Ledger + let hd_path_bytes = hd_path_to_bytes(&seed_phrase_hd_path); + + let mut data: Vec = vec![]; + data.extend(hd_path_bytes); + data.extend_from_slice(&borsh::to_vec(payload).unwrap()); + + let chunks = data.chunks(CHUNK_SIZE); + let chunks_count = chunks.len(); + + for (i, chunk) in chunks.enumerate() { + let is_last_chunk = chunks_count == i + 1; + let command = APDUCommand { + cla: CLA, + ins: INS_SIGN_NEP413_MESSAGE, + p1: if is_last_chunk { + P1_SIGN_NORMAL_LAST_CHUNK + } else { + P1_SIGN_NORMAL + }, // Instruction parameter 1 (offset) + p2: NETWORK_ID, + data: chunk.to_vec(), + }; + log_command(i, is_last_chunk, &command); + match transport.exchange(&command) { + Ok(response) => { + log::info!( + "APDU out: {}\nAPDU ret code: {:x}", + hex::encode(response.apdu_data()), + response.retcode(), + ); + // Ok means we successfully exchanged with the Ledger + // but doesn't mean our request succeeded + // we need to check it based on `response.retcode` + if response.retcode() == RETURN_CODE_OK { + if is_last_chunk { + return Ok(response.data().to_vec()); } + } else { + let retcode = response.retcode(); let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); return Err(NEARLedgerError::APDUExchangeError(error_string)); @@ -300,8 +421,8 @@ pub fn sign_transaction( )) } -pub fn blind_sign_transaction( - payload: CryptoHash, +pub fn sign_message_nep366_delegate_action( + payload: &DelegateAction, seed_phrase_hd_path: slip10::BIP32Path, ) -> Result { let transport = get_transport()?; @@ -310,41 +431,50 @@ pub fn blind_sign_transaction( let mut data: Vec = vec![]; data.extend(hd_path_bytes); - data.extend(payload.0); + data.extend_from_slice(&borsh::to_vec(payload).unwrap()); - let command = APDUCommand { - cla: CLA, - ins: INS_SIGN_TRANSACTION, - p1: SIGN_BLIND, // Instruction parameter 1 (offset) - p2: NETWORK_ID, - data, - }; - log::info!("APDU in: {}", hex::encode(&command.serialize())); - match transport.exchange(&command) { - Ok(response) => { - log::info!( - "APDU out: {}\nAPDU ret code: {:x}", - hex::encode(response.apdu_data()), - response.retcode(), - ); - // Ok means we successfully exchanged with the Ledger - // but doesn't mean our request succeeded - // we need to check it based on `response.retcode` - if response.retcode() == RETURN_CODE_OK { - Ok(response.data().to_vec()) + let chunks = data.chunks(CHUNK_SIZE); + let chunks_count = chunks.len(); + + for (i, chunk) in chunks.enumerate() { + let is_last_chunk = chunks_count == i + 1; + let command = APDUCommand { + cla: CLA, + ins: INS_SIGN_NEP366_DELEGATE_ACTION, + p1: if is_last_chunk { + P1_SIGN_NORMAL_LAST_CHUNK } else { - let retcode = response.retcode(); - if retcode == SW_INCORRECT_P1_P2 { - return Err(NEARLedgerError::BlindSignatureNotSupported); - } - if retcode == SW_SETTING_BLIND_DISABLED { - return Err(NEARLedgerError::BlindSignatureDisabled); - } + P1_SIGN_NORMAL + }, // Instruction parameter 1 (offset) + p2: NETWORK_ID, + data: chunk.to_vec(), + }; + log_command(i, is_last_chunk, &command); + match transport.exchange(&command) { + Ok(response) => { + log::info!( + "APDU out: {}\nAPDU ret code: {:x}", + hex::encode(response.apdu_data()), + response.retcode(), + ); + // Ok means we successfully exchanged with the Ledger + // but doesn't mean our request succeeded + // we need to check it based on `response.retcode` + if response.retcode() == RETURN_CODE_OK { + if is_last_chunk { + return Ok(response.data().to_vec()); + } + } else { + let retcode = response.retcode(); - let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); - Err(NEARLedgerError::APDUExchangeError(error_string)) + let error_string = format!("Ledger APDU retcode: 0x{:X}", retcode); + return Err(NEARLedgerError::APDUExchangeError(error_string)); + } } - } - Err(err) => Err(NEARLedgerError::LedgerHIDError(err)), + Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)), + }; } + Err(NEARLedgerError::APDUExchangeError( + "Unable to process request".to_owned(), + )) }