soroban_cli/commands/keys/
add.rs

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