Skip to content

Commit

Permalink
feat: impl sign_message_nep366_delegate_action
Browse files Browse the repository at this point in the history
  • Loading branch information
dj8yf0μl committed Feb 1, 2024
1 parent 3e0e44e commit 16c8b62
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 0 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ path = "examples/sign_transaction/delegate.rs"
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"

[[example]]
name = "blind_sign_transaction"

Expand All @@ -95,6 +99,7 @@ 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"
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ RUST_LOG=sign_multiple_transfers,near_ledger=info cargo run --example sign_multi
RUST_LOG=sign_nep_413_message,near_ledger=info cargo run --example sign_nep_413_message
```

### Sign a NEP-366 delegate action

```bash
RUST_LOG=sign_nep_366_delegate_action,near_ledger=info cargo run --example sign_nep_366_delegate_action
```

### Blind sign a transaction

```bash
Expand Down
80 changes: 80 additions & 0 deletions examples/sign_nep_366_delegate_action.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use std::{convert::TryInto, str::FromStr};

use near_account_id::AccountId;
use near_crypto::{SecretKey, 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 public_key = near_ledger::get_public_key_with_display_flag(hd_path.clone(), false)?;
display_pub_key(public_key);

let delegate_public_key =
near_crypto::PublicKey::ED25519(near_crypto::ED25519PublicKey::from(public_key.to_bytes()));

let sender_id = AccountId::from_str("bob.near").unwrap();

let sk = SecretKey::from_seed(
near_crypto::KeyType::SECP256K1,
&format!("{:?}", public_key),
);
let public_key_secp = sk.public_key();

let transfer = near_primitives::transaction::Action::Transfer(
near_primitives::transaction::TransferAction {
deposit: 150000000000000000000000, // 0.15 NEAR
},
);

let stake = near_primitives::transaction::Action::Stake(Box::new(
near_primitives::transaction::StakeAction {
stake: 1157130000000000000000000, // 1.15713 NEAR,
public_key: public_key_secp.clone(),
},
));

let add_key_fullaccess = near_primitives::transaction::Action::AddKey(Box::new(
near_primitives::transaction::AddKeyAction {
public_key: public_key_secp.clone(),
access_key: near_primitives_core::account::AccessKey {
nonce: 127127127127,
permission: near_primitives_core::account::AccessKeyPermission::FullAccess,
},
},
));
let actions = vec![transfer, stake, add_key_fullaccess]
.into_iter()
.map(|action| action.try_into().unwrap())
.collect::<Vec<_>>();

let delegate_action = DelegateAction {
sender_id,
receiver_id: AccountId::from_str("alice.near").unwrap(),
actions,
nonce: 127127122121,
max_block_height: 100500,
public_key: delegate_public_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());
Ok(())
}
60 changes: 60 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use ledger_transport_hid::{
hidapi::{HidApi, HidError},
LedgerHIDError, TransportNativeHID,
};
use near_primitives::action::delegate::DelegateAction;
use near_primitives_core::{
borsh::{self, BorshSerialize},
hash::CryptoHash,
Expand All @@ -19,6 +20,7 @@ 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 = 250; // Chunk size to be sent to Ledger
Expand Down Expand Up @@ -429,6 +431,64 @@ pub fn sign_message_nep413(
"Unable to process request".to_owned(),
))
}

pub fn sign_message_nep366_delegate_action(
payload: &DelegateAction,
seed_phrase_hd_path: slip10::BIP32Path,
) -> Result<SignatureBytes, NEARLedgerError> {
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<u8> = 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_NEP366_DELEGATE_ACTION,
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::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 {
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));
}
}
Err(err) => return Err(NEARLedgerError::LedgerHIDError(err)),
};
}
Err(NEARLedgerError::APDUExchangeError(
"Unable to process request".to_owned(),
))
}
pub fn blind_sign_transaction(
payload: CryptoHash,
seed_phrase_hd_path: slip10::BIP32Path,
Expand Down

0 comments on commit 16c8b62

Please sign in to comment.