Merge pull request #110 from vacp2p/Pravdyvy/key-protocol-update-private

Key protocol update (private)
This commit is contained in:
Pravdyvy 2025-09-20 04:57:35 +03:00 committed by GitHub
commit 6fda3d84dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 297 additions and 408 deletions

View File

@ -41,6 +41,8 @@ ark-bn254 = "0.5.0"
ark-ff = "0.5.0" ark-ff = "0.5.0"
tiny-keccak = { version = "2.0.2", features = ["keccak"] } tiny-keccak = { version = "2.0.2", features = ["keccak"] }
base64 = "0.22.1" base64 = "0.22.1"
bip39 = "2.2.0"
hmac-sha512 = "1.1.7"
chrono = "0.4.41" chrono = "0.4.41"
rocksdb = { version = "0.21.0", default-features = false, features = [ rocksdb = { version = "0.21.0", default-features = false, features = [

View File

@ -15,6 +15,9 @@ elliptic-curve.workspace = true
hex.workspace = true hex.workspace = true
aes-gcm.workspace = true aes-gcm.workspace = true
lazy_static.workspace = true lazy_static.workspace = true
bip39.workspace = true
hmac-sha512.workspace = true
nssa-core = { path = "../nssa/core", features = ["host"] }
[dependencies.common] [dependencies.common]
path = "../common" path = "../common"

View File

@ -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<u8>;
pub type Nonce = GenericArray<u8, UInt<UInt<UInt<UInt<UTerm, B1>, B1>, B0>, B0>>;

View File

@ -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 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)] #[derive(Debug)]
///Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral public keys. Can produce shared secret for sender. ///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 { pub struct EphemeralKeyHolder {
ephemeral_secret_key: Scalar, ephemeral_secret_key: EphemeralSecretKey,
} }
impl EphemeralKeyHolder { impl EphemeralKeyHolder {
pub fn new_os_random() -> Self { pub fn new(
let mut bytes = FieldBytes::default(); 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 { Self {
ephemeral_secret_key: Scalar::from_repr(bytes).unwrap(), ephemeral_secret_key: hasher.finalize().into(),
} }
} }
pub fn generate_ephemeral_public_key(&self) -> AffinePoint { pub fn generate_ephemeral_public_key(&self) -> EphemeralPublicKey {
(AffinePoint::GENERATOR * self.ephemeral_secret_key).into() EphemeralPublicKey::from_scalar(self.ephemeral_secret_key)
} }
pub fn calculate_shared_secret_sender( pub fn calculate_shared_secret_sender(
&self, &self,
viewing_public_key_receiver: AffinePoint, receiver_incoming_viewing_public_key: IncomingViewingPublicKey,
) -> AffinePoint { ) -> SharedSecretKey {
(viewing_public_key_receiver * self.ephemeral_secret_key).into() SharedSecretKey::new(
} &self.ephemeral_secret_key,
&receiver_incoming_viewing_public_key,
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)
} }
pub fn log(&self) { pub fn log(&self) {

View File

@ -1,29 +1,25 @@
use std::collections::HashMap; use common::TreeHashType;
use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead};
use constants_types::{CipherText, Nonce};
use elliptic_curve::point::AffineCoordinates;
use k256::AffinePoint;
use log::info; 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 serde::{Deserialize, Serialize};
use sha2::{Digest, digest::FixedOutput};
use crate::key_protocol_core::PublicKey;
pub type PublicAccountSigningKey = [u8; 32]; pub type PublicAccountSigningKey = [u8; 32];
pub mod constants_types;
pub mod ephemeral_key_holder; pub mod ephemeral_key_holder;
pub mod secret_holders; pub mod secret_holders;
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
///Entrypoint to key management ///Entrypoint to key management
pub struct KeyChain { pub struct KeyChain {
top_secret_key_holder: TopSecretKeyHolder, secret_spending_key: SecretSpendingKey,
pub utxo_secret_key_holder: UTXOSecretKeyHolder, pub private_key_holder: PrivateKeyHolder,
///Map for all users accounts pub nullifer_public_key: NullifierPublicKey,
pub pub_account_signing_keys: HashMap<nssa::Address, nssa::PrivateKey>, pub incoming_viewing_public_key: IncomingViewingPublicKey,
pub nullifer_public_key: PublicKey,
pub viewing_public_key: PublicKey,
} }
impl KeyChain { impl KeyChain {
@ -31,95 +27,61 @@ impl KeyChain {
//Currently dropping SeedHolder at the end of initialization. //Currently dropping SeedHolder at the end of initialization.
//Now entirely sure if we need it in the future. //Now entirely sure if we need it in the future.
let seed_holder = SeedHolder::new_os_random(); 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 nullifer_public_key = private_key_holder.generate_nullifier_public_key();
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key(); let incoming_viewing_public_key = private_key_holder.generate_incoming_viewing_public_key();
Self { Self {
top_secret_key_holder, secret_spending_key,
utxo_secret_key_holder, private_key_holder,
nullifer_public_key, nullifer_public_key,
viewing_public_key, incoming_viewing_public_key,
pub_account_signing_keys: HashMap::new(),
} }
} }
pub fn new_os_random_with_accounts(accounts: HashMap<nssa::Address, nssa::PrivateKey>) -> Self { pub fn produce_user_address(&self) -> [u8; 32] {
//Currently dropping SeedHolder at the end of initialization. let mut hasher = sha2::Sha256::new();
//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 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(); <TreeHashType>::from(hasher.finalize_fixed())
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)
} }
pub fn calculate_shared_secret_receiver( pub fn calculate_shared_secret_receiver(
&self, &self,
ephemeral_public_key_sender: AffinePoint, ephemeral_public_key_sender: EphemeralPublicKey,
) -> AffinePoint { ) -> SharedSecretKey {
(ephemeral_public_key_sender * self.utxo_secret_key_holder.viewing_secret_key).into() SharedSecretKey::new(
} &self
.secret_spending_key
pub fn decrypt_data( .generate_incoming_viewing_secret_key(),
&self, &ephemeral_public_key_sender,
ephemeral_public_key_sender: AffinePoint, )
ciphertext: CipherText,
nonce: Nonce,
) -> Result<Vec<u8>, 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())
} }
pub fn log(&self) { pub fn log(&self) {
info!( info!(
"Secret spending key is {:?}", "Secret spending key is {:?}",
hex::encode( hex::encode(serde_json::to_vec(&self.secret_spending_key).unwrap()),
serde_json::to_vec(&self.top_secret_key_holder.secret_spending_key).unwrap()
),
); );
info!( info!(
"Nulifier secret key is {:?}", "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( 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!( info!(
"Viewing secret key is {:?}", "Viewing secret key is {:?}",
hex::encode( 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!( info!(
@ -128,25 +90,16 @@ impl KeyChain {
); );
info!( info!(
"Viewing public key is {:?}", "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)] #[cfg(test)]
mod tests { mod tests {
use aes_gcm::{ use aes_gcm::aead::OsRng;
Aes256Gcm, use k256::AffinePoint;
aead::{Aead, KeyInit, OsRng}, use rand::RngCore;
};
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 super::*; use super::*;
@ -156,12 +109,7 @@ mod tests {
let address_key_holder = KeyChain::new_os_random(); let address_key_holder = KeyChain::new_os_random();
// Check that key holder fields are initialized with expected types // Check that key holder fields are initialized with expected types
assert!(!Into::<bool>::into( assert_ne!(address_key_holder.nullifer_public_key.as_ref(), &[0u8; 32]);
address_key_holder.nullifer_public_key.is_identity()
));
assert!(!Into::<bool>::into(
address_key_holder.viewing_public_key.is_identity()
));
} }
#[test] #[test]
@ -169,176 +117,13 @@ mod tests {
let address_key_holder = KeyChain::new_os_random(); let address_key_holder = KeyChain::new_os_random();
// Generate a random ephemeral public key sender // Generate a random ephemeral public key sender
let scalar = Scalar::random(&mut OsRng); let mut scalar = [0; 32];
let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine(); OsRng.fill_bytes(&mut scalar);
let ephemeral_public_key_sender = EphemeralPublicKey::from_scalar(scalar);
// Calculate shared secret // Calculate shared secret
let shared_secret = let _shared_secret =
address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); 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::<bool>::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<u8> = 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::<bool>::into(
address_key_holder.nullifer_public_key.is_identity()
));
assert!(!Into::<bool>::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::<bool>::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] #[test]
@ -346,10 +131,10 @@ mod tests {
let seed_holder = SeedHolder::new_os_random(); let seed_holder = SeedHolder::new_os_random();
let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); 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 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(); let pub_account_signing_key = nssa::PrivateKey::new_os_random();
@ -364,11 +149,6 @@ mod tests {
"Group generator {:?}", "Group generator {:?}",
hex::encode(serde_json::to_vec(&AffinePoint::GENERATOR).unwrap()) 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!();
println!("======Holders======"); println!("======Holders======");

View File

@ -1,101 +1,157 @@
use bip39::Mnemonic;
use common::TreeHashType; use common::TreeHashType;
use elliptic_curve::PrimeField; use nssa_core::{
use k256::{AffinePoint, FieldBytes, Scalar}; NullifierPublicKey, NullifierSecretKey,
encryption::{IncomingViewingPublicKey, Scalar},
};
use rand::{RngCore, rngs::OsRng}; use rand::{RngCore, rngs::OsRng};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::{Digest, digest::FixedOutput}; use sha2::{Digest, digest::FixedOutput};
use super::constants_types::{NULLIFIER_SECRET_CONST, VIEWING_SECRET_CONST};
#[derive(Debug)] #[derive(Debug)]
///Seed holder. Non-clonable to ensure that different holders use different seeds. ///Seed holder. Non-clonable to ensure that different holders use different seeds.
/// Produces `TopSecretKeyHolder` objects. /// Produces `TopSecretKeyHolder` objects.
pub struct SeedHolder { pub struct SeedHolder {
seed: Scalar, //ToDo: Needs to be vec as serde derives is not implemented for [u8; 64]
pub(crate) seed: Vec<u8>,
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
///Secret spending key holder. Produces `UTXOSecretKeyHolder` objects. ///Secret spending key object. Can produce `PrivateKeyHolder` objects.
pub struct TopSecretKeyHolder { pub struct SecretSpendingKey(pub(crate) [u8; 32]);
pub secret_spending_key: Scalar,
} pub type IncomingViewingSecretKey = Scalar;
pub type OutgoingViewingSecretKey = Scalar;
#[derive(Serialize, Deserialize, Debug, Clone)] #[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. ///Private key holder. Produces public keys. Can produce address. Can produce shared secret for recepient.
pub struct UTXOSecretKeyHolder { pub struct PrivateKeyHolder {
pub nullifier_secret_key: Scalar, pub(crate) nullifier_secret_key: NullifierSecretKey,
pub viewing_secret_key: Scalar, pub(crate) incoming_viewing_secret_key: IncomingViewingSecretKey,
pub(crate) outgoing_viewing_secret_key: OutgoingViewingSecretKey,
} }
impl SeedHolder { impl SeedHolder {
pub fn new_os_random() -> Self { 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 { Self {
seed: Scalar::from_repr(bytes).unwrap(), seed: seed_wide.to_vec(),
} }
} }
pub fn generate_secret_spending_key_hash(&self) -> TreeHashType { 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(); 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]);
<NullifierSecretKey>::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]);
<TreeHashType>::from(hasher.finalize_fixed()) <TreeHashType>::from(hasher.finalize_fixed())
} }
pub fn generate_secret_spending_key_scalar(&self) -> Scalar { pub fn generate_outgoing_viewing_secret_key(&self) -> OutgoingViewingSecretKey {
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 {
let mut hasher = sha2::Sha256::new(); let mut hasher = sha2::Sha256::new();
hasher.update(self.secret_spending_key.to_bytes()); hasher.update("NSSA_keys");
hasher.update(*NULLIFIER_SECRET_CONST); hasher.update(self.0);
hasher.update([3u8]);
hasher.update([0u8; 22]);
let hash = <TreeHashType>::from(hasher.finalize_fixed()); <TreeHashType>::from(hasher.finalize_fixed())
Scalar::from_repr(hash.into()).unwrap()
} }
pub fn generate_viewing_secret_key(&self) -> Scalar { pub fn produce_private_key_holder(&self) -> PrivateKeyHolder {
let mut hasher = sha2::Sha256::new(); PrivateKeyHolder {
hasher.update(self.secret_spending_key.to_bytes());
hasher.update(*VIEWING_SECRET_CONST);
let hash = <TreeHashType>::from(hasher.finalize_fixed());
Scalar::from_repr(hash.into()).unwrap()
}
pub fn produce_utxo_secret_holder(&self) -> UTXOSecretKeyHolder {
UTXOSecretKeyHolder {
nullifier_secret_key: self.generate_nullifier_secret_key(), 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 { impl PrivateKeyHolder {
pub fn generate_nullifier_public_key(&self) -> AffinePoint { pub fn generate_nullifier_public_key(&self) -> NullifierPublicKey {
(AffinePoint::GENERATOR * self.nullifier_secret_key).into() (&self.nullifier_secret_key).into()
} }
pub fn generate_viewing_public_key(&self) -> AffinePoint { pub fn generate_incoming_viewing_public_key(&self) -> IncomingViewingPublicKey {
(AffinePoint::GENERATOR * self.viewing_secret_key).into() 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();
} }
} }

View File

@ -10,17 +10,14 @@ pub type PublicKey = AffinePoint;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NSSAUserData { pub struct NSSAUserData {
pub key_holder: KeyChain, ///Map for all user public accounts
pub pub_account_signing_keys: HashMap<nssa::Address, nssa::PrivateKey>,
///Map for all user private accounts
user_private_accounts: HashMap<nssa::Address, KeyChain>,
} }
impl NSSAUserData { impl NSSAUserData {
pub fn new() -> Self { fn valid_public_key_transaction_pairing_check(
let key_holder = KeyChain::new_os_random();
Self { key_holder }
}
fn valid_key_transaction_pairing_check(
accounts_keys_map: &HashMap<nssa::Address, nssa::PrivateKey>, accounts_keys_map: &HashMap<nssa::Address, nssa::PrivateKey>,
) -> bool { ) -> bool {
let mut check_res = true; let mut check_res = true;
@ -32,32 +29,82 @@ impl NSSAUserData {
check_res check_res
} }
fn valid_private_key_transaction_pairing_check(
accounts_keys_map: &HashMap<nssa::Address, KeyChain>,
) -> 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( pub fn new_with_accounts(
accounts_keys: HashMap<nssa::Address, nssa::PrivateKey>, accounts_keys: HashMap<nssa::Address, nssa::PrivateKey>,
accounts_key_chains: HashMap<nssa::Address, KeyChain>,
) -> Result<Self> { ) -> Result<Self> {
if !Self::valid_key_transaction_pairing_check(&accounts_keys) { if !Self::valid_public_key_transaction_pairing_check(&accounts_keys) {
anyhow::bail!( anyhow::bail!(
"Key transaction pairing check not satisfied, there is addresses, which is not derived from keys" "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 { /// Generated new private key for public transaction signatures
self.key_holder.generate_new_private_key() ///
/// 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> { /// Returns the signing key for public transaction signatures
self.key_holder.get_pub_account_signing_key(address) 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 { impl Default for NSSAUserData {
fn default() -> Self { 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] #[test]
fn test_new_account() { 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);
} }
} }

View File

@ -14,6 +14,7 @@ secp256k1 = "0.31.1"
rand = "0.8" rand = "0.8"
borsh = "1.5.7" borsh = "1.5.7"
hex = "0.4.3" hex = "0.4.3"
k256 = "0.13.3"
[dev-dependencies] [dev-dependencies]
test-program-methods = { path = "test_program_methods" } test-program-methods = { path = "test_program_methods" }

View File

@ -13,6 +13,8 @@ pub use shared_key_derivation::{EphemeralPublicKey, EphemeralSecretKey, Incoming
use crate::{Commitment, account::Account}; use crate::{Commitment, account::Account};
pub type Scalar = [u8; 32];
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub struct SharedSecretKey([u8; 32]); pub struct SharedSecretKey([u8; 32]);

View File

@ -1,22 +1,22 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use k256::{ use k256::{
AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, Scalar, AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint,
elliptic_curve::{ elliptic_curve::{
PrimeField, PrimeField,
sec1::{FromEncodedPoint, ToEncodedPoint}, sec1::{FromEncodedPoint, ToEncodedPoint},
}, },
}; };
use crate::SharedSecretKey; use crate::{SharedSecretKey, encryption::Scalar};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Secp256k1Point(pub(crate) Vec<u8>); pub struct Secp256k1Point(pub(crate) Vec<u8>);
impl Secp256k1Point { 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_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 p = ProjectivePoint::GENERATOR * x;
let q = AffinePoint::from(p); 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 EphemeralPublicKey = Secp256k1Point;
pub type IncomingViewingPublicKey = Secp256k1Point; pub type IncomingViewingPublicKey = Secp256k1Point;
impl From<&EphemeralSecretKey> for EphemeralPublicKey { impl From<&EphemeralSecretKey> for EphemeralPublicKey {
@ -36,8 +36,8 @@ impl From<&EphemeralSecretKey> for EphemeralPublicKey {
} }
impl SharedSecretKey { impl SharedSecretKey {
pub fn new(scalar: &[u8; 32], point: &Secp256k1Point) -> Self { pub fn new(scalar: &Scalar, point: &Secp256k1Point) -> Self {
let scalar = Scalar::from_repr((*scalar).into()).unwrap(); let scalar = k256::Scalar::from_repr((*scalar).into()).unwrap();
let point: [u8; 33] = point.0.clone().try_into().unwrap(); let point: [u8; 33] = point.0.clone().try_into().unwrap();
let encoded = EncodedPoint::from_bytes(point).unwrap(); let encoded = EncodedPoint::from_bytes(point).unwrap();

View File

@ -7,6 +7,12 @@ use crate::Commitment;
#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, Hash))] #[cfg_attr(any(feature = "host", test), derive(Debug, Clone, Hash))]
pub struct NullifierPublicKey(pub(super) [u8; 32]); 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 { impl From<&NullifierSecretKey> for NullifierPublicKey {
fn from(value: &NullifierSecretKey) -> Self { fn from(value: &NullifierSecretKey) -> Self {
let mut bytes = Vec::new(); let mut bytes = Vec::new();

View File

@ -151,7 +151,7 @@ pub mod tests {
#[test] #[test]
fn test_encrypted_account_data_constructor() { fn test_encrypted_account_data_constructor() {
let npk = NullifierPublicKey::from(&[1; 32]); let npk = NullifierPublicKey::from(&[1; 32]);
let ivk = IncomingViewingPublicKey::from(&[2; 32]); let ivk = IncomingViewingPublicKey::from_scalar([2; 32]);
let account = Account::default(); let account = Account::default();
let commitment = Commitment::new(&npk, &account); let commitment = Commitment::new(&npk, &account);
let esk = [3; 32]; let esk = [3; 32];

View File

@ -225,7 +225,7 @@ pub mod tests {
use nssa_core::{ use nssa_core::{
Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
account::{Account, AccountWithMetadata, Nonce}, account::{Account, AccountWithMetadata, Nonce},
encryption::{EphemeralPublicKey, IncomingViewingPublicKey}, encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar},
}; };
fn transfer_transaction( fn transfer_transaction(
@ -738,7 +738,7 @@ pub mod tests {
pub struct TestPrivateKeys { pub struct TestPrivateKeys {
pub nsk: NullifierSecretKey, pub nsk: NullifierSecretKey,
pub isk: [u8; 32], pub isk: Scalar,
} }
impl TestPrivateKeys { impl TestPrivateKeys {

View File

@ -20,14 +20,13 @@ impl WalletChainStore {
.collect(); .collect();
Ok(Self { Ok(Self {
user_data: NSSAUserData::new_with_accounts(accounts_keys)?, user_data: NSSAUserData::new_with_accounts(accounts_keys, HashMap::new())?,
wallet_config: config, wallet_config: config,
}) })
} }
pub(crate) fn insert_account_data(&mut self, acc_data: PersistentAccountData) { pub(crate) fn insert_account_data(&mut self, acc_data: PersistentAccountData) {
self.user_data self.user_data
.key_holder
.pub_account_signing_keys .pub_account_signing_keys
.insert(acc_data.address, acc_data.pub_sign_key); .insert(acc_data.address, acc_data.pub_sign_key);
} }

View File

@ -55,7 +55,7 @@ pub fn fetch_persistent_accounts() -> Result<Vec<PersistentAccountData>> {
pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec<PersistentAccountData> { pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec<PersistentAccountData> {
let mut vec_for_storage = 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 { vec_for_storage.push(PersistentAccountData {
address: *addr, address: *addr,
pub_sign_key: key.clone(), pub_sign_key: key.clone(),

View File

@ -73,7 +73,9 @@ impl WalletCore {
} }
pub fn create_new_account(&mut self) -> Address { 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<Account> { pub fn search_for_initial_account(&self, acc_addr: Address) -> Option<Account> {
@ -110,7 +112,7 @@ impl WalletCore {
) )
.unwrap(); .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 { let Some(signing_key) = signing_key else {
return Err(ExecutionFailureKind::KeyNotFoundError); return Err(ExecutionFailureKind::KeyNotFoundError);