soroban_cli/signer/
ledger.rs1use 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 #[error(transparent)]
21 Validation(#[from] crate::signer::validation::Error),
22}
23
24#[cfg(feature = "additional-libs")]
25mod ledger_impl {
26 use super::Error;
27 use crate::xdr::{DecoratedSignature, Hash, Signature, SignatureHint, Transaction};
28 use ed25519_dalek::Signature as Ed25519Signature;
29 use sha2::{Digest, Sha256};
30 use stellar_ledger::{Blob as _, Exchange, LedgerSigner};
31
32 #[cfg(not(feature = "emulator-tests"))]
33 pub type LedgerType = Ledger<stellar_ledger::TransportNativeHID>;
34 #[cfg(feature = "emulator-tests")]
35 pub type LedgerType = Ledger<stellar_ledger::emulator_test_support::http_transport::Emulator>;
36
37 pub struct LedgerEntry {
42 pub hd_path: u32,
43 pub public_key: Option<stellar_strkey::ed25519::PublicKey>,
44 }
45
46 impl LedgerEntry {
47 pub async fn sign_tx_hash(&self, tx_hash: [u8; 32]) -> Result<DecoratedSignature, Error> {
48 let live = new(self.hd_path).await?;
49 let key = match self.public_key {
50 Some(pk) => pk,
51 None => live.public_key().await?,
52 };
53 let sig_bytes = live
54 .signer
55 .sign_transaction_hash(live.index, &tx_hash)
56 .await?;
57 if let Some(pk) = self.public_key {
60 crate::signer::validation::verify_signature(&pk, &tx_hash, &sig_bytes)?;
61 }
62 let hint = SignatureHint(key.0[28..].try_into()?);
63 let signature = Signature(sig_bytes.try_into()?);
64 Ok(DecoratedSignature { hint, signature })
65 }
66
67 pub async fn sign_payload(&self, payload: [u8; 32]) -> Result<Ed25519Signature, Error> {
68 let live = new(self.hd_path).await?;
69 let bytes = live
70 .signer
71 .sign_transaction_hash(live.index, &payload)
72 .await?;
73 if let Some(pk) = self.public_key {
74 crate::signer::validation::verify_signature(&pk, &payload, &bytes)?;
75 }
76 Ok(Ed25519Signature::from_bytes(bytes.as_slice().try_into()?))
77 }
78 }
79
80 pub struct Ledger<T: Exchange> {
81 pub(crate) index: u32,
82 pub(crate) signer: LedgerSigner<T>,
83 }
84
85 #[cfg(not(feature = "emulator-tests"))]
86 #[allow(clippy::unused_async)]
87 pub async fn new(hd_path: u32) -> Result<Ledger<stellar_ledger::TransportNativeHID>, Error> {
88 let signer = stellar_ledger::native()?;
89 Ok(Ledger {
90 index: hd_path,
91 signer,
92 })
93 }
94
95 #[cfg(feature = "emulator-tests")]
96 pub async fn new(
97 hd_path: u32,
98 ) -> Result<Ledger<stellar_ledger::emulator_test_support::http_transport::Emulator>, Error>
99 {
100 use stellar_ledger::emulator_test_support::ledger as emulator_ledger;
101 let host_port: u16 = std::env::var("SPECULOS_PORT")
103 .expect("SPECULOS_PORT env var not set")
104 .parse()
105 .expect("port must be a number");
106 let signer = emulator_ledger(host_port).await;
107
108 Ok(Ledger {
109 index: hd_path,
110 signer,
111 })
112 }
113
114 impl<T: Exchange> Ledger<T> {
115 pub async fn sign_transaction(
116 &self,
117 tx: Transaction,
118 network_passphrase: &str,
119 ) -> Result<DecoratedSignature, Error> {
120 let network_id = Hash(Sha256::digest(network_passphrase).into());
121 let signature = self
122 .signer
123 .sign_transaction(self.index, tx, network_id)
124 .await?;
125 let key = self.public_key().await?;
126 let hint = SignatureHint(key.0[28..].try_into()?);
127 let signature = Signature(signature.try_into()?);
128 Ok(DecoratedSignature { hint, signature })
129 }
130
131 pub async fn public_key(&self) -> Result<stellar_strkey::ed25519::PublicKey, Error> {
132 Ok(self.signer.get_public_key(&self.index.into()).await?)
133 }
134 }
135}
136
137#[cfg(not(feature = "additional-libs"))]
138mod ledger_impl {
139 use super::Error;
140 use crate::xdr::{DecoratedSignature, Transaction};
141 use ed25519_dalek::Signature as Ed25519Signature;
142 use std::marker::PhantomData;
143
144 pub type LedgerType = Ledger<GenericExchange>;
145
146 pub trait Exchange {}
147 pub struct Ledger<T: Exchange> {
148 _marker: PhantomData<T>,
149 }
150
151 pub struct LedgerEntry {
152 pub hd_path: u32,
153 pub public_key: Option<stellar_strkey::ed25519::PublicKey>,
154 }
155
156 impl LedgerEntry {
157 #[allow(clippy::unused_async)]
158 pub async fn sign_tx_hash(&self, _tx_hash: [u8; 32]) -> Result<DecoratedSignature, Error> {
159 Err(Error::FeatureNotEnabled)
160 }
161
162 #[allow(clippy::unused_async)]
163 pub async fn sign_payload(&self, _payload: [u8; 32]) -> Result<Ed25519Signature, Error> {
164 Err(Error::FeatureNotEnabled)
165 }
166 }
167
168 #[allow(clippy::unused_async)]
169 pub async fn new(_hd_path: u32) -> Result<Ledger<GenericExchange>, Error> {
170 Err(Error::FeatureNotEnabled)
171 }
172
173 impl<T: Exchange> Ledger<T> {
174 #[allow(clippy::unused_async)]
175 pub async fn sign_transaction(
176 &self,
177 _tx: Transaction,
178 _network_passphrase: &str,
179 ) -> Result<DecoratedSignature, Error> {
180 Err(Error::FeatureNotEnabled)
181 }
182
183 #[allow(clippy::unused_async)]
184 pub async fn public_key(&self) -> Result<stellar_strkey::ed25519::PublicKey, Error> {
185 Err(Error::FeatureNotEnabled)
186 }
187 }
188
189 pub struct GenericExchange {}
190
191 impl Exchange for GenericExchange {}
192
193 impl GenericExchange {}
194}