From 9788f189b195cf649d417ab119002c166b4e675a Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Tue, 4 Nov 2025 16:09:04 +0200 Subject: [PATCH 01/21] feat: stat of deterministic passwords --- key_protocol/src/key_management/mod.rs | 19 +++++++++ .../src/key_management/secret_holders.rs | 12 ++++++ key_protocol/src/key_protocol_core/mod.rs | 42 +++++++++++++++++++ 3 files changed, 73 insertions(+) diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index f22a99f..b642e13 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -39,6 +39,25 @@ impl KeyChain { } } + pub fn new_mnemonic(passphrase: String) -> Self { + //Currently dropping SeedHolder at the end of initialization. + //Now entirely sure if we need it in the future. + let seed_holder = SeedHolder::new_mnemonic(passphrase); + let secret_spending_key = seed_holder.produce_top_secret_key_holder(); + + let private_key_holder = secret_spending_key.produce_private_key_holder(); + + let nullifer_public_key = private_key_holder.generate_nullifier_public_key(); + let incoming_viewing_public_key = private_key_holder.generate_incoming_viewing_public_key(); + + Self { + secret_spending_key, + private_key_holder, + nullifer_public_key, + incoming_viewing_public_key, + } + } + pub fn calculate_shared_secret_receiver( &self, ephemeral_public_key_sender: EphemeralPublicKey, diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 57dea90..f05a641 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -44,6 +44,18 @@ impl SeedHolder { } } + pub fn new_mnemonic(passphrase: String) -> Self { + let mut enthopy_bytes: [u8; 32] = [0; 32]; + OsRng.fill_bytes(&mut enthopy_bytes); + + let mnemonic = Mnemonic::from_entropy(&enthopy_bytes).unwrap(); + let seed_wide = mnemonic.to_seed(passphrase); + + Self { + seed: seed_wide.to_vec(), + } + } + pub fn generate_secret_spending_key_hash(&self) -> HashType { let mut hash = hmac_sha512::HMAC::mac(&self.seed, "NSSA_seed"); diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index b1ebe71..33a007a 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -14,6 +14,8 @@ pub struct NSSAUserData { pub pub_account_signing_keys: HashMap, ///Map for all user private accounts pub user_private_accounts: HashMap, + ///Mnemonic passphrase + pub password: String, } impl NSSAUserData { @@ -64,6 +66,31 @@ impl NSSAUserData { Ok(Self { pub_account_signing_keys: accounts_keys, user_private_accounts: accounts_key_chains, + password: "mnemonic".to_string(), + }) + } + + pub fn new_with_accounts_and_password( + accounts_keys: HashMap, + accounts_key_chains: HashMap, + password: String, + ) -> Result { + if !Self::valid_public_key_transaction_pairing_check(&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(&accounts_key_chains) { + anyhow::bail!( + "Key transaction pairing check not satisfied, there is addresses, which is not derived from keys" + ); + } + + Ok(Self { + pub_account_signing_keys: accounts_keys, + user_private_accounts: accounts_key_chains, + password, }) } @@ -100,6 +127,21 @@ impl NSSAUserData { address } + /// Generated new private key for privacy preserving transactions + /// + /// Returns the address of new account + pub fn generate_new_privacy_preserving_transaction_key_chain_mnemonic( + &mut self, + ) -> nssa::Address { + let key_chain = KeyChain::new_mnemonic(self.password.clone()); + let address = nssa::Address::from(&key_chain.nullifer_public_key); + + self.user_private_accounts + .insert(address, (key_chain, nssa_core::account::Account::default())); + + address + } + /// Returns the signing key for public transaction signatures pub fn get_private_account( &self, From af1e129b6baa70f7c25ecc8b4efa66e4f8de80f0 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 5 Nov 2025 15:15:29 +0200 Subject: [PATCH 02/21] feat: public keys tree --- key_protocol/src/key_management/key_tree.rs | 205 ++++++++++++++++++++ key_protocol/src/key_management/mod.rs | 1 + 2 files changed, 206 insertions(+) create mode 100644 key_protocol/src/key_management/key_tree.rs diff --git a/key_protocol/src/key_management/key_tree.rs b/key_protocol/src/key_management/key_tree.rs new file mode 100644 index 0000000..8f3772f --- /dev/null +++ b/key_protocol/src/key_management/key_tree.rs @@ -0,0 +1,205 @@ +use std::{ + collections::{BTreeMap, HashMap}, + str::FromStr, + u32, +}; + +use crate::key_management::secret_holders::SeedHolder; + +#[derive(Debug)] +pub struct ChildKeysPublic { + pub csk: nssa::PrivateKey, + pub cpk: nssa::PublicKey, + pub ccc: [u8; 32], + ///Can be None if root + pub cci: Option, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] +pub struct ChainIndex(Vec); + +impl FromStr for ChainIndex { + type Err = hex::FromHexError; + + fn from_str(s: &str) -> Result { + if s == "" { + return Ok(Self(vec![])); + } + + 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)) + } + } +} + +impl ToString for ChainIndex { + fn to_string(&self) -> String { + if self.0.is_empty() { + return "".to_string(); + } + + let mut res_vec = vec![]; + + for index in &self.0 { + res_vec.extend_from_slice(&index.to_le_bytes()); + } + + hex::encode(res_vec) + } +} + +impl ChainIndex { + pub fn next_in_line(&self) -> ChainIndex { + let mut chain = self.0.clone(); + //ToDo: Add overflow check + chain.last_mut().map(|last_p| *last_p += 1); + + ChainIndex(chain) + } + + pub fn n_th_son(&self, son_id: u32) -> ChainIndex { + let mut chain = self.0.clone(); + chain.push(son_id); + + ChainIndex(chain) + } +} + +#[derive(Debug)] +pub struct KeyTreePublic { + pub key_map: BTreeMap, + pub addr_map: HashMap, +} + +impl KeyTreePublic { + pub fn new(seed: &SeedHolder) -> Self { + let seed_fit: [u8; 64] = seed.seed.clone().try_into().unwrap(); + let hash_value = hmac_sha512::HMAC::mac(&seed_fit, "NSSA_master_pub"); + + let csk = nssa::PrivateKey::try_new(*hash_value.first_chunk::<32>().unwrap()).unwrap(); + let ccc = *hash_value.last_chunk::<32>().unwrap(); + let cpk = nssa::PublicKey::new_from_private_key(&csk); + let address = nssa::Address::from(&cpk); + + let root_keys = ChildKeysPublic { + csk, + cpk, + ccc, + cci: None, + }; + + let mut key_map = BTreeMap::new(); + let mut addr_map = HashMap::new(); + + key_map.insert(ChainIndex::from_str("").unwrap(), root_keys); + addr_map.insert(address, ChainIndex::from_str("").unwrap()); + + Self { key_map, addr_map } + } + + pub fn find_last_son_of_id(&self, father_id: &ChainIndex) -> Option { + if !self.key_map.contains_key(father_id) { + return None; + } + + let leftmost_son = father_id.n_th_son(u32::MIN); + + if !self.key_map.contains_key(&leftmost_son) { + Some(0) + } else { + let mut right = u32::MAX - 1; + let mut left_border = u32::MIN; + let mut right_border = u32::MAX; + + loop { + let rightmost_son = father_id.n_th_son(right); + + let rightmost_ref = self.key_map.get(&rightmost_son); + let rightmost_ref_next = self.key_map.get(&rightmost_son.next_in_line()); + + match (&rightmost_ref, &rightmost_ref_next) { + (Some(_), Some(_)) => { + left_border = right; + right = (right + right_border) / 2; + } + (Some(_), None) => { + break Some(right); + } + (None, None) => { + right_border = right; + right = (left_border + right) / 2; + } + (None, Some(_)) => { + unreachable!(); + } + } + } + } + } + + pub fn generate_new_pub_keys(&mut self, father_cci: ChainIndex) -> Option { + if !self.key_map.contains_key(&father_cci) { + return None; + } + + let father_keys = self.key_map.get(&father_cci).unwrap(); + let next_son_id = self.find_last_son_of_id(&father_cci).unwrap(); + let next_son_cci = father_cci.n_th_son(next_son_id); + + let mut hash_input = vec![]; + hash_input.extend_from_slice(father_keys.csk.value()); + hash_input.extend_from_slice(&next_son_id.to_le_bytes()); + + let hash_value = hmac_sha512::HMAC::mac(&hash_input, father_keys.ccc); + + let csk = nssa::PrivateKey::try_new(*hash_value.first_chunk::<32>().unwrap()).unwrap(); + let ccc = *hash_value.last_chunk::<32>().unwrap(); + let cpk = nssa::PublicKey::new_from_private_key(&csk); + let address = nssa::Address::from(&cpk); + + let child_keys = ChildKeysPublic { + csk, + cpk, + ccc, + cci: None, + }; + + self.key_map.insert(next_son_cci.clone(), child_keys); + self.addr_map.insert(address, next_son_cci); + + Some(address) + } + + pub fn get_pub_keys(&self, addr: nssa::Address) -> Option<&ChildKeysPublic> { + self.addr_map + .get(&addr) + .map(|chain_id| self.key_map.get(chain_id)) + .flatten() + } + + pub fn topology_hexdump(&self) -> String { + let mut hex_dump = String::new(); + + //Very inefficient + for chain_id in self.key_map.keys() { + hex_dump = format!("{hex_dump}{}", chain_id.to_string()); + } + + hex_dump + } +} diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index b642e13..4c3d5e0 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; pub type PublicAccountSigningKey = [u8; 32]; pub mod ephemeral_key_holder; +pub mod key_tree; pub mod secret_holders; #[derive(Serialize, Deserialize, Clone, Debug)] From 4a430622f42ca202bfe9eaa410fd26c7086d5b60 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Thu, 6 Nov 2025 16:50:32 +0200 Subject: [PATCH 03/21] fix: testing and debugging --- key_protocol/src/key_management/key_tree.rs | 429 ++++++++++++++++---- 1 file changed, 357 insertions(+), 72 deletions(-) diff --git a/key_protocol/src/key_management/key_tree.rs b/key_protocol/src/key_management/key_tree.rs index 8f3772f..dd59363 100644 --- a/key_protocol/src/key_management/key_tree.rs +++ b/key_protocol/src/key_management/key_tree.rs @@ -1,20 +1,10 @@ use std::{ collections::{BTreeMap, HashMap}, str::FromStr, - u32, }; use crate::key_management::secret_holders::SeedHolder; -#[derive(Debug)] -pub struct ChildKeysPublic { - pub csk: nssa::PrivateKey, - pub cpk: nssa::PublicKey, - pub ccc: [u8; 32], - ///Can be None if root - pub cci: Option, -} - #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct ChainIndex(Vec); @@ -22,7 +12,7 @@ impl FromStr for ChainIndex { type Err = hex::FromHexError; fn from_str(s: &str) -> Result { - if s == "" { + if s.is_empty() { return Ok(Self(vec![])); } @@ -47,6 +37,7 @@ impl FromStr for ChainIndex { } } +#[allow(clippy::to_string_trait_impl)] impl ToString for ChainIndex { fn to_string(&self) -> String { if self.0.is_empty() { @@ -64,22 +55,77 @@ impl ToString for ChainIndex { } impl ChainIndex { + pub fn root() -> Self { + ChainIndex::from_str("").unwrap() + } + + pub fn chain(&self) -> &[u32] { + &self.0 + } + pub fn next_in_line(&self) -> ChainIndex { let mut chain = self.0.clone(); //ToDo: Add overflow check - chain.last_mut().map(|last_p| *last_p += 1); + if let Some(last_p) = chain.last_mut() { + *last_p += 1 + } ChainIndex(chain) } - pub fn n_th_son(&self, son_id: u32) -> ChainIndex { + pub fn n_th_child(&self, child_id: u32) -> ChainIndex { let mut chain = self.0.clone(); - chain.push(son_id); + chain.push(child_id); ChainIndex(chain) } } +#[derive(Debug)] +pub struct ChildKeysPublic { + pub csk: nssa::PrivateKey, + pub cpk: nssa::PublicKey, + pub ccc: [u8; 32], + ///Can be None if root + pub cci: Option, +} + +impl ChildKeysPublic { + pub fn root(seed: [u8; 64]) -> Self { + let hash_value = hmac_sha512::HMAC::mac(seed, "NSSA_master_pub"); + + let csk = nssa::PrivateKey::try_new(*hash_value.first_chunk::<32>().unwrap()).unwrap(); + let ccc = *hash_value.last_chunk::<32>().unwrap(); + let cpk = nssa::PublicKey::new_from_private_key(&csk); + + Self { + csk, + cpk, + ccc, + cci: None, + } + } + + pub fn n_th_child(&self, cci: u32) -> Self { + let mut hash_input = vec![]; + hash_input.extend_from_slice(self.csk.value()); + hash_input.extend_from_slice(&cci.to_le_bytes()); + + let hash_value = hmac_sha512::HMAC::mac(&hash_input, self.ccc); + + let csk = nssa::PrivateKey::try_new(*hash_value.first_chunk::<32>().unwrap()).unwrap(); + let ccc = *hash_value.last_chunk::<32>().unwrap(); + let cpk = nssa::PublicKey::new_from_private_key(&csk); + + Self { + csk, + cpk, + ccc, + cci: Some(cci), + } + } +} + #[derive(Debug)] pub struct KeyTreePublic { pub key_map: BTreeMap, @@ -89,37 +135,27 @@ pub struct KeyTreePublic { impl KeyTreePublic { pub fn new(seed: &SeedHolder) -> Self { let seed_fit: [u8; 64] = seed.seed.clone().try_into().unwrap(); - let hash_value = hmac_sha512::HMAC::mac(&seed_fit, "NSSA_master_pub"); - let csk = nssa::PrivateKey::try_new(*hash_value.first_chunk::<32>().unwrap()).unwrap(); - let ccc = *hash_value.last_chunk::<32>().unwrap(); - let cpk = nssa::PublicKey::new_from_private_key(&csk); - let address = nssa::Address::from(&cpk); - - let root_keys = ChildKeysPublic { - csk, - cpk, - ccc, - cci: None, - }; + let root_keys = ChildKeysPublic::root(seed_fit); + let address = nssa::Address::from(&root_keys.cpk); let mut key_map = BTreeMap::new(); let mut addr_map = HashMap::new(); - key_map.insert(ChainIndex::from_str("").unwrap(), root_keys); - addr_map.insert(address, ChainIndex::from_str("").unwrap()); + key_map.insert(ChainIndex::root(), root_keys); + addr_map.insert(address, ChainIndex::root()); Self { key_map, addr_map } } - pub fn find_last_son_of_id(&self, father_id: &ChainIndex) -> Option { - if !self.key_map.contains_key(father_id) { + pub fn find_next_last_child_of_id(&self, parent_id: &ChainIndex) -> Option { + if !self.key_map.contains_key(parent_id) { return None; } - let leftmost_son = father_id.n_th_son(u32::MIN); + let leftmost_child = parent_id.n_th_child(u32::MIN); - if !self.key_map.contains_key(&leftmost_son) { + if !self.key_map.contains_key(&leftmost_child) { Some(0) } else { let mut right = u32::MAX - 1; @@ -127,10 +163,10 @@ impl KeyTreePublic { let mut right_border = u32::MAX; loop { - let rightmost_son = father_id.n_th_son(right); + let rightmost_child = parent_id.n_th_child(right); - let rightmost_ref = self.key_map.get(&rightmost_son); - let rightmost_ref_next = self.key_map.get(&rightmost_son.next_in_line()); + let rightmost_ref = self.key_map.get(&rightmost_child); + let rightmost_ref_next = self.key_map.get(&rightmost_child.next_in_line()); match (&rightmost_ref, &rightmost_ref_next) { (Some(_), Some(_)) => { @@ -138,7 +174,7 @@ impl KeyTreePublic { right = (right + right_border) / 2; } (Some(_), None) => { - break Some(right); + break Some(right + 1); } (None, None) => { right_border = right; @@ -152,35 +188,21 @@ impl KeyTreePublic { } } - pub fn generate_new_pub_keys(&mut self, father_cci: ChainIndex) -> Option { - if !self.key_map.contains_key(&father_cci) { + pub fn generate_new_pub_keys(&mut self, parent_cci: ChainIndex) -> Option { + if !self.key_map.contains_key(&parent_cci) { return None; } - let father_keys = self.key_map.get(&father_cci).unwrap(); - let next_son_id = self.find_last_son_of_id(&father_cci).unwrap(); - let next_son_cci = father_cci.n_th_son(next_son_id); + let father_keys = self.key_map.get(&parent_cci).unwrap(); + let next_child_id = self.find_next_last_child_of_id(&parent_cci).unwrap(); + let next_cci = parent_cci.n_th_child(next_child_id); - let mut hash_input = vec![]; - hash_input.extend_from_slice(father_keys.csk.value()); - hash_input.extend_from_slice(&next_son_id.to_le_bytes()); + let child_keys = father_keys.n_th_child(next_child_id); - let hash_value = hmac_sha512::HMAC::mac(&hash_input, father_keys.ccc); + let address = nssa::Address::from(&child_keys.cpk); - let csk = nssa::PrivateKey::try_new(*hash_value.first_chunk::<32>().unwrap()).unwrap(); - let ccc = *hash_value.last_chunk::<32>().unwrap(); - let cpk = nssa::PublicKey::new_from_private_key(&csk); - let address = nssa::Address::from(&cpk); - - let child_keys = ChildKeysPublic { - csk, - cpk, - ccc, - cci: None, - }; - - self.key_map.insert(next_son_cci.clone(), child_keys); - self.addr_map.insert(address, next_son_cci); + self.key_map.insert(next_cci.clone(), child_keys); + self.addr_map.insert(address, next_cci); Some(address) } @@ -188,18 +210,281 @@ impl KeyTreePublic { pub fn get_pub_keys(&self, addr: nssa::Address) -> Option<&ChildKeysPublic> { self.addr_map .get(&addr) - .map(|chain_id| self.key_map.get(chain_id)) - .flatten() - } - - pub fn topology_hexdump(&self) -> String { - let mut hex_dump = String::new(); - - //Very inefficient - for chain_id in self.key_map.keys() { - hex_dump = format!("{hex_dump}{}", chain_id.to_string()); - } - - hex_dump + .and_then(|chain_id| self.key_map.get(chain_id)) + } +} + +#[cfg(test)] +mod tests { + use nssa::Address; + + use super::*; + + #[test] + fn test_chain_id_root_correct() { + let chain_id = ChainIndex::root(); + 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(); + + assert_eq!(chain_id.chain(), &[257]); + } + + #[test] + fn test_chain_id_next_in_line_correct() { + let chain_id = ChainIndex::from_str("01010000").unwrap(); + let next_in_line = chain_id.next_in_line(); + + assert_eq!(next_in_line, ChainIndex::from_str("02010000").unwrap()); + } + + #[test] + fn test_chain_id_child_correct() { + let chain_id = ChainIndex::from_str("01010000").unwrap(); + let child = chain_id.n_th_child(3); + + assert_eq!(child, ChainIndex::from_str("0101000003000000").unwrap()); + } + + #[test] + fn test_keys_deterministic_generation() { + let root_keys = ChildKeysPublic::root([42; 64]); + let child_keys = root_keys.n_th_child(5); + + assert_eq!(root_keys.cci, None); + assert_eq!(child_keys.cci, Some(5)); + + assert_eq!( + root_keys.ccc, + [ + 61, 30, 91, 26, 133, 91, 236, 192, 231, 53, 186, 139, 11, 221, 202, 11, 178, 215, + 254, 103, 191, 60, 117, 112, 1, 226, 31, 156, 83, 104, 150, 224 + ] + ); + assert_eq!( + child_keys.ccc, + [ + 67, 26, 102, 68, 189, 155, 102, 80, 199, 188, 112, 142, 207, 157, 36, 210, 48, 224, + 35, 6, 112, 180, 11, 190, 135, 218, 9, 14, 84, 231, 58, 98 + ] + ); + + assert_eq!( + root_keys.csk.value(), + &[ + 241, 82, 246, 237, 62, 130, 116, 47, 189, 112, 99, 67, 178, 40, 115, 245, 141, 193, + 77, 164, 243, 76, 222, 64, 50, 146, 23, 145, 91, 164, 92, 116 + ] + ); + assert_eq!( + child_keys.csk.value(), + &[ + 11, 151, 27, 212, 167, 26, 77, 234, 103, 145, 53, 191, 184, 25, 240, 191, 156, 25, + 60, 144, 65, 22, 193, 163, 246, 227, 212, 81, 49, 170, 33, 158 + ] + ); + + assert_eq!( + root_keys.cpk.value(), + &[ + 220, 170, 95, 177, 121, 37, 86, 166, 56, 238, 232, 72, 21, 106, 107, 217, 158, 74, + 133, 91, 143, 244, 155, 15, 2, 230, 223, 169, 13, 20, 163, 138 + ] + ); + assert_eq!( + child_keys.cpk.value(), + &[ + 152, 249, 236, 111, 132, 96, 184, 122, 21, 179, 240, 15, 234, 155, 164, 144, 108, + 110, 120, 74, 176, 147, 196, 168, 243, 186, 203, 79, 97, 17, 194, 52 + ] + ); + } + + fn seed_holder_for_tests() -> SeedHolder { + SeedHolder { + seed: [42; 64].to_vec(), + } + } + + #[test] + fn test_simple_key_tree() { + let seed_holder = seed_holder_for_tests(); + + let tree = KeyTreePublic::new(&seed_holder); + + assert!(tree.key_map.contains_key(&ChainIndex::root())); + assert!(tree.addr_map.contains_key(&Address::new([ + 46, 223, 229, 177, 59, 18, 189, 219, 153, 31, 249, 90, 112, 230, 180, 164, 80, 25, 106, + 159, 14, 238, 1, 192, 91, 8, 210, 165, 199, 41, 60, 104, + ]))); + } + + #[test] + fn test_small_key_tree() { + let seed_holder = seed_holder_for_tests(); + + let mut tree = KeyTreePublic::new(&seed_holder); + + let next_last_child_for_parent_id = tree + .find_next_last_child_of_id(&ChainIndex::root()) + .unwrap(); + + assert_eq!(next_last_child_for_parent_id, 0); + + tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + + assert!( + tree.key_map + .contains_key(&ChainIndex::from_str("00000000").unwrap()) + ); + + let next_last_child_for_parent_id = tree + .find_next_last_child_of_id(&ChainIndex::root()) + .unwrap(); + + assert_eq!(next_last_child_for_parent_id, 1); + + tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + + let next_last_child_for_parent_id = tree + .find_next_last_child_of_id(&ChainIndex::root()) + .unwrap(); + + assert_eq!(next_last_child_for_parent_id, 7); + } + + #[test] + fn test_key_tree_can_not_make_child_keys() { + let seed_holder = seed_holder_for_tests(); + + let mut tree = KeyTreePublic::new(&seed_holder); + + let next_last_child_for_parent_id = tree + .find_next_last_child_of_id(&ChainIndex::root()) + .unwrap(); + + assert_eq!(next_last_child_for_parent_id, 0); + + tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + + assert!( + tree.key_map + .contains_key(&ChainIndex::from_str("00000000").unwrap()) + ); + + let next_last_child_for_parent_id = tree + .find_next_last_child_of_id(&ChainIndex::root()) + .unwrap(); + + assert_eq!(next_last_child_for_parent_id, 1); + + let key_opt = tree.generate_new_pub_keys(ChainIndex::from_str("03000000").unwrap()); + + assert_eq!(key_opt, None); + } + + #[test] + fn test_key_tree_complex_structure() { + let seed_holder = seed_holder_for_tests(); + + let mut tree = KeyTreePublic::new(&seed_holder); + + let next_last_child_for_parent_id = tree + .find_next_last_child_of_id(&ChainIndex::root()) + .unwrap(); + + assert_eq!(next_last_child_for_parent_id, 0); + + tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + + assert!( + tree.key_map + .contains_key(&ChainIndex::from_str("00000000").unwrap()) + ); + + let next_last_child_for_parent_id = tree + .find_next_last_child_of_id(&ChainIndex::root()) + .unwrap(); + + assert_eq!(next_last_child_for_parent_id, 1); + + tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + + assert!( + tree.key_map + .contains_key(&ChainIndex::from_str("01000000").unwrap()) + ); + + let next_last_child_for_parent_id = tree + .find_next_last_child_of_id(&ChainIndex::root()) + .unwrap(); + + assert_eq!(next_last_child_for_parent_id, 2); + + tree.generate_new_pub_keys(ChainIndex::from_str("00000000").unwrap()) + .unwrap(); + + let next_last_child_for_parent_id = tree + .find_next_last_child_of_id(&ChainIndex::from_str("00000000").unwrap()) + .unwrap(); + + assert_eq!(next_last_child_for_parent_id, 1); + + assert!( + tree.key_map + .contains_key(&ChainIndex::from_str("0000000000000000").unwrap()) + ); + + tree.generate_new_pub_keys(ChainIndex::from_str("00000000").unwrap()) + .unwrap(); + + let next_last_child_for_parent_id = tree + .find_next_last_child_of_id(&ChainIndex::from_str("00000000").unwrap()) + .unwrap(); + + assert_eq!(next_last_child_for_parent_id, 2); + + assert!( + tree.key_map + .contains_key(&ChainIndex::from_str("0000000001000000").unwrap()) + ); + + tree.generate_new_pub_keys(ChainIndex::from_str("00000000").unwrap()) + .unwrap(); + + let next_last_child_for_parent_id = tree + .find_next_last_child_of_id(&ChainIndex::from_str("00000000").unwrap()) + .unwrap(); + + assert_eq!(next_last_child_for_parent_id, 3); + + assert!( + tree.key_map + .contains_key(&ChainIndex::from_str("0000000002000000").unwrap()) + ); + + tree.generate_new_pub_keys(ChainIndex::from_str("0000000001000000").unwrap()) + .unwrap(); + + assert!( + tree.key_map + .contains_key(&ChainIndex::from_str("000000000100000000000000").unwrap()) + ); + + let next_last_child_for_parent_id = tree + .find_next_last_child_of_id(&ChainIndex::from_str("0000000001000000").unwrap()) + .unwrap(); + + assert_eq!(next_last_child_for_parent_id, 1); } } From d9a130cc55eb9fe154ba434bb35c1c60fbb6b2ea Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Fri, 7 Nov 2025 16:21:14 +0200 Subject: [PATCH 04/21] feat: private tree --- .../key_management/key_tree/chain_index.rs | 115 ++++++++ .../key_management/key_tree/keys_private.rs | 201 ++++++++++++++ .../key_management/key_tree/keys_public.rs | 119 +++++++++ .../{key_tree.rs => key_tree/mod.rs} | 245 ++---------------- .../src/key_management/key_tree/traits.rs | 11 + .../src/key_management/secret_holders.rs | 4 +- key_protocol/src/key_protocol_core/mod.rs | 34 ++- nssa/src/signature/public_key.rs | 3 +- 8 files changed, 498 insertions(+), 234 deletions(-) create mode 100644 key_protocol/src/key_management/key_tree/chain_index.rs create mode 100644 key_protocol/src/key_management/key_tree/keys_private.rs create mode 100644 key_protocol/src/key_management/key_tree/keys_public.rs rename key_protocol/src/key_management/{key_tree.rs => key_tree/mod.rs} (56%) create mode 100644 key_protocol/src/key_management/key_tree/traits.rs diff --git a/key_protocol/src/key_management/key_tree/chain_index.rs b/key_protocol/src/key_management/key_tree/chain_index.rs new file mode 100644 index 0000000..dad9b2a --- /dev/null +++ b/key_protocol/src/key_management/key_tree/chain_index.rs @@ -0,0 +1,115 @@ +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)] +pub struct ChainIndex(Vec); + +impl FromStr for ChainIndex { + type Err = hex::FromHexError; + + fn from_str(s: &str) -> Result { + if s.is_empty() { + return Ok(Self(vec![])); + } + + 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)) + } + } +} + +#[allow(clippy::to_string_trait_impl)] +impl ToString for ChainIndex { + fn to_string(&self) -> String { + if self.0.is_empty() { + return "".to_string(); + } + + let mut res_vec = vec![]; + + for index in &self.0 { + res_vec.extend_from_slice(&index.to_le_bytes()); + } + + hex::encode(res_vec) + } +} + +impl ChainIndex { + pub fn root() -> Self { + ChainIndex::from_str("").unwrap() + } + + pub fn chain(&self) -> &[u32] { + &self.0 + } + + pub fn next_in_line(&self) -> ChainIndex { + let mut chain = self.0.clone(); + //ToDo: Add overflow check + if let Some(last_p) = chain.last_mut() { + *last_p += 1 + } + + ChainIndex(chain) + } + + pub fn n_th_child(&self, child_id: u32) -> ChainIndex { + let mut chain = self.0.clone(); + chain.push(child_id); + + ChainIndex(chain) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_chain_id_root_correct() { + let chain_id = ChainIndex::root(); + 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(); + + assert_eq!(chain_id.chain(), &[257]); + } + + #[test] + fn test_chain_id_next_in_line_correct() { + let chain_id = ChainIndex::from_str("01010000").unwrap(); + let next_in_line = chain_id.next_in_line(); + + assert_eq!(next_in_line, ChainIndex::from_str("02010000").unwrap()); + } + + #[test] + fn test_chain_id_child_correct() { + let chain_id = ChainIndex::from_str("01010000").unwrap(); + let child = chain_id.n_th_child(3); + + assert_eq!(child, ChainIndex::from_str("0101000003000000").unwrap()); + } +} diff --git a/key_protocol/src/key_management/key_tree/keys_private.rs b/key_protocol/src/key_management/key_tree/keys_private.rs new file mode 100644 index 0000000..46ade9f --- /dev/null +++ b/key_protocol/src/key_management/key_tree/keys_private.rs @@ -0,0 +1,201 @@ +use k256::{Scalar, elliptic_curve::PrimeField}; +use nssa_core::{NullifierPublicKey, NullifierSecretKey, encryption::IncomingViewingPublicKey}; +use serde::{Deserialize, Serialize}; + +use crate::key_management::{ + key_tree::traits::KeyNode, + secret_holders::{IncomingViewingSecretKey, OutgoingViewingSecretKey, SecretSpendingKey}, +}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ChildKeysPrivate { + pub ssk: SecretSpendingKey, + pub nsk: NullifierSecretKey, + pub isk: IncomingViewingSecretKey, + pub ovk: OutgoingViewingSecretKey, + pub npk: NullifierPublicKey, + pub ipk: IncomingViewingPublicKey, + pub ccc: [u8; 32], + ///Can be None if root + pub cci: Option, + pub account: nssa::Account, +} + +impl KeyNode for ChildKeysPrivate { + fn root(seed: [u8; 64]) -> Self { + let hash_value = hmac_sha512::HMAC::mac(seed, "NSSA_master_priv"); + + let ssk = SecretSpendingKey(*hash_value.first_chunk::<32>().unwrap()); + let ccc = *hash_value.last_chunk::<32>().unwrap(); + + let nsk = ssk.generate_nullifier_secret_key(); + let isk = ssk.generate_incoming_viewing_secret_key(); + let ovk = ssk.generate_outgoing_viewing_secret_key(); + + let npk = (&nsk).into(); + let ipk = IncomingViewingPublicKey::from_scalar(isk); + + Self { + ssk, + nsk, + isk, + ovk, + npk, + ipk, + ccc, + cci: None, + account: nssa::Account::default(), + } + } + + fn n_th_child(&self, cci: u32) -> Self { + let parent_pt = Scalar::from_repr(self.ovk.into()).unwrap() + + Scalar::from_repr(self.nsk.into()).unwrap() + * Scalar::from_repr(self.isk.into()).unwrap(); + let mut input = vec![]; + + input.extend_from_slice(b"NSSA_seed_priv"); + input.extend_from_slice(&parent_pt.to_bytes()); + input.extend_from_slice(&cci.to_le_bytes()); + + let hash_value = hmac_sha512::HMAC::mac(input, self.ccc); + + let ssk = SecretSpendingKey(*hash_value.first_chunk::<32>().unwrap()); + let ccc = *hash_value.last_chunk::<32>().unwrap(); + + let nsk = ssk.generate_nullifier_secret_key(); + let isk = ssk.generate_incoming_viewing_secret_key(); + let ovk = ssk.generate_outgoing_viewing_secret_key(); + + let npk = (&nsk).into(); + let ipk = IncomingViewingPublicKey::from_scalar(isk); + + Self { + ssk, + nsk, + isk, + ovk, + npk, + ipk, + ccc, + cci: Some(cci), + account: nssa::Account::default(), + } + } + + fn chain_code(&self) -> &[u8; 32] { + &self.ccc + } + + fn child_index(&self) -> &Option { + &self.cci + } + + fn address(&self) -> nssa::Address { + nssa::Address::from(&self.npk) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_keys_deterministic_generation() { + let root_keys = ChildKeysPrivate::root([42; 64]); + let child_keys = root_keys.n_th_child(5); + + assert_eq!(root_keys.cci, None); + assert_eq!(child_keys.cci, Some(5)); + + assert_eq!( + root_keys.ssk.0, + [ + 249, 83, 253, 32, 174, 204, 185, 44, 253, 167, 61, 92, 128, 5, 152, 4, 220, 21, 88, + 84, 167, 180, 154, 249, 44, 77, 33, 136, 59, 131, 203, 152 + ] + ); + assert_eq!( + child_keys.ssk.0, + [ + 16, 242, 229, 242, 252, 158, 153, 210, 234, 120, 70, 85, 83, 196, 5, 53, 28, 26, + 187, 230, 22, 193, 146, 232, 237, 3, 166, 184, 122, 1, 233, 93 + ] + ); + + assert_eq!( + root_keys.nsk, + [ + 38, 195, 52, 182, 16, 66, 167, 156, 9, 14, 65, 100, 17, 93, 166, 71, 27, 148, 93, + 85, 116, 109, 130, 8, 195, 222, 159, 214, 141, 41, 124, 57 + ] + ); + assert_eq!( + child_keys.nsk, + [ + 215, 46, 2, 151, 174, 60, 86, 154, 5, 3, 175, 245, 12, 176, 220, 58, 250, 118, 236, + 49, 254, 221, 229, 58, 40, 1, 170, 145, 175, 108, 23, 170 + ] + ); + + assert_eq!( + root_keys.isk, + [ + 153, 161, 15, 34, 96, 184, 165, 165, 27, 244, 155, 40, 70, 5, 241, 133, 78, 40, 61, + 118, 48, 148, 226, 5, 97, 18, 201, 128, 82, 248, 163, 72 + ] + ); + assert_eq!( + child_keys.isk, + [ + 192, 155, 55, 43, 164, 115, 71, 145, 227, 225, 21, 57, 55, 12, 226, 44, 10, 103, + 39, 73, 230, 173, 60, 69, 69, 122, 110, 241, 164, 3, 192, 57 + ] + ); + + assert_eq!( + root_keys.ovk, + [ + 205, 87, 71, 129, 90, 242, 217, 200, 140, 252, 124, 46, 207, 7, 33, 156, 83, 166, + 150, 81, 98, 131, 182, 156, 110, 92, 78, 140, 125, 218, 152, 154 + ] + ); + assert_eq!( + child_keys.ovk, + [ + 131, 202, 219, 172, 219, 29, 48, 120, 226, 209, 209, 10, 216, 173, 48, 167, 233, + 17, 35, 155, 30, 217, 176, 120, 72, 146, 250, 226, 165, 178, 255, 90 + ] + ); + + assert_eq!( + root_keys.npk.0, + [ + 65, 176, 149, 243, 192, 45, 216, 177, 169, 56, 229, 7, 28, 66, 204, 87, 109, 83, + 152, 64, 14, 188, 179, 210, 147, 60, 22, 251, 203, 70, 89, 215 + ] + ); + assert_eq!( + child_keys.npk.0, + [ + 69, 104, 130, 115, 48, 134, 19, 188, 67, 148, 163, 54, 155, 237, 57, 27, 136, 228, + 111, 233, 205, 158, 149, 31, 84, 11, 241, 176, 243, 12, 138, 249 + ] + ); + + assert_eq!( + root_keys.ipk.0, + &[ + 3, 174, 56, 136, 244, 179, 18, 122, 38, 220, 36, 50, 200, 41, 104, 167, 70, 18, 60, + 202, 93, 193, 29, 16, 125, 252, 96, 51, 199, 152, 47, 233, 178 + ] + ); + assert_eq!( + child_keys.ipk.0, + &[ + 3, 18, 202, 246, 79, 141, 169, 51, 55, 202, 120, 169, 244, 201, 156, 162, 216, 115, + 126, 53, 46, 94, 235, 125, 114, 178, 215, 81, 171, 93, 93, 88, 117 + ] + ); + } +} diff --git a/key_protocol/src/key_management/key_tree/keys_public.rs b/key_protocol/src/key_management/key_tree/keys_public.rs new file mode 100644 index 0000000..4e1bf1e --- /dev/null +++ b/key_protocol/src/key_management/key_tree/keys_public.rs @@ -0,0 +1,119 @@ +use serde::{Deserialize, Serialize}; + +use crate::key_management::key_tree::traits::KeyNode; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ChildKeysPublic { + pub csk: nssa::PrivateKey, + pub cpk: nssa::PublicKey, + pub ccc: [u8; 32], + ///Can be None if root + pub cci: Option, +} + +impl KeyNode for ChildKeysPublic { + fn root(seed: [u8; 64]) -> Self { + let hash_value = hmac_sha512::HMAC::mac(seed, "NSSA_master_pub"); + + let csk = nssa::PrivateKey::try_new(*hash_value.first_chunk::<32>().unwrap()).unwrap(); + let ccc = *hash_value.last_chunk::<32>().unwrap(); + let cpk = nssa::PublicKey::new_from_private_key(&csk); + + Self { + csk, + cpk, + ccc, + cci: None, + } + } + + fn n_th_child(&self, cci: u32) -> Self { + let mut hash_input = vec![]; + hash_input.extend_from_slice(self.csk.value()); + hash_input.extend_from_slice(&cci.to_le_bytes()); + + let hash_value = hmac_sha512::HMAC::mac(&hash_input, self.ccc); + + let csk = nssa::PrivateKey::try_new(*hash_value.first_chunk::<32>().unwrap()).unwrap(); + let ccc = *hash_value.last_chunk::<32>().unwrap(); + let cpk = nssa::PublicKey::new_from_private_key(&csk); + + Self { + csk, + cpk, + ccc, + cci: Some(cci), + } + } + + fn chain_code(&self) -> &[u8; 32] { + &self.ccc + } + + fn child_index(&self) -> &Option { + &self.cci + } + + fn address(&self) -> nssa::Address { + nssa::Address::from(&self.cpk) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_keys_deterministic_generation() { + let root_keys = ChildKeysPublic::root([42; 64]); + let child_keys = root_keys.n_th_child(5); + + assert_eq!(root_keys.cci, None); + assert_eq!(child_keys.cci, Some(5)); + + assert_eq!( + root_keys.ccc, + [ + 61, 30, 91, 26, 133, 91, 236, 192, 231, 53, 186, 139, 11, 221, 202, 11, 178, 215, + 254, 103, 191, 60, 117, 112, 1, 226, 31, 156, 83, 104, 150, 224 + ] + ); + assert_eq!( + child_keys.ccc, + [ + 67, 26, 102, 68, 189, 155, 102, 80, 199, 188, 112, 142, 207, 157, 36, 210, 48, 224, + 35, 6, 112, 180, 11, 190, 135, 218, 9, 14, 84, 231, 58, 98 + ] + ); + + assert_eq!( + root_keys.csk.value(), + &[ + 241, 82, 246, 237, 62, 130, 116, 47, 189, 112, 99, 67, 178, 40, 115, 245, 141, 193, + 77, 164, 243, 76, 222, 64, 50, 146, 23, 145, 91, 164, 92, 116 + ] + ); + assert_eq!( + child_keys.csk.value(), + &[ + 11, 151, 27, 212, 167, 26, 77, 234, 103, 145, 53, 191, 184, 25, 240, 191, 156, 25, + 60, 144, 65, 22, 193, 163, 246, 227, 212, 81, 49, 170, 33, 158 + ] + ); + + assert_eq!( + root_keys.cpk.value(), + &[ + 220, 170, 95, 177, 121, 37, 86, 166, 56, 238, 232, 72, 21, 106, 107, 217, 158, 74, + 133, 91, 143, 244, 155, 15, 2, 230, 223, 169, 13, 20, 163, 138 + ] + ); + assert_eq!( + child_keys.cpk.value(), + &[ + 152, 249, 236, 111, 132, 96, 184, 122, 21, 179, 240, 15, 234, 155, 164, 144, 108, + 110, 120, 74, 176, 147, 196, 168, 243, 186, 203, 79, 97, 17, 194, 52 + ] + ); + } +} diff --git a/key_protocol/src/key_management/key_tree.rs b/key_protocol/src/key_management/key_tree/mod.rs similarity index 56% rename from key_protocol/src/key_management/key_tree.rs rename to key_protocol/src/key_management/key_tree/mod.rs index dd59363..c21cc6e 100644 --- a/key_protocol/src/key_management/key_tree.rs +++ b/key_protocol/src/key_management/key_tree/mod.rs @@ -1,143 +1,35 @@ -use std::{ - collections::{BTreeMap, HashMap}, - str::FromStr, +use std::collections::{BTreeMap, HashMap}; + +use serde::{Deserialize, Serialize}; + +use crate::key_management::{ + key_tree::{ + chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic, + traits::KeyNode, + }, + secret_holders::SeedHolder, }; -use crate::key_management::secret_holders::SeedHolder; +pub mod chain_index; +pub mod keys_private; +pub mod keys_public; +pub mod traits; -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] -pub struct ChainIndex(Vec); - -impl FromStr for ChainIndex { - type Err = hex::FromHexError; - - fn from_str(s: &str) -> Result { - if s.is_empty() { - return Ok(Self(vec![])); - } - - 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)) - } - } -} - -#[allow(clippy::to_string_trait_impl)] -impl ToString for ChainIndex { - fn to_string(&self) -> String { - if self.0.is_empty() { - return "".to_string(); - } - - let mut res_vec = vec![]; - - for index in &self.0 { - res_vec.extend_from_slice(&index.to_le_bytes()); - } - - hex::encode(res_vec) - } -} - -impl ChainIndex { - pub fn root() -> Self { - ChainIndex::from_str("").unwrap() - } - - pub fn chain(&self) -> &[u32] { - &self.0 - } - - pub fn next_in_line(&self) -> ChainIndex { - let mut chain = self.0.clone(); - //ToDo: Add overflow check - if let Some(last_p) = chain.last_mut() { - *last_p += 1 - } - - ChainIndex(chain) - } - - pub fn n_th_child(&self, child_id: u32) -> ChainIndex { - let mut chain = self.0.clone(); - chain.push(child_id); - - ChainIndex(chain) - } -} - -#[derive(Debug)] -pub struct ChildKeysPublic { - pub csk: nssa::PrivateKey, - pub cpk: nssa::PublicKey, - pub ccc: [u8; 32], - ///Can be None if root - pub cci: Option, -} - -impl ChildKeysPublic { - pub fn root(seed: [u8; 64]) -> Self { - let hash_value = hmac_sha512::HMAC::mac(seed, "NSSA_master_pub"); - - let csk = nssa::PrivateKey::try_new(*hash_value.first_chunk::<32>().unwrap()).unwrap(); - let ccc = *hash_value.last_chunk::<32>().unwrap(); - let cpk = nssa::PublicKey::new_from_private_key(&csk); - - Self { - csk, - cpk, - ccc, - cci: None, - } - } - - pub fn n_th_child(&self, cci: u32) -> Self { - let mut hash_input = vec![]; - hash_input.extend_from_slice(self.csk.value()); - hash_input.extend_from_slice(&cci.to_le_bytes()); - - let hash_value = hmac_sha512::HMAC::mac(&hash_input, self.ccc); - - let csk = nssa::PrivateKey::try_new(*hash_value.first_chunk::<32>().unwrap()).unwrap(); - let ccc = *hash_value.last_chunk::<32>().unwrap(); - let cpk = nssa::PublicKey::new_from_private_key(&csk); - - Self { - csk, - cpk, - ccc, - cci: Some(cci), - } - } -} - -#[derive(Debug)] -pub struct KeyTreePublic { - pub key_map: BTreeMap, +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct KeyTree { + pub key_map: BTreeMap, pub addr_map: HashMap, } -impl KeyTreePublic { +pub type KeyTreePublic = KeyTree; +pub type KeyTreePrivate = KeyTree; + +impl KeyTree { pub fn new(seed: &SeedHolder) -> Self { let seed_fit: [u8; 64] = seed.seed.clone().try_into().unwrap(); - let root_keys = ChildKeysPublic::root(seed_fit); - let address = nssa::Address::from(&root_keys.cpk); + let root_keys = Node::root(seed_fit); + let address = root_keys.address(); let mut key_map = BTreeMap::new(); let mut addr_map = HashMap::new(); @@ -199,7 +91,7 @@ impl KeyTreePublic { let child_keys = father_keys.n_th_child(next_child_id); - let address = nssa::Address::from(&child_keys.cpk); + let address = child_keys.address(); self.key_map.insert(next_cci.clone(), child_keys); self.addr_map.insert(address, next_cci); @@ -207,7 +99,7 @@ impl KeyTreePublic { Some(address) } - pub fn get_pub_keys(&self, addr: nssa::Address) -> Option<&ChildKeysPublic> { + pub fn get_pub_keys(&self, addr: nssa::Address) -> Option<&Node> { self.addr_map .get(&addr) .and_then(|chain_id| self.key_map.get(chain_id)) @@ -216,95 +108,12 @@ impl KeyTreePublic { #[cfg(test)] mod tests { + use std::str::FromStr; + use nssa::Address; use super::*; - #[test] - fn test_chain_id_root_correct() { - let chain_id = ChainIndex::root(); - 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(); - - assert_eq!(chain_id.chain(), &[257]); - } - - #[test] - fn test_chain_id_next_in_line_correct() { - let chain_id = ChainIndex::from_str("01010000").unwrap(); - let next_in_line = chain_id.next_in_line(); - - assert_eq!(next_in_line, ChainIndex::from_str("02010000").unwrap()); - } - - #[test] - fn test_chain_id_child_correct() { - let chain_id = ChainIndex::from_str("01010000").unwrap(); - let child = chain_id.n_th_child(3); - - assert_eq!(child, ChainIndex::from_str("0101000003000000").unwrap()); - } - - #[test] - fn test_keys_deterministic_generation() { - let root_keys = ChildKeysPublic::root([42; 64]); - let child_keys = root_keys.n_th_child(5); - - assert_eq!(root_keys.cci, None); - assert_eq!(child_keys.cci, Some(5)); - - assert_eq!( - root_keys.ccc, - [ - 61, 30, 91, 26, 133, 91, 236, 192, 231, 53, 186, 139, 11, 221, 202, 11, 178, 215, - 254, 103, 191, 60, 117, 112, 1, 226, 31, 156, 83, 104, 150, 224 - ] - ); - assert_eq!( - child_keys.ccc, - [ - 67, 26, 102, 68, 189, 155, 102, 80, 199, 188, 112, 142, 207, 157, 36, 210, 48, 224, - 35, 6, 112, 180, 11, 190, 135, 218, 9, 14, 84, 231, 58, 98 - ] - ); - - assert_eq!( - root_keys.csk.value(), - &[ - 241, 82, 246, 237, 62, 130, 116, 47, 189, 112, 99, 67, 178, 40, 115, 245, 141, 193, - 77, 164, 243, 76, 222, 64, 50, 146, 23, 145, 91, 164, 92, 116 - ] - ); - assert_eq!( - child_keys.csk.value(), - &[ - 11, 151, 27, 212, 167, 26, 77, 234, 103, 145, 53, 191, 184, 25, 240, 191, 156, 25, - 60, 144, 65, 22, 193, 163, 246, 227, 212, 81, 49, 170, 33, 158 - ] - ); - - assert_eq!( - root_keys.cpk.value(), - &[ - 220, 170, 95, 177, 121, 37, 86, 166, 56, 238, 232, 72, 21, 106, 107, 217, 158, 74, - 133, 91, 143, 244, 155, 15, 2, 230, 223, 169, 13, 20, 163, 138 - ] - ); - assert_eq!( - child_keys.cpk.value(), - &[ - 152, 249, 236, 111, 132, 96, 184, 122, 21, 179, 240, 15, 234, 155, 164, 144, 108, - 110, 120, 74, 176, 147, 196, 168, 243, 186, 203, 79, 97, 17, 194, 52 - ] - ); - } - fn seed_holder_for_tests() -> SeedHolder { SeedHolder { seed: [42; 64].to_vec(), diff --git a/key_protocol/src/key_management/key_tree/traits.rs b/key_protocol/src/key_management/key_tree/traits.rs new file mode 100644 index 0000000..662481a --- /dev/null +++ b/key_protocol/src/key_management/key_tree/traits.rs @@ -0,0 +1,11 @@ +pub trait KeyNode { + fn root(seed: [u8; 64]) -> Self; + + fn n_th_child(&self, cci: u32) -> Self; + + fn chain_code(&self) -> &[u8; 32]; + + fn child_index(&self) -> &Option; + + fn address(&self) -> nssa::Address; +} diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index f05a641..ee7aced 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -45,8 +45,8 @@ impl SeedHolder { } pub fn new_mnemonic(passphrase: String) -> Self { - let mut enthopy_bytes: [u8; 32] = [0; 32]; - OsRng.fill_bytes(&mut enthopy_bytes); + //Enthropy bytes must be deterministic as well + let enthopy_bytes: [u8; 32] = [0; 32]; let mnemonic = Mnemonic::from_entropy(&enthopy_bytes).unwrap(); let seed_wide = mnemonic.to_seed(passphrase); diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 33a007a..6731da8 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -4,18 +4,26 @@ use anyhow::Result; use k256::AffinePoint; use serde::{Deserialize, Serialize}; -use crate::key_management::KeyChain; +use crate::key_management::{ + KeyChain, + key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex}, +}; pub type PublicKey = AffinePoint; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NSSAUserData { - ///Map for all user public accounts - pub pub_account_signing_keys: HashMap, - ///Map for all user private accounts - pub user_private_accounts: HashMap, + ///Default public accounts + pub default_pub_account_signing_keys: HashMap, + ///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 + pub private_key_tree: KeyTreePrivate, } impl NSSAUserData { @@ -97,13 +105,13 @@ impl NSSAUserData { /// Generated new private key for public transaction signatures /// /// Returns the address of new account - pub fn generate_new_public_transaction_private_key(&mut self) -> nssa::Address { - let private_key = nssa::PrivateKey::new_os_random(); - let address = nssa::Address::from(&nssa::PublicKey::new_from_private_key(&private_key)); - - self.pub_account_signing_keys.insert(address, private_key); - - address + pub fn generate_new_public_transaction_private_key( + &mut self, + parent_cci: ChainIndex, + ) -> nssa::Address { + self.public_key_tree + .generate_new_pub_keys(parent_cci) + .unwrap() } /// Returns the signing key for public transaction signatures @@ -111,7 +119,7 @@ impl NSSAUserData { &self, address: &nssa::Address, ) -> Option<&nssa::PrivateKey> { - self.pub_account_signing_keys.get(address) + self.public_key_tree.get_pub_keys(address) } /// Generated new private key for privacy preserving transactions diff --git a/nssa/src/signature/public_key.rs b/nssa/src/signature/public_key.rs index dbd7d64..095025d 100644 --- a/nssa/src/signature/public_key.rs +++ b/nssa/src/signature/public_key.rs @@ -1,10 +1,11 @@ use nssa_core::address::Address; +use serde::{Deserialize, Serialize}; use crate::{PrivateKey, error::NssaError}; use sha2::{Digest, Sha256}; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PublicKey([u8; 32]); impl PublicKey { From 20c276e63eb0c75a553bf5b3454fa6e67732d981 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Mon, 10 Nov 2025 16:29:33 +0200 Subject: [PATCH 05/21] fix: continuation of integration --- .../key_management/key_tree/keys_private.rs | 128 ++++++++++++------ .../key_management/key_tree/keys_public.rs | 6 + .../src/key_management/key_tree/mod.rs | 45 +++--- key_protocol/src/key_management/mod.rs | 2 +- key_protocol/src/key_protocol_core/mod.rs | 111 +++++++++------ wallet/src/chain_storage/mod.rs | 69 ++++++---- wallet/src/cli/account.rs | 22 ++- wallet/src/config.rs | 15 +- wallet/src/helperfunctions.rs | 31 +++-- wallet/src/lib.rs | 22 ++- 10 files changed, 290 insertions(+), 161 deletions(-) diff --git a/key_protocol/src/key_management/key_tree/keys_private.rs b/key_protocol/src/key_management/key_tree/keys_private.rs index 46ade9f..84b44fa 100644 --- a/key_protocol/src/key_management/key_tree/keys_private.rs +++ b/key_protocol/src/key_management/key_tree/keys_private.rs @@ -1,24 +1,19 @@ use k256::{Scalar, elliptic_curve::PrimeField}; -use nssa_core::{NullifierPublicKey, NullifierSecretKey, encryption::IncomingViewingPublicKey}; +use nssa_core::encryption::IncomingViewingPublicKey; use serde::{Deserialize, Serialize}; use crate::key_management::{ + KeyChain, key_tree::traits::KeyNode, - secret_holders::{IncomingViewingSecretKey, OutgoingViewingSecretKey, SecretSpendingKey}, + secret_holders::{PrivateKeyHolder, SecretSpendingKey}, }; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ChildKeysPrivate { - pub ssk: SecretSpendingKey, - pub nsk: NullifierSecretKey, - pub isk: IncomingViewingSecretKey, - pub ovk: OutgoingViewingSecretKey, - pub npk: NullifierPublicKey, - pub ipk: IncomingViewingPublicKey, + pub value: (KeyChain, nssa::Account), pub ccc: [u8; 32], ///Can be None if root pub cci: Option, - pub account: nssa::Account, } impl KeyNode for ChildKeysPrivate { @@ -36,22 +31,43 @@ impl KeyNode for ChildKeysPrivate { let ipk = IncomingViewingPublicKey::from_scalar(isk); Self { - ssk, - nsk, - isk, - ovk, - npk, - ipk, + value: ( + KeyChain { + secret_spending_key: ssk, + nullifer_public_key: npk, + incoming_viewing_public_key: ipk, + private_key_holder: PrivateKeyHolder { + nullifier_secret_key: nsk, + incoming_viewing_secret_key: isk, + outgoing_viewing_secret_key: ovk, + }, + }, + nssa::Account::default(), + ), ccc, cci: None, - account: nssa::Account::default(), } } fn n_th_child(&self, cci: u32) -> Self { - let parent_pt = Scalar::from_repr(self.ovk.into()).unwrap() - + Scalar::from_repr(self.nsk.into()).unwrap() - * Scalar::from_repr(self.isk.into()).unwrap(); + let parent_pt = Scalar::from_repr( + self.value + .0 + .private_key_holder + .outgoing_viewing_secret_key + .into(), + ) + .unwrap() + + Scalar::from_repr(self.value.0.private_key_holder.nullifier_secret_key.into()) + .unwrap() + * Scalar::from_repr( + self.value + .0 + .private_key_holder + .incoming_viewing_secret_key + .into(), + ) + .unwrap(); let mut input = vec![]; input.extend_from_slice(b"NSSA_seed_priv"); @@ -71,15 +87,21 @@ impl KeyNode for ChildKeysPrivate { let ipk = IncomingViewingPublicKey::from_scalar(isk); Self { - ssk, - nsk, - isk, - ovk, - npk, - ipk, + value: ( + KeyChain { + secret_spending_key: ssk, + nullifer_public_key: npk, + incoming_viewing_public_key: ipk, + private_key_holder: PrivateKeyHolder { + nullifier_secret_key: nsk, + incoming_viewing_secret_key: isk, + outgoing_viewing_secret_key: ovk, + }, + }, + nssa::Account::default(), + ), ccc, cci: Some(cci), - account: nssa::Account::default(), } } @@ -92,7 +114,19 @@ impl KeyNode for ChildKeysPrivate { } fn address(&self) -> nssa::Address { - nssa::Address::from(&self.npk) + nssa::Address::from(&self.value.0.nullifer_public_key) + } +} + +impl<'a> From<&'a ChildKeysPrivate> for &'a (KeyChain, nssa::Account) { + fn from(value: &'a ChildKeysPrivate) -> Self { + &value.value + } +} + +impl<'a> From<&'a mut ChildKeysPrivate> for &'a mut (KeyChain, nssa::Account) { + fn from(value: &'a mut ChildKeysPrivate) -> Self { + &mut value.value } } @@ -109,14 +143,14 @@ mod tests { assert_eq!(child_keys.cci, Some(5)); assert_eq!( - root_keys.ssk.0, + root_keys.value.0.secret_spending_key.0, [ 249, 83, 253, 32, 174, 204, 185, 44, 253, 167, 61, 92, 128, 5, 152, 4, 220, 21, 88, 84, 167, 180, 154, 249, 44, 77, 33, 136, 59, 131, 203, 152 ] ); assert_eq!( - child_keys.ssk.0, + child_keys.value.0.secret_spending_key.0, [ 16, 242, 229, 242, 252, 158, 153, 210, 234, 120, 70, 85, 83, 196, 5, 53, 28, 26, 187, 230, 22, 193, 146, 232, 237, 3, 166, 184, 122, 1, 233, 93 @@ -124,14 +158,14 @@ mod tests { ); assert_eq!( - root_keys.nsk, + root_keys.value.0.private_key_holder.nullifier_secret_key, [ 38, 195, 52, 182, 16, 66, 167, 156, 9, 14, 65, 100, 17, 93, 166, 71, 27, 148, 93, 85, 116, 109, 130, 8, 195, 222, 159, 214, 141, 41, 124, 57 ] ); assert_eq!( - child_keys.nsk, + child_keys.value.0.private_key_holder.nullifier_secret_key, [ 215, 46, 2, 151, 174, 60, 86, 154, 5, 3, 175, 245, 12, 176, 220, 58, 250, 118, 236, 49, 254, 221, 229, 58, 40, 1, 170, 145, 175, 108, 23, 170 @@ -139,14 +173,22 @@ mod tests { ); assert_eq!( - root_keys.isk, + root_keys + .value + .0 + .private_key_holder + .incoming_viewing_secret_key, [ 153, 161, 15, 34, 96, 184, 165, 165, 27, 244, 155, 40, 70, 5, 241, 133, 78, 40, 61, 118, 48, 148, 226, 5, 97, 18, 201, 128, 82, 248, 163, 72 ] ); assert_eq!( - child_keys.isk, + child_keys + .value + .0 + .private_key_holder + .incoming_viewing_secret_key, [ 192, 155, 55, 43, 164, 115, 71, 145, 227, 225, 21, 57, 55, 12, 226, 44, 10, 103, 39, 73, 230, 173, 60, 69, 69, 122, 110, 241, 164, 3, 192, 57 @@ -154,14 +196,22 @@ mod tests { ); assert_eq!( - root_keys.ovk, + root_keys + .value + .0 + .private_key_holder + .outgoing_viewing_secret_key, [ 205, 87, 71, 129, 90, 242, 217, 200, 140, 252, 124, 46, 207, 7, 33, 156, 83, 166, 150, 81, 98, 131, 182, 156, 110, 92, 78, 140, 125, 218, 152, 154 ] ); assert_eq!( - child_keys.ovk, + child_keys + .value + .0 + .private_key_holder + .outgoing_viewing_secret_key, [ 131, 202, 219, 172, 219, 29, 48, 120, 226, 209, 209, 10, 216, 173, 48, 167, 233, 17, 35, 155, 30, 217, 176, 120, 72, 146, 250, 226, 165, 178, 255, 90 @@ -169,14 +219,14 @@ mod tests { ); assert_eq!( - root_keys.npk.0, + root_keys.value.0.nullifer_public_key.0, [ 65, 176, 149, 243, 192, 45, 216, 177, 169, 56, 229, 7, 28, 66, 204, 87, 109, 83, 152, 64, 14, 188, 179, 210, 147, 60, 22, 251, 203, 70, 89, 215 ] ); assert_eq!( - child_keys.npk.0, + child_keys.value.0.nullifer_public_key.0, [ 69, 104, 130, 115, 48, 134, 19, 188, 67, 148, 163, 54, 155, 237, 57, 27, 136, 228, 111, 233, 205, 158, 149, 31, 84, 11, 241, 176, 243, 12, 138, 249 @@ -184,14 +234,14 @@ mod tests { ); assert_eq!( - root_keys.ipk.0, + root_keys.value.0.incoming_viewing_public_key.0, &[ 3, 174, 56, 136, 244, 179, 18, 122, 38, 220, 36, 50, 200, 41, 104, 167, 70, 18, 60, 202, 93, 193, 29, 16, 125, 252, 96, 51, 199, 152, 47, 233, 178 ] ); assert_eq!( - child_keys.ipk.0, + child_keys.value.0.incoming_viewing_public_key.0, &[ 3, 18, 202, 246, 79, 141, 169, 51, 55, 202, 120, 169, 244, 201, 156, 162, 216, 115, 126, 53, 46, 94, 235, 125, 114, 178, 215, 81, 171, 93, 93, 88, 117 diff --git a/key_protocol/src/key_management/key_tree/keys_public.rs b/key_protocol/src/key_management/key_tree/keys_public.rs index 4e1bf1e..7ca6247 100644 --- a/key_protocol/src/key_management/key_tree/keys_public.rs +++ b/key_protocol/src/key_management/key_tree/keys_public.rs @@ -59,6 +59,12 @@ impl KeyNode for ChildKeysPublic { } } +impl<'a> From<&'a ChildKeysPublic> for &'a nssa::PrivateKey { + fn from(value: &'a ChildKeysPublic) -> Self { + &value.csk + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/key_protocol/src/key_management/key_tree/mod.rs b/key_protocol/src/key_management/key_tree/mod.rs index c21cc6e..7ea2a2a 100644 --- a/key_protocol/src/key_management/key_tree/mod.rs +++ b/key_protocol/src/key_management/key_tree/mod.rs @@ -80,7 +80,7 @@ impl KeyTree { } } - pub fn generate_new_pub_keys(&mut self, parent_cci: ChainIndex) -> Option { + pub fn generate_new_node(&mut self, parent_cci: ChainIndex) -> Option { if !self.key_map.contains_key(&parent_cci) { return None; } @@ -99,11 +99,22 @@ impl KeyTree { Some(address) } - pub fn get_pub_keys(&self, addr: nssa::Address) -> Option<&Node> { + pub fn get_node(&self, addr: nssa::Address) -> Option<&Node> { self.addr_map .get(&addr) .and_then(|chain_id| self.key_map.get(chain_id)) } + + pub fn get_node_mut(&mut self, addr: nssa::Address) -> Option<&mut Node> { + self.addr_map + .get(&addr) + .and_then(|chain_id| self.key_map.get_mut(chain_id)) + } + + pub fn insert(&mut self, addr: nssa::Address, chain_index: ChainIndex, node: Node) { + self.addr_map.insert(addr, chain_index.clone()); + self.key_map.insert(chain_index, node); + } } #[cfg(test)] @@ -145,7 +156,7 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 0); - tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + tree.generate_new_node(ChainIndex::root()).unwrap(); assert!( tree.key_map @@ -158,12 +169,12 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 1); - tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); - tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); - tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); - tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); - tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); - tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + tree.generate_new_node(ChainIndex::root()).unwrap(); + tree.generate_new_node(ChainIndex::root()).unwrap(); + tree.generate_new_node(ChainIndex::root()).unwrap(); + tree.generate_new_node(ChainIndex::root()).unwrap(); + tree.generate_new_node(ChainIndex::root()).unwrap(); + tree.generate_new_node(ChainIndex::root()).unwrap(); let next_last_child_for_parent_id = tree .find_next_last_child_of_id(&ChainIndex::root()) @@ -184,7 +195,7 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 0); - tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + tree.generate_new_node(ChainIndex::root()).unwrap(); assert!( tree.key_map @@ -197,7 +208,7 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 1); - let key_opt = tree.generate_new_pub_keys(ChainIndex::from_str("03000000").unwrap()); + let key_opt = tree.generate_new_node(ChainIndex::from_str("03000000").unwrap()); assert_eq!(key_opt, None); } @@ -214,7 +225,7 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 0); - tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + tree.generate_new_node(ChainIndex::root()).unwrap(); assert!( tree.key_map @@ -227,7 +238,7 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 1); - tree.generate_new_pub_keys(ChainIndex::root()).unwrap(); + tree.generate_new_node(ChainIndex::root()).unwrap(); assert!( tree.key_map @@ -240,7 +251,7 @@ mod tests { assert_eq!(next_last_child_for_parent_id, 2); - tree.generate_new_pub_keys(ChainIndex::from_str("00000000").unwrap()) + tree.generate_new_node(ChainIndex::from_str("00000000").unwrap()) .unwrap(); let next_last_child_for_parent_id = tree @@ -254,7 +265,7 @@ mod tests { .contains_key(&ChainIndex::from_str("0000000000000000").unwrap()) ); - tree.generate_new_pub_keys(ChainIndex::from_str("00000000").unwrap()) + tree.generate_new_node(ChainIndex::from_str("00000000").unwrap()) .unwrap(); let next_last_child_for_parent_id = tree @@ -268,7 +279,7 @@ mod tests { .contains_key(&ChainIndex::from_str("0000000001000000").unwrap()) ); - tree.generate_new_pub_keys(ChainIndex::from_str("00000000").unwrap()) + tree.generate_new_node(ChainIndex::from_str("00000000").unwrap()) .unwrap(); let next_last_child_for_parent_id = tree @@ -282,7 +293,7 @@ mod tests { .contains_key(&ChainIndex::from_str("0000000002000000").unwrap()) ); - tree.generate_new_pub_keys(ChainIndex::from_str("0000000001000000").unwrap()) + tree.generate_new_node(ChainIndex::from_str("0000000001000000").unwrap()) .unwrap(); assert!( diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index 4c3d5e0..8a58d4a 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -14,7 +14,7 @@ pub mod secret_holders; #[derive(Serialize, Deserialize, Clone, Debug)] ///Entrypoint to key management pub struct KeyChain { - secret_spending_key: SecretSpendingKey, + pub secret_spending_key: SecretSpendingKey, pub private_key_holder: PrivateKeyHolder, pub nullifer_public_key: NullifierPublicKey, pub incoming_viewing_public_key: IncomingViewingPublicKey, diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 6731da8..f50088a 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::key_management::{ KeyChain, key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex}, + secret_holders::SeedHolder, }; pub type PublicKey = AffinePoint; @@ -56,48 +57,62 @@ impl NSSAUserData { } pub fn new_with_accounts( - accounts_keys: HashMap, - accounts_key_chains: HashMap, + default_accounts_keys: HashMap, + default_accounts_key_chains: HashMap< + nssa::Address, + (KeyChain, nssa_core::account::Account), + >, + public_key_tree: KeyTreePublic, + private_key_tree: KeyTreePrivate, ) -> Result { - if !Self::valid_public_key_transaction_pairing_check(&accounts_keys) { + 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(&accounts_key_chains) { + 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 { - pub_account_signing_keys: accounts_keys, - user_private_accounts: accounts_key_chains, + default_pub_account_signing_keys: default_accounts_keys, + 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( - accounts_keys: HashMap, - accounts_key_chains: HashMap, + 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(&accounts_keys) { + 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(&accounts_key_chains) { + 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 { - pub_account_signing_keys: accounts_keys, - user_private_accounts: accounts_key_chains, + default_pub_account_signing_keys: default_accounts_keys, + default_user_private_accounts: default_accounts_key_chains, + public_key_tree, + private_key_tree, password, }) } @@ -109,9 +124,7 @@ impl NSSAUserData { &mut self, parent_cci: ChainIndex, ) -> nssa::Address { - self.public_key_tree - .generate_new_pub_keys(parent_cci) - .unwrap() + self.public_key_tree.generate_new_node(parent_cci).unwrap() } /// Returns the signing key for public transaction signatures @@ -119,35 +132,25 @@ impl NSSAUserData { &self, address: &nssa::Address, ) -> Option<&nssa::PrivateKey> { - self.public_key_tree.get_pub_keys(address) + //First seek in defaults + if let Some(key) = self.default_pub_account_signing_keys.get(address) { + Some(key) + //Then seek in tree + } else { + self.public_key_tree + .get_node(*address) + .and_then(|chain_keys| Some(chain_keys.into())) + } } /// Generated new private key for privacy preserving transactions /// /// Returns the address of new account - pub fn generate_new_privacy_preserving_transaction_key_chain(&mut self) -> nssa::Address { - let key_chain = KeyChain::new_os_random(); - let address = nssa::Address::from(&key_chain.nullifer_public_key); - - self.user_private_accounts - .insert(address, (key_chain, nssa_core::account::Account::default())); - - address - } - - /// Generated new private key for privacy preserving transactions - /// - /// Returns the address of new account - pub fn generate_new_privacy_preserving_transaction_key_chain_mnemonic( + pub fn generate_new_privacy_preserving_transaction_key_chain( &mut self, + parent_cci: ChainIndex, ) -> nssa::Address { - let key_chain = KeyChain::new_mnemonic(self.password.clone()); - let address = nssa::Address::from(&key_chain.nullifer_public_key); - - self.user_private_accounts - .insert(address, (key_chain, nssa_core::account::Account::default())); - - address + self.private_key_tree.generate_new_node(parent_cci).unwrap() } /// Returns the signing key for public transaction signatures @@ -155,7 +158,15 @@ impl NSSAUserData { &self, address: &nssa::Address, ) -> Option<&(KeyChain, nssa_core::account::Account)> { - self.user_private_accounts.get(address) + //First seek in defaults + if let Some(key) = self.default_user_private_accounts.get(address) { + Some(key) + //Then seek in tree + } else { + self.private_key_tree + .get_node(*address) + .and_then(|chain_keys| Some(chain_keys.into())) + } } /// Returns the signing key for public transaction signatures @@ -163,14 +174,27 @@ impl NSSAUserData { &mut self, address: &nssa::Address, ) -> Option<&mut (KeyChain, nssa_core::account::Account)> { - self.user_private_accounts.get_mut(address) + //First seek in defaults + if let Some(key) = self.default_user_private_accounts.get_mut(address) { + Some(key) + //Then seek in tree + } else { + self.private_key_tree + .get_node_mut(*address) + .and_then(|chain_keys| Some(chain_keys.into())) + } } } impl Default for NSSAUserData { fn default() -> Self { - //Safe unwrap as maps are empty - Self::new_with_accounts(HashMap::default(), HashMap::default()).unwrap() + Self::new_with_accounts( + HashMap::new(), + HashMap::new(), + KeyTreePublic::new(&SeedHolder::new_mnemonic("default".to_string())), + KeyTreePrivate::new(&SeedHolder::new_mnemonic("default".to_string())), + ) + .unwrap() } } @@ -182,8 +206,9 @@ mod tests { fn test_new_account() { let mut user_data = NSSAUserData::default(); - let addr_pub = user_data.generate_new_public_transaction_private_key(); - let addr_private = user_data.generate_new_privacy_preserving_transaction_key_chain(); + let addr_pub = user_data.generate_new_public_transaction_private_key(ChainIndex::root()); + let addr_private = + user_data.generate_new_privacy_preserving_transaction_key_chain(ChainIndex::root()); let is_private_key_generated = user_data.get_pub_account_signing_key(&addr_pub).is_some(); diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index 4a845af..a1a8517 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -1,7 +1,13 @@ use std::collections::HashMap; use anyhow::Result; -use key_protocol::key_protocol_core::NSSAUserData; +use key_protocol::{ + key_management::{ + key_tree::{KeyTreePrivate, KeyTreePublic}, + secret_holders::SeedHolder, + }, + key_protocol_core::NSSAUserData, +}; use nssa::program::Program; use crate::config::{InitialAccountData, PersistentAccountData, WalletConfig}; @@ -12,7 +18,11 @@ pub struct WalletChainStore { } impl WalletChainStore { - pub fn new(config: WalletConfig) -> Result { + pub fn new( + config: WalletConfig, + persistent_accounts: Vec, + password: String, + ) -> Result { let mut public_init_acc_map = HashMap::new(); let mut private_init_acc_map = HashMap::new(); @@ -32,8 +42,27 @@ 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); + } + } + } + Ok(Self { - user_data: NSSAUserData::new_with_accounts(public_init_acc_map, private_init_acc_map)?, + user_data: NSSAUserData::new_with_accounts( + public_init_acc_map, + private_init_acc_map, + public_tree, + private_tree, + )?, wallet_config: config, }) } @@ -43,26 +72,20 @@ impl WalletChainStore { addr: nssa::Address, account: nssa_core::account::Account, ) { - println!("inserting at addres {}, this account {:?}", addr, account); + println!("inserting at address {}, this account {:?}", addr, account); self.user_data - .user_private_accounts - .entry(addr) - .and_modify(|(_, acc)| *acc = account); - } - - pub(crate) fn insert_account_data(&mut self, acc_data: PersistentAccountData) { - match acc_data { - PersistentAccountData::Public(acc_data) => { - self.user_data - .pub_account_signing_keys - .insert(acc_data.address, acc_data.pub_sign_key); - } - PersistentAccountData::Private(acc_data) => { - self.user_data - .user_private_accounts - .insert(acc_data.address, (acc_data.key_chain, acc_data.account)); - } - } + .private_key_tree + .addr_map + .get(&addr) + .and_then(|chain_index| { + Some( + self.user_data + .private_key_tree + .key_map + .entry(chain_index.clone()) + .and_modify(|data| data.value.1 = account), + ) + }); } } @@ -180,6 +203,6 @@ mod tests { fn test_new_initializes_correctly() { let config = create_sample_wallet_config(); - let _ = WalletChainStore::new(config.clone()).unwrap(); + let _ = WalletChainStore::new(config.clone(), vec![], "test_pass".to_string()).unwrap(); } } diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 3ff4470..7574a80 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -1,6 +1,7 @@ use anyhow::Result; use base58::ToBase58; use clap::Subcommand; +use key_protocol::key_management::key_tree::chain_index::ChainIndex; use nssa::{Account, Address, program::Program}; use serde::Serialize; @@ -89,9 +90,15 @@ pub enum AccountSubcommand { #[derive(Subcommand, Debug, Clone)] pub enum NewSubcommand { ///Register new public account - Public {}, + Public { + #[arg(long)] + cci: ChainIndex + }, ///Register new private account - Private {}, + Private { + #[arg(long)] + cci: ChainIndex + }, } impl WalletSubcommand for NewSubcommand { @@ -100,8 +107,8 @@ impl WalletSubcommand for NewSubcommand { wallet_core: &mut WalletCore, ) -> Result { match self { - NewSubcommand::Public {} => { - let addr = wallet_core.create_new_account_public(); + NewSubcommand::Public { cci } => { + let addr = wallet_core.create_new_account_public(cci); println!("Generated new account with addr Public/{addr}"); @@ -111,8 +118,8 @@ impl WalletSubcommand for NewSubcommand { Ok(SubcommandReturnValue::RegisterAccount { addr }) } - NewSubcommand::Private {} => { - let addr = wallet_core.create_new_account_private(); + NewSubcommand::Private { cci } => { + let addr = wallet_core.create_new_account_private(cci); let (key, _) = wallet_core .storage @@ -270,7 +277,8 @@ impl WalletSubcommand for AccountSubcommand { if !wallet_core .storage .user_data - .user_private_accounts + .private_key_tree + .addr_map .is_empty() { parse_block_range( diff --git a/wallet/src/config.rs b/wallet/src/config.rs index bf7f5d2..8fdc450 100644 --- a/wallet/src/config.rs +++ b/wallet/src/config.rs @@ -1,4 +1,9 @@ -use key_protocol::key_management::KeyChain; +use key_protocol::key_management::{ + KeyChain, + key_tree::{ + chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic, + }, +}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -10,7 +15,8 @@ pub struct InitialAccountDataPublic { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PersistentAccountDataPublic { pub address: nssa::Address, - pub pub_sign_key: nssa::PrivateKey, + pub chain_index: ChainIndex, + pub data: ChildKeysPublic, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -23,8 +29,8 @@ pub struct InitialAccountDataPrivate { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PersistentAccountDataPrivate { pub address: nssa::Address, - pub account: nssa_core::account::Account, - pub key_chain: KeyChain, + pub chain_index: ChainIndex, + pub data: ChildKeysPrivate, } //Big difference in enum variants sizes @@ -48,6 +54,7 @@ pub enum PersistentAccountData { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PersistentStorage { pub accounts: Vec, + pub password: String, pub last_synced_block: u64, } diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index f959d17..b3e225d 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -104,6 +104,7 @@ pub async fn fetch_persistent_storage() -> Result { Err(err) => match err.kind() { std::io::ErrorKind::NotFound => Ok(PersistentStorage { accounts: vec![], + password: "default".to_string(), last_synced_block: 0, }), _ => { @@ -120,29 +121,29 @@ pub fn produce_data_for_storage( ) -> PersistentStorage { let mut vec_for_storage = vec![]; - for (addr, key) in &user_data.pub_account_signing_keys { - vec_for_storage.push( - PersistentAccountDataPublic { + 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, - pub_sign_key: key.clone(), - } - .into(), - ); + chain_index: key.clone(), + data: data.clone(), + }.into()); + } } - for (addr, (key, acc)) in &user_data.user_private_accounts { - vec_for_storage.push( - PersistentAccountDataPrivate { + 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, - account: acc.clone(), - key_chain: key.clone(), - } - .into(), - ); + chain_index: key.clone(), + data: data.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 dd6dd32..ba2570a 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -10,6 +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 log::info; use nssa::{ Account, Address, privacy_preserving_transaction::message::EncryptedAccountData, @@ -59,15 +60,13 @@ impl WalletCore { let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?); let tx_poller = TxPoller::new(config.clone(), client.clone()); - let mut storage = WalletChainStore::new(config)?; - let PersistentStorage { accounts: persistent_accounts, + password, last_synced_block, } = fetch_persistent_storage().await?; - for pers_acc_data in persistent_accounts { - storage.insert_account_data(pers_acc_data); - } + + let storage = WalletChainStore::new(config, persistent_accounts, password)?; Ok(Self { storage, @@ -107,16 +106,16 @@ impl WalletCore { Ok(config_path) } - pub fn create_new_account_public(&mut self) -> Address { + pub fn create_new_account_public(&mut self, chain_index: ChainIndex) -> Address { self.storage .user_data - .generate_new_public_transaction_private_key() + .generate_new_public_transaction_private_key(chain_index) } - pub fn create_new_account_private(&mut self) -> Address { + pub fn create_new_account_private(&mut self, chain_index: ChainIndex) -> Address { self.storage .user_data - .generate_new_privacy_preserving_transaction_key_chain() + .generate_new_privacy_preserving_transaction_key_chain(chain_index) } ///Get account balance @@ -146,13 +145,12 @@ impl WalletCore { pub fn get_account_private(&self, addr: &Address) -> Option { self.storage .user_data - .user_private_accounts - .get(addr) + .get_private_account(addr) .map(|value| value.1.clone()) } pub fn get_private_account_commitment(&self, addr: &Address) -> Option { - let (keys, account) = self.storage.user_data.user_private_accounts.get(addr)?; + let (keys, account) = self.storage.user_data.get_private_account(addr)?; Some(Commitment::new(&keys.nullifer_public_key, account)) } From ec149d3227c718c0db80da17a0b8b9adf8e0ac06 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Tue, 11 Nov 2025 12:15:20 +0200 Subject: [PATCH 06/21] 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 { From fcb3993a4780458a455615448b551cad58efb3f4 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Tue, 11 Nov 2025 15:41:32 +0200 Subject: [PATCH 07/21] fix: merge fix --- integration_tests/Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 11d7d07..58be5a5 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -42,6 +42,3 @@ path = "../key_protocol" [dependencies.nssa] path = "../nssa" features = ["no_docker"] - -[dependencies.key_protocol] -path = "../key_protocol" From 463942df8034072f475aa0a5b8208e00644fd0e4 Mon Sep 17 00:00:00 2001 From: Pravdyvy <46261001+Pravdyvy@users.noreply.github.com> Date: Wed, 26 Nov 2025 07:07:58 +0200 Subject: [PATCH 08/21] Apply suggestions from code review 1 Co-authored-by: Daniil Polyakov --- integration_tests/src/lib.rs | 2 +- .../key_management/key_tree/chain_index.rs | 28 +++++---------- .../key_management/key_tree/keys_private.rs | 8 ++--- .../key_management/key_tree/keys_public.rs | 8 ++--- .../src/key_management/key_tree/mod.rs | 16 +++------ .../src/key_management/key_tree/traits.rs | 2 +- key_protocol/src/key_management/mod.rs | 4 +-- .../src/key_management/secret_holders.rs | 2 +- key_protocol/src/key_protocol_core/mod.rs | 4 +-- wallet/src/chain_storage/mod.rs | 36 +++++++------------ wallet/src/lib.rs | 6 ++-- 11 files changed, 45 insertions(+), 71 deletions(-) diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 75e39b8..df39ccd 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -56,7 +56,7 @@ 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?; + wallet::execute_setup("test_pass".to_owned()).await?; let home_dir_sequencer = home_dir.join("sequencer"); 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 e22abf0..8da2fd6 100644 --- a/key_protocol/src/key_management/key_tree/chain_index.rs +++ b/key_protocol/src/key_management/key_tree/chain_index.rs @@ -17,25 +17,15 @@ impl FromStr for ChainIndex { type Err = ChainIndexError; fn from_str(s: &str) -> Result { - if !s.starts_with("/") { - return Err(ChainIndexError::NoRootFound); + if !s.starts_with('/') { + return Err(ChainIndexError:NoRootFound); } - - 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)) + + s + .split("/") + .map(u32::from_str) + .collect() + .map_err(Into::into) } } @@ -68,7 +58,7 @@ impl ChainIndex { ChainIndex(chain) } - pub fn n_th_child(&self, child_id: u32) -> ChainIndex { + pub fn nth_child(&self, child_id: u32) -> ChainIndex { let mut chain = self.0.clone(); chain.push(child_id); diff --git a/key_protocol/src/key_management/key_tree/keys_private.rs b/key_protocol/src/key_management/key_tree/keys_private.rs index 84b44fa..2addd12 100644 --- a/key_protocol/src/key_management/key_tree/keys_private.rs +++ b/key_protocol/src/key_management/key_tree/keys_private.rs @@ -12,7 +12,7 @@ use crate::key_management::{ pub struct ChildKeysPrivate { pub value: (KeyChain, nssa::Account), pub ccc: [u8; 32], - ///Can be None if root + /// Can be [`None`] if root pub cci: Option, } @@ -49,7 +49,7 @@ impl KeyNode for ChildKeysPrivate { } } - fn n_th_child(&self, cci: u32) -> Self { + fn nth_child(&self, cci: u32) -> Self { let parent_pt = Scalar::from_repr( self.value .0 @@ -109,8 +109,8 @@ impl KeyNode for ChildKeysPrivate { &self.ccc } - fn child_index(&self) -> &Option { - &self.cci + fn child_index(&self) -> Option { + self.cci } fn address(&self) -> nssa::Address { diff --git a/key_protocol/src/key_management/key_tree/keys_public.rs b/key_protocol/src/key_management/key_tree/keys_public.rs index 7ca6247..2f694af 100644 --- a/key_protocol/src/key_management/key_tree/keys_public.rs +++ b/key_protocol/src/key_management/key_tree/keys_public.rs @@ -7,7 +7,7 @@ pub struct ChildKeysPublic { pub csk: nssa::PrivateKey, pub cpk: nssa::PublicKey, pub ccc: [u8; 32], - ///Can be None if root + /// Can be [`None`] if root pub cci: Option, } @@ -27,7 +27,7 @@ impl KeyNode for ChildKeysPublic { } } - fn n_th_child(&self, cci: u32) -> Self { + fn nth_child(&self, cci: u32) -> Self { let mut hash_input = vec![]; hash_input.extend_from_slice(self.csk.value()); hash_input.extend_from_slice(&cci.to_le_bytes()); @@ -50,8 +50,8 @@ impl KeyNode for ChildKeysPublic { &self.ccc } - fn child_index(&self) -> &Option { - &self.cci + fn child_index(&self) -> Option { + self.cci } fn address(&self) -> nssa::Address { diff --git a/key_protocol/src/key_management/key_tree/mod.rs b/key_protocol/src/key_management/key_tree/mod.rs index dcc027b..73f3491 100644 --- a/key_protocol/src/key_management/key_tree/mod.rs +++ b/key_protocol/src/key_management/key_tree/mod.rs @@ -31,11 +31,8 @@ impl KeyTree { let root_keys = Node::root(seed_fit); let address = root_keys.address(); - let mut key_map = BTreeMap::new(); - let mut addr_map = HashMap::new(); - - key_map.insert(ChainIndex::root(), root_keys); - addr_map.insert(address, ChainIndex::root()); + let key_map = BTreeMap::from_iter([(ChainIndex::root(), root_keys)]); + let addr_map = HashMap::from_iter([(address, ChainIndex::root())]); Self { key_map, addr_map } } @@ -60,7 +57,8 @@ impl KeyTree { let leftmost_child = parent_id.n_th_child(u32::MIN); if !self.key_map.contains_key(&leftmost_child) { - Some(0) + return Some(0) + } } else { let mut right = u32::MAX - 1; let mut left_border = u32::MIN; @@ -93,11 +91,7 @@ impl KeyTree { } pub fn generate_new_node(&mut self, parent_cci: ChainIndex) -> Option { - if !self.key_map.contains_key(&parent_cci) { - return None; - } - - let father_keys = self.key_map.get(&parent_cci).unwrap(); + let father_keys = self.key_map.get(&parent_cci)?; let next_child_id = self.find_next_last_child_of_id(&parent_cci).unwrap(); let next_cci = parent_cci.n_th_child(next_child_id); diff --git a/key_protocol/src/key_management/key_tree/traits.rs b/key_protocol/src/key_management/key_tree/traits.rs index 662481a..0eb619d 100644 --- a/key_protocol/src/key_management/key_tree/traits.rs +++ b/key_protocol/src/key_management/key_tree/traits.rs @@ -1,7 +1,7 @@ pub trait KeyNode { fn root(seed: [u8; 64]) -> Self; - fn n_th_child(&self, cci: u32) -> Self; + fn nth_child(&self, cci: u32) -> Self; fn chain_code(&self) -> &[u8; 32]; diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index 8a58d4a..bbccd12 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -41,8 +41,8 @@ impl KeyChain { } pub fn new_mnemonic(passphrase: String) -> Self { - //Currently dropping SeedHolder at the end of initialization. - //Now entirely sure if we need it in the future. + // Currently dropping SeedHolder at the end of initialization. + // Not entirely sure if we need it in the future. let seed_holder = SeedHolder::new_mnemonic(passphrase); let secret_spending_key = seed_holder.produce_top_secret_key_holder(); diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index e60a9f5..89808b6 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -45,7 +45,7 @@ impl SeedHolder { } pub fn new_mnemonic(passphrase: String) -> Self { - //Enthropy bytes must be deterministic as well + // Enthropy bytes must be deterministic as well let enthopy_bytes: [u8; 32] = [0; 32]; let mnemonic = Mnemonic::from_entropy(&enthopy_bytes).unwrap(); diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 6ea75db..81ae156 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -14,9 +14,9 @@ pub type PublicKey = AffinePoint; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NSSAUserData { - ///Default public accounts + /// Default public accounts pub default_pub_account_signing_keys: HashMap, - ///Default private accounts + /// Default private accounts pub default_user_private_accounts: HashMap, /// Tree of public keys diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index 5215d99..cfc0ff0 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -126,17 +126,13 @@ impl WalletChainStore { addr: nssa::Address, account: nssa_core::account::Account, ) { - println!("inserting at address {}, this account {:?}", addr, account); + println!("inserting at address {addr}, this account {account:?}"); - if self - .user_data - .default_user_private_accounts - .contains_key(&addr) - { - self.user_data + let entry = self.user_data .default_user_private_accounts .entry(addr) .and_modify(|data| data.1 = account); + if matches!(entry, Entry::Vacant(_)) { } else { self.user_data .private_key_tree @@ -270,27 +266,21 @@ 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 { + + vec![ + PersistentAccountData::Public(PersistentAccountDataPublic { + address: public_data.address(), + chain_index: ChainIndex::root(), + data: public_data, + }), + PersistentAccountData::Private(PersistentAccountDataPrivate { address: private_data.address(), chain_index: ChainIndex::root(), data: private_data, - }, - )); - - accs + }) + ] } #[test] diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 3b494a9..75d06df 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -249,7 +249,7 @@ pub enum Command { Config(ConfigSubcommand), } -///Represents CLI command for a wallet with setup included +/// Represents CLI command for a wallet with setup included #[derive(Debug, Subcommand, Clone)] #[clap(about)] pub enum OverCommand { @@ -410,12 +410,12 @@ pub async fn parse_block_range( } } - for (_, keys_node) in wallet_core + for keys_node in wallet_core .storage .user_data .private_key_tree .key_map - .iter() + .values() { let acc_addr = keys_node.address(); let key_chain = &keys_node.value.0; From 0d11c3688e58d64ba527a8ab5421064eec481b8f Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 26 Nov 2025 07:18:25 +0200 Subject: [PATCH 09/21] fix: suggestions hotfix --- .../key_management/key_tree/chain_index.rs | 26 ++++++--- .../key_management/key_tree/keys_private.rs | 2 +- .../key_management/key_tree/keys_public.rs | 2 +- .../src/key_management/key_tree/mod.rs | 53 +++++++++---------- .../src/key_management/key_tree/traits.rs | 2 +- wallet/src/chain_storage/mod.rs | 16 +++--- 6 files changed, 56 insertions(+), 45 deletions(-) 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 8da2fd6..1ab431a 100644 --- a/key_protocol/src/key_management/key_tree/chain_index.rs +++ b/key_protocol/src/key_management/key_tree/chain_index.rs @@ -18,14 +18,24 @@ impl FromStr for ChainIndex { fn from_str(s: &str) -> Result { if !s.starts_with('/') { - return Err(ChainIndexError:NoRootFound); + return Err(ChainIndexError::NoRootFound); } - - s - .split("/") - .map(u32::from_str) - .collect() - .map_err(Into::into) + + 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)) } } @@ -96,7 +106,7 @@ mod tests { #[test] fn test_chain_id_child_correct() { let chain_id = ChainIndex::from_str("/257").unwrap(); - let child = chain_id.n_th_child(3); + let child = chain_id.nth_child(3); assert_eq!(child, ChainIndex::from_str("/257/3").unwrap()); } diff --git a/key_protocol/src/key_management/key_tree/keys_private.rs b/key_protocol/src/key_management/key_tree/keys_private.rs index 2addd12..c29f7d6 100644 --- a/key_protocol/src/key_management/key_tree/keys_private.rs +++ b/key_protocol/src/key_management/key_tree/keys_private.rs @@ -137,7 +137,7 @@ mod tests { #[test] fn test_keys_deterministic_generation() { let root_keys = ChildKeysPrivate::root([42; 64]); - let child_keys = root_keys.n_th_child(5); + let child_keys = root_keys.nth_child(5); assert_eq!(root_keys.cci, None); assert_eq!(child_keys.cci, Some(5)); diff --git a/key_protocol/src/key_management/key_tree/keys_public.rs b/key_protocol/src/key_management/key_tree/keys_public.rs index 2f694af..8d31720 100644 --- a/key_protocol/src/key_management/key_tree/keys_public.rs +++ b/key_protocol/src/key_management/key_tree/keys_public.rs @@ -72,7 +72,7 @@ mod tests { #[test] fn test_keys_deterministic_generation() { let root_keys = ChildKeysPublic::root([42; 64]); - let child_keys = root_keys.n_th_child(5); + let child_keys = root_keys.nth_child(5); assert_eq!(root_keys.cci, None); assert_eq!(child_keys.cci, Some(5)); diff --git a/key_protocol/src/key_management/key_tree/mod.rs b/key_protocol/src/key_management/key_tree/mod.rs index 73f3491..e478ce5 100644 --- a/key_protocol/src/key_management/key_tree/mod.rs +++ b/key_protocol/src/key_management/key_tree/mod.rs @@ -54,37 +54,36 @@ impl KeyTree { return None; } - let leftmost_child = parent_id.n_th_child(u32::MIN); + let leftmost_child = parent_id.nth_child(u32::MIN); if !self.key_map.contains_key(&leftmost_child) { - return Some(0) + return Some(0); } - } else { - let mut right = u32::MAX - 1; - let mut left_border = u32::MIN; - let mut right_border = u32::MAX; - loop { - let rightmost_child = parent_id.n_th_child(right); + let mut right = u32::MAX - 1; + let mut left_border = u32::MIN; + let mut right_border = u32::MAX; - let rightmost_ref = self.key_map.get(&rightmost_child); - let rightmost_ref_next = self.key_map.get(&rightmost_child.next_in_line()); + loop { + let rightmost_child = parent_id.nth_child(right); - match (&rightmost_ref, &rightmost_ref_next) { - (Some(_), Some(_)) => { - left_border = right; - right = (right + right_border) / 2; - } - (Some(_), None) => { - break Some(right + 1); - } - (None, None) => { - right_border = right; - right = (left_border + right) / 2; - } - (None, Some(_)) => { - unreachable!(); - } + let rightmost_ref = self.key_map.get(&rightmost_child); + let rightmost_ref_next = self.key_map.get(&rightmost_child.next_in_line()); + + match (&rightmost_ref, &rightmost_ref_next) { + (Some(_), Some(_)) => { + left_border = right; + right = (right + right_border) / 2; + } + (Some(_), None) => { + break Some(right + 1); + } + (None, None) => { + right_border = right; + right = (left_border + right) / 2; + } + (None, Some(_)) => { + unreachable!(); } } } @@ -93,9 +92,9 @@ impl KeyTree { pub fn generate_new_node(&mut self, parent_cci: ChainIndex) -> Option { let father_keys = self.key_map.get(&parent_cci)?; let next_child_id = self.find_next_last_child_of_id(&parent_cci).unwrap(); - let next_cci = parent_cci.n_th_child(next_child_id); + let next_cci = parent_cci.nth_child(next_child_id); - let child_keys = father_keys.n_th_child(next_child_id); + let child_keys = father_keys.nth_child(next_child_id); let address = child_keys.address(); diff --git a/key_protocol/src/key_management/key_tree/traits.rs b/key_protocol/src/key_management/key_tree/traits.rs index 0eb619d..d8b2de9 100644 --- a/key_protocol/src/key_management/key_tree/traits.rs +++ b/key_protocol/src/key_management/key_tree/traits.rs @@ -5,7 +5,7 @@ pub trait KeyNode { fn chain_code(&self) -> &[u8; 32]; - fn child_index(&self) -> &Option; + fn child_index(&self) -> Option; fn address(&self) -> nssa::Address; } diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index cfc0ff0..c7d1700 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, hash_map::Entry}; use anyhow::Result; use key_protocol::{ @@ -128,10 +128,12 @@ impl WalletChainStore { ) { println!("inserting at address {addr}, this account {account:?}"); - let entry = self.user_data - .default_user_private_accounts - .entry(addr) - .and_modify(|data| data.1 = account); + let entry = self + .user_data + .default_user_private_accounts + .entry(addr) + .and_modify(|data| data.1 = account.clone()); + if matches!(entry, Entry::Vacant(_)) { } else { self.user_data @@ -268,7 +270,7 @@ mod tests { fn create_sample_persistent_accounts() -> Vec { let public_data = ChildKeysPublic::root([42; 64]); let private_data = ChildKeysPrivate::root([47; 64]); - + vec![ PersistentAccountData::Public(PersistentAccountDataPublic { address: public_data.address(), @@ -279,7 +281,7 @@ mod tests { address: private_data.address(), chain_index: ChainIndex::root(), data: private_data, - }) + }), ] } From fc531021fb4f5f34b0d5abc3b091775095516499 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 26 Nov 2025 07:32:35 +0200 Subject: [PATCH 10/21] fix: comments fix 1 --- wallet/src/chain_storage/mod.rs | 4 ++-- wallet/src/cli/account.rs | 16 +++++++++------- wallet/src/helperfunctions.rs | 2 +- wallet/src/lib.rs | 11 +++++------ 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index c7d1700..b5b1c9f 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -36,7 +36,7 @@ impl WalletChainStore { _ => false, }) .cloned() - .unwrap(); + .expect("Malformed persistent account data, must have public root"); let private_root = persistent_accounts .iter() @@ -45,7 +45,7 @@ impl WalletChainStore { _ => false, }) .cloned() - .unwrap(); + .expect("Malformed persistent account data, must have private root"); let mut public_tree = KeyTreePublic::new_from_root(match public_root { PersistentAccountData::Public(data) => data.data, diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index b9a7699..abd7a4f 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -92,11 +92,13 @@ pub enum NewSubcommand { ///Register new public account Public { #[arg(long)] + /// Chain index of a parent node cci: ChainIndex, }, ///Register new private account Private { #[arg(long)] + /// Chain index of a parent node cci: ChainIndex, }, } @@ -274,13 +276,19 @@ impl WalletSubcommand for AccountSubcommand { .await? .last_block; - if !wallet_core + if wallet_core .storage .user_data .private_key_tree .addr_map .is_empty() { + wallet_core.last_synced_block = curr_last_block; + + let path = wallet_core.store_persistent_data().await?; + + println!("Stored persistent data at {path:#?}"); + } else { parse_block_range( last_synced_block + 1, curr_last_block, @@ -288,12 +296,6 @@ impl WalletSubcommand for AccountSubcommand { wallet_core, ) .await?; - } else { - wallet_core.last_synced_block = curr_last_block; - - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent data at {path:#?}"); } Ok(SubcommandReturnValue::SyncedToBlock(curr_last_block)) diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 0d08348..f876777 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -91,7 +91,7 @@ pub async fn fetch_config() -> Result { /// Fetch data stored at home /// -/// If file not present, it is considered as empty list of persistent accounts +/// File must be created through setup beforehand. pub async fn fetch_persistent_storage() -> Result { let home = get_home()?; let accs_path = home.join("storage.json"); diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 75d06df..8b6b71b 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -249,12 +249,14 @@ pub enum Command { Config(ConfigSubcommand), } -/// Represents CLI command for a wallet with setup included +/// Represents overarching CLI command for a wallet with setup included #[derive(Debug, Subcommand, Clone)] #[clap(about)] pub enum OverCommand { + /// Represents CLI command for a wallet #[command(subcommand)] Command(Command), + /// Setup of a storage. Initializes rots for public and private trees from `password`. Setup { #[arg(short, long)] password: String, @@ -369,11 +371,8 @@ 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 - .default_user_private_accounts - .iter() + for (acc_addr, (key_chain, _)) in + &wallet_core.storage.user_data.default_user_private_accounts { let view_tag = EncryptedAccountData::compute_view_tag( key_chain.nullifer_public_key.clone(), From 77570c48e9962d93d903fbbf881c869ef31e12c7 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 26 Nov 2025 14:27:09 +0200 Subject: [PATCH 11/21] fix: suggestions 2 --- .../key_management/key_tree/chain_index.rs | 25 +++++++++++++++++- .../key_management/key_tree/keys_private.rs | 26 ++++++++++++++----- 2 files changed, 43 insertions(+), 8 deletions(-) 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 1ab431a..9d641b3 100644 --- a/key_protocol/src/key_management/key_tree/chain_index.rs +++ b/key_protocol/src/key_management/key_tree/chain_index.rs @@ -49,9 +49,15 @@ impl Display for ChainIndex { } } +impl Default for ChainIndex { + fn default() -> Self { + ChainIndex::from_str("/").expect("Root parsing failure") + } +} + impl ChainIndex { pub fn root() -> Self { - ChainIndex::from_str("/").unwrap() + ChainIndex::default() } pub fn chain(&self) -> &[u32] { @@ -95,6 +101,23 @@ mod tests { assert_eq!(chain_id.chain(), &[257]); } + #[test] + fn test_chain_id_deser_failure_no_root() { + let chain_index_error = ChainIndex::from_str("257").err().unwrap(); + + assert!(matches!(chain_index_error, ChainIndexError::NoRootFound)); + } + + #[test] + fn test_chain_id_deser_failure_int_parsing_failure() { + let chain_index_error = ChainIndex::from_str("/hello").err().unwrap(); + + assert!(matches!( + chain_index_error, + ChainIndexError::ParseIntError(_) + )); + } + #[test] fn test_chain_id_next_in_line_correct() { let chain_id = ChainIndex::from_str("/257").unwrap(); diff --git a/key_protocol/src/key_management/key_tree/keys_private.rs b/key_protocol/src/key_management/key_tree/keys_private.rs index c29f7d6..c13a6b3 100644 --- a/key_protocol/src/key_management/key_tree/keys_private.rs +++ b/key_protocol/src/key_management/key_tree/keys_private.rs @@ -20,8 +20,14 @@ impl KeyNode for ChildKeysPrivate { fn root(seed: [u8; 64]) -> Self { let hash_value = hmac_sha512::HMAC::mac(seed, "NSSA_master_priv"); - let ssk = SecretSpendingKey(*hash_value.first_chunk::<32>().unwrap()); - let ccc = *hash_value.last_chunk::<32>().unwrap(); + let ssk = SecretSpendingKey( + *hash_value + .first_chunk::<32>() + .expect("hash_value is 64 bytes, must be safe to get first 32"), + ); + let ccc = *hash_value + .last_chunk::<32>() + .expect("hash_value is 64 bytes, must be safe to get last 32"); let nsk = ssk.generate_nullifier_secret_key(); let isk = ssk.generate_incoming_viewing_secret_key(); @@ -57,9 +63,9 @@ impl KeyNode for ChildKeysPrivate { .outgoing_viewing_secret_key .into(), ) - .unwrap() + .expect("Key generated as scalar, must be valid representation") + Scalar::from_repr(self.value.0.private_key_holder.nullifier_secret_key.into()) - .unwrap() + .expect("Key generated as scalar, must be valid representation") * Scalar::from_repr( self.value .0 @@ -67,7 +73,7 @@ impl KeyNode for ChildKeysPrivate { .incoming_viewing_secret_key .into(), ) - .unwrap(); + .expect("Key generated as scalar, must be valid representation"); let mut input = vec![]; input.extend_from_slice(b"NSSA_seed_priv"); @@ -76,8 +82,14 @@ impl KeyNode for ChildKeysPrivate { let hash_value = hmac_sha512::HMAC::mac(input, self.ccc); - let ssk = SecretSpendingKey(*hash_value.first_chunk::<32>().unwrap()); - let ccc = *hash_value.last_chunk::<32>().unwrap(); + let ssk = SecretSpendingKey( + *hash_value + .first_chunk::<32>() + .expect("hash_value is 64 bytes, must be safe to get first 32"), + ); + let ccc = *hash_value + .last_chunk::<32>() + .expect("hash_value is 64 bytes, must be safe to get last 32"); let nsk = ssk.generate_nullifier_secret_key(); let isk = ssk.generate_incoming_viewing_secret_key(); From 30f19b245d0c8cf3de93b41aed83aec2dfdb981f Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 26 Nov 2025 14:53:26 +0200 Subject: [PATCH 12/21] fix: suggestions fix 3 --- .../key_management/key_tree/keys_public.rs | 11 +++++-- .../src/key_management/key_tree/mod.rs | 33 ++++++++++--------- .../src/key_management/key_tree/traits.rs | 3 ++ .../src/key_management/secret_holders.rs | 11 ++++--- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/key_protocol/src/key_management/key_tree/keys_public.rs b/key_protocol/src/key_management/key_tree/keys_public.rs index 8d31720..a7bfc4f 100644 --- a/key_protocol/src/key_management/key_tree/keys_public.rs +++ b/key_protocol/src/key_management/key_tree/keys_public.rs @@ -34,8 +34,15 @@ impl KeyNode for ChildKeysPublic { let hash_value = hmac_sha512::HMAC::mac(&hash_input, self.ccc); - let csk = nssa::PrivateKey::try_new(*hash_value.first_chunk::<32>().unwrap()).unwrap(); - let ccc = *hash_value.last_chunk::<32>().unwrap(); + let csk = nssa::PrivateKey::try_new( + *hash_value + .first_chunk::<32>() + .expect("hash_value is 64 bytes, must be safe to get first 32"), + ) + .unwrap(); + let ccc = *hash_value + .last_chunk::<32>() + .expect("hash_value is 64 bytes, must be safe to get last 32"); let cpk = nssa::PublicKey::new_from_private_key(&csk); Self { diff --git a/key_protocol/src/key_management/key_tree/mod.rs b/key_protocol/src/key_management/key_tree/mod.rs index e478ce5..5a32a65 100644 --- a/key_protocol/src/key_management/key_tree/mod.rs +++ b/key_protocol/src/key_management/key_tree/mod.rs @@ -16,19 +16,23 @@ pub mod keys_public; pub mod traits; #[derive(Debug, Serialize, Deserialize, Clone)] -pub struct KeyTree { - pub key_map: BTreeMap, +pub struct KeyTree { + pub key_map: BTreeMap, pub addr_map: HashMap, } pub type KeyTreePublic = KeyTree; pub type KeyTreePrivate = KeyTree; -impl KeyTree { +impl KeyTree { pub fn new(seed: &SeedHolder) -> Self { - let seed_fit: [u8; 64] = seed.seed.clone().try_into().unwrap(); + let seed_fit: [u8; 64] = seed + .seed + .clone() + .try_into() + .expect("SeedHolder seed is 64 bytes long"); - let root_keys = Node::root(seed_fit); + let root_keys = N::root(seed_fit); let address = root_keys.address(); let key_map = BTreeMap::from_iter([(ChainIndex::root(), root_keys)]); @@ -37,12 +41,9 @@ 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); + pub fn new_from_root(root: N) -> Self { + let addr_map = HashMap::from_iter([(root.address(), ChainIndex::root())]); + let key_map = BTreeMap::from_iter([(ChainIndex::root(), root)]); Self { key_map, addr_map } } @@ -91,7 +92,9 @@ impl KeyTree { pub fn generate_new_node(&mut self, parent_cci: ChainIndex) -> Option { let father_keys = self.key_map.get(&parent_cci)?; - let next_child_id = self.find_next_last_child_of_id(&parent_cci).unwrap(); + let next_child_id = self + .find_next_last_child_of_id(&parent_cci) + .expect("Can be None only if parent is not present"); let next_cci = parent_cci.nth_child(next_child_id); let child_keys = father_keys.nth_child(next_child_id); @@ -104,19 +107,19 @@ impl KeyTree { Some(address) } - pub fn get_node(&self, addr: nssa::Address) -> Option<&Node> { + pub fn get_node(&self, addr: nssa::Address) -> Option<&N> { self.addr_map .get(&addr) .and_then(|chain_id| self.key_map.get(chain_id)) } - pub fn get_node_mut(&mut self, addr: nssa::Address) -> Option<&mut Node> { + pub fn get_node_mut(&mut self, addr: nssa::Address) -> Option<&mut N> { self.addr_map .get(&addr) .and_then(|chain_id| self.key_map.get_mut(chain_id)) } - pub fn insert(&mut self, addr: nssa::Address, chain_index: ChainIndex, node: Node) { + pub fn insert(&mut self, addr: nssa::Address, chain_index: ChainIndex, node: N) { self.addr_map.insert(addr, chain_index.clone()); self.key_map.insert(chain_index, node); } diff --git a/key_protocol/src/key_management/key_tree/traits.rs b/key_protocol/src/key_management/key_tree/traits.rs index d8b2de9..47b3a12 100644 --- a/key_protocol/src/key_management/key_tree/traits.rs +++ b/key_protocol/src/key_management/key_tree/traits.rs @@ -1,6 +1,9 @@ +/// Trait, that reperesents a Node in hierarchical key tree pub trait KeyNode { + /// Tree root node fn root(seed: [u8; 64]) -> Self; + /// `cci`'s child of node fn nth_child(&self, cci: u32) -> Self; fn chain_code(&self) -> &[u8; 32]; diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 89808b6..d9dd9fa 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -8,6 +8,8 @@ use rand::{RngCore, rngs::OsRng}; use serde::{Deserialize, Serialize}; use sha2::{Digest, digest::FixedOutput}; +const NSSA_ENTROPY_BYTES: [u8; 32] = [0; 32]; + #[derive(Debug)] ///Seed holder. Non-clonable to ensure that different holders use different seeds. /// Produces `TopSecretKeyHolder` objects. @@ -36,7 +38,8 @@ impl SeedHolder { let mut enthopy_bytes: [u8; 32] = [0; 32]; OsRng.fill_bytes(&mut enthopy_bytes); - let mnemonic = Mnemonic::from_entropy(&enthopy_bytes).unwrap(); + let mnemonic = Mnemonic::from_entropy(&enthopy_bytes) + .expect("Enthropy must be a multiple of 32 bytes"); let seed_wide = mnemonic.to_seed("mnemonic"); Self { @@ -45,10 +48,8 @@ impl SeedHolder { } pub fn new_mnemonic(passphrase: String) -> Self { - // Enthropy bytes must be deterministic as well - let enthopy_bytes: [u8; 32] = [0; 32]; - - let mnemonic = Mnemonic::from_entropy(&enthopy_bytes).unwrap(); + let mnemonic = Mnemonic::from_entropy(&NSSA_ENTROPY_BYTES) + .expect("Enthropy must be a multiple of 32 bytes"); let seed_wide = mnemonic.to_seed(passphrase); Self { From 64e2fb73a89b482b9e61231300cf28b2cda88de0 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Fri, 28 Nov 2025 08:48:11 +0200 Subject: [PATCH 13/21] fix: merge update --- integration_tests/src/test_suite_map.rs | 2 +- key_protocol/src/key_management/key_tree/chain_index.rs | 7 +++++-- wallet/src/chain_storage/mod.rs | 1 - 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs index 7706b1b..1cf71ad 100644 --- a/integration_tests/src/test_suite_map.rs +++ b/integration_tests/src/test_suite_map.rs @@ -1583,7 +1583,7 @@ pub fn prepare_function_map() -> HashMap { #[nssa_integration_test] pub async fn test_pinata_private_receiver_new_account() { - info!("########## test_pinata_private_receiver ##########"); + info!("########## test_pinata_private_receiver_new_account ##########"); let pinata_account_id = PINATA_BASE58; let pinata_prize = 150; let solution = 989106; 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 9ba9986..e46fc0f 100644 --- a/key_protocol/src/key_management/key_tree/chain_index.rs +++ b/key_protocol/src/key_management/key_tree/chain_index.rs @@ -42,10 +42,13 @@ impl FromStr for ChainIndex { 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)] { + for cci in &self.0[..(self.0.len().saturating_sub(1))] { write!(f, "{cci}/")?; } - write!(f, "{}", self.0.last().unwrap()) + if let Some(last) = self.0.last() { + write!(f, "{}", last)?; + } + Ok(()) } } diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index 00378f1..14e931a 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -136,7 +136,6 @@ impl WalletChainStore { .and_modify(|data| data.1 = account.clone()); if matches!(entry, Entry::Vacant(_)) { - } else { self.user_data .private_key_tree .account_id_map From c7ce31d00c4785ed68d6732adc36ba4a13c680d0 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Fri, 28 Nov 2025 09:49:05 +0200 Subject: [PATCH 14/21] fix: fmt --- wallet/src/cli/account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index ee71f82..6e831d1 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -1,8 +1,8 @@ use anyhow::Result; use base58::ToBase58; use clap::Subcommand; -use key_protocol::key_management::key_tree::chain_index::ChainIndex; use itertools::Itertools as _; +use key_protocol::key_management::key_tree::chain_index::ChainIndex; use nssa::{Account, AccountId, program::Program}; use serde::Serialize; From c7bcd20a388bc20a09b26be67a0d12f5d91783fa Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Fri, 28 Nov 2025 11:37:49 -0300 Subject: [PATCH 15/21] add init function to the token program --- nssa/program_methods/guest/src/bin/token.rs | 137 ++++++++++++++++++-- 1 file changed, 126 insertions(+), 11 deletions(-) diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs index e5680be..821438a 100644 --- a/nssa/program_methods/guest/src/bin/token.rs +++ b/nssa/program_methods/guest/src/bin/token.rs @@ -3,7 +3,7 @@ use nssa_core::{ program::{ProgramInput, read_nssa_inputs, write_nssa_outputs}, }; -// The token program has two functions: +// The token program has three functions: // 1. New token definition. // Arguments to this function are: // * Two **default** accounts: [definition_account, holding_account]. @@ -18,6 +18,11 @@ use nssa_core::{ // * Two accounts: [sender_account, recipient_account]. // * An instruction data byte string of length 23, indicating the total supply with the following layout // [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. +// 3. Initialize account with zero balance +// Arguments to this function are: +// * Two accounts: [definition_account, account_to_initialize]. +// * An dummy byte string of length 23, with the following layout +// [0x02 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00]. const TOKEN_DEFINITION_TYPE: u8 = 0; const TOKEN_DEFINITION_DATA_SIZE: usize = 23; @@ -45,6 +50,25 @@ impl TokenDefinition { bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes()); bytes.into() } + + fn parse(data: &[u8]) -> Option { + if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE { + None + } else { + let account_type = data[0]; + let name = data[1..7].try_into().unwrap(); + let total_supply = u128::from_le_bytes( + data[7..] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); + Some(Self { + account_type, + name, + total_supply, + }) + } + } } impl TokenHolding { @@ -61,8 +85,16 @@ impl TokenHolding { None } else { let account_type = data[0]; - let definition_id = AccountId::new(data[1..33].try_into().unwrap()); - let balance = u128::from_le_bytes(data[33..].try_into().unwrap()); + let definition_id = AccountId::new( + data[1..33] + .try_into() + .expect("Defintion ID must be 32 bytes long"), + ); + let balance = u128::from_le_bytes( + data[33..] + .try_into() + .expect("balance must be 16 bytes little-endian"), + ); Some(Self { definition_id, balance, @@ -167,6 +199,33 @@ fn new_definition( vec![definition_target_account_post, holding_target_account_post] } +fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { + if pre_states.len() != 2 { + panic!("Invalid number of accounts"); + } + + let definition = &pre_states[0]; + let account_to_initialize = &pre_states[1]; + + if account_to_initialize.account != Account::default() { + panic!("Only uninitialized accounts can be initialized"); + } + + // TODO: We should check that this is an account owned by the token program. + // This check can't be done here since the ID of the program is known only after compiling it + // + // Check definition account is valid + let _definition_values = + TokenDefinition::parse(&definition.account.data).expect("Definition account must be valid"); + let holding_values = TokenHolding::new(&definition.account_id); + + let definition_post = definition.account.clone(); + let mut account_to_initialize_post = account_to_initialize.account.clone(); + account_to_initialize_post.data = holding_values.into_data(); + + vec![definition_post, account_to_initialize_post] +} + type Instruction = [u8; 23]; fn main() { @@ -175,36 +234,59 @@ fn main() { instruction, } = read_nssa_inputs::(); - match instruction[0] { + let (pre_states, post_states) = match instruction[0] { 0 => { // Parse instruction - let total_supply = u128::from_le_bytes(instruction[1..17].try_into().unwrap()); - let name: [u8; 6] = instruction[17..].try_into().unwrap(); + let total_supply = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); + let name: [u8; 6] = instruction[17..] + .try_into() + .expect("Name must be 6 bytes long"); assert_ne!(name, [0; 6]); // Execute let post_states = new_definition(&pre_states, name, total_supply); - write_nssa_outputs(pre_states, post_states); + (pre_states, post_states) } 1 => { // Parse instruction - let balance_to_move = u128::from_le_bytes(instruction[1..17].try_into().unwrap()); - let name: [u8; 6] = instruction[17..].try_into().unwrap(); + let balance_to_move = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Balance to move must be 16 bytes little-endian"), + ); + let name: [u8; 6] = instruction[17..] + .try_into() + .expect("Name must be 6 bytes long"); assert_eq!(name, [0; 6]); // Execute let post_states = transfer(&pre_states, balance_to_move); - write_nssa_outputs(pre_states, post_states); + (pre_states, post_states) + } + 2 => { + // Initialize account + assert_eq!(instruction[1..], [0; 22]); + let post_states = initialize_account(&pre_states); + (pre_states, post_states) } _ => panic!("Invalid instruction"), }; + + write_nssa_outputs(pre_states, post_states); } #[cfg(test)] mod tests { use nssa_core::account::{Account, AccountId, AccountWithMetadata}; - use crate::{new_definition, transfer, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE}; + use crate::{ + TOKEN_DEFINITION_DATA_SIZE, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_TYPE, + initialize_account, new_definition, transfer, + }; #[should_panic(expected = "Invalid number of input accounts")] #[test] @@ -551,4 +633,37 @@ mod tests { ] ); } + + #[test] + fn test_token_initialize_account_succeeds() { + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // Definition ID with + data: vec![0; TOKEN_DEFINITION_DATA_SIZE - 16] + .into_iter() + .chain(u128::to_le_bytes(1000)) + .collect(), + ..Account::default() + }, + is_authorized: false, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: AccountId::new([2; 32]), + }, + ]; + let post_states = initialize_account(&pre_states); + let [definition, holding] = post_states.try_into().ok().unwrap(); + assert_eq!(definition.data, pre_states[0].account.data); + assert_eq!( + holding.data, + vec![ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + } } From aee6f45a8b973027d9875fa3a3bcfad1c80c031c Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Thu, 27 Nov 2025 22:07:53 +0300 Subject: [PATCH 16/21] refactor: move some stuff to a more suitable place --- integration_tests/src/lib.rs | 2 +- integration_tests/src/test_suite_map.rs | 120 +++---- .../mod.rs => chain_storage.rs} | 0 wallet/src/cli/account.rs | 9 +- wallet/src/cli/chain.rs | 5 +- wallet/src/cli/config.rs | 5 +- wallet/src/cli/mod.rs | 195 ++++++++++- wallet/src/cli/programs/mod.rs | 3 + .../native_token_transfer.rs} | 4 +- .../{pinata_program.rs => programs/pinata.rs} | 4 +- .../{token_program.rs => programs/token.rs} | 4 +- wallet/src/helperfunctions.rs | 132 +++++++- wallet/src/lib.rs | 309 +----------------- wallet/src/main.rs | 24 +- wallet/src/program_interactions/mod.rs | 2 + .../pinata.rs} | 0 .../token.rs} | 0 17 files changed, 420 insertions(+), 398 deletions(-) rename wallet/src/{chain_storage/mod.rs => chain_storage.rs} (100%) create mode 100644 wallet/src/cli/programs/mod.rs rename wallet/src/cli/{native_token_transfer_program.rs => programs/native_token_transfer.rs} (99%) rename wallet/src/cli/{pinata_program.rs => programs/pinata.rs} (99%) rename wallet/src/cli/{token_program.rs => programs/token.rs} (99%) create mode 100644 wallet/src/program_interactions/mod.rs rename wallet/src/{pinata_interactions.rs => program_interactions/pinata.rs} (100%) rename wallet/src/{token_program_interactions.rs => program_interactions/token.rs} (100%) diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index dc7188b..31cd177 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -56,7 +56,7 @@ fn make_private_account_input_from_str(account_id: &str) -> String { pub async fn pre_test( home_dir: PathBuf, ) -> Result<(ServerHandle, JoinHandle>, TempDir)> { - wallet::execute_setup("test_pass".to_owned()).await?; + wallet::cli::execute_setup("test_pass".to_owned()).await?; let home_dir_sequencer = home_dir.join("sequencer"); diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs index 55be519..e7ef4d1 100644 --- a/integration_tests/src/test_suite_map.rs +++ b/integration_tests/src/test_suite_map.rs @@ -16,13 +16,15 @@ use sequencer_runner::startup_sequencer; use tempfile::TempDir; use tokio::task::JoinHandle; use wallet::{ - Command, SubcommandReturnValue, WalletCore, + WalletCore, cli::{ + Command, SubcommandReturnValue, account::{AccountSubcommand, NewSubcommand}, config::ConfigSubcommand, - native_token_transfer_program::AuthTransferSubcommand, - pinata_program::PinataProgramAgnosticSubcommand, - token_program::TokenProgramAgnosticSubcommand, + programs::{ + native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand, + token::TokenProgramAgnosticSubcommand, + }, }, config::PersistentStorage, helperfunctions::{fetch_config, fetch_persistent_storage}, @@ -57,7 +59,7 @@ pub fn prepare_function_map() -> HashMap { let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; @@ -92,7 +94,7 @@ pub fn prepare_function_map() -> HashMap { let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); let PersistentStorage { accounts: persistent_accounts, @@ -123,7 +125,7 @@ pub fn prepare_function_map() -> HashMap { amount: 100, }); - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; @@ -162,7 +164,7 @@ pub fn prepare_function_map() -> HashMap { let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let failed_send = wallet::execute_subcommand(command).await; + let failed_send = wallet::cli::execute_subcommand(command).await; assert!(failed_send.is_err()); @@ -203,7 +205,7 @@ pub fn prepare_function_map() -> HashMap { let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; @@ -234,7 +236,7 @@ pub fn prepare_function_map() -> HashMap { amount: 100, }); - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; @@ -289,7 +291,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token definition let SubcommandReturnValue::RegisterAccount { account_id: definition_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Public { cci: ChainIndex::root(), }, @@ -302,7 +304,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token supply holder let SubcommandReturnValue::RegisterAccount { account_id: supply_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Public { cci: ChainIndex::root(), }, @@ -315,7 +317,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for receiving a token transaction let SubcommandReturnValue::RegisterAccount { account_id: recipient_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Public { cci: ChainIndex::root(), }, @@ -335,7 +337,7 @@ pub fn prepare_function_map() -> HashMap { name: "A NAME".to_string(), total_supply: 37, }; - wallet::execute_subcommand(Command::Token(subcommand)) + wallet::cli::execute_subcommand(Command::Token(subcommand)) .await .unwrap(); info!("Waiting for next block creation"); @@ -394,7 +396,7 @@ pub fn prepare_function_map() -> HashMap { amount: 7, }; - wallet::execute_subcommand(Command::Token(subcommand)) + wallet::cli::execute_subcommand(Command::Token(subcommand)) .await .unwrap(); info!("Waiting for next block creation"); @@ -449,7 +451,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token definition (public) let SubcommandReturnValue::RegisterAccount { account_id: definition_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Public { cci: ChainIndex::root(), }, @@ -462,7 +464,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token supply holder (private) let SubcommandReturnValue::RegisterAccount { account_id: supply_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Private { cci: ChainIndex::root(), }, @@ -475,7 +477,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for receiving a token transaction let SubcommandReturnValue::RegisterAccount { account_id: recipient_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Private { cci: ChainIndex::root(), }, @@ -496,7 +498,7 @@ pub fn prepare_function_map() -> HashMap { total_supply: 37, }; - wallet::execute_subcommand(Command::Token(subcommand)) + wallet::cli::execute_subcommand(Command::Token(subcommand)) .await .unwrap(); @@ -543,7 +545,7 @@ pub fn prepare_function_map() -> HashMap { amount: 7, }; - wallet::execute_subcommand(Command::Token(subcommand)) + wallet::cli::execute_subcommand(Command::Token(subcommand)) .await .unwrap(); @@ -577,7 +579,7 @@ pub fn prepare_function_map() -> HashMap { amount: 7, }; - wallet::execute_subcommand(Command::Token(subcommand)) + wallet::cli::execute_subcommand(Command::Token(subcommand)) .await .unwrap(); @@ -610,7 +612,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token definition (public) let SubcommandReturnValue::RegisterAccount { account_id: definition_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Public { cci: ChainIndex::root(), }, @@ -623,7 +625,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token supply holder (private) let SubcommandReturnValue::RegisterAccount { account_id: supply_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Private { cci: ChainIndex::root(), }, @@ -636,7 +638,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for receiving a token transaction let SubcommandReturnValue::RegisterAccount { account_id: recipient_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Private { cci: ChainIndex::root(), }, @@ -657,7 +659,7 @@ pub fn prepare_function_map() -> HashMap { total_supply: 37, }; - wallet::execute_subcommand(Command::Token(subcommand)) + wallet::cli::execute_subcommand(Command::Token(subcommand)) .await .unwrap(); @@ -711,7 +713,7 @@ pub fn prepare_function_map() -> HashMap { }; let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = - wallet::execute_subcommand(Command::Token(subcommand)) + wallet::cli::execute_subcommand(Command::Token(subcommand)) .await .unwrap() else { @@ -723,7 +725,7 @@ pub fn prepare_function_map() -> HashMap { let command = Command::Account(AccountSubcommand::SyncPrivate {}); - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); let wallet_config = fetch_config().await.unwrap(); let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) @@ -752,7 +754,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token definition (public) let SubcommandReturnValue::RegisterAccount { account_id: definition_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Public { cci: ChainIndex::root(), }, @@ -765,7 +767,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token supply holder (public) let SubcommandReturnValue::RegisterAccount { account_id: supply_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Public { cci: ChainIndex::root(), }, @@ -778,7 +780,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for receiving a token transaction let SubcommandReturnValue::RegisterAccount { account_id: recipient_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Private { cci: ChainIndex::root(), }, @@ -799,7 +801,7 @@ pub fn prepare_function_map() -> HashMap { total_supply: 37, }; - wallet::execute_subcommand(Command::Token(subcommand)) + wallet::cli::execute_subcommand(Command::Token(subcommand)) .await .unwrap(); @@ -836,7 +838,7 @@ pub fn prepare_function_map() -> HashMap { amount: 7, }; - wallet::execute_subcommand(Command::Token(subcommand)) + wallet::cli::execute_subcommand(Command::Token(subcommand)) .await .unwrap(); @@ -865,7 +867,7 @@ pub fn prepare_function_map() -> HashMap { amount: 7, }; - wallet::execute_subcommand(Command::Token(subcommand)) + wallet::cli::execute_subcommand(Command::Token(subcommand)) .await .unwrap(); @@ -894,7 +896,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token definition (public) let SubcommandReturnValue::RegisterAccount { account_id: definition_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Public { cci: ChainIndex::root(), }, @@ -907,7 +909,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token supply holder (private) let SubcommandReturnValue::RegisterAccount { account_id: supply_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Private { cci: ChainIndex::root(), }, @@ -920,7 +922,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for receiving a token transaction let SubcommandReturnValue::RegisterAccount { account_id: recipient_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Public { cci: ChainIndex::root(), }, @@ -941,7 +943,7 @@ pub fn prepare_function_map() -> HashMap { total_supply: 37, }; - wallet::execute_subcommand(Command::Token(subcommand)) + wallet::cli::execute_subcommand(Command::Token(subcommand)) .await .unwrap(); @@ -988,7 +990,7 @@ pub fn prepare_function_map() -> HashMap { amount: 7, }; - wallet::execute_subcommand(Command::Token(subcommand)) + wallet::cli::execute_subcommand(Command::Token(subcommand)) .await .unwrap(); @@ -1017,7 +1019,7 @@ pub fn prepare_function_map() -> HashMap { amount: 7, }; - wallet::execute_subcommand(Command::Token(subcommand)) + wallet::cli::execute_subcommand(Command::Token(subcommand)) .await .unwrap(); @@ -1049,7 +1051,7 @@ pub fn prepare_function_map() -> HashMap { amount: 100, }); - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; @@ -1088,7 +1090,7 @@ pub fn prepare_function_map() -> HashMap { }); let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = - wallet::execute_subcommand(command).await.unwrap() + wallet::cli::execute_subcommand(command).await.unwrap() else { panic!("invalid subcommand return value"); }; @@ -1128,7 +1130,7 @@ pub fn prepare_function_map() -> HashMap { cci: ChainIndex::root(), })); - let sub_ret = wallet::execute_subcommand(command).await.unwrap(); + let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); let SubcommandReturnValue::RegisterAccount { account_id: to_account_id, } = sub_ret @@ -1157,7 +1159,7 @@ pub fn prepare_function_map() -> HashMap { amount: 100, }); - let sub_ret = wallet::execute_subcommand(command).await.unwrap(); + let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { panic!("FAILED TO SEND TX"); }; @@ -1165,7 +1167,7 @@ pub fn prepare_function_map() -> HashMap { let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; let command = Command::Account(AccountSubcommand::SyncPrivate {}); - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) .await .unwrap(); @@ -1192,13 +1194,13 @@ pub fn prepare_function_map() -> HashMap { // info!( // "########## test_success_private_transfer_to_another_owned_account_cont_run_path // ##########" ); - // let continious_run_handle = tokio::spawn(wallet::execute_continious_run()); + // let continious_run_handle = tokio::spawn(wallet::cli::execute_continious_run()); // let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); // let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {})); - // let sub_ret = wallet::execute_subcommand(command).await.unwrap(); + // let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); // let SubcommandReturnValue::RegisterAccount { // account_id: to_account_id, // } = sub_ret @@ -1228,7 +1230,7 @@ pub fn prepare_function_map() -> HashMap { // amount: 100, // }); - // let sub_ret = wallet::execute_subcommand(command).await.unwrap(); + // let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); // let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { // panic!("FAILED TO SEND TX"); // }; @@ -1279,7 +1281,7 @@ pub fn prepare_function_map() -> HashMap { let from_acc = wallet_storage.get_account_private(&from).unwrap(); assert_eq!(from_acc.balance, 10000); - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; @@ -1322,7 +1324,7 @@ pub fn prepare_function_map() -> HashMap { let wallet_config = fetch_config().await.unwrap(); let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; @@ -1368,7 +1370,7 @@ pub fn prepare_function_map() -> HashMap { let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = - wallet::execute_subcommand(command).await.unwrap() + wallet::cli::execute_subcommand(command).await.unwrap() else { panic!("invalid subcommand return value"); }; @@ -1414,7 +1416,7 @@ pub fn prepare_function_map() -> HashMap { .unwrap() .balance; - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; @@ -1493,7 +1495,7 @@ pub fn prepare_function_map() -> HashMap { cci: ChainIndex::root(), })); let SubcommandReturnValue::RegisterAccount { account_id } = - wallet::execute_subcommand(command).await.unwrap() + wallet::cli::execute_subcommand(command).await.unwrap() else { panic!("Error creating account"); }; @@ -1501,7 +1503,7 @@ pub fn prepare_function_map() -> HashMap { let command = Command::AuthTransfer(AuthTransferSubcommand::Init { account_id: make_public_account_input_from_str(&account_id.to_string()), }); - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); info!("Checking correct execution"); let wallet_config = fetch_config().await.unwrap(); @@ -1547,7 +1549,7 @@ pub fn prepare_function_map() -> HashMap { .balance; let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = - wallet::execute_subcommand(command).await.unwrap() + wallet::cli::execute_subcommand(command).await.unwrap() else { panic!("invalid subcommand return value"); }; @@ -1563,7 +1565,7 @@ pub fn prepare_function_map() -> HashMap { .balance; let command = Command::Account(AccountSubcommand::SyncPrivate {}); - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); let wallet_config = fetch_config().await.unwrap(); let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); @@ -1591,7 +1593,7 @@ pub fn prepare_function_map() -> HashMap { // Create new account for the token supply holder (private) let SubcommandReturnValue::RegisterAccount { account_id: winner_account_id, - } = wallet::execute_subcommand(Command::Account(AccountSubcommand::New( + } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( NewSubcommand::Private { cci: ChainIndex::root(), }, @@ -1617,7 +1619,7 @@ pub fn prepare_function_map() -> HashMap { .unwrap() .balance; - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; @@ -1657,7 +1659,7 @@ pub fn prepare_function_map() -> HashMap { key: "seq_poll_retry_delay_millis".to_string(), value: "1000".to_string(), }); - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); let wallet_config = fetch_config().await.unwrap(); @@ -1668,7 +1670,7 @@ pub fn prepare_function_map() -> HashMap { key: "seq_poll_retry_delay_millis".to_string(), value: old_seq_poll_retry_delay_millis.to_string(), }); - wallet::execute_subcommand(command).await.unwrap(); + wallet::cli::execute_subcommand(command).await.unwrap(); info!("Success!"); } diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage.rs similarity index 100% rename from wallet/src/chain_storage/mod.rs rename to wallet/src/chain_storage.rs diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 6e831d1..5b23b2b 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -7,10 +7,11 @@ use nssa::{Account, AccountId, program::Program}; use serde::Serialize; use crate::{ - SubcommandReturnValue, WalletCore, - cli::WalletSubcommand, - helperfunctions::{AccountPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix}, - parse_block_range, + WalletCore, + cli::{SubcommandReturnValue, WalletSubcommand}, + helperfunctions::{ + AccountPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix, parse_block_range, + }, }; const TOKEN_DEFINITION_TYPE: u8 = 0; diff --git a/wallet/src/cli/chain.rs b/wallet/src/cli/chain.rs index a606066..419fa5e 100644 --- a/wallet/src/cli/chain.rs +++ b/wallet/src/cli/chain.rs @@ -1,7 +1,10 @@ use anyhow::Result; use clap::Subcommand; -use crate::{SubcommandReturnValue, WalletCore, cli::WalletSubcommand}; +use crate::{ + WalletCore, + cli::{SubcommandReturnValue, WalletSubcommand}, +}; /// Represents generic chain CLI subcommand #[derive(Subcommand, Debug, Clone)] diff --git a/wallet/src/cli/config.rs b/wallet/src/cli/config.rs index c41aa32..68670af 100644 --- a/wallet/src/cli/config.rs +++ b/wallet/src/cli/config.rs @@ -1,7 +1,10 @@ use anyhow::Result; use clap::Subcommand; -use crate::{SubcommandReturnValue, WalletCore, cli::WalletSubcommand}; +use crate::{ + WalletCore, + cli::{SubcommandReturnValue, WalletSubcommand}, +}; /// Represents generic config CLI subcommand #[derive(Subcommand, Debug, Clone)] diff --git a/wallet/src/cli/mod.rs b/wallet/src/cli/mod.rs index fff14cb..39c7874 100644 --- a/wallet/src/cli/mod.rs +++ b/wallet/src/cli/mod.rs @@ -1,15 +1,200 @@ -use anyhow::Result; +use std::sync::Arc; -use crate::{SubcommandReturnValue, WalletCore}; +use anyhow::Result; +use clap::{Parser, Subcommand}; +use common::sequencer_client::SequencerClient; +use nssa::program::Program; + +use crate::{ + WalletCore, + cli::{ + account::AccountSubcommand, + chain::ChainSubcommand, + config::ConfigSubcommand, + programs::{ + native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand, + token::TokenProgramAgnosticSubcommand, + }, + }, + helperfunctions::{fetch_config, parse_block_range}, +}; pub mod account; pub mod chain; pub mod config; -pub mod native_token_transfer_program; -pub mod pinata_program; -pub mod token_program; +pub mod programs; pub(crate) trait WalletSubcommand { async fn handle_subcommand(self, wallet_core: &mut WalletCore) -> Result; } + +/// Represents CLI command for a wallet +#[derive(Subcommand, Debug, Clone)] +#[clap(about)] +pub enum Command { + /// Authenticated transfer subcommand + #[command(subcommand)] + AuthTransfer(AuthTransferSubcommand), + /// Generic chain info subcommand + #[command(subcommand)] + ChainInfo(ChainSubcommand), + /// Account view and sync subcommand + #[command(subcommand)] + Account(AccountSubcommand), + /// Pinata program interaction subcommand + #[command(subcommand)] + Pinata(PinataProgramAgnosticSubcommand), + /// Token program interaction subcommand + #[command(subcommand)] + Token(TokenProgramAgnosticSubcommand), + /// Check the wallet can connect to the node and builtin local programs + /// match the remote versions + CheckHealth {}, + /// Command to setup config, get and set config fields + #[command(subcommand)] + Config(ConfigSubcommand), +} + +/// Represents overarching CLI command for a wallet with setup included +#[derive(Debug, Subcommand, Clone)] +#[clap(about)] +pub enum OverCommand { + /// Represents CLI command for a wallet + #[command(subcommand)] + Command(Command), + /// Setup of a storage. Initializes rots for public and private trees from `password`. + 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. +/// +/// All account account_ids must be provided as {privacy_prefix}/{account_id}, +/// where valid options for `privacy_prefix` is `Public` and `Private` +#[derive(Parser, Debug)] +#[clap(version, about)] +pub struct Args { + /// Continious run flag + #[arg(short, long)] + pub continious_run: bool, + /// Wallet command + #[command(subcommand)] + pub command: Option, +} + +#[derive(Debug, Clone)] +pub enum SubcommandReturnValue { + PrivacyPreservingTransfer { tx_hash: String }, + RegisterAccount { account_id: nssa::AccountId }, + Account(nssa::Account), + Empty, + SyncedToBlock(u64), +} + +pub async fn execute_subcommand(command: Command) -> Result { + let wallet_config = fetch_config().await?; + let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?; + + let subcommand_ret = match command { + Command::AuthTransfer(transfer_subcommand) => { + transfer_subcommand + .handle_subcommand(&mut wallet_core) + .await? + } + Command::ChainInfo(chain_subcommand) => { + chain_subcommand.handle_subcommand(&mut wallet_core).await? + } + Command::Account(account_subcommand) => { + account_subcommand + .handle_subcommand(&mut wallet_core) + .await? + } + Command::Pinata(pinata_subcommand) => { + pinata_subcommand + .handle_subcommand(&mut wallet_core) + .await? + } + Command::CheckHealth {} => { + let remote_program_ids = wallet_core + .sequencer_client + .get_program_ids() + .await + .expect("Error fetching program ids"); + let Some(authenticated_transfer_id) = remote_program_ids.get("authenticated_transfer") + else { + panic!("Missing authenticated transfer ID from remote"); + }; + if authenticated_transfer_id != &Program::authenticated_transfer_program().id() { + panic!("Local ID for authenticated transfer program is different from remote"); + } + let Some(token_id) = remote_program_ids.get("token") else { + panic!("Missing token program ID from remote"); + }; + if token_id != &Program::token().id() { + panic!("Local ID for token program is different from remote"); + } + let Some(circuit_id) = remote_program_ids.get("privacy_preserving_circuit") else { + panic!("Missing privacy preserving circuit ID from remote"); + }; + if circuit_id != &nssa::PRIVACY_PRESERVING_CIRCUIT_ID { + panic!("Local ID for privacy preserving circuit is different from remote"); + } + + println!("✅All looks good!"); + + SubcommandReturnValue::Empty + } + Command::Token(token_subcommand) => { + token_subcommand.handle_subcommand(&mut wallet_core).await? + } + Command::Config(config_subcommand) => { + config_subcommand + .handle_subcommand(&mut wallet_core) + .await? + } + }; + + Ok(subcommand_ret) +} + +pub async fn execute_continious_run() -> Result<()> { + let config = fetch_config().await?; + let seq_client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?); + let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?; + + let mut latest_block_num = seq_client.get_last_block().await?.last_block; + let mut curr_last_block = latest_block_num; + + loop { + parse_block_range( + curr_last_block, + latest_block_num, + seq_client.clone(), + &mut wallet_core, + ) + .await?; + + curr_last_block = latest_block_num + 1; + + tokio::time::sleep(std::time::Duration::from_millis( + config.seq_poll_timeout_millis, + )) + .await; + + 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/cli/programs/mod.rs b/wallet/src/cli/programs/mod.rs new file mode 100644 index 0000000..3ffb7bb --- /dev/null +++ b/wallet/src/cli/programs/mod.rs @@ -0,0 +1,3 @@ +pub mod native_token_transfer; +pub mod pinata; +pub mod token; diff --git a/wallet/src/cli/native_token_transfer_program.rs b/wallet/src/cli/programs/native_token_transfer.rs similarity index 99% rename from wallet/src/cli/native_token_transfer_program.rs rename to wallet/src/cli/programs/native_token_transfer.rs index f5fdb9a..2a9b4bf 100644 --- a/wallet/src/cli/native_token_transfer_program.rs +++ b/wallet/src/cli/programs/native_token_transfer.rs @@ -4,8 +4,8 @@ use common::transaction::NSSATransaction; use nssa::AccountId; use crate::{ - SubcommandReturnValue, WalletCore, - cli::WalletSubcommand, + WalletCore, + cli::{SubcommandReturnValue, WalletSubcommand}, helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix}, }; diff --git a/wallet/src/cli/pinata_program.rs b/wallet/src/cli/programs/pinata.rs similarity index 99% rename from wallet/src/cli/pinata_program.rs rename to wallet/src/cli/programs/pinata.rs index cc71a51..3d8dac3 100644 --- a/wallet/src/cli/pinata_program.rs +++ b/wallet/src/cli/programs/pinata.rs @@ -4,8 +4,8 @@ use common::{PINATA_BASE58, transaction::NSSATransaction}; use log::info; use crate::{ - SubcommandReturnValue, WalletCore, - cli::WalletSubcommand, + WalletCore, + cli::{SubcommandReturnValue, WalletSubcommand}, helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix}, }; diff --git a/wallet/src/cli/token_program.rs b/wallet/src/cli/programs/token.rs similarity index 99% rename from wallet/src/cli/token_program.rs rename to wallet/src/cli/programs/token.rs index b412e2f..a2a2d54 100644 --- a/wallet/src/cli/token_program.rs +++ b/wallet/src/cli/programs/token.rs @@ -4,8 +4,8 @@ use common::transaction::NSSATransaction; use nssa::AccountId; use crate::{ - SubcommandReturnValue, WalletCore, - cli::WalletSubcommand, + WalletCore, + cli::{SubcommandReturnValue, WalletSubcommand}, helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix}, }; diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 770d2bb..19d2d56 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -1,16 +1,21 @@ -use std::{path::PathBuf, str::FromStr}; +use std::{path::PathBuf, str::FromStr, sync::Arc}; use anyhow::Result; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; -use key_protocol::key_protocol_core::NSSAUserData; -use nssa::Account; +use common::{ + block::HashableBlockData, sequencer_client::SequencerClient, transaction::NSSATransaction, +}; +use key_protocol::{ + key_management::key_tree::traits::KeyNode as _, key_protocol_core::NSSAUserData, +}; +use nssa::{Account, privacy_preserving_transaction::message::EncryptedAccountData}; use nssa_core::account::Nonce; use rand::{RngCore, rngs::OsRng}; use serde::Serialize; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::{ - HOME_DIR_ENV_VAR, + HOME_DIR_ENV_VAR, WalletCore, config::{ InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic, PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, WalletConfig, @@ -225,6 +230,125 @@ impl From for HumanReadableAccount { } } +pub async fn parse_block_range( + start: u64, + stop: u64, + seq_client: Arc, + wallet_core: &mut WalletCore, +) -> Result<()> { + for block_id in start..(stop + 1) { + let block = + borsh::from_slice::(&seq_client.get_block(block_id).await?.block)?; + + for tx in block.transactions { + let nssa_tx = NSSATransaction::try_from(&tx)?; + + if let NSSATransaction::PrivacyPreserving(tx) = nssa_tx { + let mut affected_accounts = vec![]; + + for (acc_account_id, (key_chain, _)) in + &wallet_core.storage.user_data.default_user_private_accounts + { + 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 account_id {acc_account_id:#?} with account object {res_acc:#?}" + ); + + affected_accounts.push((*acc_account_id, res_acc)); + } + } + } + } + + for keys_node in wallet_core + .storage + .user_data + .private_key_tree + .key_map + .values() + { + let acc_account_id = keys_node.account_id(); + 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 account_id {acc_account_id:#?} with account object {res_acc:#?}" + ); + + affected_accounts.push((acc_account_id, res_acc)); + } + } + } + } + + for (affected_account_id, new_acc) in affected_accounts { + wallet_core + .storage + .insert_private_account_data(affected_account_id, new_acc); + } + } + } + + wallet_core.last_synced_block = block_id; + wallet_core.store_persistent_data().await?; + + println!( + "Block at id {block_id} with timestamp {} parsed", + block.timestamp + ); + } + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 6cb52fe..04ee92e 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -3,31 +3,20 @@ use std::{path::PathBuf, sync::Arc}; use anyhow::Result; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use chain_storage::WalletChainStore; -use clap::{Parser, Subcommand}; use common::{ - block::HashableBlockData, sequencer_client::SequencerClient, transaction::{EncodedTransaction, NSSATransaction}, }; use config::WalletConfig; -use key_protocol::key_management::key_tree::{chain_index::ChainIndex, traits::KeyNode}; +use key_protocol::key_management::key_tree::chain_index::ChainIndex; use log::info; -use nssa::{ - Account, AccountId, privacy_preserving_transaction::message::EncryptedAccountData, - program::Program, -}; +use nssa::{Account, AccountId}; use nssa_core::{Commitment, MembershipProof}; use tokio::io::AsyncWriteExt; use crate::{ - cli::{ - WalletSubcommand, account::AccountSubcommand, chain::ChainSubcommand, - config::ConfigSubcommand, native_token_transfer_program::AuthTransferSubcommand, - pinata_program::PinataProgramAgnosticSubcommand, - token_program::TokenProgramAgnosticSubcommand, - }, config::PersistentStorage, - helperfunctions::{fetch_config, fetch_persistent_storage, get_home, produce_data_for_storage}, + helperfunctions::{fetch_persistent_storage, get_home, produce_data_for_storage}, poller::TxPoller, }; @@ -37,9 +26,8 @@ pub mod chain_storage; pub mod cli; pub mod config; pub mod helperfunctions; -pub mod pinata_interactions; pub mod poller; -pub mod token_program_interactions; +pub mod program_interactions; pub mod token_transfers; pub mod transaction_utils; @@ -219,292 +207,3 @@ impl WalletCore { Ok(()) } } - -/// Represents CLI command for a wallet -#[derive(Subcommand, Debug, Clone)] -#[clap(about)] -pub enum Command { - /// Authenticated transfer subcommand - #[command(subcommand)] - AuthTransfer(AuthTransferSubcommand), - /// Generic chain info subcommand - #[command(subcommand)] - ChainInfo(ChainSubcommand), - /// Account view and sync subcommand - #[command(subcommand)] - Account(AccountSubcommand), - /// Pinata program interaction subcommand - #[command(subcommand)] - Pinata(PinataProgramAgnosticSubcommand), - /// Token program interaction subcommand - #[command(subcommand)] - Token(TokenProgramAgnosticSubcommand), - /// Check the wallet can connect to the node and builtin local programs - /// match the remote versions - CheckHealth {}, - /// Command to setup config, get and set config fields - #[command(subcommand)] - Config(ConfigSubcommand), -} - -/// Represents overarching CLI command for a wallet with setup included -#[derive(Debug, Subcommand, Clone)] -#[clap(about)] -pub enum OverCommand { - /// Represents CLI command for a wallet - #[command(subcommand)] - Command(Command), - /// Setup of a storage. Initializes rots for public and private trees from `password`. - 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. -/// -/// All account account_ids must be provided as {privacy_prefix}/{account_id}, -/// where valid options for `privacy_prefix` is `Public` and `Private` -#[derive(Parser, Debug)] -#[clap(version, about)] -pub struct Args { - /// Continious run flag - #[arg(short, long)] - pub continious_run: bool, - /// Wallet command - #[command(subcommand)] - pub command: Option, -} - -#[derive(Debug, Clone)] -pub enum SubcommandReturnValue { - PrivacyPreservingTransfer { tx_hash: String }, - RegisterAccount { account_id: nssa::AccountId }, - Account(nssa::Account), - Empty, - SyncedToBlock(u64), -} - -pub async fn execute_subcommand(command: Command) -> Result { - let wallet_config = fetch_config().await?; - let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?; - - let subcommand_ret = match command { - Command::AuthTransfer(transfer_subcommand) => { - transfer_subcommand - .handle_subcommand(&mut wallet_core) - .await? - } - Command::ChainInfo(chain_subcommand) => { - chain_subcommand.handle_subcommand(&mut wallet_core).await? - } - Command::Account(account_subcommand) => { - account_subcommand - .handle_subcommand(&mut wallet_core) - .await? - } - Command::Pinata(pinata_subcommand) => { - pinata_subcommand - .handle_subcommand(&mut wallet_core) - .await? - } - Command::CheckHealth {} => { - let remote_program_ids = wallet_core - .sequencer_client - .get_program_ids() - .await - .expect("Error fetching program ids"); - let Some(authenticated_transfer_id) = remote_program_ids.get("authenticated_transfer") - else { - panic!("Missing authenticated transfer ID from remote"); - }; - if authenticated_transfer_id != &Program::authenticated_transfer_program().id() { - panic!("Local ID for authenticated transfer program is different from remote"); - } - let Some(token_id) = remote_program_ids.get("token") else { - panic!("Missing token program ID from remote"); - }; - if token_id != &Program::token().id() { - panic!("Local ID for token program is different from remote"); - } - let Some(circuit_id) = remote_program_ids.get("privacy_preserving_circuit") else { - panic!("Missing privacy preserving circuit ID from remote"); - }; - if circuit_id != &nssa::PRIVACY_PRESERVING_CIRCUIT_ID { - panic!("Local ID for privacy preserving circuit is different from remote"); - } - - println!("✅All looks good!"); - - SubcommandReturnValue::Empty - } - Command::Token(token_subcommand) => { - token_subcommand.handle_subcommand(&mut wallet_core).await? - } - Command::Config(config_subcommand) => { - config_subcommand - .handle_subcommand(&mut wallet_core) - .await? - } - }; - - Ok(subcommand_ret) -} - -pub async fn parse_block_range( - start: u64, - stop: u64, - seq_client: Arc, - wallet_core: &mut WalletCore, -) -> Result<()> { - for block_id in start..(stop + 1) { - let block = - borsh::from_slice::(&seq_client.get_block(block_id).await?.block)?; - - for tx in block.transactions { - let nssa_tx = NSSATransaction::try_from(&tx)?; - - if let NSSATransaction::PrivacyPreserving(tx) = nssa_tx { - let mut affected_accounts = vec![]; - - for (acc_account_id, (key_chain, _)) in - &wallet_core.storage.user_data.default_user_private_accounts - { - 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 account_id {acc_account_id:#?} with account object {res_acc:#?}" - ); - - affected_accounts.push((*acc_account_id, res_acc)); - } - } - } - } - - for keys_node in wallet_core - .storage - .user_data - .private_key_tree - .key_map - .values() - { - let acc_account_id = keys_node.account_id(); - 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 account_id {acc_account_id:#?} with account object {res_acc:#?}" - ); - - affected_accounts.push((acc_account_id, res_acc)); - } - } - } - } - - for (affected_account_id, new_acc) in affected_accounts { - wallet_core - .storage - .insert_private_account_data(affected_account_id, new_acc); - } - } - } - - wallet_core.last_synced_block = block_id; - wallet_core.store_persistent_data().await?; - - println!( - "Block at id {block_id} with timestamp {} parsed", - block.timestamp - ); - } - - Ok(()) -} - -pub async fn execute_continious_run() -> Result<()> { - let config = fetch_config().await?; - let seq_client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?); - let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?; - - let mut latest_block_num = seq_client.get_last_block().await?.last_block; - let mut curr_last_block = latest_block_num; - - loop { - parse_block_range( - curr_last_block, - latest_block_num, - seq_client.clone(), - &mut wallet_core, - ) - .await?; - - curr_last_block = latest_block_num + 1; - - tokio::time::sleep(std::time::Duration::from_millis( - config.seq_poll_timeout_millis, - )) - .await; - - 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 304d788..7360d47 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,14 +1,16 @@ use anyhow::Result; -use clap::{CommandFactory, Parser}; +use clap::{CommandFactory as _, Parser as _}; use tokio::runtime::Builder; -use wallet::{Args, OverCommand, execute_continious_run, execute_setup, execute_subcommand}; +use wallet::cli::{Args, OverCommand, execute_continious_run, execute_setup, execute_subcommand}; pub const NUM_THREADS: usize = 2; // TODO #169: We have sample configs for sequencer, but not for wallet // TODO #168: Why it requires config as a directory? Maybe better to deduce directory from config -// file path? TODO #172: Why it requires config as env var while sequencer_runner accepts as -// argument? TODO #171: Running pinata doesn't give output about transaction hash and etc. +// file path? +// TODO #172: Why it requires config as env var while sequencer_runner accepts as +// argument? +// TODO #171: Running pinata doesn't give output about transaction hash and etc. fn main() -> Result<()> { let runtime = Builder::new_multi_thread() .worker_threads(NUM_THREADS) @@ -24,19 +26,17 @@ fn main() -> Result<()> { if let Some(overcommand) = args.command { match overcommand { OverCommand::Command(command) => { - execute_subcommand(command).await.unwrap(); - } - OverCommand::Setup { password } => { - execute_setup(password).await.unwrap(); + let _output = execute_subcommand(command).await?; + Ok(()) } + OverCommand::Setup { password } => execute_setup(password).await, } } else if args.continious_run { - execute_continious_run().await.unwrap(); + execute_continious_run().await } else { let help = Args::command().render_long_help(); println!("{help}"); + Ok(()) } - }); - - Ok(()) + }) } diff --git a/wallet/src/program_interactions/mod.rs b/wallet/src/program_interactions/mod.rs new file mode 100644 index 0000000..fbdd6ab --- /dev/null +++ b/wallet/src/program_interactions/mod.rs @@ -0,0 +1,2 @@ +pub mod pinata; +pub mod token; diff --git a/wallet/src/pinata_interactions.rs b/wallet/src/program_interactions/pinata.rs similarity index 100% rename from wallet/src/pinata_interactions.rs rename to wallet/src/program_interactions/pinata.rs diff --git a/wallet/src/token_program_interactions.rs b/wallet/src/program_interactions/token.rs similarity index 100% rename from wallet/src/token_program_interactions.rs rename to wallet/src/program_interactions/token.rs From df64f8864f85ed1c80ab2b21288abc6107f56f8a Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Sun, 30 Nov 2025 01:19:06 +0300 Subject: [PATCH 17/21] refactor: implement universal interface for privacy-preserving transactions --- .../src/cli/programs/native_token_transfer.rs | 38 +- wallet/src/cli/programs/token.rs | 62 +-- wallet/src/lib.rs | 85 ++- wallet/src/privacy_preserving_tx.rs | 173 +++++++ wallet/src/program_interactions/token.rs | 164 +++--- wallet/src/token_transfers/deshielded.rs | 23 +- wallet/src/token_transfers/mod.rs | 5 +- wallet/src/token_transfers/private.rs | 57 +- wallet/src/token_transfers/shielded.rs | 64 ++- wallet/src/transaction_utils.rs | 487 +----------------- 10 files changed, 460 insertions(+), 698 deletions(-) create mode 100644 wallet/src/privacy_preserving_tx.rs diff --git a/wallet/src/cli/programs/native_token_transfer.rs b/wallet/src/cli/programs/native_token_transfer.rs index 2a9b4bf..3b1f2ae 100644 --- a/wallet/src/cli/programs/native_token_transfer.rs +++ b/wallet/src/cli/programs/native_token_transfer.rs @@ -317,21 +317,9 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { let from: AccountId = from.parse().unwrap(); let to: AccountId = to.parse().unwrap(); - let to_initialization = wallet_core.check_private_account_initialized(&to).await?; - - let (res, [secret_from, secret_to]) = if let Some(to_proof) = to_initialization { - wallet_core - .send_private_native_token_transfer_owned_account_already_initialized( - from, to, amount, to_proof, - ) - .await? - } else { - wallet_core - .send_private_native_token_transfer_owned_account_not_initialized( - from, to, amount, - ) - .await? - }; + let (res, [secret_from, secret_to]) = wallet_core + .send_private_native_token_transfer_owned_account(from, to, amount) + .await?; println!("Results of tx send is {res:#?}"); @@ -413,19 +401,9 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { let from: AccountId = from.parse().unwrap(); let to: AccountId = to.parse().unwrap(); - let to_initialization = wallet_core.check_private_account_initialized(&to).await?; - - let (res, [secret]) = if let Some(to_proof) = to_initialization { - wallet_core - .send_shielded_native_token_transfer_already_initialized( - from, to, amount, to_proof, - ) - .await? - } else { - wallet_core - .send_shielded_native_token_transfer_not_initialized(from, to, amount) - .await? - }; + let (res, secret) = wallet_core + .send_shielded_native_token_transfer(from, to, amount) + .await?; println!("Results of tx send is {res:#?}"); @@ -468,7 +446,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { let to_ipk = nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_ipk.to_vec()); - let res = wallet_core + let (res, _) = wallet_core .send_shielded_native_token_transfer_outer_account(from, to_npk, to_ipk, amount) .await?; @@ -502,7 +480,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { let from: AccountId = from.parse().unwrap(); let to: AccountId = to.parse().unwrap(); - let (res, [secret]) = wallet_core + let (res, secret) = wallet_core .send_deshielded_native_token_transfer(from, to, amount) .await?; diff --git a/wallet/src/cli/programs/token.rs b/wallet/src/cli/programs/token.rs index a2a2d54..0671e4b 100644 --- a/wallet/src/cli/programs/token.rs +++ b/wallet/src/cli/programs/token.rs @@ -389,7 +389,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { let definition_account_id: AccountId = definition_account_id.parse().unwrap(); let supply_account_id: AccountId = supply_account_id.parse().unwrap(); - let (res, [secret_supply]) = wallet_core + let (res, secret_supply) = wallet_core .send_new_token_definition_private_owned( definition_account_id, supply_account_id, @@ -428,30 +428,14 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { let sender_account_id: AccountId = sender_account_id.parse().unwrap(); let recipient_account_id: AccountId = recipient_account_id.parse().unwrap(); - let recipient_initialization = wallet_core - .check_private_account_initialized(&recipient_account_id) + let (res, [secret_sender, secret_recipient]) = wallet_core + .send_transfer_token_transaction_private_owned_account( + sender_account_id, + recipient_account_id, + balance_to_move, + ) .await?; - let (res, [secret_sender, secret_recipient]) = - if let Some(recipient_proof) = recipient_initialization { - wallet_core - .send_transfer_token_transaction_private_owned_account_already_initialized( - sender_account_id, - recipient_account_id, - balance_to_move, - recipient_proof, - ) - .await? - } else { - wallet_core - .send_transfer_token_transaction_private_owned_account_not_initialized( - sender_account_id, - recipient_account_id, - balance_to_move, - ) - .await? - }; - println!("Results of tx send is {res:#?}"); let tx_hash = res.tx_hash; @@ -545,7 +529,7 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded { let sender_account_id: AccountId = sender_account_id.parse().unwrap(); let recipient_account_id: AccountId = recipient_account_id.parse().unwrap(); - let (res, [secret_sender]) = wallet_core + let (res, secret_sender) = wallet_core .send_transfer_token_transaction_deshielded( sender_account_id, recipient_account_id, @@ -604,7 +588,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { recipient_ipk.to_vec(), ); - let res = wallet_core + let (res, _) = wallet_core .send_transfer_token_transaction_shielded_foreign_account( sender_account_id, recipient_npk, @@ -638,30 +622,14 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { let sender_account_id: AccountId = sender_account_id.parse().unwrap(); let recipient_account_id: AccountId = recipient_account_id.parse().unwrap(); - let recipient_initialization = wallet_core - .check_private_account_initialized(&recipient_account_id) + let (res, secret_recipient) = wallet_core + .send_transfer_token_transaction_shielded_owned_account( + sender_account_id, + recipient_account_id, + balance_to_move, + ) .await?; - let (res, [secret_recipient]) = - if let Some(recipient_proof) = recipient_initialization { - wallet_core - .send_transfer_token_transaction_shielded_owned_account_already_initialized( - sender_account_id, - recipient_account_id, - balance_to_move, - recipient_proof, - ) - .await? - } else { - wallet_core - .send_transfer_token_transaction_shielded_owned_account_not_initialized( - sender_account_id, - recipient_account_id, - balance_to_move, - ) - .await? - }; - println!("Results of tx send is {res:#?}"); let tx_hash = res.tx_hash; diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 04ee92e..4cfbab3 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -4,19 +4,23 @@ use anyhow::Result; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use chain_storage::WalletChainStore; use common::{ - sequencer_client::SequencerClient, + error::ExecutionFailureKind, + sequencer_client::{SequencerClient, json::SendTxResponse}, transaction::{EncodedTransaction, NSSATransaction}, }; use config::WalletConfig; use key_protocol::key_management::key_tree::chain_index::ChainIndex; use log::info; -use nssa::{Account, AccountId}; -use nssa_core::{Commitment, MembershipProof}; +use nssa::{Account, AccountId, PrivacyPreservingTransaction, program::Program}; +use nssa_core::{Commitment, MembershipProof, SharedSecretKey, program::InstructionData}; +pub use privacy_preserving_tx::PrivacyPreservingAccount; use tokio::io::AsyncWriteExt; use crate::{ config::PersistentStorage, - helperfunctions::{fetch_persistent_storage, get_home, produce_data_for_storage}, + helperfunctions::{ + fetch_persistent_storage, get_home, produce_data_for_storage, produce_random_nonces, + }, poller::TxPoller, }; @@ -27,6 +31,7 @@ pub mod cli; pub mod config; pub mod helperfunctions; pub mod poller; +mod privacy_preserving_tx; pub mod program_interactions; pub mod token_transfers; pub mod transaction_utils; @@ -144,6 +149,15 @@ impl WalletCore { Ok(response.account) } + pub fn get_account_public_signing_key( + &self, + account_id: &AccountId, + ) -> Option<&nssa::PrivateKey> { + self.storage + .user_data + .get_pub_account_signing_key(account_id) + } + pub fn get_account_private(&self, account_id: &AccountId) -> Option { self.storage .user_data @@ -206,4 +220,67 @@ impl WalletCore { Ok(()) } + + pub async fn send_privacy_preserving_tx( + &self, + accounts: Vec, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result<(SendTxResponse, Vec), ExecutionFailureKind> { + let payload = privacy_preserving_tx::Payload::new(self, accounts).await?; + + let pre_states = payload.pre_states(); + tx_pre_check( + &pre_states + .iter() + .map(|pre| &pre.account) + .collect::>(), + )?; + + let private_account_keys = payload.private_account_keys(); + let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove( + &pre_states, + &instruction_data, + payload.visibility_mask(), + &produce_random_nonces(private_account_keys.len()), + &private_account_keys + .iter() + .map(|keys| (keys.npk.clone(), keys.ssk.clone())) + .collect::>(), + &payload.private_account_auth(), + &program, + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + payload.public_account_ids(), + Vec::from_iter(payload.public_account_nonces()), + private_account_keys + .iter() + .map(|keys| (keys.npk.clone(), keys.ipk.clone(), keys.epk.clone())) + .collect(), + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &payload.witness_signing_keys(), + ); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + let shared_secrets = private_account_keys + .into_iter() + .map(|keys| keys.ssk) + .collect(); + + Ok(( + self.sequencer_client.send_tx_private(tx).await?, + shared_secrets, + )) + } } diff --git a/wallet/src/privacy_preserving_tx.rs b/wallet/src/privacy_preserving_tx.rs new file mode 100644 index 0000000..be96a7a --- /dev/null +++ b/wallet/src/privacy_preserving_tx.rs @@ -0,0 +1,173 @@ +use common::error::ExecutionFailureKind; +use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; +use nssa::{AccountId, PrivateKey}; +use nssa_core::{ + MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, + account::{AccountWithMetadata, Nonce}, + encryption::{EphemeralPublicKey, IncomingViewingPublicKey}, +}; + +use crate::{WalletCore, transaction_utils::AccountPreparedData}; + +pub enum PrivacyPreservingAccount { + Public(AccountId), + PrivateLocal(AccountId), + PrivateForeign { + npk: NullifierPublicKey, + ipk: IncomingViewingPublicKey, + }, +} + +pub struct PrivateAccountKeys { + pub npk: NullifierPublicKey, + pub ssk: SharedSecretKey, + pub ipk: IncomingViewingPublicKey, + pub epk: EphemeralPublicKey, +} + +enum State { + Public { + account: AccountWithMetadata, + sk: Option, + }, + Private(AccountPreparedData), +} + +pub struct Payload { + states: Vec, + visibility_mask: Vec, +} + +impl Payload { + pub async fn new( + wallet: &WalletCore, + accounts: Vec, + ) -> Result { + let mut pre_states = Vec::with_capacity(accounts.len()); + let mut visibility_mask = Vec::with_capacity(accounts.len()); + + for account in accounts { + let (state, mask) = match account { + PrivacyPreservingAccount::Public(account_id) => { + let acc = wallet + .get_account_public(account_id) + .await + .map_err(|_| ExecutionFailureKind::KeyNotFoundError)?; + + let sk = wallet.get_account_public_signing_key(&account_id).cloned(); + let account = AccountWithMetadata::new(acc.clone(), sk.is_some(), account_id); + + (State::Public { account, sk }, 0) + } + PrivacyPreservingAccount::PrivateLocal(account_id) => { + let mut pre = wallet + .private_acc_preparation(account_id, true, true) + .await?; + let mut mask = 1; + + if pre.proof.is_none() { + pre.auth_acc.is_authorized = false; + pre.nsk = None; + mask = 2 + }; + + (State::Private(pre), mask) + } + PrivacyPreservingAccount::PrivateForeign { npk, ipk } => { + let acc = nssa_core::account::Account::default(); + let auth_acc = AccountWithMetadata::new(acc, false, &npk); + let pre = AccountPreparedData { + nsk: None, + npk, + ipk, + auth_acc, + proof: None, + }; + + (State::Private(pre), 2) + } + }; + + pre_states.push(state); + visibility_mask.push(mask); + } + + Ok(Self { + states: pre_states, + visibility_mask, + }) + } + + pub fn pre_states(&self) -> Vec { + self.states + .iter() + .map(|state| match state { + State::Public { account, .. } => account.clone(), + State::Private(pre) => pre.auth_acc.clone(), + }) + .collect() + } + + pub fn visibility_mask(&self) -> &[u8] { + &self.visibility_mask + } + + pub fn public_account_nonces(&self) -> Vec { + self.states + .iter() + .filter_map(|state| match state { + State::Public { account, .. } => Some(account.account.nonce), + _ => None, + }) + .collect() + } + + pub fn private_account_keys(&self) -> Vec { + self.states + .iter() + .filter_map(|state| match state { + State::Private(pre) => { + let eph_holder = EphemeralKeyHolder::new(&pre.npk); + + Some(PrivateAccountKeys { + npk: pre.npk.clone(), + ssk: eph_holder.calculate_shared_secret_sender(&pre.ipk), + ipk: pre.ipk.clone(), + epk: eph_holder.generate_ephemeral_public_key(), + }) + } + _ => None, + }) + .collect() + } + + pub fn private_account_auth(&self) -> Vec<(NullifierSecretKey, MembershipProof)> { + self.states + .iter() + .filter_map(|state| match state { + State::Private(pre) => Some((pre.nsk?, pre.proof.clone()?)), + _ => None, + }) + .collect() + } + + pub fn public_account_ids(&self) -> Vec { + self.states + .iter() + .filter_map(|state| match state { + State::Public { account, .. } => Some(account.account_id), + _ => None, + }) + .collect() + } + + pub fn witness_signing_keys(&self) -> Vec<&PrivateKey> { + self.states + .iter() + .filter_map(|state| match state { + State::Public { sk, .. } => sk.as_ref(), + _ => None, + }) + .collect() + } +} diff --git a/wallet/src/program_interactions/token.rs b/wallet/src/program_interactions/token.rs index c441842..91f76d4 100644 --- a/wallet/src/program_interactions/token.rs +++ b/wallet/src/program_interactions/token.rs @@ -1,11 +1,11 @@ use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; use nssa::{Account, AccountId, program::Program}; use nssa_core::{ - MembershipProof, NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey, + NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey, program::InstructionData, }; -use crate::WalletCore; +use crate::{PrivacyPreservingAccount, WalletCore}; impl WalletCore { pub fn token_program_preparation_transfer( @@ -13,7 +13,7 @@ impl WalletCore { ) -> ( InstructionData, Program, - impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, ) { // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || // 0x00 || 0x00 || 0x00]. @@ -22,7 +22,7 @@ impl WalletCore { instruction[1..17].copy_from_slice(&amount.to_le_bytes()); let instruction_data = Program::serialize_instruction(instruction).unwrap(); let program = Program::token(); - let tx_pre_check = |_: &Account, _: &Account| Ok(()); + let tx_pre_check = |_: &[&Account]| Ok(()); (instruction_data, program, tx_pre_check) } @@ -33,7 +33,7 @@ impl WalletCore { ) -> ( InstructionData, Program, - impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, ) { // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] let mut instruction = [0; 23]; @@ -41,7 +41,7 @@ impl WalletCore { instruction[17..].copy_from_slice(&name); let instruction_data = Program::serialize_instruction(instruction).unwrap(); let program = Program::token(); - let tx_pre_check = |_: &Account, _: &Account| Ok(()); + let tx_pre_check = |_: &[&Account]| Ok(()); (instruction_data, program, tx_pre_check) } @@ -80,20 +80,27 @@ impl WalletCore { supply_account_id: AccountId, name: [u8; 6], total_supply: u128, - ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { let (instruction_data, program, tx_pre_check) = WalletCore::token_program_preparation_definition(name, total_supply); - // Kind of non-obvious naming - // Basically this funtion is called because authentication mask is [0, 2] - self.shielded_two_accs_receiver_uninit( - definition_account_id, - supply_account_id, + self.send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(definition_account_id), + PrivacyPreservingAccount::PrivateLocal(supply_account_id), + ], instruction_data, tx_pre_check, program, ) .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected recipient's secret"); + (resp, first) + }) } pub async fn send_transfer_token_transaction( @@ -135,28 +142,7 @@ impl WalletCore { Ok(self.sequencer_client.send_tx_public(tx).await?) } - pub async fn send_transfer_token_transaction_private_owned_account_already_initialized( - &self, - sender_account_id: AccountId, - recipient_account_id: AccountId, - amount: u128, - recipient_proof: MembershipProof, - ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::token_program_preparation_transfer(amount); - - self.private_tx_two_accs_all_init( - sender_account_id, - recipient_account_id, - instruction_data, - tx_pre_check, - program, - recipient_proof, - ) - .await - } - - pub async fn send_transfer_token_transaction_private_owned_account_not_initialized( + pub async fn send_transfer_token_transaction_private_owned_account( &self, sender_account_id: AccountId, recipient_account_id: AccountId, @@ -165,14 +151,22 @@ impl WalletCore { let (instruction_data, program, tx_pre_check) = WalletCore::token_program_preparation_transfer(amount); - self.private_tx_two_accs_receiver_uninit( - sender_account_id, - recipient_account_id, + self.send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateLocal(sender_account_id), + PrivacyPreservingAccount::PrivateLocal(recipient_account_id), + ], instruction_data, tx_pre_check, program, ) .await + .map(|(resp, secrets)| { + let mut iter = secrets.into_iter(); + let first = iter.next().expect("expected sender's secret"); + let second = iter.next().expect("expected recipient's secret"); + (resp, [first, second]) + }) } pub async fn send_transfer_token_transaction_private_foreign_account( @@ -185,15 +179,25 @@ impl WalletCore { let (instruction_data, program, tx_pre_check) = WalletCore::token_program_preparation_transfer(amount); - self.private_tx_two_accs_receiver_outer( - sender_account_id, - recipient_npk, - recipient_ipk, + self.send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateLocal(sender_account_id), + PrivacyPreservingAccount::PrivateForeign { + npk: recipient_npk, + ipk: recipient_ipk, + }, + ], instruction_data, tx_pre_check, program, ) .await + .map(|(resp, secrets)| { + let mut iter = secrets.into_iter(); + let first = iter.next().expect("expected sender's secret"); + let second = iter.next().expect("expected recipient's secret"); + (resp, [first, second]) + }) } pub async fn send_transfer_token_transaction_deshielded( @@ -201,58 +205,55 @@ impl WalletCore { sender_account_id: AccountId, recipient_account_id: AccountId, amount: u128, - ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { let (instruction_data, program, tx_pre_check) = WalletCore::token_program_preparation_transfer(amount); - self.deshielded_tx_two_accs( - sender_account_id, - recipient_account_id, + self.send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateLocal(sender_account_id), + PrivacyPreservingAccount::Public(recipient_account_id), + ], instruction_data, tx_pre_check, program, ) .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected sender's secret"); + (resp, first) + }) } - pub async fn send_transfer_token_transaction_shielded_owned_account_already_initialized( + pub async fn send_transfer_token_transaction_shielded_owned_account( &self, sender_account_id: AccountId, recipient_account_id: AccountId, amount: u128, - recipient_proof: MembershipProof, - ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { let (instruction_data, program, tx_pre_check) = WalletCore::token_program_preparation_transfer(amount); - self.shielded_two_accs_all_init( - sender_account_id, - recipient_account_id, - instruction_data, - tx_pre_check, - program, - recipient_proof, - ) - .await - } - - pub async fn send_transfer_token_transaction_shielded_owned_account_not_initialized( - &self, - sender_account_id: AccountId, - recipient_account_id: AccountId, - amount: u128, - ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::token_program_preparation_transfer(amount); - - self.shielded_two_accs_receiver_uninit( - sender_account_id, - recipient_account_id, + self.send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(sender_account_id), + PrivacyPreservingAccount::PrivateLocal(recipient_account_id), + ], instruction_data, tx_pre_check, program, ) .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected recipient's secret"); + (resp, first) + }) } pub async fn send_transfer_token_transaction_shielded_foreign_account( @@ -261,18 +262,29 @@ impl WalletCore { recipient_npk: NullifierPublicKey, recipient_ipk: IncomingViewingPublicKey, amount: u128, - ) -> Result { + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { let (instruction_data, program, tx_pre_check) = WalletCore::token_program_preparation_transfer(amount); - self.shielded_two_accs_receiver_outer( - sender_account_id, - recipient_npk, - recipient_ipk, + self.send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(sender_account_id), + PrivacyPreservingAccount::PrivateForeign { + npk: recipient_npk, + ipk: recipient_ipk, + }, + ], instruction_data, tx_pre_check, program, ) .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected recipient's secret"); + (resp, first) + }) } } diff --git a/wallet/src/token_transfers/deshielded.rs b/wallet/src/token_transfers/deshielded.rs index 4c8cbe3..216bfb5 100644 --- a/wallet/src/token_transfers/deshielded.rs +++ b/wallet/src/token_transfers/deshielded.rs @@ -1,7 +1,7 @@ use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; use nssa::AccountId; -use crate::WalletCore; +use crate::{PrivacyPreservingAccount, WalletCore}; impl WalletCore { pub async fn send_deshielded_native_token_transfer( @@ -9,11 +9,26 @@ impl WalletCore { from: AccountId, to: AccountId, balance_to_move: u128, - ) -> Result<(SendTxResponse, [nssa_core::SharedSecretKey; 1]), ExecutionFailureKind> { + ) -> Result<(SendTxResponse, nssa_core::SharedSecretKey), ExecutionFailureKind> { let (instruction_data, program, tx_pre_check) = WalletCore::auth_transfer_preparation(balance_to_move); - self.deshielded_tx_two_accs(from, to, instruction_data, tx_pre_check, program) - .await + self.send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateLocal(from), + PrivacyPreservingAccount::Public(to), + ], + instruction_data, + tx_pre_check, + program, + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected sender's secret"); + (resp, first) + }) } } diff --git a/wallet/src/token_transfers/mod.rs b/wallet/src/token_transfers/mod.rs index a785763..6b09698 100644 --- a/wallet/src/token_transfers/mod.rs +++ b/wallet/src/token_transfers/mod.rs @@ -15,11 +15,12 @@ impl WalletCore { ) -> ( InstructionData, Program, - impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, ) { let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); let program = Program::authenticated_transfer_program(); - let tx_pre_check = move |from: &Account, _: &Account| { + let tx_pre_check = move |accounts: &[&Account]| { + let from = accounts[0]; if from.balance >= balance_to_move { Ok(()) } else { diff --git a/wallet/src/token_transfers/private.rs b/wallet/src/token_transfers/private.rs index 35d3e3b..59af480 100644 --- a/wallet/src/token_transfers/private.rs +++ b/wallet/src/token_transfers/private.rs @@ -1,10 +1,10 @@ +use std::vec; + use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; use nssa::AccountId; -use nssa_core::{ - MembershipProof, NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey, -}; +use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey}; -use crate::WalletCore; +use crate::{PrivacyPreservingAccount, WalletCore}; impl WalletCore { pub async fn send_private_native_token_transfer_outer_account( @@ -17,18 +17,28 @@ impl WalletCore { let (instruction_data, program, tx_pre_check) = WalletCore::auth_transfer_preparation(balance_to_move); - self.private_tx_two_accs_receiver_outer( - from, - to_npk, - to_ipk, + self.send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateLocal(from), + PrivacyPreservingAccount::PrivateForeign { + npk: to_npk, + ipk: to_ipk, + }, + ], instruction_data, tx_pre_check, program, ) .await + .map(|(resp, secrets)| { + let mut secrets_iter = secrets.into_iter(); + let first = secrets_iter.next().expect("expected sender's secret"); + let second = secrets_iter.next().expect("expected receiver's secret"); + (resp, [first, second]) + }) } - pub async fn send_private_native_token_transfer_owned_account_not_initialized( + pub async fn send_private_native_token_transfer_owned_account( &self, from: AccountId, to: AccountId, @@ -37,28 +47,21 @@ impl WalletCore { let (instruction_data, program, tx_pre_check) = WalletCore::auth_transfer_preparation(balance_to_move); - self.private_tx_two_accs_receiver_uninit(from, to, instruction_data, tx_pre_check, program) - .await - } - - pub async fn send_private_native_token_transfer_owned_account_already_initialized( - &self, - from: AccountId, - to: AccountId, - balance_to_move: u128, - to_proof: MembershipProof, - ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::auth_transfer_preparation(balance_to_move); - - self.private_tx_two_accs_all_init( - from, - to, + self.send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateLocal(from), + PrivacyPreservingAccount::PrivateLocal(to), + ], instruction_data, tx_pre_check, program, - to_proof, ) .await + .map(|(resp, secrets)| { + let mut secrets_iter = secrets.into_iter(); + let first = secrets_iter.next().expect("expected sender's secret"); + let second = secrets_iter.next().expect("expected receiver's secret"); + (resp, [first, second]) + }) } } diff --git a/wallet/src/token_transfers/shielded.rs b/wallet/src/token_transfers/shielded.rs index 8ba260c..a8d28ee 100644 --- a/wallet/src/token_transfers/shielded.rs +++ b/wallet/src/token_transfers/shielded.rs @@ -1,37 +1,36 @@ use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; use nssa::AccountId; -use nssa_core::{ - MembershipProof, NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey, -}; +use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey}; -use crate::WalletCore; +use crate::{PrivacyPreservingAccount, WalletCore}; impl WalletCore { - pub async fn send_shielded_native_token_transfer_already_initialized( + pub async fn send_shielded_native_token_transfer( &self, from: AccountId, to: AccountId, balance_to_move: u128, - to_proof: MembershipProof, - ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { let (instruction_data, program, tx_pre_check) = WalletCore::auth_transfer_preparation(balance_to_move); - self.shielded_two_accs_all_init(from, to, instruction_data, tx_pre_check, program, to_proof) - .await - } - - pub async fn send_shielded_native_token_transfer_not_initialized( - &self, - from: AccountId, - to: AccountId, - balance_to_move: u128, - ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::auth_transfer_preparation(balance_to_move); - - self.shielded_two_accs_receiver_uninit(from, to, instruction_data, tx_pre_check, program) - .await + self.send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(from), + PrivacyPreservingAccount::PrivateLocal(to), + ], + instruction_data, + tx_pre_check, + program, + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected sender's secret"); + (resp, first) + }) } pub async fn send_shielded_native_token_transfer_outer_account( @@ -40,18 +39,29 @@ impl WalletCore { to_npk: NullifierPublicKey, to_ipk: IncomingViewingPublicKey, balance_to_move: u128, - ) -> Result { + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { let (instruction_data, program, tx_pre_check) = WalletCore::auth_transfer_preparation(balance_to_move); - self.shielded_two_accs_receiver_outer( - from, - to_npk, - to_ipk, + self.send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(from), + PrivacyPreservingAccount::PrivateForeign { + npk: to_npk, + ipk: to_ipk, + }, + ], instruction_data, tx_pre_check, program, ) .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected sender's secret"); + (resp, first) + }) } } diff --git a/wallet/src/transaction_utils.rs b/wallet/src/transaction_utils.rs index a54f81c..10854d9 100644 --- a/wallet/src/transaction_utils.rs +++ b/wallet/src/transaction_utils.rs @@ -1,13 +1,13 @@ use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; use nssa::{ - Account, AccountId, PrivacyPreservingTransaction, + AccountId, PrivacyPreservingTransaction, privacy_preserving_transaction::{circuit, message::Message, witness_set::WitnessSet}, program::Program, }; use nssa_core::{ - Commitment, MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, - account::AccountWithMetadata, encryption::IncomingViewingPublicKey, program::InstructionData, + MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, + account::AccountWithMetadata, encryption::IncomingViewingPublicKey, }; use crate::{WalletCore, helperfunctions::produce_random_nonces}; @@ -42,8 +42,6 @@ impl WalletCore { let from_npk = from_keys.nullifer_public_key; let from_ipk = from_keys.incoming_viewing_public_key; - let sender_commitment = Commitment::new(&from_npk, &from_acc); - let sender_pre = AccountWithMetadata::new(from_acc.clone(), is_authorized, &from_npk); if is_authorized { @@ -51,9 +49,9 @@ impl WalletCore { } if needs_proof { + // TODO: Remove this unwrap, error types must be compatible proof = self - .sequencer_client - .get_proof_for_commitment(sender_commitment) + .check_private_account_initialized(&account_id) .await .unwrap(); } @@ -67,480 +65,7 @@ impl WalletCore { }) } - pub(crate) async fn private_tx_two_accs_all_init( - &self, - from: AccountId, - to: AccountId, - instruction_data: InstructionData, - tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, - program: Program, - to_proof: MembershipProof, - ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let AccountPreparedData { - nsk: from_nsk, - npk: from_npk, - ipk: from_ipk, - auth_acc: sender_pre, - proof: from_proof, - } = self.private_acc_preparation(from, true, true).await?; - - let AccountPreparedData { - nsk: to_nsk, - npk: to_npk, - ipk: to_ipk, - auth_acc: recipient_pre, - proof: _, - } = self.private_acc_preparation(to, true, false).await?; - - tx_pre_check(&sender_pre.account, &recipient_pre.account)?; - - let eph_holder_from = EphemeralKeyHolder::new(&from_npk); - let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk); - - let eph_holder_to = EphemeralKeyHolder::new(&to_npk); - let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_ipk); - - let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &instruction_data, - &[1, 1], - &produce_random_nonces(2), - &[ - (from_npk.clone(), shared_secret_from.clone()), - (to_npk.clone(), shared_secret_to.clone()), - ], - &[ - (from_nsk.unwrap(), from_proof.unwrap()), - (to_nsk.unwrap(), to_proof), - ], - &program, - ) - .unwrap(); - - let message = Message::try_from_circuit_output( - vec![], - vec![], - vec![ - ( - from_npk.clone(), - from_ipk.clone(), - eph_holder_from.generate_ephemeral_public_key(), - ), - ( - to_npk.clone(), - to_ipk.clone(), - eph_holder_to.generate_ephemeral_public_key(), - ), - ], - output, - ) - .unwrap(); - - let witness_set = WitnessSet::for_message(&message, proof, &[]); - let tx = PrivacyPreservingTransaction::new(message, witness_set); - - Ok(( - self.sequencer_client.send_tx_private(tx).await?, - [shared_secret_from, shared_secret_to], - )) - } - - pub(crate) async fn private_tx_two_accs_receiver_uninit( - &self, - from: AccountId, - to: AccountId, - instruction_data: InstructionData, - tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, - program: Program, - ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let AccountPreparedData { - nsk: from_nsk, - npk: from_npk, - ipk: from_ipk, - auth_acc: sender_pre, - proof: from_proof, - } = self.private_acc_preparation(from, true, true).await?; - - let AccountPreparedData { - nsk: _, - npk: to_npk, - ipk: to_ipk, - auth_acc: recipient_pre, - proof: _, - } = self.private_acc_preparation(to, false, false).await?; - - tx_pre_check(&sender_pre.account, &recipient_pre.account)?; - - let eph_holder_from = EphemeralKeyHolder::new(&from_npk); - let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk); - - let eph_holder_to = EphemeralKeyHolder::new(&to_npk); - let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_ipk); - - let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &instruction_data, - &[1, 2], - &produce_random_nonces(2), - &[ - (from_npk.clone(), shared_secret_from.clone()), - (to_npk.clone(), shared_secret_to.clone()), - ], - &[(from_nsk.unwrap(), from_proof.unwrap())], - &program, - ) - .unwrap(); - - let message = Message::try_from_circuit_output( - vec![], - vec![], - vec![ - ( - from_npk.clone(), - from_ipk.clone(), - eph_holder_from.generate_ephemeral_public_key(), - ), - ( - to_npk.clone(), - to_ipk.clone(), - eph_holder_to.generate_ephemeral_public_key(), - ), - ], - output, - ) - .unwrap(); - - let witness_set = WitnessSet::for_message(&message, proof, &[]); - let tx = PrivacyPreservingTransaction::new(message, witness_set); - - Ok(( - self.sequencer_client.send_tx_private(tx).await?, - [shared_secret_from, shared_secret_to], - )) - } - - pub(crate) async fn private_tx_two_accs_receiver_outer( - &self, - from: AccountId, - to_npk: NullifierPublicKey, - to_ipk: IncomingViewingPublicKey, - instruction_data: InstructionData, - tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, - program: Program, - ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let AccountPreparedData { - nsk: from_nsk, - npk: from_npk, - ipk: from_ipk, - auth_acc: sender_pre, - proof: from_proof, - } = self.private_acc_preparation(from, true, true).await?; - - let to_acc = nssa_core::account::Account::default(); - - tx_pre_check(&sender_pre.account, &to_acc)?; - - let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk); - - let eph_holder = EphemeralKeyHolder::new(&to_npk); - - let shared_secret_from = eph_holder.calculate_shared_secret_sender(&from_ipk); - let shared_secret_to = eph_holder.calculate_shared_secret_sender(&to_ipk); - - let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &instruction_data, - &[1, 2], - &produce_random_nonces(2), - &[ - (from_npk.clone(), shared_secret_from.clone()), - (to_npk.clone(), shared_secret_to.clone()), - ], - &[(from_nsk.unwrap(), from_proof.unwrap())], - &program, - ) - .unwrap(); - - let message = Message::try_from_circuit_output( - vec![], - vec![], - vec![ - ( - from_npk.clone(), - from_ipk.clone(), - eph_holder.generate_ephemeral_public_key(), - ), - ( - to_npk.clone(), - to_ipk.clone(), - eph_holder.generate_ephemeral_public_key(), - ), - ], - output, - ) - .unwrap(); - - let witness_set = WitnessSet::for_message(&message, proof, &[]); - - let tx = PrivacyPreservingTransaction::new(message, witness_set); - - Ok(( - self.sequencer_client.send_tx_private(tx).await?, - [shared_secret_from, shared_secret_to], - )) - } - - pub(crate) async fn deshielded_tx_two_accs( - &self, - from: AccountId, - to: AccountId, - instruction_data: InstructionData, - tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, - program: Program, - ) -> Result<(SendTxResponse, [nssa_core::SharedSecretKey; 1]), ExecutionFailureKind> { - let AccountPreparedData { - nsk: from_nsk, - npk: from_npk, - ipk: from_ipk, - auth_acc: sender_pre, - proof: from_proof, - } = self.private_acc_preparation(from, true, true).await?; - - let Ok(to_acc) = self.get_account_public(to).await else { - return Err(ExecutionFailureKind::KeyNotFoundError); - }; - - tx_pre_check(&sender_pre.account, &to_acc)?; - - let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, to); - - let eph_holder = EphemeralKeyHolder::new(&from_npk); - let shared_secret = eph_holder.calculate_shared_secret_sender(&from_ipk); - - let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &instruction_data, - &[1, 0], - &produce_random_nonces(1), - &[(from_npk.clone(), shared_secret.clone())], - &[(from_nsk.unwrap(), from_proof.unwrap())], - &program, - ) - .unwrap(); - - let message = Message::try_from_circuit_output( - vec![to], - vec![], - vec![( - from_npk.clone(), - from_ipk.clone(), - eph_holder.generate_ephemeral_public_key(), - )], - output, - ) - .unwrap(); - - let witness_set = WitnessSet::for_message(&message, proof, &[]); - - let tx = PrivacyPreservingTransaction::new(message, witness_set); - - Ok(( - self.sequencer_client.send_tx_private(tx).await?, - [shared_secret], - )) - } - - pub(crate) async fn shielded_two_accs_all_init( - &self, - from: AccountId, - to: AccountId, - instruction_data: InstructionData, - tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, - program: Program, - to_proof: MembershipProof, - ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { - let Ok(from_acc) = self.get_account_public(from).await else { - return Err(ExecutionFailureKind::KeyNotFoundError); - }; - - let AccountPreparedData { - nsk: to_nsk, - npk: to_npk, - ipk: to_ipk, - auth_acc: recipient_pre, - proof: _, - } = self.private_acc_preparation(to, true, false).await?; - - tx_pre_check(&from_acc, &recipient_pre.account)?; - - let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); - - let eph_holder = EphemeralKeyHolder::new(&to_npk); - let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk); - - let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &instruction_data, - &[0, 1], - &produce_random_nonces(1), - &[(to_npk.clone(), shared_secret.clone())], - &[(to_nsk.unwrap(), to_proof)], - &program, - ) - .unwrap(); - - let message = Message::try_from_circuit_output( - vec![from], - vec![from_acc.nonce], - vec![( - to_npk.clone(), - to_ipk.clone(), - eph_holder.generate_ephemeral_public_key(), - )], - output, - ) - .unwrap(); - - let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); - - let Some(signing_key) = signing_key else { - return Err(ExecutionFailureKind::KeyNotFoundError); - }; - - let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]); - - let tx = PrivacyPreservingTransaction::new(message, witness_set); - - Ok(( - self.sequencer_client.send_tx_private(tx).await?, - [shared_secret], - )) - } - - pub(crate) async fn shielded_two_accs_receiver_uninit( - &self, - from: AccountId, - to: AccountId, - instruction_data: InstructionData, - tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, - program: Program, - ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { - let Ok(from_acc) = self.get_account_public(from).await else { - return Err(ExecutionFailureKind::KeyNotFoundError); - }; - - let AccountPreparedData { - nsk: _, - npk: to_npk, - ipk: to_ipk, - auth_acc: recipient_pre, - proof: _, - } = self.private_acc_preparation(to, false, false).await?; - - tx_pre_check(&from_acc, &recipient_pre.account)?; - - let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); - - let eph_holder = EphemeralKeyHolder::new(&to_npk); - let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk); - - let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &instruction_data, - &[0, 2], - &produce_random_nonces(1), - &[(to_npk.clone(), shared_secret.clone())], - &[], - &program, - ) - .unwrap(); - - let message = Message::try_from_circuit_output( - vec![from], - vec![from_acc.nonce], - vec![( - to_npk.clone(), - to_ipk.clone(), - eph_holder.generate_ephemeral_public_key(), - )], - output, - ) - .unwrap(); - - let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); - - let Some(signing_key) = signing_key else { - return Err(ExecutionFailureKind::KeyNotFoundError); - }; - - let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]); - - let tx = PrivacyPreservingTransaction::new(message, witness_set); - - Ok(( - self.sequencer_client.send_tx_private(tx).await?, - [shared_secret], - )) - } - - pub(crate) async fn shielded_two_accs_receiver_outer( - &self, - from: AccountId, - to_npk: NullifierPublicKey, - to_ipk: IncomingViewingPublicKey, - instruction_data: InstructionData, - tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, - program: Program, - ) -> Result { - let Ok(from_acc) = self.get_account_public(from).await else { - return Err(ExecutionFailureKind::KeyNotFoundError); - }; - - let to_acc = Account::default(); - - tx_pre_check(&from_acc, &to_acc)?; - - let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); - let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk); - - let eph_holder = EphemeralKeyHolder::new(&to_npk); - let shared_secret = eph_holder.calculate_shared_secret_sender(&to_ipk); - - let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &instruction_data, - &[0, 2], - &produce_random_nonces(1), - &[(to_npk.clone(), shared_secret.clone())], - &[], - &program, - ) - .unwrap(); - - let message = Message::try_from_circuit_output( - vec![from], - vec![from_acc.nonce], - vec![( - to_npk.clone(), - to_ipk.clone(), - eph_holder.generate_ephemeral_public_key(), - )], - output, - ) - .unwrap(); - - let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); - - let Some(signing_key) = signing_key else { - return Err(ExecutionFailureKind::KeyNotFoundError); - }; - - let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]); - let tx = PrivacyPreservingTransaction::new(message, witness_set); - - Ok(self.sequencer_client.send_tx_private(tx).await?) - } - + // TODO: Remove pub async fn register_account_under_authenticated_transfers_programs_private( &self, from: AccountId, From 55fc4e977770f51d96a9400f001093bfaa2bfdfb Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Sun, 30 Nov 2025 01:57:59 +0300 Subject: [PATCH 18/21] refactor: implement program interactions as facades --- .../src/cli/programs/native_token_transfer.rs | 29 +- wallet/src/cli/programs/pinata.rs | 28 +- wallet/src/cli/programs/token.rs | 33 +- wallet/src/lib.rs | 11 +- wallet/src/privacy_preserving_tx.rs | 4 +- wallet/src/program_facades/mod.rs | 6 + .../native_token_transfer/deshielded.rs | 35 +++ .../native_token_transfer/mod.rs | 33 ++ .../native_token_transfer/private.rs | 68 ++++ .../native_token_transfer}/public.rs | 22 +- .../native_token_transfer/shielded.rs | 68 ++++ wallet/src/program_facades/pinata.rs | 53 ++++ wallet/src/program_facades/token.rs | 294 ++++++++++++++++++ wallet/src/program_interactions/mod.rs | 2 - wallet/src/program_interactions/pinata.rs | 161 ---------- wallet/src/program_interactions/token.rs | 290 ----------------- wallet/src/token_transfers/deshielded.rs | 34 -- wallet/src/token_transfers/mod.rs | 33 -- wallet/src/token_transfers/private.rs | 67 ---- wallet/src/token_transfers/shielded.rs | 67 ---- 20 files changed, 612 insertions(+), 726 deletions(-) create mode 100644 wallet/src/program_facades/mod.rs create mode 100644 wallet/src/program_facades/native_token_transfer/deshielded.rs create mode 100644 wallet/src/program_facades/native_token_transfer/mod.rs create mode 100644 wallet/src/program_facades/native_token_transfer/private.rs rename wallet/src/{token_transfers => program_facades/native_token_transfer}/public.rs (73%) create mode 100644 wallet/src/program_facades/native_token_transfer/shielded.rs create mode 100644 wallet/src/program_facades/pinata.rs create mode 100644 wallet/src/program_facades/token.rs delete mode 100644 wallet/src/program_interactions/mod.rs delete mode 100644 wallet/src/program_interactions/pinata.rs delete mode 100644 wallet/src/program_interactions/token.rs delete mode 100644 wallet/src/token_transfers/deshielded.rs delete mode 100644 wallet/src/token_transfers/mod.rs delete mode 100644 wallet/src/token_transfers/private.rs delete mode 100644 wallet/src/token_transfers/shielded.rs diff --git a/wallet/src/cli/programs/native_token_transfer.rs b/wallet/src/cli/programs/native_token_transfer.rs index 3b1f2ae..12c263f 100644 --- a/wallet/src/cli/programs/native_token_transfer.rs +++ b/wallet/src/cli/programs/native_token_transfer.rs @@ -7,6 +7,7 @@ use crate::{ WalletCore, cli::{SubcommandReturnValue, WalletSubcommand}, helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix}, + program_facades::native_token_transfer::NativeTokenTransfer, }; /// Represents generic CLI subcommand for a wallet working with native token transfer program @@ -56,8 +57,8 @@ impl WalletSubcommand for AuthTransferSubcommand { AccountPrivacyKind::Public => { let account_id = account_id.parse()?; - let res = wallet_core - .register_account_under_authenticated_transfers_programs(account_id) + let res = NativeTokenTransfer(wallet_core) + .register_account(account_id) .await?; println!("Results of tx send is {res:#?}"); @@ -317,8 +318,8 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { let from: AccountId = from.parse().unwrap(); let to: AccountId = to.parse().unwrap(); - let (res, [secret_from, secret_to]) = wallet_core - .send_private_native_token_transfer_owned_account(from, to, amount) + let (res, [secret_from, secret_to]) = NativeTokenTransfer(wallet_core) + .send_private_transfer_to_owned_account(from, to, amount) .await?; println!("Results of tx send is {res:#?}"); @@ -361,8 +362,8 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { let to_ipk = nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_ipk.to_vec()); - let (res, [secret_from, _]) = wallet_core - .send_private_native_token_transfer_outer_account(from, to_npk, to_ipk, amount) + let (res, [secret_from, _]) = NativeTokenTransfer(wallet_core) + .send_private_transfer_to_outer_account(from, to_npk, to_ipk, amount) .await?; println!("Results of tx send is {res:#?}"); @@ -401,8 +402,8 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { let from: AccountId = from.parse().unwrap(); let to: AccountId = to.parse().unwrap(); - let (res, secret) = wallet_core - .send_shielded_native_token_transfer(from, to, amount) + let (res, secret) = NativeTokenTransfer(wallet_core) + .send_shielded_transfer(from, to, amount) .await?; println!("Results of tx send is {res:#?}"); @@ -446,8 +447,8 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { let to_ipk = nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_ipk.to_vec()); - let (res, _) = wallet_core - .send_shielded_native_token_transfer_outer_account(from, to_npk, to_ipk, amount) + let (res, _) = NativeTokenTransfer(wallet_core) + .send_shielded_transfer_to_outer_account(from, to_npk, to_ipk, amount) .await?; println!("Results of tx send is {res:#?}"); @@ -480,8 +481,8 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { let from: AccountId = from.parse().unwrap(); let to: AccountId = to.parse().unwrap(); - let (res, secret) = wallet_core - .send_deshielded_native_token_transfer(from, to, amount) + let (res, secret) = NativeTokenTransfer(wallet_core) + .send_deshielded_transfer(from, to, amount) .await?; println!("Results of tx send is {res:#?}"); @@ -510,8 +511,8 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { let from: AccountId = from.parse().unwrap(); let to: AccountId = to.parse().unwrap(); - let res = wallet_core - .send_public_native_token_transfer(from, to, amount) + let res = NativeTokenTransfer(wallet_core) + .send_public_transfer(from, to, amount) .await?; println!("Results of tx send is {res:#?}"); diff --git a/wallet/src/cli/programs/pinata.rs b/wallet/src/cli/programs/pinata.rs index 3d8dac3..cabee4c 100644 --- a/wallet/src/cli/programs/pinata.rs +++ b/wallet/src/cli/programs/pinata.rs @@ -7,6 +7,7 @@ use crate::{ WalletCore, cli::{SubcommandReturnValue, WalletSubcommand}, helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix}, + program_facades::pinata::Pinata, }; /// Represents generic CLI subcommand for a wallet working with pinata program @@ -117,8 +118,8 @@ impl WalletSubcommand for PinataProgramSubcommandPublic { winner_account_id, solution, } => { - let res = wallet_core - .claim_pinata( + let res = Pinata(wallet_core) + .claim( pinata_account_id.parse().unwrap(), winner_account_id.parse().unwrap(), solution, @@ -146,29 +147,10 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate { let pinata_account_id = pinata_account_id.parse().unwrap(); let winner_account_id = winner_account_id.parse().unwrap(); - let winner_initialization = wallet_core - .check_private_account_initialized(&winner_account_id) + let (res, secret_winner) = Pinata(wallet_core) + .claim_private_owned_account(pinata_account_id, winner_account_id, solution) .await?; - let (res, [secret_winner]) = if let Some(winner_proof) = winner_initialization { - wallet_core - .claim_pinata_private_owned_account_already_initialized( - pinata_account_id, - winner_account_id, - solution, - winner_proof, - ) - .await? - } else { - wallet_core - .claim_pinata_private_owned_account_not_initialized( - pinata_account_id, - winner_account_id, - solution, - ) - .await? - }; - info!("Results of tx send is {res:#?}"); let tx_hash = res.tx_hash; diff --git a/wallet/src/cli/programs/token.rs b/wallet/src/cli/programs/token.rs index 0671e4b..1fceb74 100644 --- a/wallet/src/cli/programs/token.rs +++ b/wallet/src/cli/programs/token.rs @@ -7,6 +7,7 @@ use crate::{ WalletCore, cli::{SubcommandReturnValue, WalletSubcommand}, helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix}, + program_facades::token::Token, }; /// Represents generic CLI subcommand for a wallet working with token program @@ -338,8 +339,8 @@ impl WalletSubcommand for TokenProgramSubcommandPublic { } let mut name_bytes = [0; 6]; name_bytes[..name.len()].copy_from_slice(name); - wallet_core - .send_new_token_definition( + Token(wallet_core) + .send_new_definition( definition_account_id.parse().unwrap(), supply_account_id.parse().unwrap(), name_bytes, @@ -353,8 +354,8 @@ impl WalletSubcommand for TokenProgramSubcommandPublic { recipient_account_id, balance_to_move, } => { - wallet_core - .send_transfer_token_transaction( + Token(wallet_core) + .send_transfer_transaction( sender_account_id.parse().unwrap(), recipient_account_id.parse().unwrap(), balance_to_move, @@ -389,8 +390,8 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { let definition_account_id: AccountId = definition_account_id.parse().unwrap(); let supply_account_id: AccountId = supply_account_id.parse().unwrap(); - let (res, secret_supply) = wallet_core - .send_new_token_definition_private_owned( + let (res, secret_supply) = Token(wallet_core) + .send_new_definition_private_owned( definition_account_id, supply_account_id, name_bytes, @@ -428,8 +429,8 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { let sender_account_id: AccountId = sender_account_id.parse().unwrap(); let recipient_account_id: AccountId = recipient_account_id.parse().unwrap(); - let (res, [secret_sender, secret_recipient]) = wallet_core - .send_transfer_token_transaction_private_owned_account( + let (res, [secret_sender, secret_recipient]) = Token(wallet_core) + .send_transfer_transaction_private_owned_account( sender_account_id, recipient_account_id, balance_to_move, @@ -480,8 +481,8 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { recipient_ipk.to_vec(), ); - let (res, [secret_sender, _]) = wallet_core - .send_transfer_token_transaction_private_foreign_account( + let (res, [secret_sender, _]) = Token(wallet_core) + .send_transfer_transaction_private_foreign_account( sender_account_id, recipient_npk, recipient_ipk, @@ -529,8 +530,8 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded { let sender_account_id: AccountId = sender_account_id.parse().unwrap(); let recipient_account_id: AccountId = recipient_account_id.parse().unwrap(); - let (res, secret_sender) = wallet_core - .send_transfer_token_transaction_deshielded( + let (res, secret_sender) = Token(wallet_core) + .send_transfer_transaction_deshielded( sender_account_id, recipient_account_id, balance_to_move, @@ -588,8 +589,8 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { recipient_ipk.to_vec(), ); - let (res, _) = wallet_core - .send_transfer_token_transaction_shielded_foreign_account( + let (res, _) = Token(wallet_core) + .send_transfer_transaction_shielded_foreign_account( sender_account_id, recipient_npk, recipient_ipk, @@ -622,8 +623,8 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { let sender_account_id: AccountId = sender_account_id.parse().unwrap(); let recipient_account_id: AccountId = recipient_account_id.parse().unwrap(); - let (res, secret_recipient) = wallet_core - .send_transfer_token_transaction_shielded_owned_account( + let (res, secret_recipient) = Token(wallet_core) + .send_transfer_transaction_shielded_owned_account( sender_account_id, recipient_account_id, balance_to_move, diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 4cfbab3..f45bf93 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -32,8 +32,7 @@ pub mod config; pub mod helperfunctions; pub mod poller; mod privacy_preserving_tx; -pub mod program_interactions; -pub mod token_transfers; +pub mod program_facades; pub mod transaction_utils; pub struct WalletCore { @@ -224,9 +223,9 @@ impl WalletCore { pub async fn send_privacy_preserving_tx( &self, accounts: Vec, - instruction_data: InstructionData, + instruction_data: &InstructionData, tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, - program: Program, + program: &Program, ) -> Result<(SendTxResponse, Vec), ExecutionFailureKind> { let payload = privacy_preserving_tx::Payload::new(self, accounts).await?; @@ -241,7 +240,7 @@ impl WalletCore { let private_account_keys = payload.private_account_keys(); let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove( &pre_states, - &instruction_data, + instruction_data, payload.visibility_mask(), &produce_random_nonces(private_account_keys.len()), &private_account_keys @@ -249,7 +248,7 @@ impl WalletCore { .map(|keys| (keys.npk.clone(), keys.ssk.clone())) .collect::>(), &payload.private_account_auth(), - &program, + program, ) .unwrap(); diff --git a/wallet/src/privacy_preserving_tx.rs b/wallet/src/privacy_preserving_tx.rs index be96a7a..2d670c3 100644 --- a/wallet/src/privacy_preserving_tx.rs +++ b/wallet/src/privacy_preserving_tx.rs @@ -11,7 +11,7 @@ use crate::{WalletCore, transaction_utils::AccountPreparedData}; pub enum PrivacyPreservingAccount { Public(AccountId), - PrivateLocal(AccountId), + PrivateOwned(AccountId), PrivateForeign { npk: NullifierPublicKey, ipk: IncomingViewingPublicKey, @@ -59,7 +59,7 @@ impl Payload { (State::Public { account, sk }, 0) } - PrivacyPreservingAccount::PrivateLocal(account_id) => { + PrivacyPreservingAccount::PrivateOwned(account_id) => { let mut pre = wallet .private_acc_preparation(account_id, true, true) .await?; diff --git a/wallet/src/program_facades/mod.rs b/wallet/src/program_facades/mod.rs new file mode 100644 index 0000000..27d30ce --- /dev/null +++ b/wallet/src/program_facades/mod.rs @@ -0,0 +1,6 @@ +//! This module contains [`WalletCore`](crate::WalletCore) facades for interacting with various +//! on-chain programs. + +pub mod native_token_transfer; +pub mod pinata; +pub mod token; diff --git a/wallet/src/program_facades/native_token_transfer/deshielded.rs b/wallet/src/program_facades/native_token_transfer/deshielded.rs new file mode 100644 index 0000000..f4e45b6 --- /dev/null +++ b/wallet/src/program_facades/native_token_transfer/deshielded.rs @@ -0,0 +1,35 @@ +use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; +use nssa::AccountId; + +use super::{NativeTokenTransfer, auth_transfer_preparation}; +use crate::PrivacyPreservingAccount; + +impl NativeTokenTransfer<'_> { + pub async fn send_deshielded_transfer( + &self, + from: AccountId, + to: AccountId, + balance_to_move: u128, + ) -> Result<(SendTxResponse, nssa_core::SharedSecretKey), ExecutionFailureKind> { + let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(from), + PrivacyPreservingAccount::Public(to), + ], + &instruction_data, + tx_pre_check, + &program, + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected sender's secret"); + (resp, first) + }) + } +} diff --git a/wallet/src/program_facades/native_token_transfer/mod.rs b/wallet/src/program_facades/native_token_transfer/mod.rs new file mode 100644 index 0000000..693ef8d --- /dev/null +++ b/wallet/src/program_facades/native_token_transfer/mod.rs @@ -0,0 +1,33 @@ +use common::error::ExecutionFailureKind; +use nssa::{Account, program::Program}; +use nssa_core::program::InstructionData; + +use crate::WalletCore; + +pub mod deshielded; +pub mod private; +pub mod public; +pub mod shielded; + +pub struct NativeTokenTransfer<'w>(pub &'w WalletCore); + +fn auth_transfer_preparation( + balance_to_move: u128, +) -> ( + InstructionData, + Program, + impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, +) { + let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); + let program = Program::authenticated_transfer_program(); + let tx_pre_check = move |accounts: &[&Account]| { + let from = accounts[0]; + if from.balance >= balance_to_move { + Ok(()) + } else { + Err(ExecutionFailureKind::InsufficientFundsError) + } + }; + + (instruction_data, program, tx_pre_check) +} diff --git a/wallet/src/program_facades/native_token_transfer/private.rs b/wallet/src/program_facades/native_token_transfer/private.rs new file mode 100644 index 0000000..39a4781 --- /dev/null +++ b/wallet/src/program_facades/native_token_transfer/private.rs @@ -0,0 +1,68 @@ +use std::vec; + +use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; +use nssa::AccountId; +use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey}; + +use super::{NativeTokenTransfer, auth_transfer_preparation}; +use crate::PrivacyPreservingAccount; + +impl NativeTokenTransfer<'_> { + pub async fn send_private_transfer_to_outer_account( + &self, + from: AccountId, + to_npk: NullifierPublicKey, + to_ipk: IncomingViewingPublicKey, + balance_to_move: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(from), + PrivacyPreservingAccount::PrivateForeign { + npk: to_npk, + ipk: to_ipk, + }, + ], + &instruction_data, + tx_pre_check, + &program, + ) + .await + .map(|(resp, secrets)| { + let mut secrets_iter = secrets.into_iter(); + let first = secrets_iter.next().expect("expected sender's secret"); + let second = secrets_iter.next().expect("expected receiver's secret"); + (resp, [first, second]) + }) + } + + pub async fn send_private_transfer_to_owned_account( + &self, + from: AccountId, + to: AccountId, + balance_to_move: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(from), + PrivacyPreservingAccount::PrivateOwned(to), + ], + &instruction_data, + tx_pre_check, + &program, + ) + .await + .map(|(resp, secrets)| { + let mut secrets_iter = secrets.into_iter(); + let first = secrets_iter.next().expect("expected sender's secret"); + let second = secrets_iter.next().expect("expected receiver's secret"); + (resp, [first, second]) + }) + } +} diff --git a/wallet/src/token_transfers/public.rs b/wallet/src/program_facades/native_token_transfer/public.rs similarity index 73% rename from wallet/src/token_transfers/public.rs rename to wallet/src/program_facades/native_token_transfer/public.rs index a63d838..2edab15 100644 --- a/wallet/src/token_transfers/public.rs +++ b/wallet/src/program_facades/native_token_transfer/public.rs @@ -5,21 +5,21 @@ use nssa::{ public_transaction::{Message, WitnessSet}, }; -use crate::WalletCore; +use super::NativeTokenTransfer; -impl WalletCore { - pub async fn send_public_native_token_transfer( +impl NativeTokenTransfer<'_> { + pub async fn send_public_transfer( &self, from: AccountId, to: AccountId, balance_to_move: u128, ) -> Result { - let Ok(balance) = self.get_account_balance(from).await else { + let Ok(balance) = self.0.get_account_balance(from).await else { return Err(ExecutionFailureKind::SequencerError); }; if balance >= balance_to_move { - let Ok(nonces) = self.get_accounts_nonces(vec![from]).await else { + let Ok(nonces) = self.0.get_accounts_nonces(vec![from]).await else { return Err(ExecutionFailureKind::SequencerError); }; @@ -28,7 +28,7 @@ impl WalletCore { let message = Message::try_new(program_id, account_ids, nonces, balance_to_move).unwrap(); - let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); + let signing_key = self.0.storage.user_data.get_pub_account_signing_key(&from); let Some(signing_key) = signing_key else { return Err(ExecutionFailureKind::KeyNotFoundError); @@ -38,17 +38,17 @@ impl WalletCore { let tx = PublicTransaction::new(message, witness_set); - Ok(self.sequencer_client.send_tx_public(tx).await?) + Ok(self.0.sequencer_client.send_tx_public(tx).await?) } else { Err(ExecutionFailureKind::InsufficientFundsError) } } - pub async fn register_account_under_authenticated_transfers_programs( + pub async fn register_account( &self, from: AccountId, ) -> Result { - let Ok(nonces) = self.get_accounts_nonces(vec![from]).await else { + let Ok(nonces) = self.0.get_accounts_nonces(vec![from]).await else { return Err(ExecutionFailureKind::SequencerError); }; @@ -57,7 +57,7 @@ impl WalletCore { let program_id = Program::authenticated_transfer_program().id(); let message = Message::try_new(program_id, account_ids, nonces, instruction).unwrap(); - let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); + let signing_key = self.0.storage.user_data.get_pub_account_signing_key(&from); let Some(signing_key) = signing_key else { return Err(ExecutionFailureKind::KeyNotFoundError); @@ -67,6 +67,6 @@ impl WalletCore { let tx = PublicTransaction::new(message, witness_set); - Ok(self.sequencer_client.send_tx_public(tx).await?) + Ok(self.0.sequencer_client.send_tx_public(tx).await?) } } diff --git a/wallet/src/program_facades/native_token_transfer/shielded.rs b/wallet/src/program_facades/native_token_transfer/shielded.rs new file mode 100644 index 0000000..d40d5d4 --- /dev/null +++ b/wallet/src/program_facades/native_token_transfer/shielded.rs @@ -0,0 +1,68 @@ +use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; +use nssa::AccountId; +use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey}; + +use super::{NativeTokenTransfer, auth_transfer_preparation}; +use crate::PrivacyPreservingAccount; + +impl NativeTokenTransfer<'_> { + pub async fn send_shielded_transfer( + &self, + from: AccountId, + to: AccountId, + balance_to_move: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(from), + PrivacyPreservingAccount::PrivateOwned(to), + ], + &instruction_data, + tx_pre_check, + &program, + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected sender's secret"); + (resp, first) + }) + } + + pub async fn send_shielded_transfer_to_outer_account( + &self, + from: AccountId, + to_npk: NullifierPublicKey, + to_ipk: IncomingViewingPublicKey, + balance_to_move: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(from), + PrivacyPreservingAccount::PrivateForeign { + npk: to_npk, + ipk: to_ipk, + }, + ], + &instruction_data, + tx_pre_check, + &program, + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected sender's secret"); + (resp, first) + }) + } +} diff --git a/wallet/src/program_facades/pinata.rs b/wallet/src/program_facades/pinata.rs new file mode 100644 index 0000000..6367bfc --- /dev/null +++ b/wallet/src/program_facades/pinata.rs @@ -0,0 +1,53 @@ +use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; +use nssa::AccountId; +use nssa_core::SharedSecretKey; + +use crate::{PrivacyPreservingAccount, WalletCore}; + +pub struct Pinata<'w>(pub &'w WalletCore); + +impl Pinata<'_> { + pub async fn claim( + &self, + pinata_account_id: AccountId, + winner_account_id: AccountId, + solution: u128, + ) -> Result { + let account_ids = vec![pinata_account_id, winner_account_id]; + let program_id = nssa::program::Program::pinata().id(); + let message = + nssa::public_transaction::Message::try_new(program_id, account_ids, vec![], solution) + .unwrap(); + + let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.0.sequencer_client.send_tx_public(tx).await?) + } + + pub async fn claim_private_owned_account( + &self, + pinata_account_id: AccountId, + winner_account_id: AccountId, + solution: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(pinata_account_id), + PrivacyPreservingAccount::PrivateOwned(winner_account_id), + ], + &nssa::program::Program::serialize_instruction(solution).unwrap(), + |_| Ok(()), + &nssa::program::Program::pinata(), + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected recipient's secret"); + (resp, first) + }) + } +} diff --git a/wallet/src/program_facades/token.rs b/wallet/src/program_facades/token.rs new file mode 100644 index 0000000..a4969de --- /dev/null +++ b/wallet/src/program_facades/token.rs @@ -0,0 +1,294 @@ +use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; +use nssa::{Account, AccountId, program::Program}; +use nssa_core::{ + NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey, + program::InstructionData, +}; + +use crate::{PrivacyPreservingAccount, WalletCore}; + +pub struct Token<'w>(pub &'w WalletCore); + +impl Token<'_> { + pub async fn send_new_definition( + &self, + definition_account_id: AccountId, + supply_account_id: AccountId, + name: [u8; 6], + total_supply: u128, + ) -> Result { + let account_ids = vec![definition_account_id, supply_account_id]; + let program_id = nssa::program::Program::token().id(); + // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] + let mut instruction = [0; 23]; + instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); + instruction[17..].copy_from_slice(&name); + let message = nssa::public_transaction::Message::try_new( + program_id, + account_ids, + vec![], + instruction, + ) + .unwrap(); + + let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.0.sequencer_client.send_tx_public(tx).await?) + } + + pub async fn send_new_definition_private_owned( + &self, + definition_account_id: AccountId, + supply_account_id: AccountId, + name: [u8; 6], + total_supply: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let (instruction_data, program, tx_pre_check) = + token_program_preparation_definition(name, total_supply); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(definition_account_id), + PrivacyPreservingAccount::PrivateOwned(supply_account_id), + ], + &instruction_data, + tx_pre_check, + &program, + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected recipient's secret"); + (resp, first) + }) + } + + pub async fn send_transfer_transaction( + &self, + sender_account_id: AccountId, + recipient_account_id: AccountId, + amount: u128, + ) -> Result { + let account_ids = vec![sender_account_id, recipient_account_id]; + let program_id = nssa::program::Program::token().id(); + // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || + // 0x00 || 0x00 || 0x00]. + let mut instruction = [0; 23]; + instruction[0] = 0x01; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + let Ok(nonces) = self.0.get_accounts_nonces(vec![sender_account_id]).await else { + return Err(ExecutionFailureKind::SequencerError); + }; + let message = nssa::public_transaction::Message::try_new( + program_id, + account_ids, + nonces, + instruction, + ) + .unwrap(); + + let Some(signing_key) = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&sender_account_id) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + let witness_set = + nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.0.sequencer_client.send_tx_public(tx).await?) + } + + pub async fn send_transfer_transaction_private_owned_account( + &self, + sender_account_id: AccountId, + recipient_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let (instruction_data, program, tx_pre_check) = token_program_preparation_transfer(amount); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(sender_account_id), + PrivacyPreservingAccount::PrivateOwned(recipient_account_id), + ], + &instruction_data, + tx_pre_check, + &program, + ) + .await + .map(|(resp, secrets)| { + let mut iter = secrets.into_iter(); + let first = iter.next().expect("expected sender's secret"); + let second = iter.next().expect("expected recipient's secret"); + (resp, [first, second]) + }) + } + + pub async fn send_transfer_transaction_private_foreign_account( + &self, + sender_account_id: AccountId, + recipient_npk: NullifierPublicKey, + recipient_ipk: IncomingViewingPublicKey, + amount: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let (instruction_data, program, tx_pre_check) = token_program_preparation_transfer(amount); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(sender_account_id), + PrivacyPreservingAccount::PrivateForeign { + npk: recipient_npk, + ipk: recipient_ipk, + }, + ], + &instruction_data, + tx_pre_check, + &program, + ) + .await + .map(|(resp, secrets)| { + let mut iter = secrets.into_iter(); + let first = iter.next().expect("expected sender's secret"); + let second = iter.next().expect("expected recipient's secret"); + (resp, [first, second]) + }) + } + + pub async fn send_transfer_transaction_deshielded( + &self, + sender_account_id: AccountId, + recipient_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let (instruction_data, program, tx_pre_check) = token_program_preparation_transfer(amount); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(sender_account_id), + PrivacyPreservingAccount::Public(recipient_account_id), + ], + &instruction_data, + tx_pre_check, + &program, + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected sender's secret"); + (resp, first) + }) + } + + pub async fn send_transfer_transaction_shielded_owned_account( + &self, + sender_account_id: AccountId, + recipient_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let (instruction_data, program, tx_pre_check) = token_program_preparation_transfer(amount); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(sender_account_id), + PrivacyPreservingAccount::PrivateOwned(recipient_account_id), + ], + &instruction_data, + tx_pre_check, + &program, + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected recipient's secret"); + (resp, first) + }) + } + + pub async fn send_transfer_transaction_shielded_foreign_account( + &self, + sender_account_id: AccountId, + recipient_npk: NullifierPublicKey, + recipient_ipk: IncomingViewingPublicKey, + amount: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let (instruction_data, program, tx_pre_check) = token_program_preparation_transfer(amount); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(sender_account_id), + PrivacyPreservingAccount::PrivateForeign { + npk: recipient_npk, + ipk: recipient_ipk, + }, + ], + &instruction_data, + tx_pre_check, + &program, + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected recipient's secret"); + (resp, first) + }) + } +} + +fn token_program_preparation_transfer( + amount: u128, +) -> ( + InstructionData, + Program, + impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, +) { + // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || + // 0x00 || 0x00 || 0x00]. + let mut instruction = [0; 23]; + instruction[0] = 0x01; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + let instruction_data = Program::serialize_instruction(instruction).unwrap(); + let program = Program::token(); + let tx_pre_check = |_: &[&Account]| Ok(()); + + (instruction_data, program, tx_pre_check) +} + +fn token_program_preparation_definition( + name: [u8; 6], + total_supply: u128, +) -> ( + InstructionData, + Program, + impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, +) { + // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] + let mut instruction = [0; 23]; + instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); + instruction[17..].copy_from_slice(&name); + let instruction_data = Program::serialize_instruction(instruction).unwrap(); + let program = Program::token(); + let tx_pre_check = |_: &[&Account]| Ok(()); + + (instruction_data, program, tx_pre_check) +} diff --git a/wallet/src/program_interactions/mod.rs b/wallet/src/program_interactions/mod.rs deleted file mode 100644 index fbdd6ab..0000000 --- a/wallet/src/program_interactions/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod pinata; -pub mod token; diff --git a/wallet/src/program_interactions/pinata.rs b/wallet/src/program_interactions/pinata.rs deleted file mode 100644 index e5150c5..0000000 --- a/wallet/src/program_interactions/pinata.rs +++ /dev/null @@ -1,161 +0,0 @@ -use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; -use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; -use nssa::{AccountId, privacy_preserving_transaction::circuit}; -use nssa_core::{MembershipProof, SharedSecretKey, account::AccountWithMetadata}; - -use crate::{ - WalletCore, helperfunctions::produce_random_nonces, transaction_utils::AccountPreparedData, -}; - -impl WalletCore { - pub async fn claim_pinata( - &self, - pinata_account_id: AccountId, - winner_account_id: AccountId, - solution: u128, - ) -> Result { - let account_ids = vec![pinata_account_id, winner_account_id]; - let program_id = nssa::program::Program::pinata().id(); - let message = - nssa::public_transaction::Message::try_new(program_id, account_ids, vec![], solution) - .unwrap(); - - let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); - let tx = nssa::PublicTransaction::new(message, witness_set); - - Ok(self.sequencer_client.send_tx_public(tx).await?) - } - - pub async fn claim_pinata_private_owned_account_already_initialized( - &self, - pinata_account_id: AccountId, - winner_account_id: AccountId, - solution: u128, - winner_proof: MembershipProof, - ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { - let AccountPreparedData { - nsk: winner_nsk, - npk: winner_npk, - ipk: winner_ipk, - auth_acc: winner_pre, - proof: _, - } = self - .private_acc_preparation(winner_account_id, true, false) - .await?; - - let pinata_acc = self.get_account_public(pinata_account_id).await.unwrap(); - - let program = nssa::program::Program::pinata(); - - let pinata_pre = AccountWithMetadata::new(pinata_acc.clone(), false, pinata_account_id); - - let eph_holder_winner = EphemeralKeyHolder::new(&winner_npk); - let shared_secret_winner = eph_holder_winner.calculate_shared_secret_sender(&winner_ipk); - - let (output, proof) = circuit::execute_and_prove( - &[pinata_pre, winner_pre], - &nssa::program::Program::serialize_instruction(solution).unwrap(), - &[0, 1], - &produce_random_nonces(1), - &[(winner_npk.clone(), shared_secret_winner.clone())], - &[(winner_nsk.unwrap(), winner_proof)], - &program, - ) - .unwrap(); - - let message = - nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( - vec![pinata_account_id], - vec![], - vec![( - winner_npk.clone(), - winner_ipk.clone(), - eph_holder_winner.generate_ephemeral_public_key(), - )], - output, - ) - .unwrap(); - - let witness_set = - nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( - &message, - proof, - &[], - ); - let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new( - message, - witness_set, - ); - - Ok(( - self.sequencer_client.send_tx_private(tx).await?, - [shared_secret_winner], - )) - } - - pub async fn claim_pinata_private_owned_account_not_initialized( - &self, - pinata_account_id: AccountId, - winner_account_id: AccountId, - solution: u128, - ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { - let AccountPreparedData { - nsk: _, - npk: winner_npk, - ipk: winner_ipk, - auth_acc: winner_pre, - proof: _, - } = self - .private_acc_preparation(winner_account_id, false, false) - .await?; - - let pinata_acc = self.get_account_public(pinata_account_id).await.unwrap(); - - let program = nssa::program::Program::pinata(); - - let pinata_pre = AccountWithMetadata::new(pinata_acc.clone(), false, pinata_account_id); - - let eph_holder_winner = EphemeralKeyHolder::new(&winner_npk); - let shared_secret_winner = eph_holder_winner.calculate_shared_secret_sender(&winner_ipk); - - let (output, proof) = circuit::execute_and_prove( - &[pinata_pre, winner_pre], - &nssa::program::Program::serialize_instruction(solution).unwrap(), - &[0, 2], - &produce_random_nonces(1), - &[(winner_npk.clone(), shared_secret_winner.clone())], - &[], - &program, - ) - .unwrap(); - - let message = - nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( - vec![pinata_account_id], - vec![], - vec![( - winner_npk.clone(), - winner_ipk.clone(), - eph_holder_winner.generate_ephemeral_public_key(), - )], - output, - ) - .unwrap(); - - let witness_set = - nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( - &message, - proof, - &[], - ); - let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new( - message, - witness_set, - ); - - Ok(( - self.sequencer_client.send_tx_private(tx).await?, - [shared_secret_winner], - )) - } -} diff --git a/wallet/src/program_interactions/token.rs b/wallet/src/program_interactions/token.rs deleted file mode 100644 index 91f76d4..0000000 --- a/wallet/src/program_interactions/token.rs +++ /dev/null @@ -1,290 +0,0 @@ -use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; -use nssa::{Account, AccountId, program::Program}; -use nssa_core::{ - NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey, - program::InstructionData, -}; - -use crate::{PrivacyPreservingAccount, WalletCore}; - -impl WalletCore { - pub fn token_program_preparation_transfer( - amount: u128, - ) -> ( - InstructionData, - Program, - impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, - ) { - // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || - // 0x00 || 0x00 || 0x00]. - let mut instruction = [0; 23]; - instruction[0] = 0x01; - instruction[1..17].copy_from_slice(&amount.to_le_bytes()); - let instruction_data = Program::serialize_instruction(instruction).unwrap(); - let program = Program::token(); - let tx_pre_check = |_: &[&Account]| Ok(()); - - (instruction_data, program, tx_pre_check) - } - - pub fn token_program_preparation_definition( - name: [u8; 6], - total_supply: u128, - ) -> ( - InstructionData, - Program, - impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, - ) { - // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] - let mut instruction = [0; 23]; - instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); - instruction[17..].copy_from_slice(&name); - let instruction_data = Program::serialize_instruction(instruction).unwrap(); - let program = Program::token(); - let tx_pre_check = |_: &[&Account]| Ok(()); - - (instruction_data, program, tx_pre_check) - } - - pub async fn send_new_token_definition( - &self, - definition_account_id: AccountId, - supply_account_id: AccountId, - name: [u8; 6], - total_supply: u128, - ) -> Result { - let account_ids = vec![definition_account_id, supply_account_id]; - let program_id = nssa::program::Program::token().id(); - // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] - let mut instruction = [0; 23]; - instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); - instruction[17..].copy_from_slice(&name); - let message = nssa::public_transaction::Message::try_new( - program_id, - account_ids, - vec![], - instruction, - ) - .unwrap(); - - let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); - - let tx = nssa::PublicTransaction::new(message, witness_set); - - Ok(self.sequencer_client.send_tx_public(tx).await?) - } - - pub async fn send_new_token_definition_private_owned( - &self, - definition_account_id: AccountId, - supply_account_id: AccountId, - name: [u8; 6], - total_supply: u128, - ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::token_program_preparation_definition(name, total_supply); - - self.send_privacy_preserving_tx( - vec![ - PrivacyPreservingAccount::Public(definition_account_id), - PrivacyPreservingAccount::PrivateLocal(supply_account_id), - ], - instruction_data, - tx_pre_check, - program, - ) - .await - .map(|(resp, secrets)| { - let first = secrets - .into_iter() - .next() - .expect("expected recipient's secret"); - (resp, first) - }) - } - - pub async fn send_transfer_token_transaction( - &self, - sender_account_id: AccountId, - recipient_account_id: AccountId, - amount: u128, - ) -> Result { - let account_ids = vec![sender_account_id, recipient_account_id]; - let program_id = nssa::program::Program::token().id(); - // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || - // 0x00 || 0x00 || 0x00]. - let mut instruction = [0; 23]; - instruction[0] = 0x01; - instruction[1..17].copy_from_slice(&amount.to_le_bytes()); - let Ok(nonces) = self.get_accounts_nonces(vec![sender_account_id]).await else { - return Err(ExecutionFailureKind::SequencerError); - }; - let message = nssa::public_transaction::Message::try_new( - program_id, - account_ids, - nonces, - instruction, - ) - .unwrap(); - - let Some(signing_key) = self - .storage - .user_data - .get_pub_account_signing_key(&sender_account_id) - else { - return Err(ExecutionFailureKind::KeyNotFoundError); - }; - let witness_set = - nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]); - - let tx = nssa::PublicTransaction::new(message, witness_set); - - Ok(self.sequencer_client.send_tx_public(tx).await?) - } - - pub async fn send_transfer_token_transaction_private_owned_account( - &self, - sender_account_id: AccountId, - recipient_account_id: AccountId, - amount: u128, - ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::token_program_preparation_transfer(amount); - - self.send_privacy_preserving_tx( - vec![ - PrivacyPreservingAccount::PrivateLocal(sender_account_id), - PrivacyPreservingAccount::PrivateLocal(recipient_account_id), - ], - instruction_data, - tx_pre_check, - program, - ) - .await - .map(|(resp, secrets)| { - let mut iter = secrets.into_iter(); - let first = iter.next().expect("expected sender's secret"); - let second = iter.next().expect("expected recipient's secret"); - (resp, [first, second]) - }) - } - - pub async fn send_transfer_token_transaction_private_foreign_account( - &self, - sender_account_id: AccountId, - recipient_npk: NullifierPublicKey, - recipient_ipk: IncomingViewingPublicKey, - amount: u128, - ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::token_program_preparation_transfer(amount); - - self.send_privacy_preserving_tx( - vec![ - PrivacyPreservingAccount::PrivateLocal(sender_account_id), - PrivacyPreservingAccount::PrivateForeign { - npk: recipient_npk, - ipk: recipient_ipk, - }, - ], - instruction_data, - tx_pre_check, - program, - ) - .await - .map(|(resp, secrets)| { - let mut iter = secrets.into_iter(); - let first = iter.next().expect("expected sender's secret"); - let second = iter.next().expect("expected recipient's secret"); - (resp, [first, second]) - }) - } - - pub async fn send_transfer_token_transaction_deshielded( - &self, - sender_account_id: AccountId, - recipient_account_id: AccountId, - amount: u128, - ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::token_program_preparation_transfer(amount); - - self.send_privacy_preserving_tx( - vec![ - PrivacyPreservingAccount::PrivateLocal(sender_account_id), - PrivacyPreservingAccount::Public(recipient_account_id), - ], - instruction_data, - tx_pre_check, - program, - ) - .await - .map(|(resp, secrets)| { - let first = secrets - .into_iter() - .next() - .expect("expected sender's secret"); - (resp, first) - }) - } - - pub async fn send_transfer_token_transaction_shielded_owned_account( - &self, - sender_account_id: AccountId, - recipient_account_id: AccountId, - amount: u128, - ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::token_program_preparation_transfer(amount); - - self.send_privacy_preserving_tx( - vec![ - PrivacyPreservingAccount::Public(sender_account_id), - PrivacyPreservingAccount::PrivateLocal(recipient_account_id), - ], - instruction_data, - tx_pre_check, - program, - ) - .await - .map(|(resp, secrets)| { - let first = secrets - .into_iter() - .next() - .expect("expected recipient's secret"); - (resp, first) - }) - } - - pub async fn send_transfer_token_transaction_shielded_foreign_account( - &self, - sender_account_id: AccountId, - recipient_npk: NullifierPublicKey, - recipient_ipk: IncomingViewingPublicKey, - amount: u128, - ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::token_program_preparation_transfer(amount); - - self.send_privacy_preserving_tx( - vec![ - PrivacyPreservingAccount::Public(sender_account_id), - PrivacyPreservingAccount::PrivateForeign { - npk: recipient_npk, - ipk: recipient_ipk, - }, - ], - instruction_data, - tx_pre_check, - program, - ) - .await - .map(|(resp, secrets)| { - let first = secrets - .into_iter() - .next() - .expect("expected recipient's secret"); - (resp, first) - }) - } -} diff --git a/wallet/src/token_transfers/deshielded.rs b/wallet/src/token_transfers/deshielded.rs deleted file mode 100644 index 216bfb5..0000000 --- a/wallet/src/token_transfers/deshielded.rs +++ /dev/null @@ -1,34 +0,0 @@ -use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; -use nssa::AccountId; - -use crate::{PrivacyPreservingAccount, WalletCore}; - -impl WalletCore { - pub async fn send_deshielded_native_token_transfer( - &self, - from: AccountId, - to: AccountId, - balance_to_move: u128, - ) -> Result<(SendTxResponse, nssa_core::SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::auth_transfer_preparation(balance_to_move); - - self.send_privacy_preserving_tx( - vec![ - PrivacyPreservingAccount::PrivateLocal(from), - PrivacyPreservingAccount::Public(to), - ], - instruction_data, - tx_pre_check, - program, - ) - .await - .map(|(resp, secrets)| { - let first = secrets - .into_iter() - .next() - .expect("expected sender's secret"); - (resp, first) - }) - } -} diff --git a/wallet/src/token_transfers/mod.rs b/wallet/src/token_transfers/mod.rs deleted file mode 100644 index 6b09698..0000000 --- a/wallet/src/token_transfers/mod.rs +++ /dev/null @@ -1,33 +0,0 @@ -use common::error::ExecutionFailureKind; -use nssa::{Account, program::Program}; -use nssa_core::program::InstructionData; - -use crate::WalletCore; - -pub mod deshielded; -pub mod private; -pub mod public; -pub mod shielded; - -impl WalletCore { - pub fn auth_transfer_preparation( - balance_to_move: u128, - ) -> ( - InstructionData, - Program, - impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, - ) { - let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); - let program = Program::authenticated_transfer_program(); - let tx_pre_check = move |accounts: &[&Account]| { - let from = accounts[0]; - if from.balance >= balance_to_move { - Ok(()) - } else { - Err(ExecutionFailureKind::InsufficientFundsError) - } - }; - - (instruction_data, program, tx_pre_check) - } -} diff --git a/wallet/src/token_transfers/private.rs b/wallet/src/token_transfers/private.rs deleted file mode 100644 index 59af480..0000000 --- a/wallet/src/token_transfers/private.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::vec; - -use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; -use nssa::AccountId; -use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey}; - -use crate::{PrivacyPreservingAccount, WalletCore}; - -impl WalletCore { - pub async fn send_private_native_token_transfer_outer_account( - &self, - from: AccountId, - to_npk: NullifierPublicKey, - to_ipk: IncomingViewingPublicKey, - balance_to_move: u128, - ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::auth_transfer_preparation(balance_to_move); - - self.send_privacy_preserving_tx( - vec![ - PrivacyPreservingAccount::PrivateLocal(from), - PrivacyPreservingAccount::PrivateForeign { - npk: to_npk, - ipk: to_ipk, - }, - ], - instruction_data, - tx_pre_check, - program, - ) - .await - .map(|(resp, secrets)| { - let mut secrets_iter = secrets.into_iter(); - let first = secrets_iter.next().expect("expected sender's secret"); - let second = secrets_iter.next().expect("expected receiver's secret"); - (resp, [first, second]) - }) - } - - pub async fn send_private_native_token_transfer_owned_account( - &self, - from: AccountId, - to: AccountId, - balance_to_move: u128, - ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::auth_transfer_preparation(balance_to_move); - - self.send_privacy_preserving_tx( - vec![ - PrivacyPreservingAccount::PrivateLocal(from), - PrivacyPreservingAccount::PrivateLocal(to), - ], - instruction_data, - tx_pre_check, - program, - ) - .await - .map(|(resp, secrets)| { - let mut secrets_iter = secrets.into_iter(); - let first = secrets_iter.next().expect("expected sender's secret"); - let second = secrets_iter.next().expect("expected receiver's secret"); - (resp, [first, second]) - }) - } -} diff --git a/wallet/src/token_transfers/shielded.rs b/wallet/src/token_transfers/shielded.rs deleted file mode 100644 index a8d28ee..0000000 --- a/wallet/src/token_transfers/shielded.rs +++ /dev/null @@ -1,67 +0,0 @@ -use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; -use nssa::AccountId; -use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey}; - -use crate::{PrivacyPreservingAccount, WalletCore}; - -impl WalletCore { - pub async fn send_shielded_native_token_transfer( - &self, - from: AccountId, - to: AccountId, - balance_to_move: u128, - ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::auth_transfer_preparation(balance_to_move); - - self.send_privacy_preserving_tx( - vec![ - PrivacyPreservingAccount::Public(from), - PrivacyPreservingAccount::PrivateLocal(to), - ], - instruction_data, - tx_pre_check, - program, - ) - .await - .map(|(resp, secrets)| { - let first = secrets - .into_iter() - .next() - .expect("expected sender's secret"); - (resp, first) - }) - } - - pub async fn send_shielded_native_token_transfer_outer_account( - &self, - from: AccountId, - to_npk: NullifierPublicKey, - to_ipk: IncomingViewingPublicKey, - balance_to_move: u128, - ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - WalletCore::auth_transfer_preparation(balance_to_move); - - self.send_privacy_preserving_tx( - vec![ - PrivacyPreservingAccount::Public(from), - PrivacyPreservingAccount::PrivateForeign { - npk: to_npk, - ipk: to_ipk, - }, - ], - instruction_data, - tx_pre_check, - program, - ) - .await - .map(|(resp, secrets)| { - let first = secrets - .into_iter() - .next() - .expect("expected sender's secret"); - (resp, first) - }) - } -} From 0c44785a073d35da05e1694f4eba35c056d75afc Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Sun, 30 Nov 2025 02:18:38 +0300 Subject: [PATCH 19/21] refactor: small adjustments to privacy preserving tx sending --- .../src/cli/programs/native_token_transfer.rs | 6 +- wallet/src/lib.rs | 31 +++-- wallet/src/privacy_preserving_tx.rs | 67 +++++++--- .../native_token_transfer/deshielded.rs | 4 +- .../native_token_transfer/private.rs | 31 ++++- .../native_token_transfer/shielded.rs | 8 +- wallet/src/program_facades/pinata.rs | 1 - wallet/src/program_facades/token.rs | 41 ++---- wallet/src/transaction_utils.rs | 117 ------------------ 9 files changed, 119 insertions(+), 187 deletions(-) delete mode 100644 wallet/src/transaction_utils.rs diff --git a/wallet/src/cli/programs/native_token_transfer.rs b/wallet/src/cli/programs/native_token_transfer.rs index 12c263f..00940b3 100644 --- a/wallet/src/cli/programs/native_token_transfer.rs +++ b/wallet/src/cli/programs/native_token_transfer.rs @@ -75,10 +75,8 @@ impl WalletSubcommand for AuthTransferSubcommand { AccountPrivacyKind::Private => { let account_id = account_id.parse()?; - let (res, [secret]) = wallet_core - .register_account_under_authenticated_transfers_programs_private( - account_id, - ) + let (res, secret) = NativeTokenTransfer(wallet_core) + .register_account_private(account_id) .await?; println!("Results of tx send is {res:#?}"); diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index f45bf93..f79d947 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -33,7 +33,6 @@ pub mod helperfunctions; pub mod poller; mod privacy_preserving_tx; pub mod program_facades; -pub mod transaction_utils; pub struct WalletCore { pub storage: WalletChainStore, @@ -224,12 +223,24 @@ impl WalletCore { &self, accounts: Vec, instruction_data: &InstructionData, - tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, program: &Program, ) -> Result<(SendTxResponse, Vec), ExecutionFailureKind> { - let payload = privacy_preserving_tx::Payload::new(self, accounts).await?; + self.send_privacy_preserving_tx_with_pre_check(accounts, instruction_data, program, |_| { + Ok(()) + }) + .await + } - let pre_states = payload.pre_states(); + pub async fn send_privacy_preserving_tx_with_pre_check( + &self, + accounts: Vec, + instruction_data: &InstructionData, + program: &Program, + tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, + ) -> Result<(SendTxResponse, Vec), ExecutionFailureKind> { + let acc_manager = privacy_preserving_tx::AccountManager::new(self, accounts).await?; + + let pre_states = acc_manager.pre_states(); tx_pre_check( &pre_states .iter() @@ -237,25 +248,25 @@ impl WalletCore { .collect::>(), )?; - let private_account_keys = payload.private_account_keys(); + let private_account_keys = acc_manager.private_account_keys(); let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove( &pre_states, instruction_data, - payload.visibility_mask(), + acc_manager.visibility_mask(), &produce_random_nonces(private_account_keys.len()), &private_account_keys .iter() .map(|keys| (keys.npk.clone(), keys.ssk.clone())) .collect::>(), - &payload.private_account_auth(), + &acc_manager.private_account_auth(), program, ) .unwrap(); let message = nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( - payload.public_account_ids(), - Vec::from_iter(payload.public_account_nonces()), + acc_manager.public_account_ids(), + Vec::from_iter(acc_manager.public_account_nonces()), private_account_keys .iter() .map(|keys| (keys.npk.clone(), keys.ipk.clone(), keys.epk.clone())) @@ -268,7 +279,7 @@ impl WalletCore { nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( &message, proof, - &payload.witness_signing_keys(), + &acc_manager.witness_signing_keys(), ); let tx = PrivacyPreservingTransaction::new(message, witness_set); diff --git a/wallet/src/privacy_preserving_tx.rs b/wallet/src/privacy_preserving_tx.rs index 2d670c3..e8e14d9 100644 --- a/wallet/src/privacy_preserving_tx.rs +++ b/wallet/src/privacy_preserving_tx.rs @@ -7,7 +7,7 @@ use nssa_core::{ encryption::{EphemeralPublicKey, IncomingViewingPublicKey}, }; -use crate::{WalletCore, transaction_utils::AccountPreparedData}; +use crate::WalletCore; pub enum PrivacyPreservingAccount { Public(AccountId), @@ -33,12 +33,12 @@ enum State { Private(AccountPreparedData), } -pub struct Payload { +pub struct AccountManager { states: Vec, visibility_mask: Vec, } -impl Payload { +impl AccountManager { pub async fn new( wallet: &WalletCore, accounts: Vec, @@ -60,16 +60,8 @@ impl Payload { (State::Public { account, sk }, 0) } PrivacyPreservingAccount::PrivateOwned(account_id) => { - let mut pre = wallet - .private_acc_preparation(account_id, true, true) - .await?; - let mut mask = 1; - - if pre.proof.is_none() { - pre.auth_acc.is_authorized = false; - pre.nsk = None; - mask = 2 - }; + let pre = private_acc_preparation(wallet, account_id).await?; + let mask = if pre.auth_acc.is_authorized { 1 } else { 2 }; (State::Private(pre), mask) } @@ -116,7 +108,7 @@ impl Payload { self.states .iter() .filter_map(|state| match state { - State::Public { account, .. } => Some(account.account.nonce), + State::Public { account, sk } => sk.as_ref().map(|_| account.account.nonce), _ => None, }) .collect() @@ -171,3 +163,50 @@ impl Payload { .collect() } } + +struct AccountPreparedData { + nsk: Option, + npk: NullifierPublicKey, + ipk: IncomingViewingPublicKey, + auth_acc: AccountWithMetadata, + proof: Option, +} + +async fn private_acc_preparation( + wallet: &WalletCore, + account_id: AccountId, +) -> Result { + let Some((from_keys, from_acc)) = wallet + .storage + .user_data + .get_private_account(&account_id) + .cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let mut nsk = Some(from_keys.private_key_holder.nullifier_secret_key); + + let from_npk = from_keys.nullifer_public_key; + let from_ipk = from_keys.incoming_viewing_public_key; + + // TODO: Remove this unwrap, error types must be compatible + let proof = wallet + .check_private_account_initialized(&account_id) + .await + .unwrap(); + + if proof.is_none() { + nsk = None; + } + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), proof.is_some(), &from_npk); + + Ok(AccountPreparedData { + nsk, + npk: from_npk, + ipk: from_ipk, + auth_acc: sender_pre, + proof, + }) +} diff --git a/wallet/src/program_facades/native_token_transfer/deshielded.rs b/wallet/src/program_facades/native_token_transfer/deshielded.rs index f4e45b6..a25be2c 100644 --- a/wallet/src/program_facades/native_token_transfer/deshielded.rs +++ b/wallet/src/program_facades/native_token_transfer/deshielded.rs @@ -14,14 +14,14 @@ impl NativeTokenTransfer<'_> { let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); self.0 - .send_privacy_preserving_tx( + .send_privacy_preserving_tx_with_pre_check( vec![ PrivacyPreservingAccount::PrivateOwned(from), PrivacyPreservingAccount::Public(to), ], &instruction_data, - tx_pre_check, &program, + tx_pre_check, ) .await .map(|(resp, secrets)| { diff --git a/wallet/src/program_facades/native_token_transfer/private.rs b/wallet/src/program_facades/native_token_transfer/private.rs index 39a4781..fcf6eee 100644 --- a/wallet/src/program_facades/native_token_transfer/private.rs +++ b/wallet/src/program_facades/native_token_transfer/private.rs @@ -1,13 +1,34 @@ use std::vec; use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; -use nssa::AccountId; +use nssa::{AccountId, program::Program}; use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey}; use super::{NativeTokenTransfer, auth_transfer_preparation}; use crate::PrivacyPreservingAccount; impl NativeTokenTransfer<'_> { + pub async fn register_account_private( + &self, + from: AccountId, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let instruction: u128 = 0; + + self.0 + .send_privacy_preserving_tx_with_pre_check( + vec![PrivacyPreservingAccount::PrivateOwned(from)], + &Program::serialize_instruction(instruction).unwrap(), + &Program::authenticated_transfer_program(), + |_| Ok(()), + ) + .await + .map(|(resp, secrets)| { + let mut secrets_iter = secrets.into_iter(); + let first = secrets_iter.next().expect("expected sender's secret"); + (resp, first) + }) + } + pub async fn send_private_transfer_to_outer_account( &self, from: AccountId, @@ -18,7 +39,7 @@ impl NativeTokenTransfer<'_> { let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); self.0 - .send_privacy_preserving_tx( + .send_privacy_preserving_tx_with_pre_check( vec![ PrivacyPreservingAccount::PrivateOwned(from), PrivacyPreservingAccount::PrivateForeign { @@ -27,8 +48,8 @@ impl NativeTokenTransfer<'_> { }, ], &instruction_data, - tx_pre_check, &program, + tx_pre_check, ) .await .map(|(resp, secrets)| { @@ -48,14 +69,14 @@ impl NativeTokenTransfer<'_> { let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); self.0 - .send_privacy_preserving_tx( + .send_privacy_preserving_tx_with_pre_check( vec![ PrivacyPreservingAccount::PrivateOwned(from), PrivacyPreservingAccount::PrivateOwned(to), ], &instruction_data, - tx_pre_check, &program, + tx_pre_check, ) .await .map(|(resp, secrets)| { diff --git a/wallet/src/program_facades/native_token_transfer/shielded.rs b/wallet/src/program_facades/native_token_transfer/shielded.rs index d40d5d4..c049b13 100644 --- a/wallet/src/program_facades/native_token_transfer/shielded.rs +++ b/wallet/src/program_facades/native_token_transfer/shielded.rs @@ -15,14 +15,14 @@ impl NativeTokenTransfer<'_> { let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); self.0 - .send_privacy_preserving_tx( + .send_privacy_preserving_tx_with_pre_check( vec![ PrivacyPreservingAccount::Public(from), PrivacyPreservingAccount::PrivateOwned(to), ], &instruction_data, - tx_pre_check, &program, + tx_pre_check, ) .await .map(|(resp, secrets)| { @@ -44,7 +44,7 @@ impl NativeTokenTransfer<'_> { let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); self.0 - .send_privacy_preserving_tx( + .send_privacy_preserving_tx_with_pre_check( vec![ PrivacyPreservingAccount::Public(from), PrivacyPreservingAccount::PrivateForeign { @@ -53,8 +53,8 @@ impl NativeTokenTransfer<'_> { }, ], &instruction_data, - tx_pre_check, &program, + tx_pre_check, ) .await .map(|(resp, secrets)| { diff --git a/wallet/src/program_facades/pinata.rs b/wallet/src/program_facades/pinata.rs index 6367bfc..46bc7a1 100644 --- a/wallet/src/program_facades/pinata.rs +++ b/wallet/src/program_facades/pinata.rs @@ -38,7 +38,6 @@ impl Pinata<'_> { PrivacyPreservingAccount::PrivateOwned(winner_account_id), ], &nssa::program::Program::serialize_instruction(solution).unwrap(), - |_| Ok(()), &nssa::program::Program::pinata(), ) .await diff --git a/wallet/src/program_facades/token.rs b/wallet/src/program_facades/token.rs index a4969de..298c4f4 100644 --- a/wallet/src/program_facades/token.rs +++ b/wallet/src/program_facades/token.rs @@ -1,5 +1,5 @@ use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; -use nssa::{Account, AccountId, program::Program}; +use nssa::{AccountId, program::Program}; use nssa_core::{ NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey, program::InstructionData, @@ -45,8 +45,7 @@ impl Token<'_> { name: [u8; 6], total_supply: u128, ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = - token_program_preparation_definition(name, total_supply); + let (instruction_data, program) = token_program_preparation_definition(name, total_supply); self.0 .send_privacy_preserving_tx( @@ -55,7 +54,6 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(supply_account_id), ], &instruction_data, - tx_pre_check, &program, ) .await @@ -114,7 +112,7 @@ impl Token<'_> { recipient_account_id: AccountId, amount: u128, ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = token_program_preparation_transfer(amount); + let (instruction_data, program) = token_program_preparation_transfer(amount); self.0 .send_privacy_preserving_tx( @@ -123,7 +121,6 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(recipient_account_id), ], &instruction_data, - tx_pre_check, &program, ) .await @@ -142,7 +139,7 @@ impl Token<'_> { recipient_ipk: IncomingViewingPublicKey, amount: u128, ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = token_program_preparation_transfer(amount); + let (instruction_data, program) = token_program_preparation_transfer(amount); self.0 .send_privacy_preserving_tx( @@ -154,7 +151,6 @@ impl Token<'_> { }, ], &instruction_data, - tx_pre_check, &program, ) .await @@ -172,7 +168,7 @@ impl Token<'_> { recipient_account_id: AccountId, amount: u128, ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = token_program_preparation_transfer(amount); + let (instruction_data, program) = token_program_preparation_transfer(amount); self.0 .send_privacy_preserving_tx( @@ -181,7 +177,6 @@ impl Token<'_> { PrivacyPreservingAccount::Public(recipient_account_id), ], &instruction_data, - tx_pre_check, &program, ) .await @@ -200,7 +195,7 @@ impl Token<'_> { recipient_account_id: AccountId, amount: u128, ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = token_program_preparation_transfer(amount); + let (instruction_data, program) = token_program_preparation_transfer(amount); self.0 .send_privacy_preserving_tx( @@ -209,7 +204,6 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(recipient_account_id), ], &instruction_data, - tx_pre_check, &program, ) .await @@ -229,7 +223,7 @@ impl Token<'_> { recipient_ipk: IncomingViewingPublicKey, amount: u128, ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program, tx_pre_check) = token_program_preparation_transfer(amount); + let (instruction_data, program) = token_program_preparation_transfer(amount); self.0 .send_privacy_preserving_tx( @@ -241,7 +235,6 @@ impl Token<'_> { }, ], &instruction_data, - tx_pre_check, &program, ) .await @@ -255,13 +248,7 @@ impl Token<'_> { } } -fn token_program_preparation_transfer( - amount: u128, -) -> ( - InstructionData, - Program, - impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, -) { +fn token_program_preparation_transfer(amount: u128) -> (InstructionData, Program) { // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || // 0x00 || 0x00 || 0x00]. let mut instruction = [0; 23]; @@ -269,26 +256,20 @@ fn token_program_preparation_transfer( instruction[1..17].copy_from_slice(&amount.to_le_bytes()); let instruction_data = Program::serialize_instruction(instruction).unwrap(); let program = Program::token(); - let tx_pre_check = |_: &[&Account]| Ok(()); - (instruction_data, program, tx_pre_check) + (instruction_data, program) } fn token_program_preparation_definition( name: [u8; 6], total_supply: u128, -) -> ( - InstructionData, - Program, - impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, -) { +) -> (InstructionData, Program) { // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] let mut instruction = [0; 23]; instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); instruction[17..].copy_from_slice(&name); let instruction_data = Program::serialize_instruction(instruction).unwrap(); let program = Program::token(); - let tx_pre_check = |_: &[&Account]| Ok(()); - (instruction_data, program, tx_pre_check) + (instruction_data, program) } diff --git a/wallet/src/transaction_utils.rs b/wallet/src/transaction_utils.rs deleted file mode 100644 index 10854d9..0000000 --- a/wallet/src/transaction_utils.rs +++ /dev/null @@ -1,117 +0,0 @@ -use common::{error::ExecutionFailureKind, sequencer_client::json::SendTxResponse}; -use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; -use nssa::{ - AccountId, PrivacyPreservingTransaction, - privacy_preserving_transaction::{circuit, message::Message, witness_set::WitnessSet}, - program::Program, -}; -use nssa_core::{ - MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, - account::AccountWithMetadata, encryption::IncomingViewingPublicKey, -}; - -use crate::{WalletCore, helperfunctions::produce_random_nonces}; - -pub(crate) struct AccountPreparedData { - pub nsk: Option, - pub npk: NullifierPublicKey, - pub ipk: IncomingViewingPublicKey, - pub auth_acc: AccountWithMetadata, - pub proof: Option, -} - -impl WalletCore { - pub(crate) async fn private_acc_preparation( - &self, - account_id: AccountId, - is_authorized: bool, - needs_proof: bool, - ) -> Result { - let Some((from_keys, from_acc)) = self - .storage - .user_data - .get_private_account(&account_id) - .cloned() - else { - return Err(ExecutionFailureKind::KeyNotFoundError); - }; - - let mut nsk = None; - let mut proof = None; - - let from_npk = from_keys.nullifer_public_key; - let from_ipk = from_keys.incoming_viewing_public_key; - - let sender_pre = AccountWithMetadata::new(from_acc.clone(), is_authorized, &from_npk); - - if is_authorized { - nsk = Some(from_keys.private_key_holder.nullifier_secret_key); - } - - if needs_proof { - // TODO: Remove this unwrap, error types must be compatible - proof = self - .check_private_account_initialized(&account_id) - .await - .unwrap(); - } - - Ok(AccountPreparedData { - nsk, - npk: from_npk, - ipk: from_ipk, - auth_acc: sender_pre, - proof, - }) - } - - // TODO: Remove - pub async fn register_account_under_authenticated_transfers_programs_private( - &self, - from: AccountId, - ) -> Result<(SendTxResponse, [SharedSecretKey; 1]), ExecutionFailureKind> { - let AccountPreparedData { - nsk: _, - npk: from_npk, - ipk: from_ipk, - auth_acc: sender_pre, - proof: _, - } = self.private_acc_preparation(from, false, false).await?; - - let eph_holder_from = EphemeralKeyHolder::new(&from_npk); - let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_ipk); - - let instruction: u128 = 0; - - let (output, proof) = circuit::execute_and_prove( - &[sender_pre], - &Program::serialize_instruction(instruction).unwrap(), - &[2], - &produce_random_nonces(1), - &[(from_npk.clone(), shared_secret_from.clone())], - &[], - &Program::authenticated_transfer_program(), - ) - .unwrap(); - - let message = Message::try_from_circuit_output( - vec![], - vec![], - vec![( - from_npk.clone(), - from_ipk.clone(), - eph_holder_from.generate_ephemeral_public_key(), - )], - output, - ) - .unwrap(); - - let witness_set = WitnessSet::for_message(&message, proof, &[]); - let tx = PrivacyPreservingTransaction::new(message, witness_set); - - Ok(( - self.sequencer_client.send_tx_private(tx).await?, - [shared_secret_from], - )) - } -} From 4afbd65e3b3edb697815e7d1b47cce1cd2341bb3 Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Sun, 30 Nov 2025 02:37:43 +0300 Subject: [PATCH 20/21] feat: add transaction output after pinata call --- wallet/src/cli/programs/pinata.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/wallet/src/cli/programs/pinata.rs b/wallet/src/cli/programs/pinata.rs index cabee4c..d7d974b 100644 --- a/wallet/src/cli/programs/pinata.rs +++ b/wallet/src/cli/programs/pinata.rs @@ -1,7 +1,6 @@ use anyhow::Result; use clap::Subcommand; use common::{PINATA_BASE58, transaction::NSSATransaction}; -use log::info; use crate::{ WalletCore, @@ -125,7 +124,19 @@ impl WalletSubcommand for PinataProgramSubcommandPublic { solution, ) .await?; - info!("Results of tx send is {res:#?}"); + + println!("Results of tx send is {res:#?}"); + + let tx_hash = res.tx_hash; + let transfer_tx = wallet_core + .poll_native_token_transfer(tx_hash.clone()) + .await?; + + println!("Transaction data is {transfer_tx:?}"); + + let path = wallet_core.store_persistent_data().await?; + + println!("Stored persistent accounts at {path:#?}"); Ok(SubcommandReturnValue::Empty) } @@ -151,13 +162,15 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate { .claim_private_owned_account(pinata_account_id, winner_account_id, solution) .await?; - info!("Results of tx send is {res:#?}"); + println!("Results of tx send is {res:#?}"); let tx_hash = res.tx_hash; let transfer_tx = wallet_core .poll_native_token_transfer(tx_hash.clone()) .await?; + println!("Transaction data is {transfer_tx:?}"); + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { let acc_decode_data = vec![(secret_winner, winner_account_id)]; From df88d8bad6b91c57e2503de92c7b0951c506ed3e Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Sun, 30 Nov 2025 03:16:47 +0300 Subject: [PATCH 21/21] feat: compute pinata solution in wallet --- integration_tests/src/test_suite_map.rs | 12 +-- wallet/Cargo.toml | 1 + wallet/src/cli/mod.rs | 4 +- .../src/cli/programs/native_token_transfer.rs | 16 ++-- wallet/src/cli/programs/pinata.rs | 92 ++++++++++++------- wallet/src/cli/programs/token.rs | 12 +-- wallet/src/main.rs | 10 +- 7 files changed, 86 insertions(+), 61 deletions(-) diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs index e7ef4d1..9903345 100644 --- a/integration_tests/src/test_suite_map.rs +++ b/integration_tests/src/test_suite_map.rs @@ -1400,10 +1400,8 @@ pub fn prepare_function_map() -> HashMap { let pinata_account_id = PINATA_BASE58; let pinata_prize = 150; - let solution = 989106; let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to_account_id: make_public_account_input_from_str(ACC_SENDER), - solution, + to: make_public_account_input_from_str(ACC_SENDER), }); let wallet_config = fetch_config().await.unwrap(); @@ -1531,11 +1529,9 @@ pub fn prepare_function_map() -> HashMap { info!("########## test_pinata_private_receiver ##########"); let pinata_account_id = PINATA_BASE58; let pinata_prize = 150; - let solution = 989106; let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to_account_id: make_private_account_input_from_str(ACC_SENDER_PRIVATE), - solution, + to: make_private_account_input_from_str(ACC_SENDER_PRIVATE), }); let wallet_config = fetch_config().await.unwrap(); @@ -1588,7 +1584,6 @@ pub fn prepare_function_map() -> HashMap { info!("########## test_pinata_private_receiver_new_account ##########"); let pinata_account_id = PINATA_BASE58; let pinata_prize = 150; - let solution = 989106; // Create new account for the token supply holder (private) let SubcommandReturnValue::RegisterAccount { @@ -1605,8 +1600,7 @@ pub fn prepare_function_map() -> HashMap { }; let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to_account_id: make_private_account_input_from_str(&winner_account_id.to_string()), - solution, + to: make_private_account_input_from_str(&winner_account_id.to_string()), }); let wallet_config = fetch_config().await.unwrap(); diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 74eb5bc..3b12d8f 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -20,6 +20,7 @@ base58.workspace = true hex = "0.4.3" rand.workspace = true itertools = "0.14.0" +sha2.workspace = true [dependencies.key_protocol] path = "../key_protocol" diff --git a/wallet/src/cli/mod.rs b/wallet/src/cli/mod.rs index 39c7874..c1def06 100644 --- a/wallet/src/cli/mod.rs +++ b/wallet/src/cli/mod.rs @@ -81,7 +81,7 @@ pub enum OverCommand { pub struct Args { /// Continious run flag #[arg(short, long)] - pub continious_run: bool, + pub continuous_run: bool, /// Wallet command #[command(subcommand)] pub command: Option, @@ -162,7 +162,7 @@ pub async fn execute_subcommand(command: Command) -> Result Result<()> { +pub async fn execute_continuous_run() -> Result<()> { let config = fetch_config().await?; let seq_client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?); let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?; diff --git a/wallet/src/cli/programs/native_token_transfer.rs b/wallet/src/cli/programs/native_token_transfer.rs index 00940b3..9dc72ae 100644 --- a/wallet/src/cli/programs/native_token_transfer.rs +++ b/wallet/src/cli/programs/native_token_transfer.rs @@ -61,7 +61,7 @@ impl WalletSubcommand for AuthTransferSubcommand { .register_account(account_id) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let transfer_tx = wallet_core.poll_native_token_transfer(res.tx_hash).await?; @@ -79,7 +79,7 @@ impl WalletSubcommand for AuthTransferSubcommand { .register_account_private(account_id) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let tx_hash = res.tx_hash; let transfer_tx = wallet_core @@ -320,7 +320,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { .send_private_transfer_to_owned_account(from, to, amount) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let tx_hash = res.tx_hash; let transfer_tx = wallet_core @@ -364,7 +364,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { .send_private_transfer_to_outer_account(from, to_npk, to_ipk, amount) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let tx_hash = res.tx_hash; let transfer_tx = wallet_core @@ -404,7 +404,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { .send_shielded_transfer(from, to, amount) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let tx_hash = res.tx_hash; let transfer_tx = wallet_core @@ -449,7 +449,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { .send_shielded_transfer_to_outer_account(from, to_npk, to_ipk, amount) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let tx_hash = res.tx_hash; @@ -483,7 +483,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { .send_deshielded_transfer(from, to, amount) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let tx_hash = res.tx_hash; let transfer_tx = wallet_core @@ -513,7 +513,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { .send_public_transfer(from, to, amount) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let transfer_tx = wallet_core.poll_native_token_transfer(res.tx_hash).await?; diff --git a/wallet/src/cli/programs/pinata.rs b/wallet/src/cli/programs/pinata.rs index d7d974b..c0e2223 100644 --- a/wallet/src/cli/programs/pinata.rs +++ b/wallet/src/cli/programs/pinata.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use clap::Subcommand; use common::{PINATA_BASE58, transaction::NSSATransaction}; @@ -14,12 +14,9 @@ use crate::{ pub enum PinataProgramAgnosticSubcommand { /// Claim pinata Claim { - /// to_account_id - valid 32 byte base58 string with privacy prefix + /// to - valid 32 byte base58 string with privacy prefix #[arg(long)] - to_account_id: String, - /// solution - solution to pinata challenge - #[arg(long)] - solution: u128, + to: String, }, } @@ -29,26 +26,20 @@ impl WalletSubcommand for PinataProgramAgnosticSubcommand { wallet_core: &mut WalletCore, ) -> Result { let underlying_subcommand = match self { - PinataProgramAgnosticSubcommand::Claim { - to_account_id, - solution, - } => { - let (to_account_id, to_addr_privacy) = - parse_addr_with_privacy_prefix(&to_account_id)?; + PinataProgramAgnosticSubcommand::Claim { to } => { + let (to, to_addr_privacy) = parse_addr_with_privacy_prefix(&to)?; match to_addr_privacy { AccountPrivacyKind::Public => { PinataProgramSubcommand::Public(PinataProgramSubcommandPublic::Claim { pinata_account_id: PINATA_BASE58.to_string(), - winner_account_id: to_account_id, - solution, + winner_account_id: to, }) } AccountPrivacyKind::Private => PinataProgramSubcommand::Private( PinataProgramSubcommandPrivate::ClaimPrivateOwned { pinata_account_id: PINATA_BASE58.to_string(), - winner_account_id: to_account_id, - solution, + winner_account_id: to, }, ), } @@ -82,9 +73,6 @@ pub enum PinataProgramSubcommandPublic { /// winner_account_id - valid 32 byte hex string #[arg(long)] winner_account_id: String, - /// solution - solution to pinata challenge - #[arg(long)] - solution: u128, }, } @@ -100,9 +88,6 @@ pub enum PinataProgramSubcommandPrivate { /// winner_account_id - valid 32 byte hex string #[arg(long)] winner_account_id: String, - /// solution - solution to pinata challenge - #[arg(long)] - solution: u128, }, } @@ -115,17 +100,21 @@ impl WalletSubcommand for PinataProgramSubcommandPublic { PinataProgramSubcommandPublic::Claim { pinata_account_id, winner_account_id, - solution, } => { + let pinata_account_id = pinata_account_id.parse().unwrap(); + let solution = find_solution(wallet_core, pinata_account_id) + .await + .context("failed to compute solution")?; + let res = Pinata(wallet_core) .claim( - pinata_account_id.parse().unwrap(), + pinata_account_id, winner_account_id.parse().unwrap(), solution, ) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let tx_hash = res.tx_hash; let transfer_tx = wallet_core @@ -134,10 +123,6 @@ impl WalletSubcommand for PinataProgramSubcommandPublic { println!("Transaction data is {transfer_tx:?}"); - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); - Ok(SubcommandReturnValue::Empty) } } @@ -153,16 +138,18 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate { PinataProgramSubcommandPrivate::ClaimPrivateOwned { pinata_account_id, winner_account_id, - solution, } => { let pinata_account_id = pinata_account_id.parse().unwrap(); let winner_account_id = winner_account_id.parse().unwrap(); + let solution = find_solution(wallet_core, pinata_account_id) + .await + .context("failed to compute solution")?; let (res, secret_winner) = Pinata(wallet_core) .claim_private_owned_account(pinata_account_id, winner_account_id, solution) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let tx_hash = res.tx_hash; let transfer_tx = wallet_core @@ -205,3 +192,46 @@ impl WalletSubcommand for PinataProgramSubcommand { } } } + +async fn find_solution(wallet: &WalletCore, pinata_account_id: nssa::AccountId) -> Result { + let account = wallet.get_account_public(pinata_account_id).await?; + let data: [u8; 33] = account + .data + .try_into() + .map_err(|_| anyhow::Error::msg("invalid pinata account data"))?; + + println!("Computing solution for pinata..."); + let now = std::time::Instant::now(); + + let solution = compute_solution(data); + + println!("Found solution {solution} in {:?}", now.elapsed()); + Ok(solution) +} + +fn compute_solution(data: [u8; 33]) -> u128 { + let difficulty = data[0]; + let seed = &data[1..]; + + let mut solution = 0u128; + while !validate_solution(difficulty, seed, solution) { + solution = solution.checked_add(1).expect("solution overflowed u128"); + } + + solution +} + +fn validate_solution(difficulty: u8, seed: &[u8], solution: u128) -> bool { + use sha2::{Digest as _, digest::FixedOutput as _}; + + let mut bytes = [0; 32 + 16]; + bytes[..32].copy_from_slice(seed); + bytes[32..].copy_from_slice(&solution.to_le_bytes()); + + let mut hasher = sha2::Sha256::new(); + hasher.update(bytes); + let digest: [u8; 32] = hasher.finalize_fixed().into(); + + let difficulty = difficulty as usize; + digest[..difficulty].iter().all(|&b| b == 0) +} diff --git a/wallet/src/cli/programs/token.rs b/wallet/src/cli/programs/token.rs index 1fceb74..d1a27dd 100644 --- a/wallet/src/cli/programs/token.rs +++ b/wallet/src/cli/programs/token.rs @@ -399,7 +399,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { ) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let tx_hash = res.tx_hash; let transfer_tx = wallet_core @@ -437,7 +437,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { ) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let tx_hash = res.tx_hash; let transfer_tx = wallet_core @@ -490,7 +490,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { ) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let tx_hash = res.tx_hash; let transfer_tx = wallet_core @@ -538,7 +538,7 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded { ) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let tx_hash = res.tx_hash; let transfer_tx = wallet_core @@ -598,7 +598,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { ) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let tx_hash = res.tx_hash; let transfer_tx = wallet_core @@ -631,7 +631,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { ) .await?; - println!("Results of tx send is {res:#?}"); + println!("Results of tx send are {res:#?}"); let tx_hash = res.tx_hash; let transfer_tx = wallet_core diff --git a/wallet/src/main.rs b/wallet/src/main.rs index 7360d47..a8a4fbe 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,7 +1,7 @@ use anyhow::Result; use clap::{CommandFactory as _, Parser as _}; use tokio::runtime::Builder; -use wallet::cli::{Args, OverCommand, execute_continious_run, execute_setup, execute_subcommand}; +use wallet::cli::{Args, OverCommand, execute_continuous_run, execute_setup, execute_subcommand}; pub const NUM_THREADS: usize = 2; @@ -23,16 +23,16 @@ fn main() -> Result<()> { env_logger::init(); runtime.block_on(async move { - if let Some(overcommand) = args.command { - match overcommand { + if let Some(over_command) = args.command { + match over_command { OverCommand::Command(command) => { let _output = execute_subcommand(command).await?; Ok(()) } OverCommand::Setup { password } => execute_setup(password).await, } - } else if args.continious_run { - execute_continious_run().await + } else if args.continuous_run { + execute_continuous_run().await } else { let help = Args::command().render_long_help(); println!("{help}");