From 503d4caa084925c67533367d1eacdb45fae3a21b Mon Sep 17 00:00:00 2001 From: Nicolas80 <nicolas.pmail@protonmail.com> Date: Fri, 2 May 2025 18:13:24 +0200 Subject: [PATCH] Adapting `vault inspect` * Added non-interactive support when providing the `password` or `no-password` * Adapted to support default human-readable and optional json format * using log::warn! to be able to get the info of why we don't have a secret seed value if we need to; while not impacting the "standard output" of the command that can still be piped to say `jq` --- src/commands/vault.rs | 164 ++++++++++++++++++++++++++++-------------- src/keys.rs | 11 +++ 2 files changed, 122 insertions(+), 53 deletions(-) diff --git a/src/commands/vault.rs b/src/commands/vault.rs index 16118a3..2f65659 100644 --- a/src/commands/vault.rs +++ b/src/commands/vault.rs @@ -119,6 +119,14 @@ pub enum Subcommand { Inspect { #[clap(flatten)] address_or_vault_name: AddressOrVaultNameGroup, + + /// Password to decrypt the <Base> account key (non-interactive mode) + #[clap(short = 'p', long, required = false, conflicts_with_all=["no_password"])] + password: Option<String>, + + /// Use empty password to decrypt the <Base> account key (non-interactive mode) + #[clap(long, required = false)] + no_password: bool, }, /// (deprecated) List available key files (needs to be migrated with command `vault migrate` in order to use them) #[deprecated( @@ -179,6 +187,17 @@ pub struct AddressOrVaultNameGroup { vault_name: Option<String>, } +/// vault inspect for JSON serialization +#[derive(Serialize)] +struct VaultInspectData { + substrate_uri: String, + crypto_scheme: CryptoScheme, + secret_seed: Option<String>, + public_key_hex: String, + ss58_address: String, + g1v1_public_key: Option<String>, +} + pub struct VaultDataToImport { secret_format: SecretFormat, secret_suri: String, @@ -336,7 +355,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE //This updated configuration will be picked up with next GCli execution conf::save(&updated_cfg); - + match data.args.output_format { OutputFormat::Human => { println!("Configuration updated!"); @@ -619,7 +638,11 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE } Subcommand::Inspect { address_or_vault_name, + password, + no_password, } => { + let is_interactive = password.is_none() && !no_password; + let account_tree_node_to_inspect = retrieve_account_tree_node(db, address_or_vault_name).await?; @@ -628,73 +651,51 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE let is_base_account = Rc::ptr_eq(&account_tree_node_to_inspect, &base_account_tree_node); - if !is_base_account { + + if is_interactive && !is_base_account { let base_account = base_account_tree_node.borrow().account.clone(); println!("The linked <Base> account is {base_account}"); } - println!("Enter password to decrypt the <Base> account key"); - let password = inputs::prompt_password()?; + // Handle password from non-interactive mode or ask for it + let password = if no_password { + String::new() + } else if let Some(password) = password { + password + } else { + println!("Enter password to decrypt the <Base> account key"); + inputs::prompt_password()? + }; - let account_to_derive_secret_suri = vault_account::compute_suri_account_tree_node( - &account_tree_node_to_inspect, - password, - )?; + let vault_inspect_data = + compute_vault_inspect_data(&account_tree_node_to_inspect, password)?; match data.args.output_format { OutputFormat::Human => { - println!("Substrate URI: '{account_to_derive_secret_suri}'"); - - let crypto_scheme: CryptoScheme = base_account_tree_node - .borrow() - .account - .crypto_scheme - .ok_or(GcliError::Logic( - "Base account without crypto_scheme".to_string(), - ))? - .into(); - println!("Crypto scheme: {}", <&'static str>::from(crypto_scheme)); - - match compute_pair_and_mini_secret_seed_from_suri_and_crypto_scheme( - &account_to_derive_secret_suri, - crypto_scheme, - ) { - Err(e) => { - println!("Secret seed/mini-secret: cannot be computed: {}", e) - } - Ok((_computed_pair, seed)) => { - println!("Secret seed/mini-secret: '0x{}'", hex::encode(seed)); + println!("Substrate URI: '{}'", vault_inspect_data.substrate_uri); + println!( + "Crypto scheme: {}", + <&'static str>::from(vault_inspect_data.crypto_scheme) + ); + match vault_inspect_data.secret_seed { + None => println!("Secret seed/mini-secret: N/A"), + Some(secret_seed) => { + println!("Secret seed/mini-secret: '0x{}'", secret_seed) } } - - let account_address: AccountId = account_tree_node_to_inspect - .borrow() - .account - .address - .clone() - .into(); - - println!("Public key (hex): '0x{}'", hex::encode(account_address.0)); - - println!("SS58 Address: '{account_address}'"); - - if CryptoScheme::Ed25519 == crypto_scheme && is_base_account { - println!( - "(potential G1v1 public key: '{}')", - cesium::compute_g1v1_public_key_from_ed25519_account_id( - &account_address - ) - ); + println!( + "Public key (hex): '0x{}'", + vault_inspect_data.public_key_hex + ); + println!("SS58 Address: '{}'", vault_inspect_data.ss58_address); + if let Some(g1v1_public_key) = vault_inspect_data.g1v1_public_key { + println!("(potential G1v1 public key: '{}')", g1v1_public_key); } } OutputFormat::Json => { - //FIXME handle same data as interactive - let json_view = serde_json::json!({ - "substrate_uri": account_to_derive_secret_suri - }); println!( "{}", - serde_json::to_string(&json_view).map_err(|e| anyhow!(e))? + serde_json::to_string(&vault_inspect_data).map_err(|e| anyhow!(e))? ); } } @@ -788,6 +789,63 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE Ok(()) } +fn compute_vault_inspect_data( + account_tree_node_to_inspect: &Rc<RefCell<AccountTreeNode>>, + password: String, +) -> Result<VaultInspectData, GcliError> { + let base_account_tree_node = + vault_account::get_base_account_tree_node(account_tree_node_to_inspect); + + let is_base_account = Rc::ptr_eq(account_tree_node_to_inspect, &base_account_tree_node); + + let account_secret_suri = + vault_account::compute_suri_account_tree_node(account_tree_node_to_inspect, password)?; + + let crypto_scheme: CryptoScheme = base_account_tree_node + .borrow() + .account + .crypto_scheme + .ok_or(GcliError::Logic( + "Base account without crypto_scheme".to_string(), + ))? + .into(); + + let opt_seed = match compute_pair_and_mini_secret_seed_from_suri_and_crypto_scheme( + &account_secret_suri, + crypto_scheme, + ) { + Err(e) => { + log::warn!("Secret seed/mini-secret cannot be computed: {}", e); + None + } + Ok((_computed_pair, seed)) => Some(hex::encode(seed)), + }; + + let account_address: AccountId = account_tree_node_to_inspect + .borrow() + .account + .address + .clone() + .into(); + + let opt_g1v1_pub_key = if CryptoScheme::Ed25519 == crypto_scheme && is_base_account { + let g1v1_public_key = + cesium::compute_g1v1_public_key_from_ed25519_account_id(&account_address); + Some(g1v1_public_key) + } else { + None + }; + + Ok(VaultInspectData { + substrate_uri: account_secret_suri, + crypto_scheme, + secret_seed: opt_seed, + public_key_hex: hex::encode(account_address.0), + ss58_address: account_address.to_string(), + g1v1_public_key: opt_g1v1_pub_key, + }) +} + /// Method used to separate vault `name` part from optional `derivation` part in computed names that can be provided by users in the different `vault` commands using `AddressOrVaultNameGroup` fn parse_vault_name_and_derivation_path_from_user_input( user_input_name: String, diff --git a/src/keys.rs b/src/keys.rs index 4db3ca2..88d0480 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1,5 +1,6 @@ use crate::commands::vault; use crate::*; +use serde::Serializer; use sp_core::crypto::AddressUri; use sp_core::crypto::Pair as PairTrait; use sp_core::DeriveJunction; @@ -64,6 +65,16 @@ pub enum CryptoScheme { Sr25519, } +impl Serialize for CryptoScheme { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let as_str: &str = (*self).into(); // Use the `From<CryptoScheme>` implementation + serializer.serialize_str(as_str) + } +} + /// Setting a default to Ed25519 /// /// required when used in Args struct inside main.rs; even though we still have to give a clap "default_value" -- GitLab