Skip to main content

soroban_cli/signer/
ledger.rs

1use crate::xdr;
2
3pub use ledger_impl::*;
4
5#[derive(thiserror::Error, Debug)]
6pub enum Error {
7    #[error("Ledger Device keys are not allowed: additional-libs feature must be enabled")]
8    FeatureNotEnabled,
9
10    #[cfg(feature = "additional-libs")]
11    #[error(transparent)]
12    StellarLedger(#[from] stellar_ledger::Error),
13
14    #[error(transparent)]
15    TryFromSlice(#[from] std::array::TryFromSliceError),
16
17    #[error(transparent)]
18    Xdr(#[from] xdr::Error),
19}
20
21#[cfg(feature = "additional-libs")]
22mod ledger_impl {
23    use super::Error;
24    use crate::xdr::{DecoratedSignature, Hash, Signature, SignatureHint, Transaction};
25    use ed25519_dalek::Signature as Ed25519Signature;
26    use sha2::{Digest, Sha256};
27    use stellar_ledger::{Blob as _, Exchange, LedgerSigner};
28
29    #[cfg(not(feature = "emulator-tests"))]
30    pub type LedgerType = Ledger<stellar_ledger::TransportNativeHID>;
31    #[cfg(feature = "emulator-tests")]
32    pub type LedgerType = Ledger<stellar_ledger::emulator_test_support::http_transport::Emulator>;
33
34    // Pure-data signer for Ledger identities. Mirrors `SecureStoreEntry`:
35    // holds no live transport, opens HID lazily inside each sign call so the
36    // device stays free between operations and can never collide with a
37    // concurrent transport elsewhere in the process.
38    pub struct LedgerEntry {
39        pub hd_path: u32,
40        pub public_key: Option<stellar_strkey::ed25519::PublicKey>,
41    }
42
43    impl LedgerEntry {
44        pub async fn sign_tx_hash(&self, tx_hash: [u8; 32]) -> Result<DecoratedSignature, Error> {
45            let live = new(self.hd_path).await?;
46            let key = match self.public_key {
47                Some(pk) => pk,
48                None => live.public_key().await?,
49            };
50            let hint = SignatureHint(key.0[28..].try_into()?);
51            let signature = Signature(
52                live.signer
53                    .sign_transaction_hash(live.index, &tx_hash)
54                    .await?
55                    .try_into()?,
56            );
57            Ok(DecoratedSignature { hint, signature })
58        }
59
60        pub async fn sign_payload(&self, payload: [u8; 32]) -> Result<Ed25519Signature, Error> {
61            let live = new(self.hd_path).await?;
62            let bytes = live
63                .signer
64                .sign_transaction_hash(live.index, &payload)
65                .await?;
66            Ok(Ed25519Signature::from_bytes(bytes.as_slice().try_into()?))
67        }
68    }
69
70    pub struct Ledger<T: Exchange> {
71        pub(crate) index: u32,
72        pub(crate) signer: LedgerSigner<T>,
73    }
74
75    #[cfg(not(feature = "emulator-tests"))]
76    #[allow(clippy::unused_async)]
77    pub async fn new(hd_path: u32) -> Result<Ledger<stellar_ledger::TransportNativeHID>, Error> {
78        let signer = stellar_ledger::native()?;
79        Ok(Ledger {
80            index: hd_path,
81            signer,
82        })
83    }
84
85    #[cfg(feature = "emulator-tests")]
86    pub async fn new(
87        hd_path: u32,
88    ) -> Result<Ledger<stellar_ledger::emulator_test_support::http_transport::Emulator>, Error>
89    {
90        use stellar_ledger::emulator_test_support::ledger as emulator_ledger;
91        // port from SPECULOS_PORT ENV var
92        let host_port: u16 = std::env::var("SPECULOS_PORT")
93            .expect("SPECULOS_PORT env var not set")
94            .parse()
95            .expect("port must be a number");
96        let signer = emulator_ledger(host_port).await;
97
98        Ok(Ledger {
99            index: hd_path,
100            signer,
101        })
102    }
103
104    impl<T: Exchange> Ledger<T> {
105        pub async fn sign_transaction(
106            &self,
107            tx: Transaction,
108            network_passphrase: &str,
109        ) -> Result<DecoratedSignature, Error> {
110            let network_id = Hash(Sha256::digest(network_passphrase).into());
111            let signature = self
112                .signer
113                .sign_transaction(self.index, tx, network_id)
114                .await?;
115            let key = self.public_key().await?;
116            let hint = SignatureHint(key.0[28..].try_into()?);
117            let signature = Signature(signature.try_into()?);
118            Ok(DecoratedSignature { hint, signature })
119        }
120
121        pub async fn public_key(&self) -> Result<stellar_strkey::ed25519::PublicKey, Error> {
122            Ok(self.signer.get_public_key(&self.index.into()).await?)
123        }
124    }
125}
126
127#[cfg(not(feature = "additional-libs"))]
128mod ledger_impl {
129    use super::Error;
130    use crate::xdr::{DecoratedSignature, Transaction};
131    use ed25519_dalek::Signature as Ed25519Signature;
132    use std::marker::PhantomData;
133
134    pub type LedgerType = Ledger<GenericExchange>;
135
136    pub trait Exchange {}
137    pub struct Ledger<T: Exchange> {
138        _marker: PhantomData<T>,
139    }
140
141    pub struct LedgerEntry {
142        pub hd_path: u32,
143        pub public_key: Option<stellar_strkey::ed25519::PublicKey>,
144    }
145
146    impl LedgerEntry {
147        #[allow(clippy::unused_async)]
148        pub async fn sign_tx_hash(&self, _tx_hash: [u8; 32]) -> Result<DecoratedSignature, Error> {
149            Err(Error::FeatureNotEnabled)
150        }
151
152        #[allow(clippy::unused_async)]
153        pub async fn sign_payload(&self, _payload: [u8; 32]) -> Result<Ed25519Signature, Error> {
154            Err(Error::FeatureNotEnabled)
155        }
156    }
157
158    #[allow(clippy::unused_async)]
159    pub async fn new(_hd_path: u32) -> Result<Ledger<GenericExchange>, Error> {
160        Err(Error::FeatureNotEnabled)
161    }
162
163    impl<T: Exchange> Ledger<T> {
164        #[allow(clippy::unused_async)]
165        pub async fn sign_transaction(
166            &self,
167            _tx: Transaction,
168            _network_passphrase: &str,
169        ) -> Result<DecoratedSignature, Error> {
170            Err(Error::FeatureNotEnabled)
171        }
172
173        #[allow(clippy::unused_async)]
174        pub async fn public_key(&self) -> Result<stellar_strkey::ed25519::PublicKey, Error> {
175            Err(Error::FeatureNotEnabled)
176        }
177    }
178
179    pub struct GenericExchange {}
180
181    impl Exchange for GenericExchange {}
182
183    impl GenericExchange {}
184}