soroban_cli/commands/keys/
add.rs1use 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 pub name: KeyName,
43
44 #[command(flatten)]
45 pub secrets: secret::Args,
46
47 #[command(flatten)]
48 pub config_locator: locator::Args,
49
50 #[arg(long, conflicts_with = "seed_phrase", conflicts_with = "secret_key")]
52 pub public_key: Option<String>,
53
54 #[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}