Skip to content

Commit

Permalink
feat: first pass at using ledger to sign
Browse files Browse the repository at this point in the history
  • Loading branch information
willemneal committed May 2, 2024
1 parent df0161e commit 8aa819c
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 46 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ captive-core/
!test.toml
*.sqlite
test_snapshots
.env
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ version = "=21.0.1-preview.1"
version = "=21.0.0-preview.1"
path = "cmd/soroban-cli"

[workspace.dependencies.stellar-ledger]
version = "=21.0.0-preview.1"
path = "./cmd/crates/stellar-ledger"

[workspace.dependencies.soroban-rpc]
package = "stellar-rpc-client"
version = "=21.0.1"
Expand Down
52 changes: 42 additions & 10 deletions cmd/crates/stellar-ledger/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use futures::executor::block_on;
use ledger_transport::{APDUCommand, Exchange};
use ledger_transport_hid::{hidapi::HidError, LedgerHIDError};
use ledger_transport_hid::{
hidapi::{self, HidError},
LedgerHIDError, TransportNativeHID,
};
use sha2::{Digest, Sha256};

use soroban_env_host::xdr::{Hash, Transaction};
Expand All @@ -11,7 +14,7 @@ use stellar_xdr::curr::{
TransactionV1Envelope, WriteXdr,
};

use crate::signer::{Error, Stellar};
pub use crate::signer::{Error, Stellar};

mod signer;
mod speculos;
Expand Down Expand Up @@ -71,11 +74,41 @@ pub struct LedgerOptions<T: Exchange> {
exchange: T,
hd_path: slip10::BIP32Path,
}
// let hidapi = HidApi::new().map_err(NEARLedgerError::HidApiError)?;
// TransportNativeHID::new(&hidapi).map_err(NEARLedgerError::LedgerHidError)
impl LedgerOptions<TransportNativeHID> {
pub fn new(hd_path: u32) -> Self {
let hd_path = bip_path_from_index(hd_path);
let hidapi = hidapi::HidApi::new().unwrap();
LedgerOptions {
exchange: TransportNativeHID::new(&hidapi).unwrap(),
hd_path,
}
}
}

pub struct LedgerSigner<T: Exchange> {
network_passphrase: String,
transport: T,
hd_path: slip10::BIP32Path,
pub hd_path: slip10::BIP32Path,
}

pub struct NativeSigner(LedgerSigner<TransportNativeHID>);

impl AsRef<LedgerSigner<TransportNativeHID>> for NativeSigner {
fn as_ref(&self) -> &LedgerSigner<TransportNativeHID> {
&self.0
}
}

impl From<(String, u32)> for NativeSigner {
fn from((network_passphrase, hd_path): (String, u32)) -> Self {
Self(LedgerSigner {
network_passphrase,
transport: TransportNativeHID::new(&hidapi::HidApi::new().unwrap()).unwrap(),
hd_path: bip_path_from_index(hd_path),
})
}
}

