use anyhow::{anyhow, Result}; use clap::builder::OsStr; use sp_core::{ crypto::{AccountId32, Pair as _, Ss58Codec}, sr25519::Pair, }; use std::str::FromStr; #[allow(dead_code)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum NeededKeys { None, Public, Secret, } #[derive(Clone, Copy, Debug, Eq, PartialEq, Default)] pub enum SecretFormat { /// Raw 32B seed Seed, /// Substrate secret key or BIP39 mnemonic (optionally followed by derivation path) #[default] Substrate, } impl FromStr for SecretFormat { type Err = std::io::Error; fn from_str(s: &str) -> std::io::Result<Self> { match s { "seed" => Ok(SecretFormat::Seed), "substrate" => Ok(SecretFormat::Substrate), _ => Err(std::io::Error::from(std::io::ErrorKind::InvalidInput)), } } } impl From<SecretFormat> for &'static str { fn from(val: SecretFormat) -> &'static str { match val { SecretFormat::Seed => "seed", SecretFormat::Substrate => "substrate", } } } impl From<SecretFormat> for OsStr { fn from(val: SecretFormat) -> OsStr { OsStr::from(Into::<&str>::into(val)) } } pub fn pair_from_str(secret_format: SecretFormat, secret: &str) -> Result<Pair> { match secret_format { SecretFormat::Seed => { let mut seed = [0; 32]; hex::decode_to_slice(secret, &mut seed).map_err(|_| anyhow!("Invalid secret"))?; let pair = Pair::from_seed(&seed); Ok(pair) } SecretFormat::Substrate => { Pair::from_string(secret, None).map_err(|_| anyhow!("Invalid secret")) } } } pub fn prompt_secret(secret_format: SecretFormat) -> Pair { loop { match pair_from_str( secret_format, &rpassword::prompt_password(format!("Secret key ({secret_format:?}): ")).unwrap(), ) { Ok(pair) => return pair, Err(_) => println!("Invalid secret"), } } } pub fn get_keys( secret_format: SecretFormat, address: &Option<String>, secret: &Option<String>, needed_keys: NeededKeys, ) -> Result<(Option<AccountId32>, Option<Pair>)> { // Get from args let mut account_id = match (address, secret) { (Some(address), Some(secret)) => { let pair = pair_from_str(secret_format, secret)?; let address = AccountId32::from_string(address) .map_err(|_| anyhow!("Invalid address {}", address))?; assert_eq!( address, pair.public().into(), "Secret and address do not match." ); return Ok((Some(pair.public().into()), Some(pair))); } (None, Some(secret)) => { let pair = pair_from_str(secret_format, secret)?; return Ok((Some(pair.public().into()), Some(pair))); } (Some(address), None) => Some( AccountId32::from_str(address).map_err(|_| anyhow!("Invalid address {}", address))?, ), (None, None) => None, }; // Prompt if needed_keys == NeededKeys::Secret || (account_id.is_none() && needed_keys == NeededKeys::Public) { loop { let pair = prompt_secret(secret_format); if let Some(account_id) = &account_id { if account_id != &pair.public().into() { println!("Secret and address do not match."); } } else { account_id = Some(pair.public().into()); return Ok((account_id, Some(pair))); } } } Ok((account_id, None)) }