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
36#[derive(Debug, clap::Parser, Clone)]
37#[group(skip)]
38pub struct Cmd {
39    /// Name of identity
40    pub name: KeyName,
41
42    #[command(flatten)]
43    pub secrets: secret::Args,
44
45    #[command(flatten)]
46    pub config_locator: locator::Args,
47
48    /// Add a public key, ed25519, or muxed account, e.g. G1.., M2..
49    #[arg(long, conflicts_with = "seed_phrase", conflicts_with = "secret_key")]
50    pub public_key: Option<String>,
51}
52
53impl Cmd {
54    pub fn run(&self, global_args: &global::Args) -> Result<(), Error> {
55        let print = Print::new(global_args.quiet);
56        let key = if let Some(key) = self.public_key.as_ref() {
57            key.parse()?
58        } else {
59            self.read_secret(&print)?.into()
60        };
61
62        let path = self.config_locator.write_key(&self.name, &key)?;
63
64        print.checkln(format!("Key saved with alias {} in {path:?}", self.name));
65
66        Ok(())
67    }
68
69    fn read_secret(&self, print: &Print) -> Result<Secret, Error> {
70        if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") {
71            Ok(Secret::SecretKey { secret_key })
72        } else if self.secrets.secure_store {
73            let prompt = "Type a 12/24 word seed phrase:";
74            let secret_key = read_password(print, prompt)?;
75            if secret_key.split_whitespace().count() < 24 {
76                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());
77                print.warnln(
78                    "To generate a new key, use the `stellar keys generate` command.".to_string(),
79                );
80            }
81
82            let seed_phrase: SeedPhrase = secret_key.parse()?;
83
84            Ok(secure_store::save_secret(print, &self.name, seed_phrase)?)
85        } else {
86            let prompt = "Type a secret key or 12/24 word seed phrase:";
87            let secret_key = read_password(print, prompt)?;
88            let secret = secret_key.parse()?;
89            if let Secret::SeedPhrase { seed_phrase } = &secret {
90                if seed_phrase.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."
94                            .to_string(),
95                    );
96                }
97            }
98            Ok(secret)
99        }
100    }
101}
102
103fn read_password(print: &Print, prompt: &str) -> Result<String, Error> {
104    print.arrowln(prompt);
105    std::io::stdout().flush().map_err(|_| Error::PasswordRead)?;
106    rpassword::read_password().map_err(|_| Error::PasswordRead)
107}