impl<T> LedgerSigner<T>
Expand Down Expand Up @@ -114,7 +147,7 @@ where
pub async fn sign_transaction_hash(
&self,
hd_path: slip10::BIP32Path,
transaction_hash: Vec<u8>,
transaction_hash: &[u8],
) -> Result<Vec<u8>, LedgerError> {
let mut hd_path_to_bytes = hd_path_to_bytes(&hd_path);

Expand All @@ -123,7 +156,7 @@ where

data.insert(0, HD_PATH_ELEMENTS_COUNT);
data.append(&mut hd_path_to_bytes);
data.append(&mut transaction_hash.clone());
data.extend_from_slice(transaction_hash);

let command = APDUCommand {
cla: CLA,
Expand Down Expand Up @@ -284,7 +317,7 @@ impl<T: Exchange> Stellar for LedgerSigner<T> {
txn: [u8; 32],
_source_account: &stellar_strkey::Strkey,
) -> Result<DecoratedSignature, Error> {
let signature = block_on(self.sign_transaction_hash(self.hd_path.clone(), txn.to_vec())) //TODO: refactor sign_transaction_hash
let signature = block_on(self.sign_transaction_hash(self.hd_path.clone(), &txn)) //TODO: refactor sign_transaction_hash
.unwrap(); // FIXME: handle error

let sig_bytes = signature.try_into().unwrap(); // FIXME: handle error
Expand Down Expand Up @@ -516,10 +549,9 @@ mod test {
let ledger = LedgerSigner::new(TEST_NETWORK_PASSPHRASE, ledger_options);

let path = slip10::BIP32Path::from_str("m/44'/148'/0'").unwrap();
let test_hash =
"3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889".as_bytes();
let test_hash = b"3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889";

let result = ledger.sign_transaction_hash(path, test_hash.into()).await;
let result = ledger.sign_transaction_hash(path, test_hash).await;
if let Err(LedgerError::APDUExchangeError(msg)) = result {
assert_eq!(msg, "Ledger APDU retcode: 0x6C66");
// this error code is SW_TX_HASH_SIGNING_MODE_NOT_ENABLED https://github.com/LedgerHQ/app-stellar/blob/develop/docs/COMMANDS.md
Expand Down Expand Up @@ -563,7 +595,7 @@ mod test {
}
}

let result = ledger.sign_transaction_hash(path, test_hash).await;
let result = ledger.sign_transaction_hash(path, &test_hash).await;

match result {
Ok(response) => {
Expand Down
2 changes: 2 additions & 0 deletions cmd/soroban-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ soroban-ledger-snapshot = { workspace = true }
stellar-strkey = { workspace = true }
soroban-sdk = { workspace = true }
soroban-rpc = { workspace = true }
stellar-ledger ={ workspace = true }

cargo_toml = "0.20.1"
clap = { workspace = true, features = [
"derive",
Expand Down
60 changes: 47 additions & 13 deletions cmd/soroban-cli/src/commands/txn/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use std::io;
// execute,
// terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
// };
use soroban_sdk::xdr::{self, Limits, TransactionEnvelope, WriteXdr};
use soroban_sdk::xdr::{self, Limits, Transaction, TransactionEnvelope, WriteXdr};
use stellar_ledger::NativeSigner;
use stellar_strkey::Strkey;

use crate::signer::{self, InMemory, Stellar};

Expand All @@ -31,35 +33,39 @@ pub enum Error {
#[group(skip)]
pub struct Cmd {
/// Confirm that a signature can be signed by the given keypair automatically.
#[arg(long, short = 'y')]
#[arg(long, short = 'y', short = 'Y')]
yes: bool,
#[clap(flatten)]
pub xdr_args: super::xdr::Args,
#[clap(flatten)]
pub config: super::super::config::Args,

#[arg(long, value_enum, default_value = "file")]
pub signer: SignerType,
}

#[derive(clap::ValueEnum, Clone, Debug)]
pub enum SignerType {
File,
Ledger,
}

impl Cmd {
#[allow(clippy::unused_async)]
pub async fn run(&self) -> Result<(), Error> {
let envelope = self.sign()?;
let envelope = self.sign().await?;
println!("{}", envelope.to_xdr_base64(Limits::none())?.trim());
Ok(())
}

pub fn sign(&self) -> Result<TransactionEnvelope, Error> {
pub async fn sign(&self) -> Result<TransactionEnvelope, Error> {
let source = &self.config.source_account;
tracing::debug!("signing transaction with source account {}", source);
let txn = self.xdr_args.txn()?;
let key = self.config.key_pair()?;
let address =
stellar_strkey::ed25519::PublicKey::from_payload(key.verifying_key().as_bytes())?;
let in_memory = InMemory {
network_passphrase: self.config.get_network()?.network_passphrase,
keypairs: vec![key],
};
self.prompt_user()?;
Ok(in_memory.sign_txn(txn, &stellar_strkey::Strkey::PublicKeyEd25519(address))?)
match self.signer {
SignerType::File => self.sign_file(txn).await,
SignerType::Ledger => self.sign_ledger(txn).await,
}
}

pub fn prompt_user(&self) -> Result<(), Error> {
Expand Down Expand Up @@ -94,4 +100,32 @@ impl Cmd {
// execute!(stdout, LeaveAlternateScreen)?;
// Ok(())
}

pub async fn sign_file(&self, txn: Transaction) -> Result<TransactionEnvelope, Error> {
let key = self.config.key_pair()?;
let address =
stellar_strkey::ed25519::PublicKey::from_payload(key.verifying_key().as_bytes())?;
let in_memory = InMemory {
network_passphrase: self.config.get_network()?.network_passphrase,
keypairs: vec![key],
};
self.prompt_user()?;
Ok(in_memory
.sign_txn(txn, &Strkey::PublicKeyEd25519(address))
.await?)
}

pub async fn sign_ledger(&self, txn: Transaction) -> Result<TransactionEnvelope, Error> {
let index: u32 = self
.config
.hd_path
.unwrap_or_default()
.try_into()
.expect("usize bigger than u32");
let signer: NativeSigner = (self.config.get_network()?.network_passphrase, index).into();
let account =
Strkey::PublicKeyEd25519(signer.as_ref().get_public_key(index).await.unwrap());
let bx_signer = Box::new(signer);
Ok(bx_signer.sign_txn(txn, &account).await.unwrap())
}
}
Loading

0 comments on commit 8aa819c

Please sign in to comment.