feat: deterministic keys

This commit is contained in:
Pravdyvy 2025-11-11 12:15:20 +02:00
parent 20c276e63e
commit ec149d3227
14 changed files with 456 additions and 213 deletions

View File

@ -36,6 +36,9 @@ path = "../wallet"
[dependencies.common]
path = "../common"
[dependencies.key_protocol]
path = "../key_protocol"
[dependencies.nssa]
path = "../nssa"
features = ["no_docker"]

View File

@ -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<Result<()>>, TempDir)> {
wallet::execute_setup("test_pass".to_string()).await?;
let home_dir_sequencer = home_dir.join("sequencer");
let mut sequencer_config =

View File

@ -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<String, TestFunction> {
#[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<String, TestFunction> {
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<String, TestFunction> {
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<String, TestFunction> {
// 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<String, TestFunction> {
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<String, TestFunction> {
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<String, TestFunction> {
// 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<String, TestFunction> {
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<String, TestFunction> {
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<String, TestFunction> {
// 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<String, TestFunction> {
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<String, TestFunction> {
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<String, TestFunction> {
// 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<String, TestFunction> {
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<String, TestFunction> {
);
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<String, TestFunction> {
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<String, TestFunction> {
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<String, TestFunction> {
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<String, TestFunction> {
#[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<String, TestFunction> {
// 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()

View File

@ -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]

View File

@ -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<u32>);
#[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<Self, Self::Err> {
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());
}
}

View File

@ -40,6 +40,18 @@ impl<Node: KeyNode> KeyTree<Node> {
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<u32> {
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);

View File

@ -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);
}
}

View File

@ -19,8 +19,6 @@ pub struct NSSAUserData {
///Default private accounts
pub default_user_private_accounts:
HashMap<nssa::Address, (KeyChain, nssa_core::account::Account)>,
///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<nssa::Address, nssa::PrivateKey>,
default_accounts_key_chains: HashMap<
nssa::Address,
(KeyChain, nssa_core::account::Account),
>,
public_key_tree: KeyTreePublic,
private_key_tree: KeyTreePrivate,
password: String,
) -> Result<Self> {
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)
}
}
}

View File

@ -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<PersistentAccountData>,
password: String,
) -> Result<Self> {
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<Self> {
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<PersistentAccountData> {
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();
}
}

View File

@ -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,
},
}

View File

@ -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<PersistentAccountData>,
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<PersistentAccountDataPrivate> for PersistentAccountData {
}
}
impl From<InitialAccountData> 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

View File

@ -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<PersistentStorage> {
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,
}
}

View File

@ -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<Self> {
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<PathBuf> {
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<Command>,
pub command: Option<OverCommand>,
}
#[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(())
}

View File

@ -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 {