diff --git a/.github/workflows/ledger-emulator.yml b/.github/workflows/ledger-emulator.yml index ec32b861d2..577e69a8c4 100644 --- a/.github/workflows/ledger-emulator.yml +++ b/.github/workflows/ledger-emulator.yml @@ -26,6 +26,6 @@ jobs: sudo apt update && sudo apt install -y libudev-dev libdbus-1-dev - run: | cargo test --manifest-path cmd/crates/stellar-ledger/Cargo.toml --features "emulator-tests" -- --nocapture - - run: cargo build --features emulator-tests + - run: cargo build --features emulator-tests,additional-libs - run: | cargo test --features emulator-tests --package soroban-test --test it -- emulator diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index 508ee9bfeb..1f29b8653b 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -40,7 +40,7 @@ version_lt_23 = [] version_gte_23 = [] opt = ["dep:wasm-opt"] emulator-tests = ["stellar-ledger/emulator-tests"] -additional-libs = ["dep:keyring"] +additional-libs = ["dep:keyring", "dep:stellar-ledger"] [dependencies] stellar-xdr = { workspace = true, features = ["cli"] } @@ -53,7 +53,7 @@ soroban-ledger-snapshot = { workspace = true } stellar-strkey = { workspace = true } soroban-sdk = { workspace = true } soroban-rpc = { workspace = true } -stellar-ledger = { workspace = true } +stellar-ledger = { workspace = true, optional = true } clap = { workspace = true, features = [ "derive", diff --git a/cmd/soroban-cli/src/config/address.rs b/cmd/soroban-cli/src/config/address.rs index 73c5cf1907..80ab300ced 100644 --- a/cmd/soroban-cli/src/config/address.rs +++ b/cmd/soroban-cli/src/config/address.rs @@ -48,6 +48,8 @@ pub enum Error { InvalidKeyName(String), #[error("Ledger not supported in this context")] LedgerNotSupported, + #[error(transparent)] + Ledger(#[from] signer::ledger::Error), } impl FromStr for UnresolvedMuxedAccount { @@ -85,7 +87,7 @@ impl UnresolvedMuxedAccount { ) -> Result { match self { UnresolvedMuxedAccount::Ledger(hd_path) => Ok(xdr::MuxedAccount::Ed25519( - ledger(*hd_path).await?.public_key().await?.0.into(), + ledger::new(*hd_path).await?.public_key().await?.0.into(), )), UnresolvedMuxedAccount::Resolved(_) | UnresolvedMuxedAccount::AliasOrSecret(_) => { self.resolve_muxed_account_sync(locator, hd_path) diff --git a/cmd/soroban-cli/src/config/secret.rs b/cmd/soroban-cli/src/config/secret.rs index 4bc55de2f0..ff91e948da 100644 --- a/cmd/soroban-cli/src/config/secret.rs +++ b/cmd/soroban-cli/src/config/secret.rs @@ -25,14 +25,14 @@ pub enum Error { InvalidSecretOrSeedPhrase, #[error(transparent)] Signer(#[from] signer::Error), - #[error("Ledger does not reveal secret key")] LedgerDoesNotRevealSecretKey, - #[error(transparent)] SecureStore(#[from] secure_store::Error), #[error("Secure Store does not reveal secret key")] SecureStoreDoesNotRevealSecretKey, + #[error(transparent)] + Ledger(#[from] signer::ledger::Error), } #[derive(Debug, clap::Args, Clone)] @@ -143,7 +143,7 @@ impl Secret { .unwrap_or_default() .try_into() .expect("uszie bigger than u32"); - SignerKind::Ledger(ledger(hd_path).await?) + SignerKind::Ledger(ledger::new(hd_path).await?) } Secret::SecureStore { entry_name } => SignerKind::SecureStore(SecureStoreEntry { name: entry_name.to_string(), diff --git a/cmd/soroban-cli/src/config/sign_with.rs b/cmd/soroban-cli/src/config/sign_with.rs index 07a4d13275..09c00a0019 100644 --- a/cmd/soroban-cli/src/config/sign_with.rs +++ b/cmd/soroban-cli/src/config/sign_with.rs @@ -29,6 +29,8 @@ pub enum Error { StrKey(#[from] stellar_strkey::DecodeError), #[error(transparent)] Xdr(#[from] xdr::Error), + #[error(transparent)] + Ledger(#[from] signer::ledger::Error), } #[derive(Debug, clap::Args, Clone, Default)] @@ -72,7 +74,7 @@ impl Args { print, } } else if self.sign_with_ledger { - let ledger = ledger( + let ledger = ledger::new( self.hd_path .unwrap_or_default() .try_into() diff --git a/cmd/soroban-cli/src/signer/keyring.rs b/cmd/soroban-cli/src/signer/keyring.rs index edd0e589b7..1d819fae9a 100644 --- a/cmd/soroban-cli/src/signer/keyring.rs +++ b/cmd/soroban-cli/src/signer/keyring.rs @@ -1,10 +1,9 @@ use crate::print::Print; use ed25519_dalek::Signer; +use keyring::Entry; use sep5::seed_phrase::SeedPhrase; use zeroize::Zeroize; -use keyring::Entry; - #[derive(thiserror::Error, Debug)] pub enum Error { #[cfg(feature = "additional-libs")] diff --git a/cmd/soroban-cli/src/signer/ledger.rs b/cmd/soroban-cli/src/signer/ledger.rs new file mode 100644 index 0000000000..aa28ce60c7 --- /dev/null +++ b/cmd/soroban-cli/src/signer/ledger.rs @@ -0,0 +1,150 @@ +use crate::xdr::{self, DecoratedSignature, Transaction}; + +pub use ledger_impl::*; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Ledger Device keys are not allowed: additional-libs feature must be enabled")] + FeatureNotEnabled, + + #[cfg(feature = "additional-libs")] + #[error(transparent)] + StellarLedger(#[from] stellar_ledger::Error), + + #[error(transparent)] + TryFromSlice(#[from] std::array::TryFromSliceError), + + #[error(transparent)] + Xdr(#[from] xdr::Error), +} + +#[cfg(feature = "additional-libs")] +mod ledger_impl { + use super::*; + use crate::xdr::{Hash, Signature, SignatureHint}; + use sha2::{Digest, Sha256}; + use stellar_ledger::{Blob as _, Exchange, LedgerSigner}; + + #[cfg(not(feature = "emulator-tests"))] + pub type LedgerType = Ledger; + #[cfg(feature = "emulator-tests")] + pub type LedgerType = Ledger; + + pub struct Ledger { + pub(crate) index: u32, + pub(crate) signer: LedgerSigner, + } + + #[cfg(not(feature = "emulator-tests"))] + pub async fn new(hd_path: u32) -> Result, Error> { + let signer = stellar_ledger::native()?; + Ok(Ledger { + index: hd_path, + signer, + }) + } + + #[cfg(feature = "emulator-tests")] + pub async fn new( + hd_path: u32, + ) -> Result, Error> + { + use stellar_ledger::emulator_test_support::ledger as emulator_ledger; + // port from SPECULOS_PORT ENV var + let host_port: u16 = std::env::var("SPECULOS_PORT") + .expect("SPECULOS_PORT env var not set") + .parse() + .expect("port must be a number"); + let signer = emulator_ledger(host_port).await; + + Ok(Ledger { + index: hd_path, + signer, + }) + } + + impl Ledger { + pub async fn sign_transaction_hash( + &self, + tx_hash: &[u8; 32], + ) -> Result { + let key = self.public_key().await?; + let hint = SignatureHint(key.0[28..].try_into()?); + let signature = Signature( + self.signer + .sign_transaction_hash(self.index, tx_hash) + .await? + .try_into()?, + ); + Ok(DecoratedSignature { hint, signature }) + } + + pub async fn sign_transaction( + &self, + tx: Transaction, + network_passphrase: &str, + ) -> Result { + let network_id = Hash(Sha256::digest(network_passphrase).into()); + let signature = self + .signer + .sign_transaction(self.index, tx, network_id) + .await?; + let key = self.public_key().await?; + let hint = SignatureHint(key.0[28..].try_into()?); + let signature = Signature(signature.try_into()?); + Ok(DecoratedSignature { hint, signature }) + } + + pub async fn public_key(&self) -> Result { + Ok(self.signer.get_public_key(&self.index.into()).await?) + } + } +} + +#[cfg(not(feature = "additional-libs"))] +mod ledger_impl { + use super::{DecoratedSignature, Error, Transaction}; + use std::marker::PhantomData; + + pub type LedgerType = Ledger; + + pub trait Exchange {} + pub struct Ledger { + _marker: PhantomData, + } + + #[allow(clippy::unused_async)] + pub async fn new(_hd_path: u32) -> Result, Error> { + Err(Error::FeatureNotEnabled) + } + + impl Ledger { + #[allow(clippy::unused_async)] + pub async fn sign_transaction_hash( + &self, + _tx_hash: &[u8; 32], + ) -> Result { + Err(Error::FeatureNotEnabled) + } + + #[allow(clippy::unused_async)] + pub async fn sign_transaction( + &self, + _tx: Transaction, + _network_passphrase: &str, + ) -> Result { + Err(Error::FeatureNotEnabled) + } + + #[allow(clippy::unused_async)] + pub async fn public_key(&self) -> Result { + Err(Error::FeatureNotEnabled) + } + } + + pub struct GenericExchange {} + + impl Exchange for GenericExchange {} + + impl GenericExchange {} +} diff --git a/cmd/soroban-cli/src/signer/mod.rs b/cmd/soroban-cli/src/signer/mod.rs index b63a843f6a..f8cbd8086e 100644 --- a/cmd/soroban-cli/src/signer/mod.rs +++ b/cmd/soroban-cli/src/signer/mod.rs @@ -1,6 +1,3 @@ -use ed25519_dalek::ed25519::signature::Signer as _; -use sha2::{Digest, Sha256}; - use crate::xdr::{ self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, @@ -8,14 +5,16 @@ use crate::xdr::{ SorobanAuthorizedFunction, SorobanCredentials, Transaction, TransactionEnvelope, TransactionV1Envelope, Uint256, VecM, WriteXdr, }; -use stellar_ledger::{Blob as _, Exchange, LedgerSigner}; +use ed25519_dalek::ed25519::signature::Signer as _; +use sha2::{Digest, Sha256}; use crate::{config::network::Network, print::Print, utils::transaction_hash}; -pub mod secure_store; +pub mod ledger; #[cfg(feature = "additional-libs")] mod keyring; +pub mod secure_store; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -30,8 +29,6 @@ pub enum Error { #[error("User cancelled signing, perhaps need to add -y")] UserCancelledSigning, #[error(transparent)] - Ledger(#[from] stellar_ledger::Error), - #[error(transparent)] Xdr(#[from] xdr::Error), #[error("Only Transaction envelope V1 type is supported")] UnsupportedTransactionEnvelopeType, @@ -43,6 +40,8 @@ pub enum Error { ReturningSignatureFromLab, #[error(transparent)] SecureStore(#[from] secure_store::Error), + #[error(transparent)] + Ledger(#[from] ledger::Error), } fn requires_auth(txn: &Transaction) -> Option { @@ -216,10 +215,7 @@ pub struct Signer { #[allow(clippy::module_name_repetitions, clippy::large_enum_variant)] pub enum SignerKind { Local(LocalKey), - #[cfg(not(feature = "emulator-tests"))] - Ledger(Ledger), - #[cfg(feature = "emulator-tests")] - Ledger(Ledger), + Ledger(ledger::LedgerType), Lab, SecureStore(SecureStoreEntry), } @@ -269,76 +265,6 @@ pub struct LocalKey { pub key: ed25519_dalek::SigningKey, } -#[allow(dead_code)] -pub struct Ledger { - pub(crate) index: u32, - pub(crate) signer: LedgerSigner, -} - -impl Ledger { - pub async fn sign_transaction_hash( - &self, - tx_hash: &[u8; 32], - ) -> Result { - let key = self.public_key().await?; - let hint = SignatureHint(key.0[28..].try_into()?); - let signature = Signature( - self.signer - .sign_transaction_hash(self.index, tx_hash) - .await? - .try_into()?, - ); - Ok(DecoratedSignature { hint, signature }) - } - - pub async fn sign_transaction( - &self, - tx: Transaction, - network_passphrase: &str, - ) -> Result { - let network_id = Hash(Sha256::digest(network_passphrase).into()); - let signature = self - .signer - .sign_transaction(self.index, tx, network_id) - .await?; - let key = self.public_key().await?; - let hint = SignatureHint(key.0[28..].try_into()?); - let signature = Signature(signature.try_into()?); - Ok(DecoratedSignature { hint, signature }) - } - - pub async fn public_key(&self) -> Result { - Ok(self.signer.get_public_key(&self.index.into()).await?) - } -} - -#[cfg(not(feature = "emulator-tests"))] -pub async fn ledger(hd_path: u32) -> Result, Error> { - let signer = stellar_ledger::native()?; - Ok(Ledger { - index: hd_path, - signer, - }) -} - -#[cfg(feature = "emulator-tests")] -pub async fn ledger( - hd_path: u32, -) -> Result, Error> { - use stellar_ledger::emulator_test_support::ledger as emulator_ledger; - // port from SPECULOS_PORT ENV var - let host_port: u16 = std::env::var("SPECULOS_PORT") - .expect("SPECULOS_PORT env var not set") - .parse() - .expect("port must be a number"); - let signer = emulator_ledger(host_port).await; - - Ok(Ledger { - index: hd_path, - signer, - }) -} - impl LocalKey { pub fn sign_tx_hash(&self, tx_hash: [u8; 32]) -> Result { let hint = SignatureHint(self.key.verifying_key().to_bytes()[28..].try_into()?);