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