This commit is contained in:
Sergio Chouhy 2026-04-20 11:27:15 -03:00
parent 6316f59777
commit 7ccd6ae331
16 changed files with 67 additions and 124 deletions

View File

@ -74,9 +74,8 @@ async fn new_private_account_with_label() -> Result<()> {
let mut ctx = TestContext::new().await?;
let label = "my-test-private-account".to_owned();
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
let command = Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: Some(label.clone()),
}));
let result = execute_subcommand(ctx.wallet_mut(), command).await?;

View File

@ -43,9 +43,8 @@ async fn new_public_account(ctx: &mut TestContext) -> Result<nssa::AccountId> {
async fn new_private_account(ctx: &mut TestContext) -> Result<nssa::AccountId> {
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;

View File

@ -159,9 +159,8 @@ async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> {
let from: AccountId = ctx.existing_private_accounts()[0];
// Create a new private account
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
let command = Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
}));
let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
@ -327,9 +326,8 @@ async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> {
let from: AccountId = ctx.existing_private_accounts()[0];
// Create a new private account
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
let command = Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
}));
let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
@ -392,9 +390,8 @@ async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> {
async fn initialize_private_account() -> Result<()> {
let mut ctx = TestContext::new().await?;
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
let command = Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
}));
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount { account_id } = result else {
@ -492,9 +489,8 @@ async fn initialize_private_account_using_label() -> Result<()> {
// Create a new private account with a label
let label = "init-private-label".to_owned();
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
let command = Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: Some(label.clone()),
}));
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount { account_id } = result else {

View File

@ -29,9 +29,8 @@ async fn sync_private_account_with_non_zero_chain_index() -> Result<()> {
let from: AccountId = ctx.existing_private_accounts()[0];
// Create a new private account
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
let command = Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
}));
for _ in 0..3 {
@ -39,9 +38,8 @@ async fn sync_private_account_with_non_zero_chain_index() -> Result<()> {
// This way we have account with child index > 0.
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;
@ -118,9 +116,8 @@ async fn restore_keys_from_seed() -> Result<()> {
let from: AccountId = ctx.existing_private_accounts()[0];
// Create first private account at root
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
let command = Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: Some(ChainIndex::root()),
label: None,
}));
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount {
@ -131,9 +128,8 @@ async fn restore_keys_from_seed() -> Result<()> {
};
// Create second private account at /0
let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {
let command = Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: Some(ChainIndex::from_str("/0")?),
label: None,
}));
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::RegisterAccount {

View File

@ -84,9 +84,8 @@ async fn claim_pinata_to_uninitialized_private_account_fails_fast() -> Result<()
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;
@ -228,9 +227,8 @@ async fn claim_pinata_to_new_private_account() -> Result<()> {
// Create new private account
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;

View File

@ -296,9 +296,8 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
// Create new account for the token supply holder (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;
@ -312,9 +311,8 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> {
// Create new account for receiving a token transaction (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;
@ -459,9 +457,8 @@ async fn create_token_with_private_definition() -> Result<()> {
// Create token definition account (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: Some(ChainIndex::root()),
label: None,
})),
)
.await?;
@ -531,9 +528,8 @@ async fn create_token_with_private_definition() -> Result<()> {
// Create private recipient account
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;
@ -661,9 +657,8 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
// Create token definition account (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;
@ -677,9 +672,8 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
// Create supply account (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;
@ -739,9 +733,8 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> {
// Create recipient account
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;
@ -854,9 +847,8 @@ async fn shielded_token_transfer() -> Result<()> {
// Create recipient account (private) for shielded transfer
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;
@ -965,9 +957,8 @@ async fn deshielded_token_transfer() -> Result<()> {
// Create supply account (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;
@ -1076,9 +1067,8 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
// Create token definition account (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;
@ -1092,9 +1082,8 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
// Create supply account (private)
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;
@ -1125,9 +1114,8 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> {
// Create new private account for claiming path
let result = wallet::cli::execute_subcommand(
ctx.wallet_mut(),
Command::Account(AccountSubcommand::New(NewSubcommand::Private {
Command::Account(AccountSubcommand::New(NewSubcommand::PrivateAccountsKey {
cci: None,
label: None,
})),
)
.await?;

View File

@ -53,8 +53,6 @@ unsafe extern "C" {
out_keys: *mut FfiPrivateAccountKeys,
) -> error::WalletFfiError;
fn wallet_ffi_free_private_account_keys(keys: *mut FfiPrivateAccountKeys);
fn wallet_ffi_list_accounts(
handle: *mut WalletHandle,
out_list: *mut FfiAccountList,
@ -294,18 +292,17 @@ fn wallet_ffi_create_private_accounts() -> Result<()> {
fn wallet_ffi_save_and_load_persistent_storage() -> Result<()> {
let ctx = BlockingTestContext::new()?;
let home = tempfile::tempdir()?;
let mut first_npk = [0u8; 32];
// Create a receiving key and save
unsafe {
let first_npk = unsafe {
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
let mut out_keys = FfiPrivateAccountKeys::default();
wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys);
first_npk = out_keys.nullifier_public_key.data;
let npk = out_keys.nullifier_public_key.data;
wallet_ffi_free_private_account_keys(&raw mut out_keys);
wallet_ffi_save(wallet_ffi_handle);
wallet_ffi_destroy(wallet_ffi_handle);
}
npk
};
// After loading, creating a new key should yield a different NPK (state was persisted)
let second_npk = unsafe {
@ -318,8 +315,8 @@ fn wallet_ffi_save_and_load_persistent_storage() -> Result<()> {
npk
};
assert_ne!(first_npk, [0u8; 32], "First NPK should be non-zero");
assert_ne!(second_npk, [0u8; 32], "Second NPK should be non-zero");
assert_ne!(first_npk, [0_u8; 32], "First NPK should be non-zero");
assert_ne!(second_npk, [0_u8; 32], "Second NPK should be non-zero");
assert_ne!(
first_npk, second_npk,
"Keys should differ after state was persisted"
@ -333,10 +330,9 @@ fn test_wallet_ffi_list_accounts() -> Result<()> {
let password = "password_for_tests";
// Create the wallet FFI and track which account IDs were created as public/private
let (wallet_ffi_handle, created_public_ids, created_private_ids) = unsafe {
let (wallet_ffi_handle, created_public_ids) = unsafe {
let handle = new_wallet_ffi_with_default_config(password)?;
let mut public_ids: Vec<[u8; 32]> = Vec::new();
let mut private_ids: Vec<[u8; 32]> = Vec::new();
// Create 5 public accounts and 5 receiving keys
for _ in 0..5 {
@ -348,9 +344,8 @@ fn test_wallet_ffi_list_accounts() -> Result<()> {
wallet_ffi_create_private_accounts_key(handle, &raw mut out_keys);
wallet_ffi_free_private_account_keys(&raw mut out_keys);
}
let _ = private_ids;
(handle, public_ids, private_ids)
(handle, public_ids)
};
// Get the account list with FFI method
@ -373,12 +368,6 @@ fn test_wallet_ffi_list_accounts() -> Result<()> {
.filter(|e| e.is_public)
.map(|e| e.account_id.data)
.collect();
let listed_private_ids: HashSet<[u8; 32]> = wallet_ffi_account_list_slice
.iter()
.filter(|e| !e.is_public)
.map(|e| e.account_id.data)
.collect();
for id in &created_public_ids {
assert!(
listed_public_ids.contains(id),

View File

@ -17,6 +17,7 @@ pub struct ChildKeysPrivate {
}
impl ChildKeysPrivate {
#[must_use]
pub fn root(seed: [u8; 64]) -> Self {
let hash_value = hmac_sha512::HMAC::mac(seed, b"LEE_master_priv");
@ -53,6 +54,7 @@ impl ChildKeysPrivate {
}
}
#[must_use]
pub fn nth_child(&self, cci: u32) -> Self {
#[expect(clippy::arithmetic_side_effects, reason = "TODO: fix later")]
let parent_pt =

View File

@ -13,6 +13,7 @@ pub struct ChildKeysPublic {
}
impl ChildKeysPublic {
#[must_use]
pub fn root(seed: [u8; 64]) -> Self {
let hash_value = hmac_sha512::HMAC::mac(seed, "LEE_master_pub");
@ -33,6 +34,7 @@ impl ChildKeysPublic {
}
}
#[must_use]
pub fn nth_child(&self, cci: u32) -> Self {
let hash_value = self.compute_hash_value(cci);
@ -65,6 +67,7 @@ impl ChildKeysPublic {
}
}
#[must_use]
pub fn account_id(&self) -> nssa::AccountId {
nssa::AccountId::from(&self.cpk)
}

View File

@ -533,41 +533,25 @@ mod tests {
.key_map
.get_mut(&ChainIndex::from_str("/1").unwrap())
.unwrap();
acc.value.1.push((0, {
let mut a = nssa::Account::default();
a.balance = 2;
a
}));
acc.value.1.push((0, nssa::Account { balance: 2, ..nssa::Account::default() }));
let acc = tree
.key_map
.get_mut(&ChainIndex::from_str("/2").unwrap())
.unwrap();
acc.value.1.push((0, {
let mut a = nssa::Account::default();
a.balance = 3;
a
}));
acc.value.1.push((0, nssa::Account { balance: 3, ..nssa::Account::default() }));
let acc = tree
.key_map
.get_mut(&ChainIndex::from_str("/0/1").unwrap())
.unwrap();
acc.value.1.push((0, {
let mut a = nssa::Account::default();
a.balance = 5;
a
}));
acc.value.1.push((0, nssa::Account { balance: 5, ..nssa::Account::default() }));
let acc = tree
.key_map
.get_mut(&ChainIndex::from_str("/1/0").unwrap())
.unwrap();
acc.value.1.push((0, {
let mut a = nssa::Account::default();
a.balance = 6;
a
}));
acc.value.1.push((0, nssa::Account { balance: 6, ..nssa::Account::default() }));
// Update account_id_map for nodes that now have entries
for chain_index_str in ["/1", "/2", "/0/1", "/1/0"] {

View File

@ -1,5 +1,8 @@
pub trait KeyTreeNode: Sized {
#[must_use]
fn from_seed(seed: [u8; 64]) -> Self;
#[must_use]
fn derive_child(&self, cci: u32) -> Self;
#[must_use]
fn account_ids(&self) -> Vec<nssa::AccountId>;
}

View File

@ -174,13 +174,7 @@ mod tests {
// /2
let second_chain_index = key_tree_private.generate_new_node_layered().unwrap();
key_tree_private
.key_map
.get(&second_chain_index)
.unwrap()
.value
.0
.clone()
key_tree_private.key_map[&second_chain_index].value.0.clone()
}
#[test]

View File

@ -12,14 +12,15 @@ use crate::key_management::{
};
pub type PublicKey = AffinePoint;
pub type DefaultPrivateAccountsMap =
BTreeMap<nssa::AccountId, (KeyChain, Vec<(Identifier, nssa_core::account::Account)>)>;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NSSAUserData {
/// Default public accounts.
pub default_pub_account_signing_keys: BTreeMap<nssa::AccountId, nssa::PrivateKey>,
/// Default private accounts.
pub default_user_private_accounts:
BTreeMap<nssa::AccountId, (KeyChain, Vec<(Identifier, nssa_core::account::Account)>)>,
pub default_user_private_accounts: DefaultPrivateAccountsMap,
/// Tree of public keys.
pub public_key_tree: KeyTreePublic,
/// Tree of private keys.
@ -43,10 +44,7 @@ impl NSSAUserData {
}
fn valid_private_key_transaction_pairing_check(
accounts_keys_map: &BTreeMap<
nssa::AccountId,
(KeyChain, Vec<(Identifier, nssa_core::account::Account)>),
>,
accounts_keys_map: &DefaultPrivateAccountsMap,
) -> bool {
let mut check_res = true;
for (account_id, (key, entries)) in accounts_keys_map {
@ -63,10 +61,7 @@ impl NSSAUserData {
pub fn new_with_accounts(
default_accounts_keys: BTreeMap<nssa::AccountId, nssa::PrivateKey>,
default_accounts_key_chains: BTreeMap<
nssa::AccountId,
(KeyChain, Vec<(Identifier, nssa_core::account::Account)>),
>,
default_accounts_key_chains: DefaultPrivateAccountsMap,
public_key_tree: KeyTreePublic,
private_key_tree: KeyTreePrivate,
) -> Result<Self> {
@ -139,11 +134,11 @@ impl NSSAUserData {
/// is already registered.
pub fn register_identifier_on_private_key_chain(
&mut self,
cci: ChainIndex,
cci: &ChainIndex,
identifier: Identifier,
) -> Option<nssa::AccountId> {
self.private_key_tree
.register_identifier_on_node(&cci, identifier)
.register_identifier_on_node(cci, identifier)
}
/// Returns the key chain and account data for the given private account ID.
@ -154,11 +149,11 @@ impl NSSAUserData {
) -> Option<(KeyChain, nssa_core::account::Account, Identifier)> {
// Check default accounts
if let Some((key_chain, entries)) = self.default_user_private_accounts.get(&account_id) {
for &(identifier, ref account) in entries {
for (identifier, account) in entries {
let expected_id =
nssa::AccountId::from((&key_chain.nullifier_public_key, identifier));
nssa::AccountId::from((&key_chain.nullifier_public_key, *identifier));
if expected_id == account_id {
return Some((key_chain.clone(), account.clone(), identifier));
return Some((key_chain.clone(), account.clone(), *identifier));
}
}
return None;
@ -166,11 +161,11 @@ impl NSSAUserData {
// Check tree
if let Some(node) = self.private_key_tree.get_node(account_id) {
let key_chain = &node.value.0;
for &(identifier, ref account) in &node.value.1 {
for (identifier, account) in &node.value.1 {
let expected_id =
nssa::AccountId::from((&key_chain.nullifier_public_key, identifier));
nssa::AccountId::from((&key_chain.nullifier_public_key, *identifier));
if expected_id == account_id {
return Some((key_chain.clone(), account.clone(), identifier));
return Some((key_chain.clone(), account.clone(), *identifier));
}
}
}

View File

@ -49,8 +49,8 @@ impl std::fmt::Debug for Commitment {
}
impl Commitment {
/// Generates the commitment to a private account owned by user for account_id:
/// SHA256( `Comm_DS` || account_id || `program_owner` || balance || nonce || SHA256(data)).
/// Generates the commitment to a private account owned by user for `account_id`:
/// SHA256( `Comm_DS` || `account_id` || `program_owner` || balance || nonce || SHA256(data)).
#[must_use]
pub fn new(account_id: &AccountId, account: &Account) -> Self {
const COMMITMENT_PREFIX: &[u8; 32] =

View File

@ -4,17 +4,17 @@ use serde::{Deserialize, Serialize};
use crate::{Commitment, account::AccountId};
const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/LEE/v0.3/AccountId/Private/\x00\x00\x00\x00";
pub type Identifier = u128;
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(any(feature = "host", test), derive(Clone, Hash))]
pub struct NullifierPublicKey(pub [u8; 32]);
pub type Identifier = u128;
impl From<(&NullifierPublicKey, Identifier)> for AccountId {
fn from(value: (&NullifierPublicKey, Identifier)) -> Self {
let (npk, identifier) = value;
const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] =
b"/LEE/v0.3/AccountId/Private/\x00\x00\x00\x00";
// 32 bytes prefix || 32 bytes npk || 16 bytes identifier
let mut bytes = [0; 80];

View File

@ -190,11 +190,8 @@ impl WalletChainStore {
let identifier = entries
.iter()
.find_map(|(id, _)| {
if nssa::AccountId::from((&key_chain.nullifier_public_key, *id)) == account_id {
Some(*id)
} else {
None
}
(nssa::AccountId::from((&key_chain.nullifier_public_key, *id)) == account_id)
.then_some(*id)
})
.unwrap_or(0);
// Update existing entry or insert new one
@ -232,7 +229,7 @@ impl WalletChainStore {
}
} else {
// Node not yet in account_id_map — find it by checking all nodes
for (ci, node) in self.user_data.private_key_tree.key_map.iter_mut() {
for (ci, node) in &mut self.user_data.private_key_tree.key_map {
let expected_id =
nssa::AccountId::from((&node.value.0.nullifier_public_key, identifier));
if expected_id == account_id {