From ebe616247f0a63a1b1374861a5292c5c82a06ae1 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Thu, 4 Sep 2025 17:49:55 +0300 Subject: [PATCH 1/5] fix: seed generation from mnemonic --- Cargo.toml | 1 + key_protocol/Cargo.toml | 1 + key_protocol/src/key_management/secret_holders.rs | 11 ++++++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b98867..5f2ee32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ ark-bn254 = "0.5.0" ark-ff = "0.5.0" tiny-keccak = { version = "2.0.2", features = ["keccak"] } base64 = "0.22.1" +bip39 = "2.2.0" rocksdb = { version = "0.21.0", default-features = false, features = [ "snappy", diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index 947bd7a..8b9701a 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -15,6 +15,7 @@ elliptic-curve.workspace = true hex.workspace = true aes-gcm.workspace = true lazy_static.workspace = true +bip39.workspace = true [dependencies.common] path = "../common" diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 47342ec..4fc1a63 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -1,3 +1,4 @@ +use bip39::Mnemonic; use common::merkle_tree_public::TreeHashType; use elliptic_curve::PrimeField; use k256::{AffinePoint, FieldBytes, Scalar}; @@ -29,12 +30,16 @@ pub struct UTXOSecretKeyHolder { impl SeedHolder { pub fn new_os_random() -> Self { - let mut bytes = FieldBytes::default(); + let mut enthopy_bytes: [u8; 32] = [0; 32]; + OsRng.fill_bytes(&mut enthopy_bytes); - OsRng.fill_bytes(&mut bytes); + let mnemonic = Mnemonic::from_entropy(&enthopy_bytes).unwrap(); + let seed = mnemonic.to_seed(""); + + let field_bytes = FieldBytes::from_slice(&seed); Self { - seed: Scalar::from_repr(bytes).unwrap(), + seed: Scalar::from_repr(*field_bytes).unwrap(), } } From 324f477b631aba96623d83d296a48ffed636ee56 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Fri, 5 Sep 2025 14:47:58 +0300 Subject: [PATCH 2/5] fix: keys corect generatin --- Cargo.toml | 1 + key_protocol/Cargo.toml | 1 + .../src/key_management/constants_types.rs | 22 --- .../key_management/ephemeral_key_holder.rs | 2 +- key_protocol/src/key_management/mod.rs | 86 +++++------ .../src/key_management/secret_holders.rs | 137 +++++++++++++----- key_protocol/src/key_management/types.rs | 8 + 7 files changed, 150 insertions(+), 107 deletions(-) delete mode 100644 key_protocol/src/key_management/constants_types.rs create mode 100644 key_protocol/src/key_management/types.rs diff --git a/Cargo.toml b/Cargo.toml index 5f2ee32..c21c9a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ ark-ff = "0.5.0" tiny-keccak = { version = "2.0.2", features = ["keccak"] } base64 = "0.22.1" bip39 = "2.2.0" +hmac-sha512 = "1.1.7" rocksdb = { version = "0.21.0", default-features = false, features = [ "snappy", diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index 8b9701a..d624505 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -16,6 +16,7 @@ hex.workspace = true aes-gcm.workspace = true lazy_static.workspace = true bip39.workspace = true +hmac-sha512.workspace = true [dependencies.common] path = "../common" diff --git a/key_protocol/src/key_management/constants_types.rs b/key_protocol/src/key_management/constants_types.rs deleted file mode 100644 index f4fca86..0000000 --- a/key_protocol/src/key_management/constants_types.rs +++ /dev/null @@ -1,22 +0,0 @@ -use elliptic_curve::{ - consts::{B0, B1}, - generic_array::GenericArray, -}; -use lazy_static::lazy_static; -use sha2::digest::typenum::{UInt, UTerm}; - -lazy_static! { - pub static ref NULLIFIER_SECRET_CONST: [u8; 32] = - hex::decode(std::env::var("NULLIFIER_SECRET_CONST").unwrap()) - .unwrap() - .try_into() - .unwrap(); - pub static ref VIEWING_SECRET_CONST: [u8; 32] = - hex::decode(std::env::var("VIEWING_SECRET_CONST").unwrap()) - .unwrap() - .try_into() - .unwrap(); -} - -pub type CipherText = Vec; -pub type Nonce = GenericArray, B1>, B0>, B0>>; diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index ecfb09e..6392678 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -5,7 +5,7 @@ use k256::{AffinePoint, FieldBytes, Scalar}; use log::info; use rand::{rngs::OsRng, RngCore}; -use super::constants_types::{CipherText, Nonce}; +use super::types::{CipherText, Nonce}; #[derive(Debug)] ///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender. diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index bc6d14c..fb75dff 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -1,29 +1,32 @@ use std::collections::HashMap; use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit}; -use constants_types::{CipherText, Nonce}; +use common::merkle_tree_public::TreeHashType; +use elliptic_curve::group::GroupEncoding; use elliptic_curve::point::AffineCoordinates; use k256::AffinePoint; use log::info; -use secret_holders::{SeedHolder, TopSecretKeyHolder, UTXOSecretKeyHolder}; +use secret_holders::{PrivateKeyHolder, SeedHolder, TopSecretKeyHolder}; use serde::{Deserialize, Serialize}; +use sha2::{digest::FixedOutput, Digest}; +use types::{CipherText, Nonce}; use crate::key_protocol_core::PublicKey; pub type PublicAccountSigningKey = [u8; 32]; -pub mod constants_types; pub mod ephemeral_key_holder; pub mod secret_holders; +pub mod types; #[derive(Serialize, Deserialize, Clone, Debug)] ///Entrypoint to key management pub struct KeyChain { top_secret_key_holder: TopSecretKeyHolder, - pub utxo_secret_key_holder: UTXOSecretKeyHolder, + pub private_key_holder: PrivateKeyHolder, ///Map for all users accounts pub_account_signing_keys: HashMap, - pub nullifer_public_key: PublicKey, - pub viewing_public_key: PublicKey, + pub nullifer_public_key: [u8; 32], + pub incoming_viewing_public_key: PublicKey, } impl KeyChain { @@ -33,16 +36,16 @@ impl KeyChain { let seed_holder = SeedHolder::new_os_random(); let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder(); + let private_key_holder = top_secret_key_holder.produce_private_key_holder(); - let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key(); - let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key(); + 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 { top_secret_key_holder, - utxo_secret_key_holder, + private_key_holder, nullifer_public_key, - viewing_public_key, + incoming_viewing_public_key, pub_account_signing_keys: HashMap::new(), } } @@ -53,20 +56,29 @@ impl KeyChain { let seed_holder = SeedHolder::new_os_random(); let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder(); + let private_key_holder = top_secret_key_holder.produce_private_key_holder(); - let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key(); - let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key(); + 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 { top_secret_key_holder, - utxo_secret_key_holder, + private_key_holder, nullifer_public_key, - viewing_public_key, + incoming_viewing_public_key, pub_account_signing_keys: accounts, } } + pub fn produce_user_address(&self) -> [u8; 32] { + let mut hasher = sha2::Sha256::new(); + + hasher.update(&self.nullifer_public_key); + hasher.update(&self.incoming_viewing_public_key.to_bytes()); + + ::from(hasher.finalize_fixed()) + } + pub fn generate_new_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)); @@ -112,14 +124,18 @@ impl KeyChain { ); info!( "Nulifier secret key is {:?}", + hex::encode(serde_json::to_vec(&self.private_key_holder.nullifier_secret_key).unwrap()), + ); + info!( + "Viewing secret key is {:?}", hex::encode( - serde_json::to_vec(&self.utxo_secret_key_holder.nullifier_secret_key).unwrap() + serde_json::to_vec(&self.private_key_holder.incoming_viewing_secret_key).unwrap() ), ); info!( "Viewing secret key is {:?}", hex::encode( - serde_json::to_vec(&self.utxo_secret_key_holder.viewing_secret_key).unwrap() + serde_json::to_vec(&self.private_key_holder.outgoing_viewing_secret_key).unwrap() ), ); info!( @@ -128,7 +144,7 @@ impl KeyChain { ); info!( "Viewing public key is {:?}", - hex::encode(serde_json::to_vec(&self.viewing_public_key).unwrap()), + hex::encode(serde_json::to_vec(&self.incoming_viewing_public_key).unwrap()), ); } } @@ -139,12 +155,11 @@ mod tests { aead::{Aead, KeyInit, OsRng}, Aes256Gcm, }; - use constants_types::{CipherText, Nonce}; - use constants_types::{NULLIFIER_SECRET_CONST, VIEWING_SECRET_CONST}; use elliptic_curve::ff::Field; use elliptic_curve::group::prime::PrimeCurveAffine; use elliptic_curve::point::AffineCoordinates; use k256::{AffinePoint, ProjectivePoint, Scalar}; + use types::{CipherText, Nonce}; use crate::key_management::ephemeral_key_holder::EphemeralKeyHolder; @@ -156,11 +171,9 @@ mod tests { let address_key_holder = KeyChain::new_os_random(); // Check that key holder fields are initialized with expected types + assert_ne!(address_key_holder.nullifer_public_key, [0u8; 32]); assert!(!Into::::into( - address_key_holder.nullifer_public_key.is_identity() - )); - assert!(!Into::::into( - address_key_holder.viewing_public_key.is_identity() + address_key_holder.incoming_viewing_public_key.is_identity() )); } @@ -211,20 +224,6 @@ mod tests { assert_eq!(decrypted_data, plaintext); } - #[test] - fn test_new_os_random_initialization() { - // Ensure that KeyChain is initialized correctly - let address_key_holder = KeyChain::new_os_random(); - - // Check that key holder fields are initialized with expected types and values - assert!(!Into::::into( - address_key_holder.nullifer_public_key.is_identity() - )); - assert!(!Into::::into( - address_key_holder.viewing_public_key.is_identity() - )); - } - #[test] fn test_calculate_shared_secret_with_identity_point() { let address_key_holder = KeyChain::new_os_random(); @@ -359,10 +358,10 @@ mod tests { let seed_holder = SeedHolder::new_os_random(); let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder(); + let utxo_secret_key_holder = top_secret_key_holder.produce_private_key_holder(); let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key(); - let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key(); + let viewing_public_key = utxo_secret_key_holder.generate_incoming_viewing_public_key(); let pub_account_signing_key = nssa::PrivateKey::new_os_random(); @@ -377,11 +376,6 @@ mod tests { "Group generator {:?}", hex::encode(serde_json::to_vec(&AffinePoint::GENERATOR).unwrap()) ); - println!( - "Nullifier constant {:?}", - hex::encode(*NULLIFIER_SECRET_CONST) - ); - println!("Viewing constatnt {:?}", hex::encode(*VIEWING_SECRET_CONST)); println!(); println!("======Holders======"); diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 4fc1a63..a5258b5 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -1,31 +1,31 @@ use bip39::Mnemonic; use common::merkle_tree_public::TreeHashType; use elliptic_curve::PrimeField; -use k256::{AffinePoint, FieldBytes, Scalar}; +use k256::{AffinePoint, Scalar}; use rand::{rngs::OsRng, RngCore}; use serde::{Deserialize, Serialize}; use sha2::{digest::FixedOutput, Digest}; -use super::constants_types::{NULLIFIER_SECRET_CONST, VIEWING_SECRET_CONST}; - #[derive(Debug)] ///Seed holder. Non-clonable to ensure that different holders use different seeds. /// Produces `TopSecretKeyHolder` objects. pub struct SeedHolder { - seed: Scalar, + //ToDo: Needs to be vec as serde derives is not implemented for [u8; 64] + pub(crate) seed: Vec, } #[derive(Serialize, Deserialize, Debug, Clone)] -///Secret spending key holder. Produces `UTXOSecretKeyHolder` objects. +///Secret spending key holder. Produces `PrivateKeyHolder` objects. pub struct TopSecretKeyHolder { - pub secret_spending_key: Scalar, + pub(crate) secret_spending_key: [u8; 32], } #[derive(Serialize, Deserialize, Debug, Clone)] -///Nullifier secret key and viewing secret key holder. Produces public keys. Can produce address. Can produce shared secret for recepient. -pub struct UTXOSecretKeyHolder { - pub nullifier_secret_key: Scalar, - pub viewing_secret_key: Scalar, +///Private key holder. Produces public keys. Can produce address. Can produce shared secret for recepient. +pub struct PrivateKeyHolder { + pub(crate) nullifier_secret_key: [u8; 32], + pub(crate) incoming_viewing_secret_key: Scalar, + pub(crate) outgoing_viewing_secret_key: Scalar, } impl SeedHolder { @@ -34,73 +34,134 @@ impl SeedHolder { OsRng.fill_bytes(&mut enthopy_bytes); let mnemonic = Mnemonic::from_entropy(&enthopy_bytes).unwrap(); - let seed = mnemonic.to_seed(""); - - let field_bytes = FieldBytes::from_slice(&seed); + let seed_wide = mnemonic.to_seed("mnemonic"); Self { - seed: Scalar::from_repr(*field_bytes).unwrap(), + seed: seed_wide.to_vec(), } } pub fn generate_secret_spending_key_hash(&self) -> TreeHashType { - let mut hasher = sha2::Sha256::new(); + let mut hash = hmac_sha512::HMAC::mac(&self.seed, "NSSA_seed"); - hasher.update(self.seed.to_bytes()); + for _ in 1..2048 { + hash = hmac_sha512::HMAC::mac(&hash, "NSSA_seed"); + } - ::from(hasher.finalize_fixed()) - } - - pub fn generate_secret_spending_key_scalar(&self) -> Scalar { - let hash = self.generate_secret_spending_key_hash(); - - Scalar::from_repr(hash.into()).unwrap() + //Safe unwrap + *hash.first_chunk::<32>().unwrap() } pub fn produce_top_secret_key_holder(&self) -> TopSecretKeyHolder { TopSecretKeyHolder { - secret_spending_key: self.generate_secret_spending_key_scalar(), + secret_spending_key: self.generate_secret_spending_key_hash(), } } } impl TopSecretKeyHolder { - pub fn generate_nullifier_secret_key(&self) -> Scalar { + pub fn generate_nullifier_secret_key(&self) -> [u8; 32] { let mut hasher = sha2::Sha256::new(); - hasher.update(self.secret_spending_key.to_bytes()); - hasher.update(*NULLIFIER_SECRET_CONST); + hasher.update("NSSA_keys"); + hasher.update(&self.secret_spending_key); + hasher.update([1u8]); + hasher.update([0u8; 176]); + + ::from(hasher.finalize_fixed()) + } + + pub fn generate_incloming_viewing_secret_key(&self) -> Scalar { + let mut hasher = sha2::Sha256::new(); + + hasher.update("NSSA_keys"); + hasher.update(&self.secret_spending_key); + hasher.update([2u8]); + hasher.update([0u8; 176]); let hash = ::from(hasher.finalize_fixed()); Scalar::from_repr(hash.into()).unwrap() } - pub fn generate_viewing_secret_key(&self) -> Scalar { + pub fn generate_outgoing_viewing_secret_key(&self) -> Scalar { let mut hasher = sha2::Sha256::new(); - hasher.update(self.secret_spending_key.to_bytes()); - hasher.update(*VIEWING_SECRET_CONST); + hasher.update("NSSA_keys"); + hasher.update(&self.secret_spending_key); + hasher.update([3u8]); + hasher.update([0u8; 176]); let hash = ::from(hasher.finalize_fixed()); Scalar::from_repr(hash.into()).unwrap() } - pub fn produce_utxo_secret_holder(&self) -> UTXOSecretKeyHolder { - UTXOSecretKeyHolder { + pub fn produce_private_key_holder(&self) -> PrivateKeyHolder { + PrivateKeyHolder { nullifier_secret_key: self.generate_nullifier_secret_key(), - viewing_secret_key: self.generate_viewing_secret_key(), + incoming_viewing_secret_key: self.generate_incloming_viewing_secret_key(), + outgoing_viewing_secret_key: self.generate_outgoing_viewing_secret_key(), } } } -impl UTXOSecretKeyHolder { - pub fn generate_nullifier_public_key(&self) -> AffinePoint { - (AffinePoint::GENERATOR * self.nullifier_secret_key).into() +impl PrivateKeyHolder { + pub fn generate_nullifier_public_key(&self) -> [u8; 32] { + let mut hasher = sha2::Sha256::new(); + + hasher.update("NSSA_keys"); + hasher.update(&self.nullifier_secret_key); + hasher.update([7u8]); + hasher.update([0u8; 176]); + + ::from(hasher.finalize_fixed()) } - pub fn generate_viewing_public_key(&self) -> AffinePoint { - (AffinePoint::GENERATOR * self.viewing_secret_key).into() + pub fn generate_incoming_viewing_public_key(&self) -> AffinePoint { + (AffinePoint::GENERATOR * self.incoming_viewing_secret_key).into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn seed_generation_test() { + let seed_holder = SeedHolder::new_os_random(); + + assert_eq!(seed_holder.seed.len(), 64); + } + + #[test] + fn ssk_generation_test() { + let seed_holder = SeedHolder::new_os_random(); + + assert_eq!(seed_holder.seed.len(), 64); + + let _ = seed_holder.generate_secret_spending_key_hash(); + } + + #[test] + fn ivs_generation_test() { + let seed_holder = SeedHolder::new_os_random(); + + assert_eq!(seed_holder.seed.len(), 64); + + let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); + + let _ = top_secret_key_holder.generate_incloming_viewing_secret_key(); + } + + #[test] + fn ovs_generation_test() { + let seed_holder = SeedHolder::new_os_random(); + + assert_eq!(seed_holder.seed.len(), 64); + + let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); + + let _ = top_secret_key_holder.generate_outgoing_viewing_secret_key(); } } diff --git a/key_protocol/src/key_management/types.rs b/key_protocol/src/key_management/types.rs new file mode 100644 index 0000000..8878f25 --- /dev/null +++ b/key_protocol/src/key_management/types.rs @@ -0,0 +1,8 @@ +use elliptic_curve::{ + consts::{B0, B1}, + generic_array::GenericArray, +}; +use sha2::digest::typenum::{UInt, UTerm}; + +pub type CipherText = Vec; +pub type Nonce = GenericArray, B1>, B0>, B0>>; From 33783e06d8a6d8e4c1072f45e7bb6fec01ae1f15 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 8 Sep 2025 14:48:58 +0300 Subject: [PATCH 3/5] fix: keys structures updates --- .../key_management/ephemeral_key_holder.rs | 45 ++++----- key_protocol/src/key_management/mod.rs | 79 ++++------------ .../src/key_management/secret_holders.rs | 10 +- key_protocol/src/key_protocol_core/mod.rs | 92 +++++++++++++++---- wallet/src/chain_storage/mod.rs | 2 +- wallet/src/lib.rs | 6 +- 6 files changed, 123 insertions(+), 111 deletions(-) diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index 6392678..108a41e 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -1,11 +1,7 @@ -use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, KeyInit}; -use elliptic_curve::point::AffineCoordinates; use elliptic_curve::PrimeField; -use k256::{AffinePoint, FieldBytes, Scalar}; +use k256::{AffinePoint, Scalar}; use log::info; -use rand::{rngs::OsRng, RngCore}; - -use super::types::{CipherText, Nonce}; +use sha2::Digest; #[derive(Debug)] ///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender. @@ -14,13 +10,24 @@ pub struct EphemeralKeyHolder { } impl EphemeralKeyHolder { - pub fn new_os_random() -> Self { - let mut bytes = FieldBytes::default(); + pub fn new( + receiver_nullifier_public_key: [u8; 32], + sender_outgoing_viewing_secret_key: Scalar, + nonce: u64, + ) -> Self { + let mut hasher = sha2::Sha256::new(); + hasher.update(receiver_nullifier_public_key); + hasher.update(nonce.to_le_bytes()); + hasher.update([0; 192]); - OsRng.fill_bytes(&mut bytes); + let hash_recepient = hasher.finalize(); + + let mut hasher = sha2::Sha256::new(); + hasher.update(sender_outgoing_viewing_secret_key.to_bytes()); + hasher.update(hash_recepient); Self { - ephemeral_secret_key: Scalar::from_repr(bytes).unwrap(), + ephemeral_secret_key: Scalar::from_repr(hasher.finalize()).unwrap(), } } @@ -30,21 +37,9 @@ impl EphemeralKeyHolder { pub fn calculate_shared_secret_sender( &self, - viewing_public_key_receiver: AffinePoint, - ) -> AffinePoint { - (viewing_public_key_receiver * self.ephemeral_secret_key).into() - } - - pub fn encrypt_data( - &self, - viewing_public_key_receiver: AffinePoint, - data: &[u8], - ) -> (CipherText, Nonce) { - let shared_secret = self.calculate_shared_secret_sender(viewing_public_key_receiver); - let cipher = Aes256Gcm::new(&shared_secret.x()); - let nonce = Aes256Gcm::generate_nonce(&mut OsRng); - - (cipher.encrypt(&nonce, data).unwrap(), nonce) + receiver_incoming_viewing_public_key: Scalar, + ) -> Scalar { + receiver_incoming_viewing_public_key * self.ephemeral_secret_key } pub fn log(&self) { diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index fb75dff..d3a4b08 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit}; use common::merkle_tree_public::TreeHashType; use elliptic_curve::group::GroupEncoding; @@ -23,8 +21,6 @@ pub mod types; pub struct KeyChain { top_secret_key_holder: TopSecretKeyHolder, pub private_key_holder: PrivateKeyHolder, - ///Map for all users accounts - pub_account_signing_keys: HashMap, pub nullifer_public_key: [u8; 32], pub incoming_viewing_public_key: PublicKey, } @@ -46,61 +42,27 @@ impl KeyChain { private_key_holder, nullifer_public_key, incoming_viewing_public_key, - pub_account_signing_keys: HashMap::new(), - } - } - - pub fn new_os_random_with_accounts(accounts: HashMap) -> Self { - //Currently dropping SeedHolder at the end of initialization. - //Now entirely sure if we need it in the future. - let seed_holder = SeedHolder::new_os_random(); - let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - - let private_key_holder = top_secret_key_holder.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 { - top_secret_key_holder, - private_key_holder, - nullifer_public_key, - incoming_viewing_public_key, - pub_account_signing_keys: accounts, } } pub fn produce_user_address(&self) -> [u8; 32] { let mut hasher = sha2::Sha256::new(); - hasher.update(&self.nullifer_public_key); - hasher.update(&self.incoming_viewing_public_key.to_bytes()); + hasher.update(self.nullifer_public_key); + hasher.update(self.incoming_viewing_public_key.to_bytes()); ::from(hasher.finalize_fixed()) } - pub fn generate_new_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 - } - - /// Returns the signing key for public transaction signatures - pub fn get_pub_account_signing_key( - &self, - address: &nssa::Address, - ) -> Option<&nssa::PrivateKey> { - self.pub_account_signing_keys.get(address) - } - pub fn calculate_shared_secret_receiver( &self, ephemeral_public_key_sender: AffinePoint, ) -> AffinePoint { - (ephemeral_public_key_sender * self.utxo_secret_key_holder.viewing_secret_key).into() + (ephemeral_public_key_sender + * self + .top_secret_key_holder + .generate_incloming_viewing_secret_key()) + .into() } pub fn decrypt_data( @@ -197,9 +159,19 @@ mod tests { fn test_decrypt_data() { let address_key_holder = KeyChain::new_os_random(); + let test_receiver_nullifier_public_key = [42; 32]; + let sender_outgoing_viewing_key = address_key_holder + .top_secret_key_holder + .generate_outgoing_viewing_secret_key(); + let nonce = 0; + // Generate an ephemeral key and shared secret - let ephemeral_public_key_sender = - EphemeralKeyHolder::new_os_random().generate_ephemeral_public_key(); + let ephemeral_public_key_sender = EphemeralKeyHolder::new( + test_receiver_nullifier_public_key, + sender_outgoing_viewing_key, + nonce, + ) + .generate_ephemeral_public_key(); let shared_secret = address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); @@ -340,19 +312,6 @@ mod tests { assert_eq!(decrypted_data, plaintext); } - #[test] - fn test_get_public_account_signing_key() { - let mut address_key_holder = KeyChain::new_os_random(); - - let address = address_key_holder.generate_new_private_key(); - - let is_private_key_generated = address_key_holder - .get_pub_account_signing_key(&address) - .is_some(); - - assert!(is_private_key_generated); - } - #[test] fn key_generation_test() { let seed_holder = SeedHolder::new_os_random(); diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index a5258b5..5180efb 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 { let mut hash = hmac_sha512::HMAC::mac(&self.seed, "NSSA_seed"); for _ in 1..2048 { - hash = hmac_sha512::HMAC::mac(&hash, "NSSA_seed"); + hash = hmac_sha512::HMAC::mac(hash, "NSSA_seed"); } //Safe unwrap @@ -64,7 +64,7 @@ impl TopSecretKeyHolder { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(&self.secret_spending_key); + hasher.update(self.secret_spending_key); hasher.update([1u8]); hasher.update([0u8; 176]); @@ -75,7 +75,7 @@ impl TopSecretKeyHolder { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(&self.secret_spending_key); + hasher.update(self.secret_spending_key); hasher.update([2u8]); hasher.update([0u8; 176]); @@ -88,7 +88,7 @@ impl TopSecretKeyHolder { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(&self.secret_spending_key); + hasher.update(self.secret_spending_key); hasher.update([3u8]); hasher.update([0u8; 176]); @@ -111,7 +111,7 @@ impl PrivateKeyHolder { let mut hasher = sha2::Sha256::new(); hasher.update("NSSA_keys"); - hasher.update(&self.nullifier_secret_key); + hasher.update(self.nullifier_secret_key); hasher.update([7u8]); hasher.update([0u8; 176]); diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 67cb52c..44d8ff6 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -10,17 +10,14 @@ pub type PublicKey = AffinePoint; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NSSAUserData { - pub key_holder: KeyChain, + ///Map for all user public accounts + pub_account_signing_keys: HashMap, + ///Map for all user private accounts + user_private_accounts: HashMap, } impl NSSAUserData { - pub fn new() -> Self { - let key_holder = KeyChain::new_os_random(); - - Self { key_holder } - } - - fn valid_key_transaction_pairing_check( + fn valid_public_key_transaction_pairing_check( accounts_keys_map: &HashMap, ) -> bool { let mut check_res = true; @@ -32,30 +29,78 @@ impl NSSAUserData { check_res } + fn valid_private_key_transaction_pairing_check( + accounts_keys_map: &HashMap, + ) -> bool { + let mut check_res = true; + for (addr, key) in accounts_keys_map { + if nssa::Address::new(key.produce_user_address()) != *addr { + check_res = false; + } + } + check_res + } + pub fn new_with_accounts( accounts_keys: HashMap, + accounts_key_chains: HashMap, ) -> Result { - if !Self::valid_key_transaction_pairing_check(&accounts_keys) { + 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"); } - let key_holder = KeyChain::new_os_random_with_accounts(accounts_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 { key_holder }) + Ok(Self { + pub_account_signing_keys: accounts_keys, + user_private_accounts: accounts_key_chains, + }) } - pub fn generate_new_account(&mut self) -> nssa::Address { - self.key_holder.generate_new_private_key() + /// 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 get_account_signing_key(&self, address: &nssa::Address) -> Option<&nssa::PrivateKey> { - self.key_holder.get_pub_account_signing_key(address) + /// Returns the signing key for public transaction signatures + pub fn get_pub_account_signing_key( + &self, + address: &nssa::Address, + ) -> Option<&nssa::PrivateKey> { + self.pub_account_signing_keys.get(address) + } + + /// 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::new(key_chain.produce_user_address()); + + self.user_private_accounts.insert(address, key_chain); + + address + } + + /// Returns the signing key for public transaction signatures + pub fn get_private_account_key_chain(&self, address: &nssa::Address) -> Option<&KeyChain> { + self.user_private_accounts.get(address) } } impl Default for NSSAUserData { fn default() -> Self { - Self::new() + //Safe unwrap as maps are empty + Self::new_with_accounts(HashMap::default(), HashMap::default()).unwrap() } } @@ -65,8 +110,19 @@ mod tests { #[test] fn test_new_account() { - let mut user_data = NSSAUserData::new(); + let mut user_data = NSSAUserData::default(); - let _addr = user_data.generate_new_account(); + let addr_pub = user_data.generate_new_public_transaction_private_key(); + let addr_private = user_data.generate_new_privacy_preserving_transaction_key_chain(); + + let is_private_key_generated = user_data.get_pub_account_signing_key(&addr_pub).is_some(); + + assert!(is_private_key_generated); + + let is_key_chain_generated = user_data + .get_private_account_key_chain(&addr_private) + .is_some(); + + assert!(is_key_chain_generated); } } diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index 7dfb8a2..e68b4a8 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -24,7 +24,7 @@ impl WalletChainStore { let utxo_commitments_store = UTXOCommitmentsMerkleTree::new(vec![]); Ok(Self { - user_data: NSSAUserData::new_with_accounts(accounts_keys)?, + user_data: NSSAUserData::new_with_accounts(accounts_keys, HashMap::new())?, utxo_commitments_store, wallet_config: config, }) diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index e2213dc..1cbbb94 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -41,7 +41,9 @@ impl WalletCore { } pub fn create_new_account(&mut self) -> Address { - self.storage.user_data.generate_new_account() + self.storage + .user_data + .generate_new_public_transaction_private_key() } pub fn search_for_initial_account(&self, acc_addr: Address) -> Option { @@ -75,7 +77,7 @@ impl WalletCore { ) .unwrap(); - let signing_key = self.storage.user_data.get_account_signing_key(&from); + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); if let Some(signing_key) = signing_key { let witness_set = From 48374139899147eccb2f26a32c97f89f944c0ebc Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 8 Sep 2025 15:03:02 +0300 Subject: [PATCH 4/5] fix: merge fix --- key_protocol/src/key_management/ephemeral_key_holder.rs | 8 ++------ key_protocol/src/key_protocol_core/mod.rs | 2 +- wallet/src/chain_storage/mod.rs | 1 - wallet/src/helperfunctions.rs | 2 +- wallet/src/lib.rs | 4 ++-- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index 55120a6..108a41e 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -1,11 +1,7 @@ -use aes_gcm::{AeadCore, Aes256Gcm, KeyInit, aead::Aead}; use elliptic_curve::PrimeField; -use elliptic_curve::point::AffineCoordinates; -use k256::{AffinePoint, FieldBytes, Scalar}; +use k256::{AffinePoint, Scalar}; use log::info; -use rand::{RngCore, rngs::OsRng}; - -use super::constants_types::{CipherText, Nonce}; +use sha2::Digest; #[derive(Debug)] ///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender. diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 44d8ff6..167f8fe 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -11,7 +11,7 @@ pub type PublicKey = AffinePoint; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NSSAUserData { ///Map for all user public accounts - pub_account_signing_keys: HashMap, + pub pub_account_signing_keys: HashMap, ///Map for all user private accounts user_private_accounts: HashMap, } diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index e08650c..9a6bd59 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -32,7 +32,6 @@ impl WalletChainStore { pub(crate) fn insert_account_data(&mut self, acc_data: PersistentAccountData) { self.user_data - .key_holder .pub_account_signing_keys .insert(acc_data.address, acc_data.pub_sign_key); } diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 8a7b7b6..45bb5f0 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -53,7 +53,7 @@ pub fn fetch_persistent_accounts() -> Result> { pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec { let mut vec_for_storage = vec![]; - for (addr, key) in &user_data.key_holder.pub_account_signing_keys { + for (addr, key) in &user_data.pub_account_signing_keys { vec_for_storage.push(PersistentAccountData { address: *addr, pub_sign_key: key.clone(), diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index a52b1c6..f9c5f24 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -111,7 +111,7 @@ impl WalletCore { ) .unwrap(); - let signing_key = self.storage.user_data.get_account_signing_key(&from); + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); let Some(signing_key) = signing_key else { return Err(ExecutionFailureKind::KeyNotFoundError); @@ -228,7 +228,7 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { Command::RegisterAccount {} => { let addr = wallet_core.create_new_account(); - let key = wallet_core.storage.user_data.get_account_signing_key(&addr); + let key = wallet_core.storage.user_data.get_pub_account_signing_key(&addr); info!("Generated new account with addr {addr:#?}"); info!("With key {key:#?}"); From 23e79d3e17dd743efd50696b9d7584008d86356f Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Mon, 8 Sep 2025 15:23:32 +0300 Subject: [PATCH 5/5] fix: fmt --- key_protocol/src/key_management/mod.rs | 4 ++-- key_protocol/src/key_management/secret_holders.rs | 2 +- key_protocol/src/key_protocol_core/mod.rs | 8 ++++++-- wallet/src/lib.rs | 5 ++++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index 9fc3f06..ea337dd 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -1,4 +1,4 @@ -use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit}; +use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead}; use common::merkle_tree_public::TreeHashType; use elliptic_curve::group::GroupEncoding; use elliptic_curve::point::AffineCoordinates; @@ -6,7 +6,7 @@ use k256::AffinePoint; use log::info; use secret_holders::{PrivateKeyHolder, SeedHolder, TopSecretKeyHolder}; use serde::{Deserialize, Serialize}; -use sha2::{digest::FixedOutput, Digest}; +use sha2::{Digest, digest::FixedOutput}; use types::{CipherText, Nonce}; use crate::key_protocol_core::PublicKey; diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 9387747..b6784cd 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -2,7 +2,7 @@ use bip39::Mnemonic; use common::merkle_tree_public::TreeHashType; use elliptic_curve::PrimeField; use k256::{AffinePoint, Scalar}; -use rand::{rngs::OsRng, RngCore}; +use rand::{RngCore, rngs::OsRng}; use serde::{Deserialize, Serialize}; use sha2::{Digest, digest::FixedOutput}; diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 167f8fe..58f60ce 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -46,11 +46,15 @@ impl NSSAUserData { accounts_key_chains: HashMap, ) -> 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"); + 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"); + anyhow::bail!( + "Key transaction pairing check not satisfied, there is addresses, which is not derived from keys" + ); } Ok(Self { diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index f9c5f24..5a24933 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -228,7 +228,10 @@ pub async fn execute_subcommand(command: Command) -> Result<()> { Command::RegisterAccount {} => { let addr = wallet_core.create_new_account(); - let key = wallet_core.storage.user_data.get_pub_account_signing_key(&addr); + let key = wallet_core + .storage + .user_data + .get_pub_account_signing_key(&addr); info!("Generated new account with addr {addr:#?}"); info!("With key {key:#?}");