1use std::{collections::HashMap, str::FromStr};
38
39use clap::{Parser, builder::RangedU64ValueParser};
40use hopr_crypto_types::{
41 keypairs::Keypair,
42 prelude::{OffchainPublicKey, PeerId},
43};
44use hopr_primitive_types::{prelude::ToHex, primitives::Address};
45use hoprd_keypair::key_pair::HoprKeys;
46use tracing::{debug, info};
47
48use crate::{
49 key_pair::{
50 ArgEnvReader, IdentityFileArgs, NewPasswordArgs, create_identity, read_identities, read_identity,
51 update_identity_password,
52 },
53 utils::{Cmd, HelperErrors, HelperErrors::UnableToParseAddress},
54};
55
56#[derive(Clone, Debug, Parser)]
58pub enum IdentitySubcommands {
59 #[command(visible_alias = "cr")]
61 Create {
62 #[command(flatten)]
64 local_identity: IdentityFileArgs,
65
66 #[clap(
68 help = "Number of identities to be generated, e.g. 1",
69 long,
70 short,
71 value_parser = RangedU64ValueParser::<u32>::new().range(1..),
72 default_value_t = 1
73 )]
74 number: u32,
75 },
76
77 #[command(visible_alias = "rd")]
79 Read {
80 #[command(flatten)]
82 local_identity: IdentityFileArgs,
83 },
84
85 #[command(visible_alias = "up")]
87 Update {
88 #[command(flatten)]
90 local_identity: IdentityFileArgs,
91
92 #[command(flatten)]
94 new_password: NewPasswordArgs,
95 },
96
97 #[command(visible_alias = "conv")]
99 ConvertPeer {
100 #[command(flatten)]
102 peer_or_key: ConvertPeerArgs,
103 },
104}
105
106#[derive(Debug, Clone, Parser, Default)]
108pub struct ConvertPeerArgs {
109 #[clap(
111 short,
112 long,
113 help = "PeerID in Base58 or public key as hex starting with 0x",
114 name = "peer_or_key",
115 value_name = "PEER_ID_OR_PUBLIC_KEY"
116 )]
117 pub peer_or_key: String,
118}
119
120impl IdentitySubcommands {
121 fn execute_identity_creation_loop(local_identity: IdentityFileArgs, number: u32) -> Result<(), HelperErrors> {
123 let pwd = local_identity.clone().password.read_default()?;
125
126 let mut node_identities: HashMap<String, HoprKeys> = HashMap::new();
127
128 match local_identity.identity_from_directory {
129 Some(local_id) => {
130 let id_dir = local_id
131 .identity_directory
132 .ok_or(HelperErrors::MissingIdentityDirectory)?;
133 for index in 0..=number - 1 {
134 let file_prefix = local_id
136 .identity_prefix
137 .as_ref()
138 .map(|provided_name| provided_name.to_owned() + &index.to_string());
139
140 let (id_filename, identity) = create_identity(&id_dir, &pwd, &file_prefix)
141 .map_err(|_| HelperErrors::UnableToCreateIdentity)?;
142 node_identities.insert(id_filename, identity);
143 }
144
145 info!("Identities: {:?}", node_identities);
146 Ok(())
147 }
148 None => Err(HelperErrors::MissingParameter(
149 "Missing identity_from_directory when creating identites".into(),
150 )),
151 }
152 }
153
154 fn execute_identity_read_loop(local_identity: IdentityFileArgs) -> Result<(), HelperErrors> {
156 let pwd = local_identity.clone().password.read_default()?;
158
159 let files = local_identity.get_files()?;
161 debug!("Identities read {:?}", files.len());
162
163 let node_identities: HashMap<String, HoprKeys> =
164 read_identities(files, &pwd).map_err(|_| HelperErrors::UnableToReadIdentity)?;
165
166 let node_addresses: Vec<Address> = node_identities
167 .values()
168 .map(|n| n.chain_key.public().to_address())
169 .collect();
170
171 let peer_ids: Vec<String> = node_identities
172 .values()
173 .map(|n| n.packet_key.public().to_peerid_str())
174 .collect();
175
176 info!("Identities: {:?}", node_identities);
177 println!("Identity addresses: {:?}", node_addresses);
178 println!("Identity peerids: [{}]", peer_ids.join(", "));
179 Ok(())
180 }
181
182 fn execute_identity_update(
184 local_identity: IdentityFileArgs,
185 new_password: NewPasswordArgs,
186 ) -> Result<(), HelperErrors> {
187 let pwd = local_identity.clone().password.read_default()?;
189 let new_pwd = new_password.read_default()?;
191
192 let files = local_identity.get_files()?;
194 debug!("Identities read {:?}", files.len());
195
196 let _ = files
197 .iter()
198 .map(|file| {
199 read_identity(file, &pwd)
200 .map_err(|_| HelperErrors::UnableToUpdateIdentityPassword)
201 .and_then(|(_, keys)| update_identity_password(keys, file, &new_pwd))
202 })
203 .collect::<Result<Vec<_>, _>>()?;
204
205 info!("Updated password for {:?} identity files", files.len());
206 Ok(())
207 }
208}
209
210impl Cmd for IdentitySubcommands {
211 fn run(self) -> Result<(), HelperErrors> {
213 match self {
214 IdentitySubcommands::Create { local_identity, number } => {
215 IdentitySubcommands::execute_identity_creation_loop(local_identity, number)
216 }
217 IdentitySubcommands::Read { local_identity } => {
218 IdentitySubcommands::execute_identity_read_loop(local_identity)
219 }
220 IdentitySubcommands::Update {
221 local_identity,
222 new_password,
223 } => IdentitySubcommands::execute_identity_update(local_identity, new_password),
224 IdentitySubcommands::ConvertPeer { peer_or_key } => {
225 if peer_or_key.peer_or_key.to_lowercase().starts_with("0x") {
226 let pk = OffchainPublicKey::from_hex(&peer_or_key.peer_or_key)
227 .map_err(|_| UnableToParseAddress(peer_or_key.peer_or_key))?;
228 println!("{}", pk.to_peerid_str());
229 Ok(())
230 } else {
231 let pk = PeerId::from_str(&peer_or_key.peer_or_key)
232 .map_err(|_| UnableToParseAddress(peer_or_key.peer_or_key.clone()))
233 .and_then(|p| {
234 OffchainPublicKey::try_from(p).map_err(|_| UnableToParseAddress(peer_or_key.peer_or_key))
235 })?;
236 println!("{}", pk.to_hex());
237 Ok(())
238 }
239 }
240 }
241 }
242
243 async fn async_run(self) -> Result<(), HelperErrors> {
244 Ok(())
245 }
246}