From ec149d3227c718c0db80da17a0b8b9adf8e0ac06 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Tue, 11 Nov 2025 12:15:20 +0200 Subject: [PATCH] feat: deterministic keys --- integration_tests/Cargo.toml | 3 + integration_tests/src/lib.rs | 2 + integration_tests/src/test_suite_map.rs | 143 ++++++++++------- key_protocol/Cargo.toml | 1 + .../key_management/key_tree/chain_index.rs | 89 ++++++----- .../src/key_management/key_tree/mod.rs | 46 ++++-- .../src/key_management/secret_holders.rs | 10 ++ key_protocol/src/key_protocol_core/mod.rs | 46 +----- wallet/src/chain_storage/mod.rs | 147 ++++++++++++++---- wallet/src/cli/account.rs | 6 +- wallet/src/config.rs | 9 +- wallet/src/helperfunctions.rs | 57 +++++-- wallet/src/lib.rs | 97 +++++++++++- wallet/src/main.rs | 13 +- 14 files changed, 456 insertions(+), 213 deletions(-) diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index c10869a..58be5a5 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -36,6 +36,9 @@ path = "../wallet" [dependencies.common] path = "../common" +[dependencies.key_protocol] +path = "../key_protocol" + [dependencies.nssa] path = "../nssa" features = ["no_docker"] diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index f718f9d..ae32f79 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -54,6 +54,8 @@ fn make_private_account_input_from_str(addr: &str) -> String { pub async fn pre_test( home_dir: PathBuf, ) -> Result<(ServerHandle, JoinHandle>, TempDir)> { + wallet::execute_setup("test_pass".to_string()).await?; + let home_dir_sequencer = home_dir.join("sequencer"); let mut sequencer_config = diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs index afcac7d..e4510ad 100644 --- a/integration_tests/src/test_suite_map.rs +++ b/integration_tests/src/test_suite_map.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, path::PathBuf, pin::Pin, time::Duration}; use common::{PINATA_BASE58, sequencer_client::SequencerClient}; +use key_protocol::key_management::key_tree::chain_index::ChainIndex; use log::info; use nssa::{Address, ProgramDeploymentTransaction, program::Program}; use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point}; @@ -13,7 +14,7 @@ use wallet::{ pinata_program::PinataProgramAgnosticSubcommand, token_program::TokenProgramAgnosticSubcommand, }, - config::{PersistentAccountData, PersistentStorage}, + config::PersistentStorage, helperfunctions::{fetch_config, fetch_persistent_storage}, }; @@ -72,7 +73,9 @@ pub fn prepare_function_map() -> HashMap { #[nssa_integration_test] pub async fn test_success_move_to_another_account() { info!("########## test_success_move_to_another_account ##########"); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public {})); + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { + cci: ChainIndex::root(), + })); let wallet_config = fetch_config().await.unwrap(); @@ -273,47 +276,43 @@ pub fn prepare_function_map() -> HashMap { let wallet_config = fetch_config().await.unwrap(); // Create new account for the token definition - wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public {}, + let SubcommandReturnValue::RegisterAccount { + addr: definition_addr, + } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Public { + cci: ChainIndex::root(), + }, ))) .await - .unwrap(); + .unwrap() + else { + panic!("invalid subcommand return value"); + }; // Create new account for the token supply holder - wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public {}, - ))) - .await - .unwrap(); + let SubcommandReturnValue::RegisterAccount { addr: supply_addr } = + wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Public { + cci: ChainIndex::root(), + }, + ))) + .await + .unwrap() + else { + panic!("invalid subcommand return value"); + }; // Create new account for receiving a token transaction - wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public {}, + let SubcommandReturnValue::RegisterAccount { + addr: recipient_addr, + } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + NewSubcommand::Public { + cci: ChainIndex::root(), + }, ))) .await - .unwrap(); - - let PersistentStorage { - accounts: persistent_accounts, - last_synced_block: _, - } = fetch_persistent_storage().await.unwrap(); - - let mut new_persistent_accounts_addr = Vec::new(); - - for per_acc in persistent_accounts { - match per_acc { - PersistentAccountData::Public(per_acc) => { - if (per_acc.address.to_string() != ACC_RECEIVER) - && (per_acc.address.to_string() != ACC_SENDER) - { - new_persistent_accounts_addr.push(per_acc.address); - } - } - _ => continue, - } - } - - let [definition_addr, supply_addr, recipient_addr] = new_persistent_accounts_addr - .try_into() - .expect("Failed to produce new account, not present in persistent accounts"); + .unwrap() + else { + panic!("invalid subcommand return value"); + }; // Create new token let subcommand = TokenProgramAgnosticSubcommand::New { @@ -433,7 +432,9 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { addr: definition_addr, } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public {}, + NewSubcommand::Public { + cci: ChainIndex::root(), + }, ))) .await .unwrap() @@ -443,7 +444,9 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token supply holder (private) let SubcommandReturnValue::RegisterAccount { addr: supply_addr } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private {}, + NewSubcommand::Private { + cci: ChainIndex::root(), + }, ))) .await .unwrap() @@ -454,7 +457,9 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { addr: recipient_addr, } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private {}, + NewSubcommand::Private { + cci: ChainIndex::root(), + }, ))) .await .unwrap() @@ -584,7 +589,9 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { addr: definition_addr, } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public {}, + NewSubcommand::Public { + cci: ChainIndex::root(), + }, ))) .await .unwrap() @@ -594,7 +601,9 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token supply holder (private) let SubcommandReturnValue::RegisterAccount { addr: supply_addr } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private {}, + NewSubcommand::Private { + cci: ChainIndex::root(), + }, ))) .await .unwrap() @@ -605,7 +614,9 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { addr: recipient_addr, } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private {}, + NewSubcommand::Private { + cci: ChainIndex::root(), + }, ))) .await .unwrap() @@ -716,7 +727,9 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { addr: definition_addr, } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public {}, + NewSubcommand::Public { + cci: ChainIndex::root(), + }, ))) .await .unwrap() @@ -726,7 +739,9 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token supply holder (public) let SubcommandReturnValue::RegisterAccount { addr: supply_addr } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public {}, + NewSubcommand::Public { + cci: ChainIndex::root(), + }, ))) .await .unwrap() @@ -737,7 +752,9 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { addr: recipient_addr, } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private {}, + NewSubcommand::Private { + cci: ChainIndex::root(), + }, ))) .await .unwrap() @@ -847,7 +864,9 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { addr: definition_addr, } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public {}, + NewSubcommand::Public { + cci: ChainIndex::root(), + }, ))) .await .unwrap() @@ -857,7 +876,9 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token supply holder (private) let SubcommandReturnValue::RegisterAccount { addr: supply_addr } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private {}, + NewSubcommand::Private { + cci: ChainIndex::root(), + }, ))) .await .unwrap() @@ -868,7 +889,9 @@ pub fn prepare_function_map() -> HashMap { let SubcommandReturnValue::RegisterAccount { addr: recipient_addr, } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public {}, + NewSubcommand::Public { + cci: ChainIndex::root(), + }, ))) .await .unwrap() @@ -1066,7 +1089,9 @@ pub fn prepare_function_map() -> HashMap { ); let from: Address = ACC_SENDER_PRIVATE.parse().unwrap(); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {})); + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + cci: ChainIndex::root(), + })); let sub_ret = wallet::execute_subcommand(command).await.unwrap(); let SubcommandReturnValue::RegisterAccount { addr: to_addr } = sub_ret else { @@ -1082,8 +1107,7 @@ pub fn prepare_function_map() -> HashMap { let (to_keys, _) = wallet_storage .storage .user_data - .user_private_accounts - .get(&to_addr) + .get_private_account(&to_addr) .cloned() .unwrap(); @@ -1134,7 +1158,9 @@ pub fn prepare_function_map() -> HashMap { let from: Address = ACC_SENDER_PRIVATE.parse().unwrap(); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {})); + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + cci: ChainIndex::root(), + })); let sub_ret = wallet::execute_subcommand(command).await.unwrap(); let SubcommandReturnValue::RegisterAccount { addr: to_addr } = sub_ret else { @@ -1150,8 +1176,7 @@ pub fn prepare_function_map() -> HashMap { let (to_keys, _) = wallet_storage .storage .user_data - .user_private_accounts - .get(&to_addr) + .get_private_account(&to_addr) .cloned() .unwrap(); @@ -1428,7 +1453,9 @@ pub fn prepare_function_map() -> HashMap { #[nssa_integration_test] pub async fn test_authenticated_transfer_initialize_function() { info!("########## test initialize account for authenticated transfer ##########"); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public {})); + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { + cci: ChainIndex::root(), + })); let SubcommandReturnValue::RegisterAccount { addr } = wallet::execute_subcommand(command).await.unwrap() else { @@ -1528,7 +1555,9 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token supply holder (private) let SubcommandReturnValue::RegisterAccount { addr: winner_addr } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private {}, + NewSubcommand::Private { + cci: ChainIndex::root(), + }, ))) .await .unwrap() diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index b0708b4..a562515 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -14,6 +14,7 @@ hex = "0.4.3" aes-gcm.workspace = true bip39.workspace = true hmac-sha512.workspace = true +thiserror.workspace = true nssa-core = { path = "../nssa/core", features = ["host"] } [dependencies.common] diff --git a/key_protocol/src/key_management/key_tree/chain_index.rs b/key_protocol/src/key_management/key_tree/chain_index.rs index dad9b2a..e22abf0 100644 --- a/key_protocol/src/key_management/key_tree/chain_index.rs +++ b/key_protocol/src/key_management/key_tree/chain_index.rs @@ -1,59 +1,57 @@ -use std::str::FromStr; +use std::{fmt::Display, str::FromStr}; use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)] pub struct ChainIndex(Vec); +#[derive(thiserror::Error, Debug)] +pub enum ChainIndexError { + #[error("No root found")] + NoRootFound, + #[error("Failed to parse segment into a number")] + ParseIntError(#[from] std::num::ParseIntError), +} + impl FromStr for ChainIndex { - type Err = hex::FromHexError; + type Err = ChainIndexError; fn from_str(s: &str) -> Result { - if s.is_empty() { - return Ok(Self(vec![])); + if !s.starts_with("/") { + return Err(ChainIndexError::NoRootFound); } - let hex_decoded = hex::decode(s)?; - - if !hex_decoded.len().is_multiple_of(4) { - Err(hex::FromHexError::InvalidStringLength) - } else { - let mut res_vec = vec![]; - - for i in 0..(hex_decoded.len() / 4) { - res_vec.push(u32::from_le_bytes([ - hex_decoded[4 * i], - hex_decoded[4 * i + 1], - hex_decoded[4 * i + 2], - hex_decoded[4 * i + 3], - ])); - } - - Ok(Self(res_vec)) + if s == "/" { + return Ok(ChainIndex(vec![])); } + + let uprooted_substring = s.strip_prefix("/").unwrap(); + + let splitted_chain: Vec<&str> = uprooted_substring.split("/").collect(); + let mut res = vec![]; + + for split_ch in splitted_chain { + let cci = split_ch.parse()?; + res.push(cci); + } + + Ok(Self(res)) } } -#[allow(clippy::to_string_trait_impl)] -impl ToString for ChainIndex { - fn to_string(&self) -> String { - if self.0.is_empty() { - return "".to_string(); +impl Display for ChainIndex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "/")?; + for cci in &self.0[..(self.0.len() - 1)] { + write!(f, "{cci}/")?; } - - let mut res_vec = vec![]; - - for index in &self.0 { - res_vec.extend_from_slice(&index.to_le_bytes()); - } - - hex::encode(res_vec) + write!(f, "{}", self.0.last().unwrap()) } } impl ChainIndex { pub fn root() -> Self { - ChainIndex::from_str("").unwrap() + ChainIndex::from_str("/").unwrap() } pub fn chain(&self) -> &[u32] { @@ -85,31 +83,40 @@ mod tests { #[test] fn test_chain_id_root_correct() { let chain_id = ChainIndex::root(); - let chain_id_2 = ChainIndex::from_str("").unwrap(); + let chain_id_2 = ChainIndex::from_str("/").unwrap(); assert_eq!(chain_id, chain_id_2); } #[test] fn test_chain_id_deser_correct() { - let chain_id = ChainIndex::from_str("01010000").unwrap(); + let chain_id = ChainIndex::from_str("/257").unwrap(); assert_eq!(chain_id.chain(), &[257]); } #[test] fn test_chain_id_next_in_line_correct() { - let chain_id = ChainIndex::from_str("01010000").unwrap(); + let chain_id = ChainIndex::from_str("/257").unwrap(); let next_in_line = chain_id.next_in_line(); - assert_eq!(next_in_line, ChainIndex::from_str("02010000").unwrap()); + assert_eq!(next_in_line, ChainIndex::from_str("/258").unwrap()); } #[test] fn test_chain_id_child_correct() { - let chain_id = ChainIndex::from_str("01010000").unwrap(); + let chain_id = ChainIndex::from_str("/257").unwrap(); let child = chain_id.n_th_child(3); - assert_eq!(child, ChainIndex::from_str("0101000003000000").unwrap()); + assert_eq!(child, ChainIndex::from_str("/257/3").unwrap()); + } + + #[test] + fn test_correct_display() { + let chainid = ChainIndex(vec![5, 7, 8]); + + let string_index = format!("{chainid}"); + + assert_eq!(string_index, "/5/7/8".to_string()); } } diff --git a/key_protocol/src/key_management/key_tree/mod.rs b/key_protocol/src/key_management/key_tree/mod.rs index 7ea2a2a..dcc027b 100644 --- a/key_protocol/src/key_management/key_tree/mod.rs +++ b/key_protocol/src/key_management/key_tree/mod.rs @@ -40,6 +40,18 @@ impl KeyTree { Self { key_map, addr_map } } + pub fn new_from_root(root: Node) -> Self { + let mut key_map = BTreeMap::new(); + let mut addr_map = HashMap::new(); + + addr_map.insert(root.address(), ChainIndex::root()); + key_map.insert(ChainIndex::root(), root); + + Self { key_map, addr_map } + } + + //ToDo: Add function to create a tree from list of nodes with consistency check. + pub fn find_next_last_child_of_id(&self, parent_id: &ChainIndex) -> Option { if !self.key_map.contains_key(parent_id) { return None; @@ -160,7 +172,7 @@ mod tests { assert!( tree.key_map - .contains_key(&ChainIndex::from_str("00000000").unwrap()) + .contains_key(&ChainIndex::from_str("/0").unwrap()) ); let next_last_child_for_parent_id = tree @@ -199,7 +211,7 @@ mod tests { assert!( tree.key_map - .contains_key(&ChainIndex::from_str("00000000").unwrap()) + .contains_key(&ChainIndex::from_str("/0").unwrap()) ); let next_last_child_for_parent_id = tree @@ -208,7 +220,7 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 1); - let key_opt = tree.generate_new_node(ChainIndex::from_str("03000000").unwrap()); + let key_opt = tree.generate_new_node(ChainIndex::from_str("/3").unwrap()); assert_eq!(key_opt, None); } @@ -229,7 +241,7 @@ mod tests { assert!( tree.key_map - .contains_key(&ChainIndex::from_str("00000000").unwrap()) + .contains_key(&ChainIndex::from_str("/0").unwrap()) ); let next_last_child_for_parent_id = tree @@ -242,7 +254,7 @@ mod tests { assert!( tree.key_map - .contains_key(&ChainIndex::from_str("01000000").unwrap()) + .contains_key(&ChainIndex::from_str("/1").unwrap()) ); let next_last_child_for_parent_id = tree @@ -251,58 +263,58 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 2); - tree.generate_new_node(ChainIndex::from_str("00000000").unwrap()) + tree.generate_new_node(ChainIndex::from_str("/0").unwrap()) .unwrap(); let next_last_child_for_parent_id = tree - .find_next_last_child_of_id(&ChainIndex::from_str("00000000").unwrap()) + .find_next_last_child_of_id(&ChainIndex::from_str("/0").unwrap()) .unwrap(); assert_eq!(next_last_child_for_parent_id, 1); assert!( tree.key_map - .contains_key(&ChainIndex::from_str("0000000000000000").unwrap()) + .contains_key(&ChainIndex::from_str("/0/0").unwrap()) ); - tree.generate_new_node(ChainIndex::from_str("00000000").unwrap()) + tree.generate_new_node(ChainIndex::from_str("/0").unwrap()) .unwrap(); let next_last_child_for_parent_id = tree - .find_next_last_child_of_id(&ChainIndex::from_str("00000000").unwrap()) + .find_next_last_child_of_id(&ChainIndex::from_str("/0").unwrap()) .unwrap(); assert_eq!(next_last_child_for_parent_id, 2); assert!( tree.key_map - .contains_key(&ChainIndex::from_str("0000000001000000").unwrap()) + .contains_key(&ChainIndex::from_str("/0/1").unwrap()) ); - tree.generate_new_node(ChainIndex::from_str("00000000").unwrap()) + tree.generate_new_node(ChainIndex::from_str("/0").unwrap()) .unwrap(); let next_last_child_for_parent_id = tree - .find_next_last_child_of_id(&ChainIndex::from_str("00000000").unwrap()) + .find_next_last_child_of_id(&ChainIndex::from_str("/0").unwrap()) .unwrap(); assert_eq!(next_last_child_for_parent_id, 3); assert!( tree.key_map - .contains_key(&ChainIndex::from_str("0000000002000000").unwrap()) + .contains_key(&ChainIndex::from_str("/0/2").unwrap()) ); - tree.generate_new_node(ChainIndex::from_str("0000000001000000").unwrap()) + tree.generate_new_node(ChainIndex::from_str("/0/1").unwrap()) .unwrap(); assert!( tree.key_map - .contains_key(&ChainIndex::from_str("000000000100000000000000").unwrap()) + .contains_key(&ChainIndex::from_str("/0/1/0").unwrap()) ); let next_last_child_for_parent_id = tree - .find_next_last_child_of_id(&ChainIndex::from_str("0000000001000000").unwrap()) + .find_next_last_child_of_id(&ChainIndex::from_str("/0/1").unwrap()) .unwrap(); assert_eq!(next_last_child_for_parent_id, 1); diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index ee7aced..e60a9f5 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -166,4 +166,14 @@ mod tests { let _ = top_secret_key_holder.generate_outgoing_viewing_secret_key(); } + + #[test] + fn two_seeds_generated_same_from_same_mnemonic() { + let mnemonic = "test_pass"; + + let seed_holder1 = SeedHolder::new_mnemonic(mnemonic.to_string()); + let seed_holder2 = SeedHolder::new_mnemonic(mnemonic.to_string()); + + assert_eq!(seed_holder1.seed, seed_holder2.seed); + } } diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index f50088a..6ea75db 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -19,8 +19,6 @@ pub struct NSSAUserData { ///Default private accounts pub default_user_private_accounts: HashMap, - ///Mnemonic passphrase - pub password: String, /// Tree of public keys pub public_key_tree: KeyTreePublic, /// Tree of private keys @@ -82,38 +80,6 @@ impl NSSAUserData { default_user_private_accounts: default_accounts_key_chains, public_key_tree, private_key_tree, - password: "mnemonic".to_string(), - }) - } - - pub fn new_with_accounts_and_password( - default_accounts_keys: HashMap, - default_accounts_key_chains: HashMap< - nssa::Address, - (KeyChain, nssa_core::account::Account), - >, - public_key_tree: KeyTreePublic, - private_key_tree: KeyTreePrivate, - password: String, - ) -> Result { - if !Self::valid_public_key_transaction_pairing_check(&default_accounts_keys) { - anyhow::bail!( - "Key transaction pairing check not satisfied, there is addresses, which is not derived from keys" - ); - } - - if !Self::valid_private_key_transaction_pairing_check(&default_accounts_key_chains) { - anyhow::bail!( - "Key transaction pairing check not satisfied, there is addresses, which is not derived from keys" - ); - } - - Ok(Self { - default_pub_account_signing_keys: default_accounts_keys, - default_user_private_accounts: default_accounts_key_chains, - public_key_tree, - private_key_tree, - password, }) } @@ -137,9 +103,7 @@ impl NSSAUserData { Some(key) //Then seek in tree } else { - self.public_key_tree - .get_node(*address) - .and_then(|chain_keys| Some(chain_keys.into())) + self.public_key_tree.get_node(*address).map(Into::into) } } @@ -163,9 +127,7 @@ impl NSSAUserData { Some(key) //Then seek in tree } else { - self.private_key_tree - .get_node(*address) - .and_then(|chain_keys| Some(chain_keys.into())) + self.private_key_tree.get_node(*address).map(Into::into) } } @@ -179,9 +141,7 @@ impl NSSAUserData { Some(key) //Then seek in tree } else { - self.private_key_tree - .get_node_mut(*address) - .and_then(|chain_keys| Some(chain_keys.into())) + self.private_key_tree.get_node_mut(*address).map(Into::into) } } } diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index a1a8517..5215d99 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use anyhow::Result; use key_protocol::{ key_management::{ - key_tree::{KeyTreePrivate, KeyTreePublic}, + key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex}, secret_holders::SeedHolder, }, key_protocol_core::NSSAUserData, @@ -21,8 +21,73 @@ impl WalletChainStore { pub fn new( config: WalletConfig, persistent_accounts: Vec, - password: String, ) -> Result { + if persistent_accounts.is_empty() { + anyhow::bail!("Roots not found; please run setup beforehand"); + } + + let mut public_init_acc_map = HashMap::new(); + let mut private_init_acc_map = HashMap::new(); + + let public_root = persistent_accounts + .iter() + .find(|data| match data { + &PersistentAccountData::Public(data) => data.chain_index == ChainIndex::root(), + _ => false, + }) + .cloned() + .unwrap(); + + let private_root = persistent_accounts + .iter() + .find(|data| match data { + &PersistentAccountData::Private(data) => data.chain_index == ChainIndex::root(), + _ => false, + }) + .cloned() + .unwrap(); + + let mut public_tree = KeyTreePublic::new_from_root(match public_root { + PersistentAccountData::Public(data) => data.data, + _ => unreachable!(), + }); + let mut private_tree = KeyTreePrivate::new_from_root(match private_root { + PersistentAccountData::Private(data) => data.data, + _ => unreachable!(), + }); + + for pers_acc_data in persistent_accounts { + match pers_acc_data { + PersistentAccountData::Public(data) => { + public_tree.insert(data.address, data.chain_index, data.data); + } + PersistentAccountData::Private(data) => { + private_tree.insert(data.address, data.chain_index, data.data); + } + PersistentAccountData::Preconfigured(acc_data) => match acc_data { + InitialAccountData::Public(data) => { + public_init_acc_map.insert(data.address.parse()?, data.pub_sign_key); + } + InitialAccountData::Private(data) => { + private_init_acc_map + .insert(data.address.parse()?, (data.key_chain, data.account)); + } + }, + } + } + + Ok(Self { + user_data: NSSAUserData::new_with_accounts( + public_init_acc_map, + private_init_acc_map, + public_tree, + private_tree, + )?, + wallet_config: config, + }) + } + + pub fn new_storage(config: WalletConfig, password: String) -> Result { let mut public_init_acc_map = HashMap::new(); let mut private_init_acc_map = HashMap::new(); @@ -42,19 +107,8 @@ impl WalletChainStore { } } - let mut public_tree = KeyTreePublic::new(&SeedHolder::new_mnemonic(password.clone())); - let mut private_tree = KeyTreePrivate::new(&SeedHolder::new_mnemonic(password)); - - for pers_acc_data in persistent_accounts { - match pers_acc_data { - PersistentAccountData::Public(data) => { - public_tree.insert(data.address, data.chain_index, data.data); - } - PersistentAccountData::Private(data) => { - private_tree.insert(data.address, data.chain_index, data.data); - } - } - } + let public_tree = KeyTreePublic::new(&SeedHolder::new_mnemonic(password.clone())); + let private_tree = KeyTreePrivate::new(&SeedHolder::new_mnemonic(password)); Ok(Self { user_data: NSSAUserData::new_with_accounts( @@ -73,25 +127,41 @@ impl WalletChainStore { account: nssa_core::account::Account, ) { println!("inserting at address {}, this account {:?}", addr, account); - self.user_data - .private_key_tree - .addr_map - .get(&addr) - .and_then(|chain_index| { - Some( + + if self + .user_data + .default_user_private_accounts + .contains_key(&addr) + { + self.user_data + .default_user_private_accounts + .entry(addr) + .and_modify(|data| data.1 = account); + } else { + self.user_data + .private_key_tree + .addr_map + .get(&addr) + .map(|chain_index| { self.user_data .private_key_tree .key_map .entry(chain_index.clone()) - .and_modify(|data| data.value.1 = account), - ) - }); + .and_modify(|data| data.value.1 = account) + }); + } } } #[cfg(test)] mod tests { - use crate::config::InitialAccountData; + use key_protocol::key_management::key_tree::{ + keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic, traits::KeyNode, + }; + + use crate::config::{ + InitialAccountData, PersistentAccountDataPrivate, PersistentAccountDataPublic, + }; use super::*; @@ -199,10 +269,35 @@ mod tests { } } + fn create_sample_persistent_accounts() -> Vec { + let mut accs = vec![]; + + let public_data = ChildKeysPublic::root([42; 64]); + + accs.push(PersistentAccountData::Public(PersistentAccountDataPublic { + address: public_data.address(), + chain_index: ChainIndex::root(), + data: public_data, + })); + + let private_data = ChildKeysPrivate::root([47; 64]); + + accs.push(PersistentAccountData::Private( + PersistentAccountDataPrivate { + address: private_data.address(), + chain_index: ChainIndex::root(), + data: private_data, + }, + )); + + accs + } + #[test] fn test_new_initializes_correctly() { let config = create_sample_wallet_config(); + let accs = create_sample_persistent_accounts(); - let _ = WalletChainStore::new(config.clone(), vec![], "test_pass".to_string()).unwrap(); + let _ = WalletChainStore::new(config.clone(), accs).unwrap(); } } diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 7574a80..b9a7699 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -90,14 +90,14 @@ pub enum AccountSubcommand { #[derive(Subcommand, Debug, Clone)] pub enum NewSubcommand { ///Register new public account - Public { + Public { #[arg(long)] - cci: ChainIndex + cci: ChainIndex, }, ///Register new private account Private { #[arg(long)] - cci: ChainIndex + cci: ChainIndex, }, } diff --git a/wallet/src/config.rs b/wallet/src/config.rs index 8fdc450..f5bd3ea 100644 --- a/wallet/src/config.rs +++ b/wallet/src/config.rs @@ -49,12 +49,12 @@ pub enum InitialAccountData { pub enum PersistentAccountData { Public(PersistentAccountDataPublic), Private(PersistentAccountDataPrivate), + Preconfigured(InitialAccountData), } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PersistentStorage { pub accounts: Vec, - pub password: String, pub last_synced_block: u64, } @@ -72,6 +72,7 @@ impl PersistentAccountData { match &self { Self::Public(acc) => acc.address, Self::Private(acc) => acc.address, + Self::Preconfigured(acc) => acc.address(), } } } @@ -100,6 +101,12 @@ impl From for PersistentAccountData { } } +impl From for PersistentAccountData { + fn from(value: InitialAccountData) -> Self { + Self::Preconfigured(value) + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GasConfig { /// Gas spent per deploying one byte of data diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index b3e225d..0d08348 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -12,6 +12,7 @@ use serde::Serialize; use crate::{ HOME_DIR_ENV_VAR, config::{ + InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic, PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, WalletConfig, }, }; @@ -102,11 +103,9 @@ pub async fn fetch_persistent_storage() -> Result { Ok(serde_json::from_slice(&storage_content)?) } Err(err) => match err.kind() { - std::io::ErrorKind::NotFound => Ok(PersistentStorage { - accounts: vec![], - password: "default".to_string(), - last_synced_block: 0, - }), + std::io::ErrorKind::NotFound => { + anyhow::bail!("Not found, please setup roots from config command beforehand"); + } _ => { anyhow::bail!("IO error {err:#?}"); } @@ -123,27 +122,53 @@ pub fn produce_data_for_storage( for (addr, key) in &user_data.public_key_tree.addr_map { if let Some(data) = user_data.public_key_tree.key_map.get(key) { - vec_for_storage.push(PersistentAccountDataPublic { - address: *addr, - chain_index: key.clone(), - data: data.clone(), - }.into()); + vec_for_storage.push( + PersistentAccountDataPublic { + address: *addr, + chain_index: key.clone(), + data: data.clone(), + } + .into(), + ); } } for (addr, key) in &user_data.private_key_tree.addr_map { if let Some(data) = user_data.private_key_tree.key_map.get(key) { - vec_for_storage.push(PersistentAccountDataPrivate { - address: *addr, - chain_index: key.clone(), - data: data.clone(), - }.into()); + vec_for_storage.push( + PersistentAccountDataPrivate { + address: *addr, + chain_index: key.clone(), + data: data.clone(), + } + .into(), + ); } } + for (addr, key) in &user_data.default_pub_account_signing_keys { + vec_for_storage.push( + InitialAccountData::Public(InitialAccountDataPublic { + address: addr.to_string(), + pub_sign_key: key.clone(), + }) + .into(), + ) + } + + for (addr, (key_chain, account)) in &user_data.default_user_private_accounts { + vec_for_storage.push( + InitialAccountData::Private(InitialAccountDataPrivate { + address: addr.to_string(), + account: account.clone(), + key_chain: key_chain.clone(), + }) + .into(), + ) + } + PersistentStorage { accounts: vec_for_storage, - password: user_data.password.clone(), last_synced_block, } } diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index ba2570a..3b494a9 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -10,7 +10,7 @@ use common::{ use anyhow::Result; use chain_storage::WalletChainStore; use config::WalletConfig; -use key_protocol::key_management::key_tree::chain_index::ChainIndex; +use key_protocol::key_management::key_tree::{chain_index::ChainIndex, traits::KeyNode}; use log::info; use nssa::{ Account, Address, privacy_preserving_transaction::message::EncryptedAccountData, @@ -62,11 +62,10 @@ impl WalletCore { let PersistentStorage { accounts: persistent_accounts, - password, last_synced_block, } = fetch_persistent_storage().await?; - let storage = WalletChainStore::new(config, persistent_accounts, password)?; + let storage = WalletChainStore::new(config, persistent_accounts)?; Ok(Self { storage, @@ -76,6 +75,23 @@ impl WalletCore { }) } + pub async fn start_from_config_new_storage( + config: WalletConfig, + password: String, + ) -> Result { + let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?); + let tx_poller = TxPoller::new(config.clone(), client.clone()); + + let storage = WalletChainStore::new_storage(config, password)?; + + Ok(Self { + storage, + poller: tx_poller, + sequencer_client: client.clone(), + last_synced_block: 0, + }) + } + ///Store persistent data at home pub async fn store_persistent_data(&self) -> Result { let home = get_home()?; @@ -233,6 +249,18 @@ pub enum Command { Config(ConfigSubcommand), } +///Represents CLI command for a wallet with setup included +#[derive(Debug, Subcommand, Clone)] +#[clap(about)] +pub enum OverCommand { + #[command(subcommand)] + Command(Command), + Setup { + #[arg(short, long)] + password: String, + }, +} + ///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config /// /// All account adresses must be valid 32 byte base58 strings. @@ -247,7 +275,7 @@ pub struct Args { pub continious_run: bool, /// Wallet command #[command(subcommand)] - pub command: Option, + pub command: Option, } #[derive(Debug, Clone)] @@ -341,8 +369,11 @@ pub async fn parse_block_range( if let NSSATransaction::PrivacyPreserving(tx) = nssa_tx { let mut affected_accounts = vec![]; - for (acc_addr, (key_chain, _)) in - &wallet_core.storage.user_data.user_private_accounts + for (acc_addr, (key_chain, _)) in wallet_core + .storage + .user_data + .default_user_private_accounts + .iter() { let view_tag = EncryptedAccountData::compute_view_tag( key_chain.nullifer_public_key.clone(), @@ -379,6 +410,51 @@ pub async fn parse_block_range( } } + for (_, keys_node) in wallet_core + .storage + .user_data + .private_key_tree + .key_map + .iter() + { + let acc_addr = keys_node.address(); + let key_chain = &keys_node.value.0; + + let view_tag = EncryptedAccountData::compute_view_tag( + key_chain.nullifer_public_key.clone(), + key_chain.incoming_viewing_public_key.clone(), + ); + + for (ciph_id, encrypted_data) in tx + .message() + .encrypted_private_post_states + .iter() + .enumerate() + { + if encrypted_data.view_tag == view_tag { + let ciphertext = &encrypted_data.ciphertext; + let commitment = &tx.message.new_commitments[ciph_id]; + let shared_secret = key_chain + .calculate_shared_secret_receiver(encrypted_data.epk.clone()); + + let res_acc = nssa_core::EncryptionScheme::decrypt( + ciphertext, + &shared_secret, + commitment, + ciph_id as u32, + ); + + if let Some(res_acc) = res_acc { + println!( + "Received new account for addr {acc_addr:#?} with account object {res_acc:#?}" + ); + + affected_accounts.push((acc_addr, res_acc)); + } + } + } + } + for (affected_addr, new_acc) in affected_accounts { wallet_core .storage @@ -426,3 +502,12 @@ pub async fn execute_continious_run() -> Result<()> { latest_block_num = seq_client.get_last_block().await?.last_block; } } + +pub async fn execute_setup(password: String) -> Result<()> { + let config = fetch_config().await?; + let wallet_core = WalletCore::start_from_config_new_storage(config.clone(), password).await?; + + wallet_core.store_persistent_data().await?; + + Ok(()) +} diff --git a/wallet/src/main.rs b/wallet/src/main.rs index ecc50d2..1fe52b3 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,7 +1,7 @@ use anyhow::Result; use clap::{CommandFactory, Parser}; use tokio::runtime::Builder; -use wallet::{Args, execute_continious_run, execute_subcommand}; +use wallet::{Args, OverCommand, execute_continious_run, execute_setup, execute_subcommand}; pub const NUM_THREADS: usize = 2; @@ -17,8 +17,15 @@ fn main() -> Result<()> { env_logger::init(); runtime.block_on(async move { - if let Some(command) = args.command { - execute_subcommand(command).await.unwrap(); + if let Some(overcommand) = args.command { + match overcommand { + OverCommand::Command(command) => { + execute_subcommand(command).await.unwrap(); + } + OverCommand::Setup { password } => { + execute_setup(password).await.unwrap(); + } + } } else if args.continious_run { execute_continious_run().await.unwrap(); } else {