rustfmt ghost client cli and fix typos

Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
This commit is contained in:
Uncle Stretch 2025-07-29 15:02:07 +03:00
parent a74e42369b
commit 767161ac9c
Signed by: str3tch
GPG Key ID: 84F3190747EE79AA
9 changed files with 598 additions and 569 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ghost-client-cli" name = "ghost-client-cli"
version = "0.1.3" version = "0.1.4"
description = "Ghost CLI interface" description = "Ghost CLI interface"
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true

View File

@ -3,13 +3,10 @@
use bip39::Mnemonic; use bip39::Mnemonic;
use clap::Parser; use clap::Parser;
use itertools::Itertools; use itertools::Itertools;
use sc_cli::{ use sc_cli::{with_crypto_scheme, CryptoSchemeFlag, Error, KeystoreParams, OutputTypeFlag};
with_crypto_scheme, KeystoreParams, OutputTypeFlag,
CryptoSchemeFlag, Error,
};
use crate::params::NetworkSchemeFlag;
use crate::commands::utils::print_from_uri; use crate::commands::utils::print_from_uri;
use crate::params::NetworkSchemeFlag;
/// The `generate` command /// The `generate` command
#[derive(Debug, Clone, Parser)] #[derive(Debug, Clone, Parser)]
@ -17,58 +14,58 @@ use crate::commands::utils::print_from_uri;
pub struct GenerateCmd { pub struct GenerateCmd {
/// The number of words in the phrase to generate. One of 12 (default), /// The number of words in the phrase to generate. One of 12 (default),
/// 15, 18, 21 and 24. /// 15, 18, 21 and 24.
#[arg(short = 'w', long, value_name = "WORDS")] #[arg(short = 'w', long, value_name = "WORDS")]
words: Option<usize>, words: Option<usize>,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub keystore_params: KeystoreParams, pub keystore_params: KeystoreParams,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub network_scheme: NetworkSchemeFlag, pub network_scheme: NetworkSchemeFlag,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub output_scheme: OutputTypeFlag, pub output_scheme: OutputTypeFlag,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub crypto_scheme: CryptoSchemeFlag, pub crypto_scheme: CryptoSchemeFlag,
} }
impl GenerateCmd { impl GenerateCmd {
/// Run the command /// Run the command
pub fn run(&self) -> Result<(), Error> { pub fn run(&self) -> Result<(), Error> {
let words = match self.words { let words = match self.words {
Some(words_count) if [12, 15, 18, 21, 24].contains(&words_count) => Ok(words_count), Some(words_count) if [12, 15, 18, 21, 24].contains(&words_count) => Ok(words_count),
Some(_) => Err(Error::Input( Some(_) => Err(Error::Input(
"Invalid number of words given for phrase: must be 12/15/18/21/24".into(), "Invalid number of words given for phrase: must be 12/15/18/21/24".into(),
)), )),
None => Ok(12), None => Ok(12),
}?; }?;
let mnemonic = Mnemonic::generate(words) let mnemonic = Mnemonic::generate(words)
.map_err(|e| Error::Input(format!("Mnemonic generation failed: {e}").into()))?; .map_err(|e| Error::Input(format!("Mnemonic generation failed: {e}").into()))?;
let password = self.keystore_params.read_password()?; let password = self.keystore_params.read_password()?;
let output = self.output_scheme.output_type; let output = self.output_scheme.output_type;
let phrase = mnemonic.words().join(" "); let phrase = mnemonic.words().join(" ");
with_crypto_scheme!( with_crypto_scheme!(
self.crypto_scheme.scheme, self.crypto_scheme.scheme,
print_from_uri(&phrase, password, self.network_scheme.network, output) print_from_uri(&phrase, password, self.network_scheme.network, output)
); );
Ok(()) Ok(())
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn generate() { fn generate() {
let generate = GenerateCmd::parse_from(&["generate", "--password", "12345"]); let generate = GenerateCmd::parse_from(&["generate", "--password", "12345"]);
assert!(generate.run().is_ok()) assert!(generate.run().is_ok())
} }
} }

View File

@ -1,102 +1,101 @@
//! Implementation of the `inspect` subcommand //! Implementation of the `inspect` subcommand
use crate::commands::utils::{print_from_public, print_from_uri};
use crate::params::NetworkSchemeFlag;
use clap::Parser; use clap::Parser;
use sc_cli::{
utils::read_uri, with_crypto_scheme, CryptoSchemeFlag, Error, KeystoreParams, OutputTypeFlag,
};
use sp_core::crypto::{ExposeSecret, SecretString, SecretUri, Ss58Codec}; use sp_core::crypto::{ExposeSecret, SecretString, SecretUri, Ss58Codec};
use std::str::FromStr; use std::str::FromStr;
use sc_cli::{
with_crypto_scheme, KeystoreParams, OutputTypeFlag, CryptoSchemeFlag, Error,
utils::read_uri,
};
use crate::params::NetworkSchemeFlag;
use crate::commands::utils::{print_from_public, print_from_uri};
/// The `inspect` command /// The `inspect` command
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command( #[command(
name = "inspect", name = "inspect",
about = "Gets a public key and a SS58 address from the provided Secret URI" about = "Gets a public key and a SS58 address from the provided Secret URI"
)] )]
pub struct InspectKeyCmd { pub struct InspectKeyCmd {
/// A Key URI to be inspected. May be a secret seed, secret URI /// A Key URI to be inspected. May be a secret seed, secret URI
/// (with derivation paths and password), SS58, public URI or a hex encoded public key. /// (with derivation paths and password), SS58, public URI or a hex encoded public key.
/// ///
/// If it is a hex encoded public key, `--public` needs to be given as argument. /// If it is a hex encoded public key, `--public` needs to be given as argument.
/// ///
/// If the given value is a file, the file content will be used /// If the given value is a file, the file content will be used
/// as URI. /// as URI.
/// ///
/// If omitted, you will be prompted for the URI. /// If omitted, you will be prompted for the URI.
uri: Option<String>, uri: Option<String>,
/// Is the given `uri` a hex encoded public key? /// Is the given `uri` a hex encoded public key?
#[arg(long)] #[arg(long)]
public: bool, public: bool,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub keystore_params: KeystoreParams, pub keystore_params: KeystoreParams,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub network_scheme: NetworkSchemeFlag, pub network_scheme: NetworkSchemeFlag,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub output_scheme: OutputTypeFlag, pub output_scheme: OutputTypeFlag,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
pub crypto_scheme: CryptoSchemeFlag, pub crypto_scheme: CryptoSchemeFlag,
/// Expect that `--uri` has the given public key/account-id. /// Expect that `--uri` has the given public key/account-id.
/// ///
/// If `--uri` has any derivations, the public key is checked against the base `uri`, i.e. the /// If `--uri` has any derivations, the public key is checked against the base `uri`, i.e. the
/// `uri` without any derivation applied. However, if `uri` has a password or there is one /// `uri` without any derivation applied. However, if `uri` has a password or there is one
/// given by `--password`, it will be used to decrypt `uri` before comparing the public /// given by `--password`, it will be used to decrypt `uri` before comparing the public
/// key/account-id. /// key/account-id.
/// ///
/// If there is no derivation in `--uri`, the public key will be checked against the public key /// If there is no derivation in `--uri`, the public key will be checked against the public key
/// of `--uri` directly. /// of `--uri` directly.
#[arg(long, conflicts_with = "public")] #[arg(long, conflicts_with = "public")]
pub expect_public: Option<String>, pub expect_public: Option<String>,
} }
impl InspectKeyCmd { impl InspectKeyCmd {
/// Run the command /// Run the command
pub fn run(&self) -> Result<(), Error> { pub fn run(&self) -> Result<(), Error> {
let uri = read_uri(self.uri.as_ref())?; let uri = read_uri(self.uri.as_ref())?;
let password = self.keystore_params.read_password()?; let password = self.keystore_params.read_password()?;
if self.public { if self.public {
with_crypto_scheme!( with_crypto_scheme!(
self.crypto_scheme.scheme, self.crypto_scheme.scheme,
print_from_public( print_from_public(
&uri, &uri,
self.network_scheme.network, self.network_scheme.network,
self.output_scheme.output_type, self.output_scheme.output_type,
) )
)?; )?;
} else { } else {
if let Some(ref expect_public) = self.expect_public { if let Some(ref expect_public) = self.expect_public {
with_crypto_scheme!( with_crypto_scheme!(
self.crypto_scheme.scheme, self.crypto_scheme.scheme,
expect_public_from_phrase(expect_public, &uri, password.as_ref()) expect_public_from_phrase(expect_public, &uri, password.as_ref())
)?; )?;
} }
with_crypto_scheme!( with_crypto_scheme!(
self.crypto_scheme.scheme, self.crypto_scheme.scheme,
print_from_uri( print_from_uri(
&uri, &uri,
password, password,
self.network_scheme.network, self.network_scheme.network,
self.output_scheme.output_type, self.output_scheme.output_type,
) )
); );
} }
Ok(()) Ok(())
} }
} }
/// Checks that `expect_public` is the public key of `suri`. /// Checks that `expect_public` is the public key of `suri`.
@ -106,148 +105,156 @@ impl InspectKeyCmd {
/// ///
/// Returns an error if the public key does not match. /// Returns an error if the public key does not match.
fn expect_public_from_phrase<Pair: sp_core::Pair>( fn expect_public_from_phrase<Pair: sp_core::Pair>(
expect_public: &str, expect_public: &str,
suri: &str, suri: &str,
password: Option<&SecretString>, password: Option<&SecretString>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let secret_uri = SecretUri::from_str(suri).map_err(|e| format!("{:?}", e))?; let secret_uri = SecretUri::from_str(suri).map_err(|e| format!("{:?}", e))?;
let expected_public = if let Some(public) = expect_public.strip_prefix("0x") { let expected_public = if let Some(public) = expect_public.strip_prefix("0x") {
let hex_public = array_bytes::hex2bytes(public) let hex_public = array_bytes::hex2bytes(public)
.map_err(|_| format!("Invalid expected public key hex: `{}`", expect_public))?; .map_err(|_| format!("Invalid expected public key hex: `{}`", expect_public))?;
Pair::Public::try_from(&hex_public) Pair::Public::try_from(&hex_public)
.map_err(|_| format!("Invalid expected public key: `{}`", expect_public))? .map_err(|_| format!("Invalid expected public key: `{}`", expect_public))?
} else { } else {
Pair::Public::from_string_with_version(expect_public) Pair::Public::from_string_with_version(expect_public)
.map_err(|_| format!("Invalid expected account id: `{}`", expect_public))? .map_err(|_| format!("Invalid expected account id: `{}`", expect_public))?
.0 .0
}; };
let pair = Pair::from_string_with_seed( let pair = Pair::from_string_with_seed(
secret_uri.phrase.expose_secret().as_str(), secret_uri.phrase.expose_secret().as_str(),
password password
.or_else(|| secret_uri.password.as_ref()) .or_else(|| secret_uri.password.as_ref())
.map(|p| p.expose_secret().as_str()), .map(|p| p.expose_secret().as_str()),
) )
.map_err(|_| format!("Invalid secret uri: {}", suri))? .map_err(|_| format!("Invalid secret uri: {}", suri))?
.0; .0;
if pair.public() == expected_public { if pair.public() == expected_public {
Ok(()) Ok(())
} else { } else {
Err(format!("Expected public ({}) key does not match.", expect_public).into()) Err(format!("Expected public ({}) key does not match.", expect_public).into())
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use sp_core::crypto::{ByteArray, Pair}; use sp_core::crypto::{ByteArray, Pair};
use sp_runtime::traits::IdentifyAccount; use sp_runtime::traits::IdentifyAccount;
#[test] #[test]
fn inspect() { fn inspect() {
let words = let words =
"remember fiber forum demise paper uniform squirrel feel access exclude casual effort"; "remember fiber forum demise paper uniform squirrel feel access exclude casual effort";
let seed = "0xad1fb77243b536b90cfe5f0d351ab1b1ac40e3890b41dc64f766ee56340cfca5"; let seed = "0xad1fb77243b536b90cfe5f0d351ab1b1ac40e3890b41dc64f766ee56340cfca5";
let inspect = InspectKeyCmd::parse_from(&["inspect-key", words, "--password", "12345"]); let inspect = InspectKeyCmd::parse_from(&["inspect-key", words, "--password", "12345"]);
assert!(inspect.run().is_ok()); assert!(inspect.run().is_ok());
let inspect = InspectKeyCmd::parse_from(&["inspect-key", seed]); let inspect = InspectKeyCmd::parse_from(&["inspect-key", seed]);
assert!(inspect.run().is_ok()); assert!(inspect.run().is_ok());
} }
#[test] #[test]
fn inspect_public_key() { fn inspect_public_key() {
let public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069"; let public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069";
let inspect = InspectKeyCmd::parse_from(&["inspect-key", "--public", public]); let inspect = InspectKeyCmd::parse_from(&["inspect-key", "--public", public]);
assert!(inspect.run().is_ok()); assert!(inspect.run().is_ok());
} }
#[test] #[test]
fn inspect_with_expected_public_key() { fn inspect_with_expected_public_key() {
let check_cmd = |seed, expected_public, success| { let check_cmd = |seed, expected_public, success| {
let inspect = InspectKeyCmd::parse_from(&[ let inspect = InspectKeyCmd::parse_from(&[
"inspect-key", "inspect-key",
"--expect-public", "--expect-public",
expected_public, expected_public,
seed, seed,
]); ]);
let res = inspect.run(); let res = inspect.run();
if success { if success {
assert!(res.is_ok()); assert!(res.is_ok());
} else { } else {
assert!(res.unwrap_err().to_string().contains(&format!( assert!(res.unwrap_err().to_string().contains(&format!(
"Expected public ({}) key does not match.", "Expected public ({}) key does not match.",
expected_public expected_public
))); )));
} }
}; };
let seed = let seed =
"remember fiber forum demise paper uniform squirrel feel access exclude casual effort"; "remember fiber forum demise paper uniform squirrel feel access exclude casual effort";
let invalid_public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069"; let invalid_public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069";
let valid_public = sp_core::sr25519::Pair::from_string_with_seed(seed, None) let valid_public = sp_core::sr25519::Pair::from_string_with_seed(seed, None)
.expect("Valid") .expect("Valid")
.0 .0
.public(); .public();
let valid_public_hex = array_bytes::bytes2hex("0x", valid_public.as_slice()); let valid_public_hex = array_bytes::bytes2hex("0x", valid_public.as_slice());
let valid_accountid = format!("{}", valid_public.into_account()); let valid_accountid = format!("{}", valid_public.into_account());
// It should fail with the invalid public key // It should fail with the invalid public key
check_cmd(seed, invalid_public, false); check_cmd(seed, invalid_public, false);
// It should work with the valid public key & account id // It should work with the valid public key & account id
check_cmd(seed, &valid_public_hex, true); check_cmd(seed, &valid_public_hex, true);
check_cmd(seed, &valid_accountid, true); check_cmd(seed, &valid_accountid, true);
let password = "test12245"; let password = "test12245";
let seed_with_password = format!("{}///{}", seed, password); let seed_with_password = format!("{}///{}", seed, password);
let valid_public_with_password = let valid_public_with_password =
sp_core::sr25519::Pair::from_string_with_seed(&seed_with_password, Some(password)) sp_core::sr25519::Pair::from_string_with_seed(&seed_with_password, Some(password))
.expect("Valid") .expect("Valid")
.0 .0
.public(); .public();
let valid_public_hex_with_password = let valid_public_hex_with_password =
array_bytes::bytes2hex("0x", valid_public_with_password.as_slice()); array_bytes::bytes2hex("0x", valid_public_with_password.as_slice());
let valid_accountid_with_password = let valid_accountid_with_password =
format!("{}", &valid_public_with_password.into_account()); format!("{}", &valid_public_with_password.into_account());
// Only the public key that corresponds to the seed with password should be accepted. // Only the public key that corresponds to the seed with password should be accepted.
check_cmd(&seed_with_password, &valid_public_hex, false); check_cmd(&seed_with_password, &valid_public_hex, false);
check_cmd(&seed_with_password, &valid_accountid, false); check_cmd(&seed_with_password, &valid_accountid, false);
check_cmd(&seed_with_password, &valid_public_hex_with_password, true); check_cmd(&seed_with_password, &valid_public_hex_with_password, true);
check_cmd(&seed_with_password, &valid_accountid_with_password, true); check_cmd(&seed_with_password, &valid_accountid_with_password, true);
let seed_with_password_and_derivation = format!("{}//test//account///{}", seed, password); let seed_with_password_and_derivation = format!("{}//test//account///{}", seed, password);
let valid_public_with_password_and_derivation = let valid_public_with_password_and_derivation =
sp_core::sr25519::Pair::from_string_with_seed( sp_core::sr25519::Pair::from_string_with_seed(
&seed_with_password_and_derivation, &seed_with_password_and_derivation,
Some(password), Some(password),
) )
.expect("Valid") .expect("Valid")
.0 .0
.public(); .public();
let valid_public_hex_with_password_and_derivation = let valid_public_hex_with_password_and_derivation =
array_bytes::bytes2hex("0x", valid_public_with_password_and_derivation.as_slice()); array_bytes::bytes2hex("0x", valid_public_with_password_and_derivation.as_slice());
// They should still be valid, because we check the base secret key. // They should still be valid, because we check the base secret key.
check_cmd(&seed_with_password_and_derivation, &valid_public_hex_with_password, true); check_cmd(
check_cmd(&seed_with_password_and_derivation, &valid_accountid_with_password, true); &seed_with_password_and_derivation,
&valid_public_hex_with_password,
true,
);
check_cmd(
&seed_with_password_and_derivation,
&valid_accountid_with_password,
true,
);
// And these should be invalid. // And these should be invalid.
check_cmd(&seed_with_password_and_derivation, &valid_public_hex, false); check_cmd(&seed_with_password_and_derivation, &valid_public_hex, false);
check_cmd(&seed_with_password_and_derivation, &valid_accountid, false); check_cmd(&seed_with_password_and_derivation, &valid_accountid, false);
// The public of the derived account should fail. // The public of the derived account should fail.
check_cmd( check_cmd(
&seed_with_password_and_derivation, &seed_with_password_and_derivation,
&valid_public_hex_with_password_and_derivation, &valid_public_hex_with_password_and_derivation,
false, false,
); );
} }
} }

View File

@ -1,40 +1,37 @@
//! Key related CLI utilities //! Key related CLI utilities
use super::{generate::GenerateCmd, inspect_key::InspectKeyCmd}; use super::{generate::GenerateCmd, inspect_key::InspectKeyCmd};
use sc_cli::{ use sc_cli::{Error, GenerateKeyCmdCommon, InsertKeyCmd, InspectNodeKeyCmd, SubstrateCli};
GenerateKeyCmdCommon, InsertKeyCmd, InspectNodeKeyCmd, Error,
SubstrateCli,
};
/// Key utilities for the cli. /// Key utilities for the cli.
#[derive(Debug, clap::Subcommand)] #[derive(Debug, clap::Subcommand)]
pub enum KeySubcommand { pub enum KeySubcommand {
/// Generate a random node key, write it to a file or stdout and write the /// Generate a random node key, write it to a file or stdout and write the
/// corresponding peer-id to stderr /// corresponding peer-id to stderr
GenerateNodeKey(GenerateKeyCmdCommon), GenerateNodeKey(GenerateKeyCmdCommon),
/// Generate a Substrate-based random account /// Generate a Substrate-based random account
Generate(GenerateCmd), Generate(GenerateCmd),
/// Gets a public key and a SS58 address from the provided Secret URI /// Gets a public key and a SS58 address from the provided Secret URI
Inspect(InspectKeyCmd), Inspect(InspectKeyCmd),
/// Load a node key from a file or stdin and print the corresponding peer-id /// Load a node key from a file or stdin and print the corresponding peer-id
InspectNodeKey(InspectNodeKeyCmd), InspectNodeKey(InspectNodeKeyCmd),
/// Insert a key to the keystore of a node. /// Insert a key to the keystore of a node.
Insert(InsertKeyCmd), Insert(InsertKeyCmd),
} }
impl KeySubcommand { impl KeySubcommand {
/// run the key subcommands /// run the key subcommands
pub fn run<C: SubstrateCli>(&self, cli: &C) -> Result<(), Error> { pub fn run<C: SubstrateCli>(&self, cli: &C) -> Result<(), Error> {
match self { match self {
KeySubcommand::GenerateNodeKey(cmd) => cmd.run(), KeySubcommand::GenerateNodeKey(cmd) => cmd.run(),
KeySubcommand::Generate(cmd) => cmd.run(), KeySubcommand::Generate(cmd) => cmd.run(),
KeySubcommand::Inspect(cmd) => cmd.run(), KeySubcommand::Inspect(cmd) => cmd.run(),
KeySubcommand::Insert(cmd) => cmd.run(cli), KeySubcommand::Insert(cmd) => cmd.run(cli),
KeySubcommand::InspectNodeKey(cmd) => cmd.run(), KeySubcommand::InspectNodeKey(cmd) => cmd.run(),
} }
} }
} }

View File

@ -1,11 +1,13 @@
mod key;
mod generate; mod generate;
mod vanity;
mod inspect_key; mod inspect_key;
mod key;
mod utils; mod utils;
mod vanity;
pub use self::{ pub use self::{
key::KeySubcommand, vanity::VanityCmd, inspect_key::InspectKeyCmd,
generate::GenerateCmd, generate::GenerateCmd,
utils::{unwrap_or_default_ss58_name, print_from_uri, print_from_public}, inspect_key::InspectKeyCmd,
key::KeySubcommand,
utils::{print_from_public, print_from_uri, unwrap_or_default_ss58_name},
vanity::VanityCmd,
}; };

View File

@ -1,194 +1,206 @@
use serde_json::json;
use sc_cli::{ use sc_cli::{
OutputType,
utils::{PublicFor, SeedFor}, utils::{PublicFor, SeedFor},
OutputType,
}; };
use sp_runtime::{traits::IdentifyAccount, MultiSigner}; use serde_json::json;
use sp_core::{ use sp_core::{
crypto::{ crypto::{
unwrap_or_default_ss58_version, unwrap_or_default_ss58_version, ExposeSecret, SecretString, Ss58AddressFormat, Ss58Codec,
Ss58Codec, ExposeSecret, Ss58AddressFormat, SecretString,
}, },
hexdisplay::HexDisplay, hexdisplay::HexDisplay,
}; };
use sp_runtime::{traits::IdentifyAccount, MultiSigner};
pub fn print_from_uri<Pair>( pub fn print_from_uri<Pair>(
uri: &str, uri: &str,
password: Option<SecretString>, password: Option<SecretString>,
network_override: Option<Ss58AddressFormat>, network_override: Option<Ss58AddressFormat>,
output: OutputType, output: OutputType,
) where ) where
Pair: sp_core::Pair, Pair: sp_core::Pair,
Pair::Public: Into<MultiSigner>, Pair::Public: Into<MultiSigner>,
{ {
let password = password.as_ref().map(|s| s.expose_secret().as_str()); let password = password.as_ref().map(|s| s.expose_secret().as_str());
let network_id = unwrap_or_default_ss58_name(network_override); let network_id = unwrap_or_default_ss58_name(network_override);
if let Ok((pair, seed)) = Pair::from_phrase(uri, password) { if let Ok((pair, seed)) = Pair::from_phrase(uri, password) {
let public_key = pair.public(); let public_key = pair.public();
let network_override = unwrap_or_default_ss58_version(network_override); let network_override = unwrap_or_default_ss58_version(network_override);
match output { match output {
OutputType::Json => { OutputType::Json => {
let json = json!({ let json = json!({
"secretPhrase": uri, "secretPhrase": uri,
"networkId": network_id, "networkId": network_id,
"secretSeed": format_seed::<Pair>(seed), "secretSeed": format_seed::<Pair>(seed),
"publicKey": format_public_key::<Pair>(public_key.clone()), "publicKey": format_public_key::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override), "ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"accountId": format_account_id::<Pair>(public_key), "accountId": format_account_id::<Pair>(public_key),
"ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override), "ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override),
}); });
println!( println!(
"{}", "{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed") serde_json::to_string_pretty(&json).expect("Json pretty print failed")
); );
}, }
OutputType::Text => { OutputType::Text => {
println!( println!(
"Secret phrase: {}\n \ "Secret phrase: {}\n \
Network ID: {}\n \ Network ID: {}\n \
Secret seed: {}\n \ Secret seed: {}\n \
Public key (hex): {}\n \ Public key (hex): {}\n \
Account ID: {}\n \ Account ID: {}\n \
Public key (SS58): {}\n \ Public key (SS58): {}\n \
SS58 Address: {}", SS58 Address: {}",
uri, uri,
network_id, network_id,
format_seed::<Pair>(seed), format_seed::<Pair>(seed),
format_public_key::<Pair>(public_key.clone()), format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()), format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override), public_key.to_ss58check_with_version(network_override),
pair.public().into().into_account().to_ss58check_with_version(network_override), pair.public()
); .into()
}, .into_account()
} .to_ss58check_with_version(network_override),
} else if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password) { );
let public_key = pair.public(); }
let network_override = unwrap_or_default_ss58_version(network_override); }
} else if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password) {
let public_key = pair.public();
let network_override = unwrap_or_default_ss58_version(network_override);
match output { match output {
OutputType::Json => { OutputType::Json => {
let json = json!({ let json = json!({
"secretKeyUri": uri, "secretKeyUri": uri,
"networkId": network_id, "networkId": network_id,
"secretSeed": if let Some(seed) = seed { format_seed::<Pair>(seed) } else { "n/a".into() }, "secretSeed": if let Some(seed) = seed { format_seed::<Pair>(seed) } else { "n/a".into() },
"publicKey": format_public_key::<Pair>(public_key.clone()), "publicKey": format_public_key::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override), "ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"accountId": format_account_id::<Pair>(public_key), "accountId": format_account_id::<Pair>(public_key),
"ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override), "ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override),
}); });
println!( println!(
"{}", "{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed") serde_json::to_string_pretty(&json).expect("Json pretty print failed")
); );
}, }
OutputType::Text => { OutputType::Text => {
println!( println!(
"Secret Key URI `{}` is account:\n \ "Secret Key URI `{}` is account:\n \
Network ID: {}\n \ Network ID: {}\n \
Secret seed: {}\n \ Secret seed: {}\n \
Public key (hex): {}\n \ Public key (hex): {}\n \
Account ID: {}\n \ Account ID: {}\n \
Public key (SS58): {}\n \ Public key (SS58): {}\n \
SS58 Address: {}", SS58 Address: {}",
uri, uri,
network_id, network_id,
if let Some(seed) = seed { format_seed::<Pair>(seed) } else { "n/a".into() }, if let Some(seed) = seed {
format_public_key::<Pair>(public_key.clone()), format_seed::<Pair>(seed)
format_account_id::<Pair>(public_key.clone()), } else {
public_key.to_ss58check_with_version(network_override), "n/a".into()
pair.public().into().into_account().to_ss58check_with_version(network_override), },
); format_public_key::<Pair>(public_key.clone()),
}, format_account_id::<Pair>(public_key.clone()),
} public_key.to_ss58check_with_version(network_override),
} else if let Ok((public_key, network)) = Pair::Public::from_string_with_version(uri) { pair.public()
let network_override = network_override.unwrap_or(network); .into()
.into_account()
.to_ss58check_with_version(network_override),
);
}
}
} else if let Ok((public_key, network)) = Pair::Public::from_string_with_version(uri) {
let network_override = network_override.unwrap_or(network);
match output { match output {
OutputType::Json => { OutputType::Json => {
let json = json!({ let json = json!({
"publicKeyUri": uri, "publicKeyUri": uri,
"networkId": String::from(network_override), "networkId": String::from(network_override),
"publicKey": format_public_key::<Pair>(public_key.clone()), "publicKey": format_public_key::<Pair>(public_key.clone()),
"accountId": format_account_id::<Pair>(public_key.clone()), "accountId": format_account_id::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override), "ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"ss58Address": public_key.to_ss58check_with_version(network_override), "ss58Address": public_key.to_ss58check_with_version(network_override),
}); });
println!( println!(
"{}", "{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed") serde_json::to_string_pretty(&json).expect("Json pretty print failed")
); );
}, }
OutputType::Text => { OutputType::Text => {
println!( println!(
"Public Key URI `{}` is account:\n \ "Public Key URI `{}` is account:\n \
Network ID/Version: {}\n \ Network ID/Version: {}\n \
Public key (hex): {}\n \ Public key (hex): {}\n \
Account ID: {}\n \ Account ID: {}\n \
Public key (SS58): {}\n \ Public key (SS58): {}\n \
SS58 Address: {}", SS58 Address: {}",
uri, uri,
String::from(network_override), String::from(network_override),
format_public_key::<Pair>(public_key.clone()), format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()), format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override), public_key.to_ss58check_with_version(network_override),
public_key.to_ss58check_with_version(network_override), public_key.to_ss58check_with_version(network_override),
); );
}, }
} }
} else { } else {
println!("Invalid phrase/URI given"); println!("Invalid phrase/URI given");
} }
} }
/// Try to parse given `public` as hex encoded public key and print relevant information. /// Try to parse given `public` as hex encoded public key and print relevant information.
pub fn print_from_public<Pair>( pub fn print_from_public<Pair>(
public_str: &str, public_str: &str,
network_override: Option<Ss58AddressFormat>, network_override: Option<Ss58AddressFormat>,
output: OutputType, output: OutputType,
) -> Result<(), sc_cli::Error> ) -> Result<(), sc_cli::Error>
where where
Pair: sp_core::Pair, Pair: sp_core::Pair,
Pair::Public: Into<MultiSigner>, Pair::Public: Into<MultiSigner>,
{ {
let public = array_bytes::hex2bytes(public_str)?; let public = array_bytes::hex2bytes(public_str)?;
let public_key = Pair::Public::try_from(&public) let public_key = Pair::Public::try_from(&public)
.map_err(|_| "Failed to construct public key from given hex")?; .map_err(|_| "Failed to construct public key from given hex")?;
let network_override = unwrap_or_default_ss58_version(network_override); let network_override = unwrap_or_default_ss58_version(network_override);
match output { match output {
OutputType::Json => { OutputType::Json => {
let json = json!({ let json = json!({
"networkId": String::from(network_override), "networkId": String::from(network_override),
"publicKey": format_public_key::<Pair>(public_key.clone()), "publicKey": format_public_key::<Pair>(public_key.clone()),
"accountId": format_account_id::<Pair>(public_key.clone()), "accountId": format_account_id::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override), "ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"ss58Address": public_key.to_ss58check_with_version(network_override), "ss58Address": public_key.to_ss58check_with_version(network_override),
}); });
println!("{}", serde_json::to_string_pretty(&json).expect("Json pretty print failed")); println!(
}, "{}",
OutputType::Text => { serde_json::to_string_pretty(&json).expect("Json pretty print failed")
println!( );
"Network ID/Version: {}\n \ }
OutputType::Text => {
println!(
"Network ID/Version: {}\n \
Public key (hex): {}\n \ Public key (hex): {}\n \
Account ID: {}\n \ Account ID: {}\n \
Public key (SS58): {}\n \ Public key (SS58): {}\n \
SS58 Address: {}", SS58 Address: {}",
String::from(network_override), String::from(network_override),
format_public_key::<Pair>(public_key.clone()), format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()), format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override), public_key.to_ss58check_with_version(network_override),
public_key.to_ss58check_with_version(network_override), public_key.to_ss58check_with_version(network_override),
); );
}, }
} }
Ok(()) Ok(())
} }
pub fn unwrap_or_default_ss58_name(x: Option<Ss58AddressFormat>) -> String { pub fn unwrap_or_default_ss58_name(x: Option<Ss58AddressFormat>) -> String {
@ -216,5 +228,8 @@ fn format_account_id<P: sp_core::Pair>(public_key: PublicFor<P>) -> String
where where
PublicFor<P>: Into<MultiSigner>, PublicFor<P>: Into<MultiSigner>,
{ {
format!("0x{}", HexDisplay::from(&public_key.into().into_account().as_ref())) format!(
"0x{}",
HexDisplay::from(&public_key.into().into_account().as_ref())
)
} }

View File

@ -2,213 +2,218 @@
use clap::Parser; use clap::Parser;
use rand::{rngs::OsRng, RngCore}; use rand::{rngs::OsRng, RngCore};
use sp_core::crypto::{ use sc_cli::{utils::format_seed, with_crypto_scheme, CryptoSchemeFlag, Error, OutputTypeFlag};
unwrap_or_default_ss58_version, Ss58AddressFormat, Ss58Codec, use sp_core::crypto::{unwrap_or_default_ss58_version, Ss58AddressFormat, Ss58Codec};
};
use sp_runtime::traits::IdentifyAccount; use sp_runtime::traits::IdentifyAccount;
use sc_cli::{
with_crypto_scheme, Error, OutputTypeFlag, CryptoSchemeFlag,
utils::format_seed,
};
use crate::commands::utils::print_from_uri; use crate::commands::utils::print_from_uri;
use crate::params::NetworkSchemeFlag; use crate::params::NetworkSchemeFlag;
/// The `vanity` command /// The `vanity` command
#[derive(Debug, Clone, Parser)] #[derive(Debug, Clone, Parser)]
#[command(name = "vanity", about = "Generate a seed that provides a vanity address")] #[command(
name = "vanity",
about = "Generate a seed that provides a vanity address"
)]
pub struct VanityCmd { pub struct VanityCmd {
/// Desired pattern /// Desired pattern
#[arg(long, value_parser = assert_non_empty_string)] #[arg(long, value_parser = assert_non_empty_string)]
pattern: String, pattern: String,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
network_scheme: NetworkSchemeFlag, network_scheme: NetworkSchemeFlag,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
output_scheme: OutputTypeFlag, output_scheme: OutputTypeFlag,
#[allow(missing_docs)] #[allow(missing_docs)]
#[clap(flatten)] #[clap(flatten)]
crypto_scheme: CryptoSchemeFlag, crypto_scheme: CryptoSchemeFlag,
} }
impl VanityCmd { impl VanityCmd {
/// Run the command /// Run the command
pub fn run(&self) -> Result<(), Error> { pub fn run(&self) -> Result<(), Error> {
let formated_seed = with_crypto_scheme!( let formated_seed = with_crypto_scheme!(
self.crypto_scheme.scheme, self.crypto_scheme.scheme,
generate_key( generate_key(
&self.pattern, &self.pattern,
unwrap_or_default_ss58_version(self.network_scheme.network) unwrap_or_default_ss58_version(self.network_scheme.network)
), ),
)?; )?;
with_crypto_scheme!( with_crypto_scheme!(
self.crypto_scheme.scheme, self.crypto_scheme.scheme,
print_from_uri( print_from_uri(
&formated_seed, &formated_seed,
None, None,
self.network_scheme.network, self.network_scheme.network,
self.output_scheme.output_type, self.output_scheme.output_type,
), ),
); );
Ok(()) Ok(())
} }
} }
/// genertae a key based on given pattern /// genertae a key based on given pattern
fn generate_key<Pair>( fn generate_key<Pair>(
desired: &str, desired: &str,
network_override: Ss58AddressFormat, network_override: Ss58AddressFormat,
) -> Result<String, &'static str> ) -> Result<String, &'static str>
where where
Pair: sp_core::Pair, Pair: sp_core::Pair,
Pair::Public: IdentifyAccount, Pair::Public: IdentifyAccount,
<Pair::Public as IdentifyAccount>::AccountId: Ss58Codec, <Pair::Public as IdentifyAccount>::AccountId: Ss58Codec,
{ {
println!("Generating key containing pattern '{}'", desired); println!("Generating key containing pattern '{}'", desired);
let top = 45 + (desired.len() * 48); let top = 45 + (desired.len() * 48);
let mut best = 0; let mut best = 0;
let mut seed = Pair::Seed::default(); let mut seed = Pair::Seed::default();
let mut done = 0; let mut done = 0;
loop { loop {
if done % 100000 == 0 { if done % 100000 == 0 {
OsRng.fill_bytes(seed.as_mut()); OsRng.fill_bytes(seed.as_mut());
} else { } else {
next_seed(seed.as_mut()); next_seed(seed.as_mut());
} }
let p = Pair::from_seed(&seed); let p = Pair::from_seed(&seed);
let ss58 = p.public().into_account().to_ss58check_with_version(network_override); let ss58 = p
.public()
.into_account()
.to_ss58check_with_version(network_override);
println!("{:?}", ss58); println!("{:?}", ss58);
let score = calculate_score(desired, &ss58); let score = calculate_score(desired, &ss58);
if score > best || desired.len() < 2 { if score > best || desired.len() < 2 {
best = score; best = score;
if best >= top { if best >= top {
println!("best: {} == top: {}", best, top); println!("best: {} == top: {}", best, top);
return Ok(format_seed::<Pair>(seed.clone())) return Ok(format_seed::<Pair>(seed.clone()));
} }
} }
done += 1; done += 1;
if done % good_waypoint(done) == 0 { if done % good_waypoint(done) == 0 {
println!("{} keys searched; best is {}/{} complete", done, best, top); println!("{} keys searched; best is {}/{} complete", done, best, top);
} }
} }
} }
fn good_waypoint(done: u64) -> u64 { fn good_waypoint(done: u64) -> u64 {
match done { match done {
0..=1_000_000 => 100_000, 0..=1_000_000 => 100_000,
1_000_001..=10_000_000 => 1_000_000, 1_000_001..=10_000_000 => 1_000_000,
10_000_001..=100_000_000 => 10_000_000, 10_000_001..=100_000_000 => 10_000_000,
100_000_001.. => 100_000_000, 100_000_001.. => 100_000_000,
} }
} }
fn next_seed(seed: &mut [u8]) { fn next_seed(seed: &mut [u8]) {
for s in seed { for s in seed {
match s { match s {
255 => { 255 => {
*s = 0; *s = 0;
}, }
_ => { _ => {
*s += 1; *s += 1;
break break;
}, }
} }
} }
} }
/// Calculate the score of a key based on the desired /// Calculate the score of a key based on the desired
/// input. /// input.
fn calculate_score(_desired: &str, key: &str) -> usize { fn calculate_score(_desired: &str, key: &str) -> usize {
for truncate in 0.._desired.len() { for truncate in 0.._desired.len() {
let snip_size = _desired.len() - truncate; let snip_size = _desired.len() - truncate;
let truncated = &_desired[0..snip_size]; let truncated = &_desired[0..snip_size];
if let Some(pos) = key.find(truncated) { if let Some(pos) = key.find(truncated) {
return (47 - pos) + (snip_size * 48) return (47 - pos) + (snip_size * 48);
} }
} }
0 0
} }
/// checks that `pattern` is non-empty /// checks that `pattern` is non-empty
fn assert_non_empty_string(pattern: &str) -> Result<String, &'static str> { fn assert_non_empty_string(pattern: &str) -> Result<String, &'static str> {
if pattern.is_empty() { if pattern.is_empty() {
Err("Pattern must not be empty") Err("Pattern must not be empty")
} else { } else {
Ok(pattern.to_string()) Ok(pattern.to_string())
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use sp_core::{ use sp_core::{
crypto::{default_ss58_version, Ss58AddressFormatRegistry, Ss58Codec}, crypto::{default_ss58_version, Ss58AddressFormatRegistry, Ss58Codec},
sr25519, Pair, sr25519, Pair,
}; };
#[test] #[test]
fn vanity() { fn vanity() {
let vanity = VanityCmd::parse_from(&["vanity", "--pattern", "j"]); let vanity = VanityCmd::parse_from(&["vanity", "--pattern", "j"]);
assert!(vanity.run().is_ok()); assert!(vanity.run().is_ok());
} }
#[test] #[test]
fn test_generation_with_single_char() { fn test_generation_with_single_char() {
let seed = generate_key::<sr25519::Pair>("ab", default_ss58_version()).unwrap(); let seed = generate_key::<sr25519::Pair>("ab", default_ss58_version()).unwrap();
assert!(sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed)) assert!(
.unwrap() sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed))
.public() .unwrap()
.to_ss58check() .public()
.contains("ab")); .to_ss58check()
} .contains("ab")
);
}
#[test] #[test]
fn generate_key_respects_network_override() { fn generate_key_respects_network_override() {
let seed = let seed =
generate_key::<sr25519::Pair>("ab", Ss58AddressFormatRegistry::PolkadotAccount.into()) generate_key::<sr25519::Pair>("ab", Ss58AddressFormatRegistry::PolkadotAccount.into())
.unwrap(); .unwrap();
assert!(sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed)) assert!(
.unwrap() sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed))
.public() .unwrap()
.to_ss58check_with_version(Ss58AddressFormatRegistry::PolkadotAccount.into()) .public()
.contains("ab")); .to_ss58check_with_version(Ss58AddressFormatRegistry::PolkadotAccount.into())
} .contains("ab")
);
}
#[test] #[test]
fn test_score_1_char_100() { fn test_score_1_char_100() {
let score = calculate_score("j", "sYrjWiZkEP1PQe2kKEZiC1Bi9L8yFSsB5RPEkzEPd5NThsB5H"); let score = calculate_score("j", "sYrjWiZkEP1PQe2kKEZiC1Bi9L8yFSsB5RPEkzEPd5NThsB5H");
assert_eq!(score, 96); assert_eq!(score, 96);
} }
#[test] #[test]
fn test_score_100() { fn test_score_100() {
let score = calculate_score("ghst", "sYsghstuhYd9p6unUC3kPxjD2gv2zRCztYQaEDCMJpYrPTqTG"); let score = calculate_score("ghst", "sYsghstuhYd9p6unUC3kPxjD2gv2zRCztYQaEDCMJpYrPTqTG");
assert_eq!(score, 246); assert_eq!(score, 246);
} }
#[test] #[test]
fn test_score_50_2() { fn test_score_50_2() {
// 50% for the position + 50% for the size // 50% for the position + 50% for the size
assert_eq!( assert_eq!(
calculate_score("ghst", "sYsghXXuhYd9p6unUC3kPxjD2gv2zRCztYQaEDCMJpYrPTqTG"), calculate_score("ghst", "sYsghXXuhYd9p6unUC3kPxjD2gv2zRCztYQaEDCMJpYrPTqTG"),
146 146
); );
} }
#[test] #[test]
fn test_score_0() { fn test_score_0() {
assert_eq!( assert_eq!(
calculate_score("ghst", "sYrjWiZkEP1PQe2kKEZiC1Bi9L8yFSsB5RPEkzEPd5NThsB5H"), calculate_score("ghst", "sYrjWiZkEP1PQe2kKEZiC1Bi9L8yFSsB5RPEkzEPd5NThsB5H"),
0 0
); );
} }
} }

