diff --git a/Cargo.toml b/Cargo.toml index 8053d62..82f6736 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,8 @@ 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" +hmac-sha512 = "1.1.7" chrono = "0.4.41" rocksdb = { version = "0.21.0", default-features = false, features = [ diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index ba8edc7..d453753 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -15,6 +15,9 @@ elliptic-curve.workspace = true hex.workspace = true aes-gcm.workspace = true lazy_static.workspace = true +bip39.workspace = true +hmac-sha512.workspace = true +nssa-core = { path = "../nssa/core", features = ["host"] } [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 b304292..b4835ff 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -1,50 +1,52 @@ -use aes_gcm::{AeadCore, Aes256Gcm, KeyInit, aead::Aead}; -use elliptic_curve::PrimeField; -use elliptic_curve::point::AffineCoordinates; -use k256::{AffinePoint, FieldBytes, Scalar}; use log::info; -use rand::{RngCore, rngs::OsRng}; +use nssa_core::{ + NullifierPublicKey, SharedSecretKey, + encryption::{EphemeralPublicKey, EphemeralSecretKey, IncomingViewingPublicKey}, +}; +use sha2::Digest; -use super::constants_types::{CipherText, Nonce}; +use crate::key_management::secret_holders::OutgoingViewingSecretKey; #[derive(Debug)] ///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender. pub struct EphemeralKeyHolder { - ephemeral_secret_key: Scalar, + ephemeral_secret_key: EphemeralSecretKey, } impl EphemeralKeyHolder { - pub fn new_os_random() -> Self { - let mut bytes = FieldBytes::default(); + pub fn new( + receiver_nullifier_public_key: NullifierPublicKey, + sender_outgoing_viewing_secret_key: OutgoingViewingSecretKey, + nonce: u64, + ) -> Self { + let mut hasher = sha2::Sha256::new(); + hasher.update(receiver_nullifier_public_key); + hasher.update(nonce.to_le_bytes()); + hasher.update([0; 24]); - OsRng.fill_bytes(&mut bytes); + let hash_recepient = hasher.finalize(); + + let mut hasher = sha2::Sha256::new(); + hasher.update(sender_outgoing_viewing_secret_key); + hasher.update(hash_recepient); Self { - ephemeral_secret_key: Scalar::from_repr(bytes).unwrap(), + ephemeral_secret_key: hasher.finalize().into(), } } - pub fn generate_ephemeral_public_key(&self) -> AffinePoint { - (AffinePoint::GENERATOR * self.ephemeral_secret_key).into() + pub fn generate_ephemeral_public_key(&self) -> EphemeralPublicKey { + EphemeralPublicKey::from_scalar(self.ephemeral_secret_key) } 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: IncomingViewingPublicKey, + ) -> SharedSecretKey { + SharedSecretKey::new( + &self.ephemeral_secret_key, + &receiver_incoming_viewing_public_key, + ) } pub fn log(&self) { diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index 1a037ef..5f5f2aa 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -1,29 +1,25 @@ -use std::collections::HashMap; - -use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead}; -use constants_types::{CipherText, Nonce}; -use elliptic_curve::point::AffineCoordinates; -use k256::AffinePoint; +use common::TreeHashType; use log::info; -use secret_holders::{SeedHolder, TopSecretKeyHolder, UTXOSecretKeyHolder}; +use nssa_core::{ + NullifierPublicKey, SharedSecretKey, + encryption::{EphemeralPublicKey, IncomingViewingPublicKey}, +}; +use secret_holders::{PrivateKeyHolder, SecretSpendingKey, SeedHolder}; use serde::{Deserialize, Serialize}; +use sha2::{Digest, digest::FixedOutput}; -use crate::key_protocol_core::PublicKey; pub type PublicAccountSigningKey = [u8; 32]; -pub mod constants_types; pub mod ephemeral_key_holder; pub mod secret_holders; #[derive(Serialize, Deserialize, Clone, Debug)] ///Entrypoint to key management pub struct KeyChain { - top_secret_key_holder: TopSecretKeyHolder, - pub utxo_secret_key_holder: UTXOSecretKeyHolder, - ///Map for all users accounts - pub pub_account_signing_keys: HashMap, - pub nullifer_public_key: PublicKey, - pub viewing_public_key: PublicKey, + secret_spending_key: SecretSpendingKey, + pub private_key_holder: PrivateKeyHolder, + pub nullifer_public_key: NullifierPublicKey, + pub incoming_viewing_public_key: IncomingViewingPublicKey, } impl KeyChain { @@ -31,95 +27,61 @@ impl KeyChain { //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 secret_spending_key = seed_holder.produce_top_secret_key_holder(); - let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder(); + let private_key_holder = secret_spending_key.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, + secret_spending_key, + private_key_holder, nullifer_public_key, - viewing_public_key, - pub_account_signing_keys: HashMap::new(), + incoming_viewing_public_key, } } - 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(); + pub fn produce_user_address(&self) -> [u8; 32] { + let mut hasher = sha2::Sha256::new(); - let utxo_secret_key_holder = top_secret_key_holder.produce_utxo_secret_holder(); + hasher.update(&self.nullifer_public_key); + hasher.update(self.incoming_viewing_public_key.to_bytes()); - let nullifer_public_key = utxo_secret_key_holder.generate_nullifier_public_key(); - let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key(); - - Self { - top_secret_key_holder, - utxo_secret_key_holder, - nullifer_public_key, - viewing_public_key, - pub_account_signing_keys: accounts, - } - } - - 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) + ::from(hasher.finalize_fixed()) } 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() - } - - pub fn decrypt_data( - &self, - ephemeral_public_key_sender: AffinePoint, - ciphertext: CipherText, - nonce: Nonce, - ) -> Result, aes_gcm::Error> { - let shared_secret = self.calculate_shared_secret_receiver(ephemeral_public_key_sender); - let cipher = Aes256Gcm::new(&shared_secret.x()); - - cipher.decrypt(&nonce, ciphertext.as_slice()) + ephemeral_public_key_sender: EphemeralPublicKey, + ) -> SharedSecretKey { + SharedSecretKey::new( + &self + .secret_spending_key + .generate_incoming_viewing_secret_key(), + &ephemeral_public_key_sender, + ) } pub fn log(&self) { info!( "Secret spending key is {:?}", - hex::encode( - serde_json::to_vec(&self.top_secret_key_holder.secret_spending_key).unwrap() - ), + hex::encode(serde_json::to_vec(&self.secret_spending_key).unwrap()), ); 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,25 +90,16 @@ 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()), ); } } #[cfg(test)] mod tests { - use aes_gcm::{ - Aes256Gcm, - aead::{Aead, KeyInit, OsRng}, - }; - 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 crate::key_management::ephemeral_key_holder::EphemeralKeyHolder; + use aes_gcm::aead::OsRng; + use k256::AffinePoint; + use rand::RngCore; use super::*; @@ -156,12 +109,7 @@ mod tests { let address_key_holder = KeyChain::new_os_random(); // Check that key holder fields are initialized with expected types - assert!(!Into::::into( - address_key_holder.nullifer_public_key.is_identity() - )); - assert!(!Into::::into( - address_key_holder.viewing_public_key.is_identity() - )); + assert_ne!(address_key_holder.nullifer_public_key.as_ref(), &[0u8; 32]); } #[test] @@ -169,176 +117,13 @@ mod tests { let address_key_holder = KeyChain::new_os_random(); // Generate a random ephemeral public key sender - let scalar = Scalar::random(&mut OsRng); - let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine(); + let mut scalar = [0; 32]; + OsRng.fill_bytes(&mut scalar); + let ephemeral_public_key_sender = EphemeralPublicKey::from_scalar(scalar); // Calculate shared secret - let shared_secret = + let _shared_secret = address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - - // Ensure the shared secret is not an identity point (suggesting non-zero output) - assert!(!Into::::into(shared_secret.is_identity())); - } - - #[test] - fn test_decrypt_data() { - let address_key_holder = KeyChain::new_os_random(); - - // Generate an ephemeral key and shared secret - let ephemeral_public_key_sender = - EphemeralKeyHolder::new_os_random().generate_ephemeral_public_key(); - let shared_secret = - address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - - // Encrypt sample data - let cipher = Aes256Gcm::new(&shared_secret.x()); - let nonce = Nonce::from_slice(b"unique nonce"); - let plaintext = b"Sensitive data"; - let ciphertext = cipher - .encrypt(nonce, plaintext.as_ref()) - .expect("encryption failure"); - - // Attempt decryption - let decrypted_data: Vec = address_key_holder - .decrypt_data( - ephemeral_public_key_sender, - CipherText::from(ciphertext), - *nonce, - ) - .unwrap(); - - // Verify decryption is successful and matches original plaintext - 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(); - - // Use identity point as ephemeral public key - let identity_point = AffinePoint::identity(); - - // Calculate shared secret - let shared_secret = address_key_holder.calculate_shared_secret_receiver(identity_point); - - // The shared secret with the identity point should also result in the identity point - assert!(Into::::into(shared_secret.is_identity())); - } - - #[test] - #[should_panic] - fn test_decrypt_data_with_incorrect_nonce() { - let address_key_holder = KeyChain::new_os_random(); - - // Generate ephemeral public key and shared secret - let scalar = Scalar::random(OsRng); - let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine(); - let shared_secret = - address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - - // Encrypt sample data with a specific nonce - let cipher = Aes256Gcm::new(&shared_secret.x()); - let nonce = Nonce::from_slice(b"unique nonce"); - let plaintext = b"Sensitive data"; - let ciphertext = cipher - .encrypt(nonce, plaintext.as_ref()) - .expect("encryption failure"); - - // Attempt decryption with an incorrect nonce - let incorrect_nonce = Nonce::from_slice(b"wrong nonce"); - let decrypted_data = address_key_holder - .decrypt_data( - ephemeral_public_key_sender, - CipherText::from(ciphertext.clone()), - *incorrect_nonce, - ) - .unwrap(); - - // The decryption should fail or produce incorrect output due to nonce mismatch - assert_ne!(decrypted_data, plaintext); - } - - #[test] - #[should_panic] - fn test_decrypt_data_with_incorrect_ciphertext() { - let address_key_holder = KeyChain::new_os_random(); - - // Generate ephemeral public key and shared secret - let scalar = Scalar::random(OsRng); - let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine(); - let shared_secret = - address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - - // Encrypt sample data - let cipher = Aes256Gcm::new(&shared_secret.x()); - let nonce = Nonce::from_slice(b"unique nonce"); - let plaintext = b"Sensitive data"; - let ciphertext = cipher - .encrypt(nonce, plaintext.as_ref()) - .expect("encryption failure"); - - // Tamper with the ciphertext to simulate corruption - let mut corrupted_ciphertext = ciphertext.clone(); - corrupted_ciphertext[0] ^= 1; // Flip a bit in the ciphertext - - // Attempt decryption - let result = address_key_holder - .decrypt_data( - ephemeral_public_key_sender, - CipherText::from(corrupted_ciphertext), - *nonce, - ) - .unwrap(); - - // The decryption should fail or produce incorrect output due to tampered ciphertext - assert_ne!(result, plaintext); - } - - #[test] - fn test_encryption_decryption_round_trip() { - let address_key_holder = KeyChain::new_os_random(); - - // Generate ephemeral key and shared secret - let scalar = Scalar::random(OsRng); - let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine(); - - // Encrypt sample data - let plaintext = b"Round-trip test data"; - let nonce = Nonce::from_slice(b"unique nonce"); - - let shared_secret = - address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); - let cipher = Aes256Gcm::new(&shared_secret.x()); - - let ciphertext = cipher - .encrypt(nonce, plaintext.as_ref()) - .expect("encryption failure"); - - // Decrypt the data using the `KeyChain` instance - let decrypted_data = address_key_holder - .decrypt_data( - ephemeral_public_key_sender, - CipherText::from(ciphertext), - *nonce, - ) - .unwrap(); - - // Verify the decrypted data matches the original plaintext - assert_eq!(decrypted_data, plaintext); } #[test] @@ -346,10 +131,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(); @@ -364,11 +149,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 87e4540..80ec1b0 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -1,101 +1,157 @@ +use bip39::Mnemonic; use common::TreeHashType; -use elliptic_curve::PrimeField; -use k256::{AffinePoint, FieldBytes, Scalar}; +use nssa_core::{ + NullifierPublicKey, NullifierSecretKey, + encryption::{IncomingViewingPublicKey, Scalar}, +}; use rand::{RngCore, rngs::OsRng}; use serde::{Deserialize, Serialize}; use sha2::{Digest, digest::FixedOutput}; -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. -pub struct TopSecretKeyHolder { - pub secret_spending_key: Scalar, -} +///Secret spending key object. Can produce `PrivateKeyHolder` objects. +pub struct SecretSpendingKey(pub(crate) [u8; 32]); + +pub type IncomingViewingSecretKey = Scalar; +pub type OutgoingViewingSecretKey = Scalar; #[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: NullifierSecretKey, + pub(crate) incoming_viewing_secret_key: IncomingViewingSecretKey, + pub(crate) outgoing_viewing_secret_key: OutgoingViewingSecretKey, } 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_wide = mnemonic.to_seed("mnemonic"); Self { - seed: Scalar::from_repr(bytes).unwrap(), + seed: seed_wide.to_vec(), } } pub fn generate_secret_spending_key_hash(&self) -> TreeHashType { + let mut hash = hmac_sha512::HMAC::mac(&self.seed, "NSSA_seed"); + + for _ in 1..2048 { + hash = hmac_sha512::HMAC::mac(hash, "NSSA_seed"); + } + + //Safe unwrap + *hash.first_chunk::<32>().unwrap() + } + + pub fn produce_top_secret_key_holder(&self) -> SecretSpendingKey { + SecretSpendingKey(self.generate_secret_spending_key_hash()) + } +} + +impl SecretSpendingKey { + pub fn generate_nullifier_secret_key(&self) -> NullifierSecretKey { let mut hasher = sha2::Sha256::new(); - hasher.update(self.seed.to_bytes()); + hasher.update("NSSA_keys"); + hasher.update(self.0); + hasher.update([1u8]); + hasher.update([0u8; 22]); + + ::from(hasher.finalize_fixed()) + } + + pub fn generate_incoming_viewing_secret_key(&self) -> IncomingViewingSecretKey { + let mut hasher = sha2::Sha256::new(); + + hasher.update("NSSA_keys"); + hasher.update(self.0); + hasher.update([2u8]); + hasher.update([0u8; 22]); ::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() - } - - pub fn produce_top_secret_key_holder(&self) -> TopSecretKeyHolder { - TopSecretKeyHolder { - secret_spending_key: self.generate_secret_spending_key_scalar(), - } - } -} - -impl TopSecretKeyHolder { - pub fn generate_nullifier_secret_key(&self) -> Scalar { + pub fn generate_outgoing_viewing_secret_key(&self) -> OutgoingViewingSecretKey { 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.0); + hasher.update([3u8]); + hasher.update([0u8; 22]); - let hash = ::from(hasher.finalize_fixed()); - - Scalar::from_repr(hash.into()).unwrap() + ::from(hasher.finalize_fixed()) } - pub fn generate_viewing_secret_key(&self) -> Scalar { - let mut hasher = sha2::Sha256::new(); - - hasher.update(self.secret_spending_key.to_bytes()); - hasher.update(*VIEWING_SECRET_CONST); - - 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_incoming_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) -> NullifierPublicKey { + (&self.nullifier_secret_key).into() } - pub fn generate_viewing_public_key(&self) -> AffinePoint { - (AffinePoint::GENERATOR * self.viewing_secret_key).into() + pub fn generate_incoming_viewing_public_key(&self) -> IncomingViewingPublicKey { + IncomingViewingPublicKey::from_scalar(self.incoming_viewing_secret_key) + } +} + +#[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_incoming_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_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 7757183..58f60ce 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 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,32 +29,82 @@ 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() } } @@ -67,8 +114,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/nssa/Cargo.toml b/nssa/Cargo.toml index 3163902..96f6530 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -14,6 +14,7 @@ secp256k1 = "0.31.1" rand = "0.8" borsh = "1.5.7" hex = "0.4.3" +k256 = "0.13.3" [dev-dependencies] test-program-methods = { path = "test_program_methods" } diff --git a/nssa/core/src/encryption/mod.rs b/nssa/core/src/encryption/mod.rs index b79e75c..7b8d51f 100644 --- a/nssa/core/src/encryption/mod.rs +++ b/nssa/core/src/encryption/mod.rs @@ -13,6 +13,8 @@ pub use shared_key_derivation::{EphemeralPublicKey, EphemeralSecretKey, Incoming use crate::{Commitment, account::Account}; +pub type Scalar = [u8; 32]; + #[derive(Serialize, Deserialize, Clone)] pub struct SharedSecretKey([u8; 32]); diff --git a/nssa/core/src/encryption/shared_key_derivation.rs b/nssa/core/src/encryption/shared_key_derivation.rs index c735105..a889123 100644 --- a/nssa/core/src/encryption/shared_key_derivation.rs +++ b/nssa/core/src/encryption/shared_key_derivation.rs @@ -1,22 +1,22 @@ use serde::{Deserialize, Serialize}; use k256::{ - AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, Scalar, + AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, elliptic_curve::{ PrimeField, sec1::{FromEncodedPoint, ToEncodedPoint}, }, }; -use crate::SharedSecretKey; +use crate::{SharedSecretKey, encryption::Scalar}; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct Secp256k1Point(pub(crate) Vec); impl Secp256k1Point { - pub fn from_scalar(value: [u8; 32]) -> Secp256k1Point { + pub fn from_scalar(value: Scalar) -> Secp256k1Point { let x_bytes: FieldBytes = value.into(); - let x = Scalar::from_repr(x_bytes).unwrap(); + let x = k256::Scalar::from_repr(x_bytes).unwrap(); let p = ProjectivePoint::GENERATOR * x; let q = AffinePoint::from(p); @@ -26,7 +26,7 @@ impl Secp256k1Point { } } -pub type EphemeralSecretKey = [u8; 32]; +pub type EphemeralSecretKey = Scalar; pub type EphemeralPublicKey = Secp256k1Point; pub type IncomingViewingPublicKey = Secp256k1Point; impl From<&EphemeralSecretKey> for EphemeralPublicKey { @@ -36,8 +36,8 @@ impl From<&EphemeralSecretKey> for EphemeralPublicKey { } impl SharedSecretKey { - pub fn new(scalar: &[u8; 32], point: &Secp256k1Point) -> Self { - let scalar = Scalar::from_repr((*scalar).into()).unwrap(); + pub fn new(scalar: &Scalar, point: &Secp256k1Point) -> Self { + let scalar = k256::Scalar::from_repr((*scalar).into()).unwrap(); let point: [u8; 33] = point.0.clone().try_into().unwrap(); let encoded = EncodedPoint::from_bytes(point).unwrap(); diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index d1410de..bc73d8c 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -7,6 +7,12 @@ use crate::Commitment; #[cfg_attr(any(feature = "host", test), derive(Debug, Clone, Hash))] pub struct NullifierPublicKey(pub(super) [u8; 32]); +impl AsRef<[u8]> for NullifierPublicKey { + fn as_ref(&self) -> &[u8] { + self.0.as_slice() + } +} + impl From<&NullifierSecretKey> for NullifierPublicKey { fn from(value: &NullifierSecretKey) -> Self { let mut bytes = Vec::new(); diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 244a81f..a769c72 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -151,7 +151,7 @@ pub mod tests { #[test] fn test_encrypted_account_data_constructor() { let npk = NullifierPublicKey::from(&[1; 32]); - let ivk = IncomingViewingPublicKey::from(&[2; 32]); + let ivk = IncomingViewingPublicKey::from_scalar([2; 32]); let account = Account::default(); let commitment = Commitment::new(&npk, &account); let esk = [3; 32]; diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 5142379..baf667c 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -225,7 +225,7 @@ pub mod tests { use nssa_core::{ Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, account::{Account, AccountWithMetadata, Nonce}, - encryption::{EphemeralPublicKey, IncomingViewingPublicKey}, + encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar}, }; fn transfer_transaction( @@ -738,7 +738,7 @@ pub mod tests { pub struct TestPrivateKeys { pub nsk: NullifierSecretKey, - pub isk: [u8; 32], + pub isk: Scalar, } impl TestPrivateKeys { diff --git a/wallet/src/chain_storage/mod.rs b/wallet/src/chain_storage/mod.rs index a803cf8..d3b07b3 100644 --- a/wallet/src/chain_storage/mod.rs +++ b/wallet/src/chain_storage/mod.rs @@ -20,14 +20,13 @@ impl WalletChainStore { .collect(); Ok(Self { - user_data: NSSAUserData::new_with_accounts(accounts_keys)?, + user_data: NSSAUserData::new_with_accounts(accounts_keys, HashMap::new())?, wallet_config: config, }) } 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 dc5b2f6..e3e6119 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -55,7 +55,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 e4c538a..5a7995b 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -73,7 +73,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 { @@ -110,7 +112,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);