Skip to main content

soroban_cli/config/
address.rs

1use std::{
2    fmt::{self, Display, Formatter},
3    str::FromStr,
4};
5
6use crate::{
7    signer::{self, ledger},
8    xdr,
9};
10
11use super::{key, locator, secret, utils};
12
13/// Address can be either a public key or eventually an alias of a address.
14#[derive(Clone, Debug)]
15pub enum UnresolvedMuxedAccount {
16    Resolved(xdr::MuxedAccount),
17    AliasOrSecret(String),
18    Ledger(u32),
19}
20
21impl Default for UnresolvedMuxedAccount {
22    fn default() -> Self {
23        UnresolvedMuxedAccount::AliasOrSecret(String::default())
24    }
25}
26
27impl Display for UnresolvedMuxedAccount {
28    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
29        match self {
30            UnresolvedMuxedAccount::Resolved(muxed_account) => write!(f, "{muxed_account}"),
31            UnresolvedMuxedAccount::AliasOrSecret(alias_or_secret) => {
32                write!(f, "{alias_or_secret}")
33            }
34            UnresolvedMuxedAccount::Ledger(hd_path) => write!(f, "ledger:{hd_path}"),
35        }
36    }
37}
38
39#[derive(thiserror::Error, Debug)]
40pub enum Error {
41    #[error(transparent)]
42    Locator(#[from] locator::Error),
43    #[error(transparent)]
44    Secret(#[from] secret::Error),
45    #[error(transparent)]
46    Signer(#[from] signer::Error),
47    #[error(transparent)]
48    Key(#[from] key::Error),
49    #[error("Address cannot be used to sign {0}")]
50    CannotSign(xdr::MuxedAccount),
51    #[error("Ledger cannot reveal private keys")]
52    LedgerPrivateKeyRevealNotSupported,
53    #[error("Invalid key name: {0}\n `ledger` is not allowed")]
54    LedgerIsInvalidKeyName(String),
55    #[error("Invalid key name: {0}\n only alphanumeric characters, underscores (_), and hyphens (-) are allowed.")]
56    InvalidKeyNameCharacters(String),
57    #[error("Invalid key name: {0}\n keys cannot exceed 250 characters")]
58    InvalidKeyNameLength(String),
59    #[error("Invalid key name: {0}\n keys cannot be the word \"ledger\"")]
60    InvalidKeyName(String),
61    #[error("Ledger not supported in this context")]
62    LedgerNotSupported,
63    #[error(transparent)]
64    Ledger(#[from] signer::ledger::Error),
65    #[error(transparent)]
66    Name(#[from] utils::Error),
67}
68
69impl FromStr for UnresolvedMuxedAccount {
70    type Err = Error;
71
72    fn from_str(value: &str) -> Result<Self, Self::Err> {
73        if value.starts_with("ledger") {
74            if let Some(ledger) = parse_ledger(value) {
75                return Ok(UnresolvedMuxedAccount::Ledger(ledger));
76            }
77        }
78        Ok(xdr::MuxedAccount::from_str(value).map_or_else(
79            |_| UnresolvedMuxedAccount::AliasOrSecret(value.to_string()),
80            UnresolvedMuxedAccount::Resolved,
81        ))
82    }
83}
84
85fn parse_ledger(value: &str) -> Option<u32> {
86    let vals: Vec<_> = value.split(':').collect();
87    if vals.len() > 2 {
88        return None;
89    }
90    if vals.len() == 1 {
91        return Some(0);
92    }
93    vals[1].parse().ok()
94}
95
96impl UnresolvedMuxedAccount {
97    pub async fn resolve_muxed_account(
98        &self,
99        locator: &locator::Args,
100        hd_path: Option<usize>,
101    ) -> Result<xdr::MuxedAccount, Error> {
102        match self {
103            UnresolvedMuxedAccount::Ledger(hd_path) => Ok(xdr::MuxedAccount::Ed25519(
104                ledger::new(*hd_path).await?.public_key().await?.0.into(),
105            )),
106            UnresolvedMuxedAccount::Resolved(_) | UnresolvedMuxedAccount::AliasOrSecret(_) => {
107                self.resolve_muxed_account_sync(locator, hd_path)
108            }
109        }
110    }
111
112    pub fn resolve_muxed_account_sync(
113        &self,
114        locator: &locator::Args,
115        hd_path: Option<usize>,
116    ) -> Result<xdr::MuxedAccount, Error> {
117        match self {
118            UnresolvedMuxedAccount::Resolved(muxed_account) => Ok(muxed_account.clone()),
119            UnresolvedMuxedAccount::AliasOrSecret(alias_or_secret) => {
120                Ok(locator.read_key(alias_or_secret)?.muxed_account(hd_path)?)
121            }
122            UnresolvedMuxedAccount::Ledger(_) => Err(Error::LedgerNotSupported),
123        }
124    }
125
126    pub fn resolve_secret(&self, locator: &locator::Args) -> Result<secret::Secret, Error> {
127        match &self {
128            UnresolvedMuxedAccount::Resolved(muxed_account) => {
129                Err(Error::CannotSign(muxed_account.clone()))
130            }
131            UnresolvedMuxedAccount::AliasOrSecret(alias_or_secret) => {
132                Ok(locator.read_key(alias_or_secret)?.try_into()?)
133            }
134            UnresolvedMuxedAccount::Ledger(_) => Err(Error::LedgerPrivateKeyRevealNotSupported),
135        }
136    }
137}
138
139#[derive(Clone, Debug)]
140pub struct KeyName(pub String);
141
142impl std::ops::Deref for KeyName {
143    type Target = str;
144    fn deref(&self) -> &Self::Target {
145        &self.0
146    }
147}
148
149impl std::str::FromStr for KeyName {
150    type Err = Error;
151    fn from_str(s: &str) -> Result<Self, Self::Err> {
152        utils::validate_name(s).map_err(|e| match e {
153            utils::Error::InvalidNameLength(s) => Error::InvalidKeyNameLength(s),
154            utils::Error::InvalidNameCharacters(s) => Error::InvalidKeyNameCharacters(s),
155        })?;
156        if s == "ledger" {
157            return Err(Error::InvalidKeyName(s.to_string()));
158        }
159        Ok(KeyName(s.to_string()))
160    }
161}
162
163impl Display for KeyName {
164    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
165        write!(f, "{}", self.0)
166    }
167}
168
169pub fn validate_name(s: &str) -> Result<(), Error> {
170    Ok(utils::validate_name(s)?)
171}
172
173#[derive(Clone, Debug)]
174pub struct NetworkName(String);
175
176impl std::ops::Deref for NetworkName {
177    type Target = str;
178    fn deref(&self) -> &Self::Target {
179        &self.0
180    }
181}
182
183impl std::str::FromStr for NetworkName {
184    type Err = Error;
185    fn from_str(s: &str) -> Result<Self, Self::Err> {
186        validate_name(s)?;
187        Ok(NetworkName(s.to_string()))
188    }
189}
190
191impl Display for NetworkName {
192    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
193        write!(f, "{}", self.0)
194    }
195}
196
197#[derive(Clone, Debug)]
198pub struct AliasName(String);
199
200impl std::ops::Deref for AliasName {
201    type Target = str;
202    fn deref(&self) -> &Self::Target {
203        &self.0
204    }
205}
206
207impl std::str::FromStr for AliasName {
208    type Err = Error;
209    fn from_str(s: &str) -> Result<Self, Self::Err> {
210        validate_name(s)?;
211        Ok(AliasName(s.to_string()))
212    }
213}
214
215impl Display for AliasName {
216    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
217        write!(f, "{}", self.0)
218    }
219}
220
221#[derive(Clone, Debug)]
222pub struct ContractName(String);
223
224impl std::ops::Deref for ContractName {
225    type Target = str;
226    fn deref(&self) -> &Self::Target {
227        &self.0
228    }
229}
230
231impl std::str::FromStr for ContractName {
232    type Err = Error;
233    fn from_str(s: &str) -> Result<Self, Self::Err> {
234        validate_name(s)?;
235        Ok(ContractName(s.to_string()))
236    }
237}
238
239impl Display for ContractName {
240    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
241        write!(f, "{}", self.0)
242    }
243}
244
245impl AsRef<std::path::Path> for ContractName {
246    fn as_ref(&self) -> &std::path::Path {
247        std::path::Path::new(&self.0)
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    #[test]
256    fn network_name_valid() {
257        assert!("my-network".parse::<NetworkName>().is_ok());
258        assert!("my_network_123".parse::<NetworkName>().is_ok());
259        assert!("ledger".parse::<NetworkName>().is_ok());
260    }
261
262    #[test]
263    fn network_name_rejects_path_traversal() {
264        assert!("../evil".parse::<NetworkName>().is_err());
265        assert!("../../etc/passwd".parse::<NetworkName>().is_err());
266        assert!("foo/bar".parse::<NetworkName>().is_err());
267        assert!("foo\\bar".parse::<NetworkName>().is_err());
268    }
269
270    #[test]
271    fn network_name_rejects_too_long() {
272        assert!("a".repeat(251).parse::<NetworkName>().is_err());
273        assert!("a".repeat(250).parse::<NetworkName>().is_ok());
274    }
275
276    #[test]
277    fn alias_name_valid() {
278        assert!("my_alias_123".parse::<AliasName>().is_ok());
279        assert!("ledger".parse::<AliasName>().is_ok());
280    }
281
282    #[test]
283    fn alias_name_rejects_path_traversal() {
284        assert!("../evil".parse::<AliasName>().is_err());
285        assert!("../../etc/passwd".parse::<AliasName>().is_err());
286        assert!("foo/bar".parse::<AliasName>().is_err());
287        assert!("foo\\bar".parse::<AliasName>().is_err());
288    }
289
290    #[test]
291    fn alias_name_rejects_too_long() {
292        assert!("a".repeat(251).parse::<AliasName>().is_err());
293        assert!("a".repeat(250).parse::<AliasName>().is_ok());
294    }
295
296    #[test]
297    fn network_name_rejects_empty() {
298        assert!("".parse::<NetworkName>().is_err());
299    }
300
301    #[test]
302    fn alias_name_rejects_empty() {
303        assert!("".parse::<AliasName>().is_err());
304    }
305
306    #[test]
307    fn contract_name_valid() {
308        assert!("hello-world".parse::<ContractName>().is_ok());
309        assert!("my_contract_123".parse::<ContractName>().is_ok());
310    }
311
312    #[test]
313    fn contract_name_rejects_path_traversal() {
314        assert!("../evil".parse::<ContractName>().is_err());
315        assert!("../../etc/passwd".parse::<ContractName>().is_err());
316        assert!("foo/bar".parse::<ContractName>().is_err());
317        assert!("foo\\bar".parse::<ContractName>().is_err());
318    }
319
320    #[test]
321    fn contract_name_rejects_too_long() {
322        assert!("a".repeat(251).parse::<ContractName>().is_err());
323        assert!("a".repeat(250).parse::<ContractName>().is_ok());
324    }
325
326    #[test]
327    fn contract_name_rejects_empty() {
328        assert!("".parse::<ContractName>().is_err());
329    }
330}