From f4978c1bde1354b99d95115d6c24952343a61dc6 Mon Sep 17 00:00:00 2001 From: fryorcraken Date: Tue, 6 Jan 2026 15:28:17 +1100 Subject: [PATCH 1/4] add `wallet account keys` command to display ipk/npk ipk and npk are needed to receive funds from an external account on a private account. They are currently only displayed at account creation (`wallet account new private`). With this command, it is now possible to print them at any time. --- wallet/src/cli/account.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 58e19847..852e6091 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -36,6 +36,12 @@ pub enum AccountSubcommand { #[arg(short, long)] long: bool, }, + /// Get keys (npk, ipk) for a private account + Keys { + /// Valid 32 byte base58 string with privacy prefix + #[arg(short, long)] + account_id: String, + }, } /// Represents generic register CLI subcommand @@ -351,6 +357,29 @@ impl WalletSubcommand for AccountSubcommand { } } + Ok(SubcommandReturnValue::Empty) + } + AccountSubcommand::Keys { account_id } => { + let (account_id, addr_kind) = parse_addr_with_privacy_prefix(&account_id)?; + + if addr_kind != AccountPrivacyKind::Private { + anyhow::bail!("Keys command only works for private accounts"); + } + + let account_id = account_id.parse()?; + + let (key, _) = wallet_core + .storage + .user_data + .get_private_account(&account_id) + .ok_or(anyhow::anyhow!("Private account not found in storage"))?; + + println!("npk {}", hex::encode(key.nullifer_public_key.0)); + println!( + "ipk {}", + hex::encode(key.incoming_viewing_public_key.to_bytes()) + ); + Ok(SubcommandReturnValue::Empty) } } From 562ab63735f80f98e3a3a11addbfb379688e1b64 Mon Sep 17 00:00:00 2001 From: fryorcraken Date: Wed, 7 Jan 2026 10:07:44 +1100 Subject: [PATCH 2/4] Move the feature to `get --keys` --- wallet/src/cli/account.rs | 56 ++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 852e6091..b934697e 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -20,6 +20,9 @@ pub enum AccountSubcommand { /// Flag to get raw account data #[arg(short, long)] raw: bool, + /// Display keys (npk, ipk) for private accounts + #[arg(short, long)] + keys: bool, /// Valid 32 byte base58 string with privacy prefix #[arg(short, long)] account_id: String, @@ -36,12 +39,6 @@ pub enum AccountSubcommand { #[arg(short, long)] long: bool, }, - /// Get keys (npk, ipk) for a private account - Keys { - /// Valid 32 byte base58 string with privacy prefix - #[arg(short, long)] - account_id: String, - }, } /// Represents generic register CLI subcommand @@ -207,7 +204,11 @@ impl WalletSubcommand for AccountSubcommand { wallet_core: &mut WalletCore, ) -> Result { match self { - AccountSubcommand::Get { raw, account_id } => { + AccountSubcommand::Get { + raw, + keys, + account_id, + } => { let (account_id, addr_kind) = parse_addr_with_privacy_prefix(&account_id)?; let account_id = account_id.parse()?; @@ -238,6 +239,24 @@ impl WalletSubcommand for AccountSubcommand { println!("{description}"); println!("{json_view}"); + if keys { + if addr_kind != AccountPrivacyKind::Private { + anyhow::bail!("--keys option only works for private accounts"); + } + + let (key, _) = wallet_core + .storage + .user_data + .get_private_account(&account_id) + .ok_or(anyhow::anyhow!("Private account not found in storage"))?; + + println!("npk {}", hex::encode(key.nullifer_public_key.0)); + println!( + "ipk {}", + hex::encode(key.incoming_viewing_public_key.to_bytes()) + ); + } + Ok(SubcommandReturnValue::Empty) } AccountSubcommand::New(new_subcommand) => { @@ -357,29 +376,6 @@ impl WalletSubcommand for AccountSubcommand { } } - Ok(SubcommandReturnValue::Empty) - } - AccountSubcommand::Keys { account_id } => { - let (account_id, addr_kind) = parse_addr_with_privacy_prefix(&account_id)?; - - if addr_kind != AccountPrivacyKind::Private { - anyhow::bail!("Keys command only works for private accounts"); - } - - let account_id = account_id.parse()?; - - let (key, _) = wallet_core - .storage - .user_data - .get_private_account(&account_id) - .ok_or(anyhow::anyhow!("Private account not found in storage"))?; - - println!("npk {}", hex::encode(key.nullifer_public_key.0)); - println!( - "ipk {}", - hex::encode(key.incoming_viewing_public_key.to_bytes()) - ); - Ok(SubcommandReturnValue::Empty) } } From 9267cb59af666e7c879fbd6b3bec0edc6537633a Mon Sep 17 00:00:00 2001 From: fryorcraken Date: Fri, 16 Jan 2026 11:03:01 +1100 Subject: [PATCH 3/4] Return keys for public accounts --- wallet/src/cli/account.rs | 51 +++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index b934697e..8d040aa5 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -3,7 +3,7 @@ use base58::ToBase58; use clap::Subcommand; use itertools::Itertools as _; use key_protocol::key_management::key_tree::chain_index::ChainIndex; -use nssa::{Account, program::Program}; +use nssa::{Account, PublicKey, program::Program}; use serde::Serialize; use crate::{ @@ -20,7 +20,7 @@ pub enum AccountSubcommand { /// Flag to get raw account data #[arg(short, long)] raw: bool, - /// Display keys (npk, ipk) for private accounts + /// Display keys (pk for public accounts, npk/ipk for private accounts) #[arg(short, long)] keys: bool, /// Valid 32 byte base58 string with privacy prefix @@ -67,9 +67,18 @@ impl WalletSubcommand for NewSubcommand { NewSubcommand::Public { cci } => { let (account_id, chain_index) = wallet_core.create_new_account_public(cci); + let private_key = wallet_core + .storage + .user_data + .get_pub_account_signing_key(&account_id) + .unwrap(); + + let public_key = PublicKey::new_from_private_key(private_key); + println!( "Generated new account with account_id Public/{account_id} at path {chain_index}" ); + println!("With pk {}", hex::encode(public_key.value())); wallet_core.store_persistent_data().await?; @@ -240,21 +249,31 @@ impl WalletSubcommand for AccountSubcommand { println!("{json_view}"); if keys { - if addr_kind != AccountPrivacyKind::Private { - anyhow::bail!("--keys option only works for private accounts"); + match addr_kind { + AccountPrivacyKind::Public => { + let private_key = wallet_core + .storage + .user_data + .get_pub_account_signing_key(&account_id) + .ok_or(anyhow::anyhow!("Public account not found in storage"))?; + + let public_key = PublicKey::new_from_private_key(private_key); + println!("pk {}", hex::encode(public_key.value())); + } + AccountPrivacyKind::Private => { + let (key, _) = wallet_core + .storage + .user_data + .get_private_account(&account_id) + .ok_or(anyhow::anyhow!("Private account not found in storage"))?; + + println!("npk {}", hex::encode(key.nullifer_public_key.0)); + println!( + "ipk {}", + hex::encode(key.incoming_viewing_public_key.to_bytes()) + ); + } } - - let (key, _) = wallet_core - .storage - .user_data - .get_private_account(&account_id) - .ok_or(anyhow::anyhow!("Private account not found in storage"))?; - - println!("npk {}", hex::encode(key.nullifer_public_key.0)); - println!( - "ipk {}", - hex::encode(key.incoming_viewing_public_key.to_bytes()) - ); } Ok(SubcommandReturnValue::Empty) From b8821693a86893e0d29154608a84b23ff9eab219 Mon Sep 17 00:00:00 2001 From: fryorcraken Date: Fri, 16 Jan 2026 11:12:57 +1100 Subject: [PATCH 4/4] Return keys for uninitialized accounts too --- wallet/src/cli/account.rs | 46 ++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 8d040aa5..608ddc85 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -231,24 +231,8 @@ impl WalletSubcommand for AccountSubcommand { .ok_or(anyhow::anyhow!("Private account not found in storage"))?, }; - if account == Account::default() { - println!("Account is Uninitialized"); - - return Ok(SubcommandReturnValue::Empty); - } - - if raw { - let account_hr: HumanReadableAccount = account.clone().into(); - println!("{}", serde_json::to_string(&account_hr).unwrap()); - - return Ok(SubcommandReturnValue::Empty); - } - - let (description, json_view) = format_account_details(&account); - println!("{description}"); - println!("{json_view}"); - - if keys { + // Helper closure to display keys for the account + let display_keys = |wallet_core: &WalletCore| -> Result<()> { match addr_kind { AccountPrivacyKind::Public => { let private_key = wallet_core @@ -274,6 +258,32 @@ impl WalletSubcommand for AccountSubcommand { ); } } + Ok(()) + }; + + if account == Account::default() { + println!("Account is Uninitialized"); + + if keys { + display_keys(wallet_core)?; + } + + return Ok(SubcommandReturnValue::Empty); + } + + if raw { + let account_hr: HumanReadableAccount = account.clone().into(); + println!("{}", serde_json::to_string(&account_hr).unwrap()); + + return Ok(SubcommandReturnValue::Empty); + } + + let (description, json_view) = format_account_details(&account); + println!("{description}"); + println!("{json_view}"); + + if keys { + display_keys(wallet_core)?; } Ok(SubcommandReturnValue::Empty)