Merge branch 'Pravdyvy/key-protocol-update-private' into Pravdyvy/wallet-privacy-preserving-transactions

This commit is contained in:
Oleksandr Pravdyvyi 2025-09-10 08:30:55 +03:00
commit 4a3af34c6a
No known key found for this signature in database
GPG Key ID: 9F8955C63C443871
12 changed files with 326 additions and 193 deletions

57
Cargo.lock generated
View File

@ -695,12 +695,29 @@ dependencies = [
"syn 2.0.106",
]
[[package]]
name = "bip39"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d193de1f7487df1914d3a568b772458861d33f9c54249612cc2893d6915054"
dependencies = [
"bitcoin_hashes 0.13.0",
"serde",
"unicode-normalization",
]
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitcoin-internals"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb"
[[package]]
name = "bitcoin-io"
version = "0.1.3"
@ -713,6 +730,16 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57"
[[package]]
name = "bitcoin_hashes"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b"
dependencies = [
"bitcoin-internals",
"hex-conservative 0.1.2",
]
[[package]]
name = "bitcoin_hashes"
version = "0.14.0"
@ -720,7 +747,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16"
dependencies = [
"bitcoin-io",
"hex-conservative",
"hex-conservative 0.2.1",
]
[[package]]
@ -938,8 +965,10 @@ checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-link",
]
@ -1769,6 +1798,12 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hex-conservative"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20"
[[package]]
name = "hex-conservative"
version = "0.2.1"
@ -1799,6 +1834,12 @@ dependencies = [
"digest",
]
[[package]]
name = "hmac-sha512"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89e8d20b3799fa526152a5301a771eaaad80857f83e01b23216ceaafb2d9280"
[[package]]
name = "http"
version = "0.2.12"
@ -2283,9 +2324,11 @@ version = "0.1.0"
dependencies = [
"aes-gcm",
"anyhow",
"bip39",
"common",
"elliptic-curve",
"hex",
"hmac-sha512",
"k256",
"lazy_static",
"log",
@ -3783,7 +3826,7 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2"
dependencies = [
"bitcoin_hashes",
"bitcoin_hashes 0.14.0",
"rand 0.9.2",
"secp256k1-sys 0.11.0",
]
@ -3866,6 +3909,7 @@ name = "sequencer_core"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"common",
"hex",
"log",
@ -4553,6 +4597,15 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-normalization"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-xid"
version = "0.2.6"

View File

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

View File

@ -15,6 +15,8 @@ elliptic-curve.workspace = true
hex.workspace = true
aes-gcm.workspace = true
lazy_static.workspace = true
bip39.workspace = true
hmac-sha512.workspace = true
[dependencies.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,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.
@ -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) {

View File

@ -1,29 +1,28 @@
use std::collections::HashMap;
use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead};
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, digest::FixedOutput};
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,
///Map for all users accounts
pub pub_account_signing_keys: HashMap<nssa::Address, nssa::PrivateKey>,
pub nullifer_public_key: PublicKey,
pub viewing_public_key: PublicKey,
pub private_key_holder: PrivateKeyHolder,
pub nullifer_public_key: [u8; 32],
pub incoming_viewing_public_key: PublicKey,
}
impl KeyChain {
@ -33,62 +32,37 @@ 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,
pub_account_signing_keys: HashMap::new(),
incoming_viewing_public_key,
}
}
pub fn new_os_random_with_accounts(accounts: HashMap<nssa::Address, nssa::PrivateKey>) -> 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)
<TreeHashType>::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()
(ephemeral_public_key_sender
* self
.top_secret_key_holder
.generate_incloming_viewing_secret_key())
.into()
}
pub fn decrypt_data(
@ -112,14 +86,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 +106,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 +117,11 @@ mod tests {
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 types::{CipherText, Nonce};
use crate::key_management::ephemeral_key_holder::EphemeralKeyHolder;
@ -156,11 +133,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::<bool>::into(
address_key_holder.nullifer_public_key.is_identity()
));
assert!(!Into::<bool>::into(
address_key_holder.viewing_public_key.is_identity()
address_key_holder.incoming_viewing_public_key.is_identity()
));
}
@ -184,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);
@ -211,20 +196,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::<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();
@ -346,10 +317,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 +335,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======");

View File

@ -1,101 +1,167 @@
use common::TreeHashType;
use bip39::Mnemonic;
use common::merkle_tree_public::TreeHashType;
use elliptic_curve::PrimeField;
use k256::{AffinePoint, FieldBytes, Scalar};
use k256::{AffinePoint, 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<u8>,
}
#[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 {
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 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");
}
<TreeHashType>::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]);
<TreeHashType>::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 = <TreeHashType>::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 = <TreeHashType>::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]);
<TreeHashType>::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();
}
}

View File

@ -0,0 +1,8 @@
use elliptic_curve::{
consts::{B0, B1},
generic_array::GenericArray,
};
use sha2::digest::typenum::{UInt, UTerm};
pub type CipherText = Vec<u8>;
pub type Nonce = GenericArray<u8, UInt<UInt<UInt<UInt<UTerm, B1>, B1>, B0>, B0>>;

View File

@ -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<nssa::Address, nssa::PrivateKey>,
///Map for all user private accounts
user_private_accounts: HashMap<nssa::Address, KeyChain>,
}
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<nssa::Address, nssa::PrivateKey>,
) -> 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<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(
accounts_keys: HashMap<nssa::Address, nssa::PrivateKey>,
accounts_key_chains: HashMap<nssa::Address, KeyChain>,
) -> Result<Self> {
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);
}
}

View File

@ -20,14 +20,14 @@ impl WalletChainStore {
.collect();
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,
})
}
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);
}

View File

@ -53,7 +53,7 @@ pub fn fetch_persistent_accounts() -> Result<Vec<PersistentAccountData>> {
pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec<PersistentAccountData> {
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(),

View File

@ -75,7 +75,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<Account> {
@ -112,7 +114,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);
@ -226,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_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:#?}");