soroban_cli/config/
secret.rs1use clap::arg;
2use serde::{Deserialize, Serialize};
3use std::str::FromStr;
4
5use sep5::SeedPhrase;
6use stellar_strkey::ed25519::{PrivateKey, PublicKey};
7
8use crate::{
9 print::Print,
10 signer::{self, ledger, secure_store, LocalKey, SecureStoreEntry, Signer, SignerKind},
11 utils,
12};
13
14use super::key::Key;
15
16#[derive(thiserror::Error, Debug)]
17pub enum Error {
18 #[error(transparent)]
19 Secret(#[from] stellar_strkey::DecodeError),
20 #[error(transparent)]
21 SeedPhrase(#[from] sep5::error::Error),
22 #[error(transparent)]
23 Ed25519(#[from] ed25519_dalek::SignatureError),
24 #[error("cannot parse secret (S) or seed phrase (12 or 24 word)")]
25 InvalidSecretOrSeedPhrase,
26 #[error(transparent)]
27 Signer(#[from] signer::Error),
28 #[error("Ledger does not reveal secret key")]
29 LedgerDoesNotRevealSecretKey,
30 #[error(transparent)]
31 SecureStore(#[from] secure_store::Error),
32 #[error("Secure Store does not reveal secret key")]
33 SecureStoreDoesNotRevealSecretKey,
34 #[error(transparent)]
35 Ledger(#[from] signer::ledger::Error),
36}
37
38#[derive(Debug, clap::Args, Clone)]
39#[group(skip)]
40pub struct Args {
41 #[arg(long)]
43 pub secret_key: bool,
44 #[arg(long)]
46 pub seed_phrase: bool,
47 #[arg(long)]
49 pub secure_store: bool,
50}
51
52#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
53#[serde(untagged)]
54pub enum Secret {
55 SecretKey { secret_key: String },
56 SeedPhrase { seed_phrase: String },
57 Ledger,
58 SecureStore { entry_name: String },
59}
60
61impl FromStr for Secret {
62 type Err = Error;
63
64 fn from_str(s: &str) -> Result<Self, Self::Err> {
65 if PrivateKey::from_string(s).is_ok() {
66 Ok(Secret::SecretKey {
67 secret_key: s.to_string(),
68 })
69 } else if sep5::SeedPhrase::from_str(s).is_ok() {
70 Ok(Secret::SeedPhrase {
71 seed_phrase: s.to_string(),
72 })
73 } else if s == "ledger" {
74 Ok(Secret::Ledger)
75 } else if s.starts_with(secure_store::ENTRY_PREFIX) {
76 Ok(Secret::SecureStore {
77 entry_name: s.to_string(),
78 })
79 } else {
80 Err(Error::InvalidSecretOrSeedPhrase)
81 }
82 }
83}
84
85impl From<PrivateKey> for Secret {
86 fn from(value: PrivateKey) -> Self {
87 Secret::SecretKey {
88 secret_key: value.to_string(),
89 }
90 }
91}
92
93impl From<Secret> for Key {
94 fn from(value: Secret) -> Self {
95 Key::Secret(value)
96 }
97}
98
99impl From<SeedPhrase> for Secret {
100 fn from(value: SeedPhrase) -> Self {
101 Secret::SeedPhrase {
102 seed_phrase: value.seed_phrase.into_phrase(),
103 }
104 }
105}
106
107impl Secret {
108 pub fn private_key(&self, index: Option<usize>) -> Result<PrivateKey, Error> {
109 Ok(match self {
110 Secret::SecretKey { secret_key } => PrivateKey::from_string(secret_key)?,
111 Secret::SeedPhrase { seed_phrase } => PrivateKey::from_payload(
112 &sep5::SeedPhrase::from_str(seed_phrase)?
113 .from_path_index(index.unwrap_or_default(), None)?
114 .private()
115 .0,
116 )?,
117 Secret::Ledger => panic!("Ledger does not reveal secret key"),
118 Secret::SecureStore { .. } => {
119 return Err(Error::SecureStoreDoesNotRevealSecretKey);
120 }
121 })
122 }
123
124 pub fn public_key(&self, index: Option<usize>) -> Result<PublicKey, Error> {
125 if let Secret::SecureStore { entry_name } = self {
126 Ok(secure_store::get_public_key(entry_name, index)?)
127 } else {
128 let key = self.key_pair(index)?;
129 Ok(stellar_strkey::ed25519::PublicKey::from_payload(
130 key.verifying_key().as_bytes(),
131 )?)
132 }
133 }
134
135 pub async fn signer(&self, hd_path: Option<usize>, print: Print) -> Result<Signer, Error> {
136 let kind = match self {
137 Secret::SecretKey { .. } | Secret::SeedPhrase { .. } => {
138 let key = self.key_pair(hd_path)?;
139 SignerKind::Local(LocalKey { key })
140 }
141 Secret::Ledger => {
142 let hd_path: u32 = hd_path
143 .unwrap_or_default()
144 .try_into()
145 .expect("uszie bigger than u32");
146 SignerKind::Ledger(ledger::new(hd_path).await?)
147 }
148 Secret::SecureStore { entry_name } => SignerKind::SecureStore(SecureStoreEntry {
149 name: entry_name.to_string(),
150 hd_path,
151 }),
152 };
153 Ok(Signer { kind, print })
154 }
155
156 pub fn key_pair(&self, index: Option<usize>) -> Result<ed25519_dalek::SigningKey, Error> {
157 Ok(utils::into_signing_key(&self.private_key(index)?))
158 }
159
160 pub fn from_seed(seed: Option<&str>) -> Result<Self, Error> {
161 Ok(seed_phrase_from_seed(seed)?.into())
162 }
163}
164
165pub fn seed_phrase_from_seed(seed: Option<&str>) -> Result<SeedPhrase, Error> {
166 Ok(if let Some(seed) = seed.map(str::as_bytes) {
167 sep5::SeedPhrase::from_entropy(seed)?
168 } else {
169 sep5::SeedPhrase::random(sep5::MnemonicType::Words24)?
170 })
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 const TEST_PUBLIC_KEY: &str = "GAREAZZQWHOCBJS236KIE3AWYBVFLSBK7E5UW3ICI3TCRWQKT5LNLCEZ";
178 const TEST_SECRET_KEY: &str = "SBF5HLRREHMS36XZNTUSKZ6FTXDZGNXOHF4EXKUL5UCWZLPBX3NGJ4BH";
179 const TEST_SEED_PHRASE: &str =
180 "depth decade power loud smile spatial sign movie judge february rate broccoli";
181
182 #[test]
183 fn test_from_str_for_secret_key() {
184 let secret = Secret::from_str(TEST_SECRET_KEY).unwrap();
185 let public_key = secret.public_key(None).unwrap();
186 let private_key = secret.private_key(None).unwrap();
187
188 assert!(matches!(secret, Secret::SecretKey { .. }));
189 assert_eq!(public_key.to_string(), TEST_PUBLIC_KEY);
190 assert_eq!(private_key.to_string(), TEST_SECRET_KEY);
191 }
192
193 #[test]
194 fn test_secret_from_seed_phrase() {
195 let secret = Secret::from_str(TEST_SEED_PHRASE).unwrap();
196 let public_key = secret.public_key(None).unwrap();
197 let private_key = secret.private_key(None).unwrap();
198
199 assert!(matches!(secret, Secret::SeedPhrase { .. }));
200 assert_eq!(public_key.to_string(), TEST_PUBLIC_KEY);
201 assert_eq!(private_key.to_string(), TEST_SECRET_KEY);
202 }
203
204 #[test]
205 fn test_secret_from_secure_store() {
206 let secret = Secret::from_str("secure_store:org.stellar.cli-alice").unwrap();
208 assert!(matches!(secret, Secret::SecureStore { .. }));
209
210 let private_key_result = secret.private_key(None);
211 assert!(private_key_result.is_err());
212 assert!(matches!(
213 private_key_result.unwrap_err(),
214 Error::SecureStoreDoesNotRevealSecretKey
215 ));
216 }
217
218 #[test]
219 fn test_secret_from_invalid_string() {
220 let secret = Secret::from_str("invalid");
221 assert!(secret.is_err());
222 }
223}