View File

@ -1,5 +1,5 @@
pub mod params;
pub mod commands; pub mod commands;
pub mod params;
pub use commands::KeySubcommand; pub use commands::KeySubcommand;
pub use commands::VanityCmd; pub use commands::VanityCmd;

View File

@ -21,19 +21,25 @@ pub struct InnerSs58AddressFormat(Ss58AddressFormat);
impl InnerSs58AddressFormat { impl InnerSs58AddressFormat {
#[inline] #[inline]
pub fn custom(prefix: u16) -> Self { pub fn custom(prefix: u16) -> Self {
Self { 0: Ss58AddressFormat::custom(prefix) } Self {
0: Ss58AddressFormat::custom(prefix),
}
} }
} }
impl std::fmt::Display for InnerSs58AddressFormat { impl std::fmt::Display for InnerSs58AddressFormat {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{} network", ALL_POSSIBLE_IDS write!(
.binary_search(&u16::from(self.0)) f,
.expect("always be found")) "{} network",
ALL_POSSIBLE_IDS
.binary_search(&u16::from(self.0))
.expect("always be found")
)
} }
} }
impl<'a>TryFrom<&'a str> for InnerSs58AddressFormat { impl<'a> TryFrom<&'a str> for InnerSs58AddressFormat {
type Error = ParseError; type Error = ParseError;
fn try_from(x: &'a str) -> Result<Self, Self::Error> { fn try_from(x: &'a str) -> Result<Self, Self::Error> {
@ -44,13 +50,13 @@ impl<'a>TryFrom<&'a str> for InnerSs58AddressFormat {
} }
} }
pub fn parse_s58_prefix_address_format(x: &str,) -> Result<Ss58AddressFormat, String> { pub fn parse_s58_prefix_address_format(x: &str) -> Result<Ss58AddressFormat, String> {
match InnerSs58AddressFormat::try_from(x) { match InnerSs58AddressFormat::try_from(x) {
Ok(format_registry) => Ok(format_registry.0.into()), Ok(format_registry) => Ok(format_registry.0.into()),
Err(_) => Err(format!( Err(_) => Err(format!(
"Unable to parse variant. Known variants: {:?}", "Unable to parse variant. Known variants: {:?}",
&ALL_POSSIBLE_NAMES &ALL_POSSIBLE_NAMES
)) )),
} }
} }