soroban_cli/commands/keys/
add.rs

1use std::io::Write;
2
3use sep5::SeedPhrase;
4
5use crate::{
6    commands::global,
7    config::{
8        address::KeyName,
9        key, locator,
10        secret::{self, Secret},
11    },
12    print::Print,
13    signer::secure_store,
14};
15
16#[derive(thiserror::Error, Debug)]
17pub enum Error {
18    #[error(transparent)]
19    Secret(#[from] secret::Error),
20    #[error(transparent)]
21    Key(#[from] key::Error),
22    #[error(transparent)]
23    Config(#[from] locator::Error),
24
25    #[error(transparent)]
26    SecureStore(#[from] secure_store::Error),
27
28    #[error(transparent)]
29    SeedPhrase(#[from] sep5::error::Error),
30
31    #[error("secret input error")]
32    PasswordRead,
33
34    #[error("An identity with the name '{0}' already exists")]
35    IdentityAlreadyExists(String),
36}
37
38#[derive(Debug, clap::Parser, Clone)]
39#[group(skip)]
40pub struct Cmd {
41    /// Name of identity
42    pub name: KeyName,
43
44    #[command(flatten)]
45    pub secrets: secret::Args,
46
47    #[command(flatten)]
48    pub config_locator: locator::Args,
49
50    /// Add a public key, ed25519, or muxed account, e.g. G1.., M2..
51    #[arg(long, conflicts_with = "seed_phrase", conflicts_with = "secret_key")]
52    pub public_key: Option<String>,
53
54    /// Overwrite existing identity if it already exists.
55    #[arg(long)]
56    pub overwrite: bool,
57}
58
59impl Cmd {
60    pub fn run(&self, global_args: &global::Args) -> Result<(), Error> {
61        let print = Print::new(global_args.quiet);
62
63        if self.config_locator.read_identity(&self.name).is_ok() {
64            if !self.overwrite {
65                return Err(Error::IdentityAlreadyExists(self.name.to_string()));
66            }
67
68            print.exclaimln(format!("Overwriting identity '{}'", &self.name.to_string()));
69        }
70
71        let key = if let Some(key) = self.public_key.as_ref() {
72            key.parse()?
73        } else {
74            self.read_secret(&print)?.into()
75        };
76
77        let path = self.config_locator.write_key(&self.name, &key)?;
78
79        print.checkln(format!("Key saved with alias {} in {path:?}", self.name));
80
81        Ok(())
82    }
83
84    fn read_secret(&self, print: &Print) -> Result<Secret, Error> {
85        if let Ok(secret_key) = std::env::var("STELLAR_SECRET_KEY") {
86            Ok(Secret::SecretKey { secret_key })
87        } else if self.secrets.secure_store {
88            let prompt = "Type a 12/24 word seed phrase:";
89            let secret_key = read_password(print, prompt)?;
90            if secret_key.split_whitespace().count() < 24 {
91                print.warnln("The provided seed phrase lacks sufficient entropy and should be avoided. Using a 24-word seed phrase is a safer option.".to_string());
92                print.warnln(
93                    "To generate a new key, use the `stellar keys generate` command.".to_string(),
94                );
95            }
96
97            let seed_phrase: SeedPhrase = secret_key.parse()?;
98
99            let secret = secure_store::save_secret(print, &self.name, &seed_phrase)?;
100            Ok(secret.parse()?)
101        } else {
102            let prompt = "Type a secret key or 12/24 word seed phrase:";
103            let secret_key = read_password(print, prompt)?;
104            let secret = secret_key.parse()?;
105            if let Secret::SeedPhrase { seed_phrase } = &secret {
106                if seed_phrase.split_whitespace().count() < 24 {
107                    print.warnln("The provided seed phrase lacks sufficient entropy and should be avoided. Using a 24-word seed phrase is a safer option.".to_string());
108                    print.warnln(
109                        "To generate a new key, use the `stellar keys generate` command."
110                            .to_string(),
111                    );
112                }
113            }
114            Ok(secret)
115        }
116    }
117}
118
119fn read_password(print: &Print, prompt: &str) -> Result<String, Error> {
120    print.arrowln(prompt);
121    std::io::stdout().flush().map_err(|_| Error::PasswordRead)?;
122    rpassword::read_password().map_err(|_| Error::PasswordRead)
123}