soroban_cli/config/
key.rs1use std::{fmt::Display, str::FromStr};
2
3use serde::{Deserialize, Serialize};
4
5use super::secret::{self, Secret};
6use crate::xdr;
7
8#[derive(thiserror::Error, Debug)]
9pub enum Error {
10 #[error("failed to extract secret from public key ")]
11 SecretPublicKey,
12 #[error(transparent)]
13 Secret(#[from] secret::Error),
14 #[error(transparent)]
15 StrKey(#[from] stellar_strkey::DecodeError),
16 #[error("failed to parse key")]
17 Parse,
18 #[error("expected a public key (G...) or muxed account (M...)")]
19 PublicKeyExpected,
20}
21
22#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
23pub enum Key {
24 #[serde(rename = "public_key")]
25 PublicKey(Public),
26 #[serde(rename = "muxed_account")]
27 MuxedAccount(MuxedAccount),
28 #[serde(untagged)]
29 Secret(Secret),
30}
31
32impl Key {
33 pub fn muxed_account(&self, hd_path: Option<u32>) -> Result<xdr::MuxedAccount, Error> {
34 let bytes = match self {
35 Key::Secret(secret) => secret.public_key(hd_path)?.0,
36 Key::PublicKey(Public(key)) => key.0,
37 Key::MuxedAccount(MuxedAccount(stellar_strkey::ed25519::MuxedAccount {
38 ed25519,
39 id,
40 })) => {
41 return Ok(xdr::MuxedAccount::MuxedEd25519(xdr::MuxedAccountMed25519 {
42 ed25519: xdr::Uint256(*ed25519),
43 id: *id,
44 }))
45 }
46 };
47 Ok(xdr::MuxedAccount::Ed25519(xdr::Uint256(bytes)))
48 }
49
50 pub fn private_key(
51 &self,
52 hd_path: Option<u32>,
53 ) -> Result<stellar_strkey::ed25519::PrivateKey, Error> {
54 match self {
55 Key::Secret(secret) => Ok(secret.private_key(hd_path)?),
56 _ => Err(Error::SecretPublicKey),
57 }
58 }
59
60 pub fn parse_public_only(s: &str) -> Result<Self, Error> {
61 match s.parse()? {
62 key @ (Key::PublicKey(_) | Key::MuxedAccount(_)) => Ok(key),
63 Key::Secret(_) => Err(Error::PublicKeyExpected),
64 }
65 }
66}
67
68impl FromStr for Key {
69 type Err = Error;
70
71 fn from_str(s: &str) -> Result<Self, Self::Err> {
72 if let Ok(secret) = s.parse() {
73 return Ok(Key::Secret(secret));
74 }
75 if let Ok(public_key) = s.parse() {
76 return Ok(Key::PublicKey(public_key));
77 }
78 if let Ok(muxed_account) = s.parse() {
79 return Ok(Key::MuxedAccount(muxed_account));
80 }
81 Err(Error::Parse)
82 }
83}
84
85impl From<stellar_strkey::ed25519::PublicKey> for Key {
86 fn from(value: stellar_strkey::ed25519::PublicKey) -> Self {
87 Key::PublicKey(Public(value))
88 }
89}
90
91impl From<&stellar_strkey::ed25519::PublicKey> for Key {
92 fn from(stellar_strkey::ed25519::PublicKey(key): &stellar_strkey::ed25519::PublicKey) -> Self {
93 stellar_strkey::ed25519::PublicKey(*key).into()
94 }
95}
96
97#[derive(Debug, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)]
98pub struct Public(pub stellar_strkey::ed25519::PublicKey);
99
100impl FromStr for Public {
101 type Err = stellar_strkey::DecodeError;
102
103 fn from_str(s: &str) -> Result<Self, Self::Err> {
104 Ok(Public(stellar_strkey::ed25519::PublicKey::from_str(s)?))
105 }
106}
107
108impl Display for Public {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 write!(f, "{}", self.0)
111 }
112}
113
114impl From<&Public> for stellar_strkey::ed25519::MuxedAccount {
115 fn from(Public(stellar_strkey::ed25519::PublicKey(key)): &Public) -> Self {
116 stellar_strkey::ed25519::MuxedAccount {
117 id: 0,
118 ed25519: *key,
119 }
120 }
121}
122
123#[derive(Debug, PartialEq, Eq, serde_with::SerializeDisplay, serde_with::DeserializeFromStr)]
124pub struct MuxedAccount(pub stellar_strkey::ed25519::MuxedAccount);
125
126impl FromStr for MuxedAccount {
127 type Err = stellar_strkey::DecodeError;
128
129 fn from_str(s: &str) -> Result<Self, Self::Err> {
130 Ok(MuxedAccount(
131 stellar_strkey::ed25519::MuxedAccount::from_str(s)?,
132 ))
133 }
134}
135
136impl Display for MuxedAccount {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 write!(f, "{}", self.0)
139 }
140}
141
142impl TryFrom<Key> for Secret {
143 type Error = Error;
144
145 fn try_from(key: Key) -> Result<Secret, Self::Error> {
146 match key {
147 Key::Secret(secret) => Ok(secret),
148 _ => Err(Error::SecretPublicKey),
149 }
150 }
151}
152
153#[cfg(test)]
154mod test {
155 use super::*;
156
157 fn round_trip(key: &Key) {
158 let serialized = toml::to_string(&key).unwrap();
159 println!("{serialized}");
160 let deserialized: Key = toml::from_str(&serialized).unwrap();
161 assert_eq!(key, &deserialized);
162 }
163
164 #[test]
165 fn public_key() {
166 let key = Key::PublicKey(Public(stellar_strkey::ed25519::PublicKey([0; 32])));
167 round_trip(&key);
168 }
169 #[test]
170 fn muxed_key() {
171 let key: stellar_strkey::ed25519::MuxedAccount =
172 "MA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAAAAAAAAAPCICBKU"
173 .parse()
174 .unwrap();
175 let key = Key::MuxedAccount(MuxedAccount(key));
176 round_trip(&key);
177 }
178 #[test]
179 fn secret_key() {
180 let secret_key = format!("{}", stellar_strkey::ed25519::PrivateKey([0; 32]));
181 let secret = Secret::SecretKey { secret_key };
182 let key = Key::Secret(secret);
183 round_trip(&key);
184 }
185 #[test]
186 fn secret_seed_phrase() {
187 let seed_phrase = "singer swing mango apple singer swing mango apple singer swing mango apple singer swing mango apple".to_string();
188 let secret = Secret::SeedPhrase {
189 seed_phrase,
190 hd_path: None,
191 };
192 let key = Key::Secret(secret);
193 round_trip(&key);
194 }
195
196 const PUBLIC_KEY: &str = "GAKSH6AD2IPJQELTHIOWDAPYX74YELUOWJLI2L4RIPIPZH6YQIFNUSDC";
197 const MUXED_ACCOUNT: &str =
198 "MA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAAAAAAAAAPCICBKU";
199 const SECRET_KEY: &str = "SBF5HLRREHMS36XZNTUSKZ6FTXDZGNXOHF4EXKUL5UCWZLPBX3NGJ4BH";
200 const SEED_PHRASE: &str =
201 "depth decade power loud smile spatial sign movie judge february rate broccoli";
202
203 #[test]
204 fn parse_public_only_accepts_public_key() {
205 let key = Key::parse_public_only(PUBLIC_KEY).unwrap();
206 assert!(matches!(key, Key::PublicKey(_)));
207 }
208
209 #[test]
210 fn parse_public_only_accepts_muxed_account() {
211 let key = Key::parse_public_only(MUXED_ACCOUNT).unwrap();
212 assert!(matches!(key, Key::MuxedAccount(_)));
213 }
214
215 #[test]
216 fn parse_public_only_rejects_secret_key() {
217 let err = Key::parse_public_only(SECRET_KEY).unwrap_err();
218 assert!(matches!(err, Error::PublicKeyExpected));
219 }
220
221 #[test]
222 fn parse_public_only_rejects_seed_phrase() {
223 let err = Key::parse_public_only(SEED_PHRASE).unwrap_err();
224 assert!(matches!(err, Error::PublicKeyExpected));
225 }
226
227 #[test]
228 fn parse_public_only_rejects_ledger() {
229 let err = Key::parse_public_only("ledger").unwrap_err();
230 assert!(matches!(err, Error::Parse));
231 }
232
233 #[test]
234 fn parse_public_only_rejects_secure_store() {
235 let err = Key::parse_public_only("secure_store:org.stellar.cli-alice").unwrap_err();
236 assert!(matches!(err, Error::PublicKeyExpected));
237 }
238
239 #[test]
240 fn parse_public_only_rejects_garbage() {
241 let err = Key::parse_public_only("not-a-key").unwrap_err();
242 assert!(matches!(err, Error::Parse));
243 }
244}