Merge branch 'main' into Pravdyvy/deterministic-key-derivation

This commit is contained in:
Pravdyvy
2025-11-27 13:46:35 +02:00
86 changed files with 1910 additions and 2032 deletions
@@ -6,7 +6,8 @@ use rand::{RngCore, rngs::OsRng};
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.
/// 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: EphemeralSecretKey,
}
@@ -66,7 +66,7 @@ impl ChainIndex {
pub fn next_in_line(&self) -> ChainIndex {
let mut chain = self.0.clone();
//ToDo: Add overflow check
// ToDo: Add overflow check
if let Some(last_p) = chain.last_mut() {
*last_p += 1
}
@@ -125,8 +125,8 @@ impl KeyNode for ChildKeysPrivate {
self.cci
}
fn address(&self) -> nssa::Address {
nssa::Address::from(&self.value.0.nullifer_public_key)
fn account_id(&self) -> nssa::AccountId {
nssa::AccountId::from(&self.value.0.nullifer_public_key)
}
}
@@ -61,8 +61,8 @@ impl KeyNode for ChildKeysPublic {
self.cci
}
fn address(&self) -> nssa::Address {
nssa::Address::from(&self.cpk)
fn account_id(&self) -> nssa::AccountId {
nssa::AccountId::from(&self.cpk)
}
}
+27 -21
View File
@@ -18,7 +18,7 @@ pub mod traits;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct KeyTree<N: KeyNode> {
pub key_map: BTreeMap<ChainIndex, N>,
pub addr_map: HashMap<nssa::Address, ChainIndex>,
pub account_id_map: HashMap<nssa::AccountId, ChainIndex>,
}
pub type KeyTreePublic = KeyTree<ChildKeysPublic>;
@@ -33,22 +33,28 @@ impl<N: KeyNode> KeyTree<N> {
.expect("SeedHolder seed is 64 bytes long");
let root_keys = N::root(seed_fit);
let address = root_keys.address();
let account_id = root_keys.account_id();
let key_map = BTreeMap::from_iter([(ChainIndex::root(), root_keys)]);
let addr_map = HashMap::from_iter([(address, ChainIndex::root())]);
let account_id_map = HashMap::from_iter([(account_id, ChainIndex::root())]);
Self { key_map, addr_map }
Self {
key_map,
account_id_map,
}
}
pub fn new_from_root(root: N) -> Self {
let addr_map = HashMap::from_iter([(root.address(), ChainIndex::root())]);
let account_id_map = HashMap::from_iter([(root.account_id(), ChainIndex::root())]);
let key_map = BTreeMap::from_iter([(ChainIndex::root(), root)]);
Self { key_map, addr_map }
Self {
key_map,
account_id_map,
}
}
//ToDo: Add function to create a tree from list of nodes with consistency check.
// ToDo: Add function to create a tree from list of nodes with consistency check.
pub fn find_next_last_child_of_id(&self, parent_id: &ChainIndex) -> Option<u32> {
if !self.key_map.contains_key(parent_id) {
@@ -90,7 +96,7 @@ impl<N: KeyNode> KeyTree<N> {
}
}
pub fn generate_new_node(&mut self, parent_cci: ChainIndex) -> Option<nssa::Address> {
pub fn generate_new_node(&mut self, parent_cci: ChainIndex) -> Option<nssa::AccountId> {
let father_keys = self.key_map.get(&parent_cci)?;
let next_child_id = self
.find_next_last_child_of_id(&parent_cci)
@@ -99,28 +105,28 @@ impl<N: KeyNode> KeyTree<N> {
let child_keys = father_keys.nth_child(next_child_id);
let address = child_keys.address();
let account_id = child_keys.account_id();
self.key_map.insert(next_cci.clone(), child_keys);
self.addr_map.insert(address, next_cci);
self.account_id_map.insert(account_id, next_cci);
Some(address)
Some(account_id)
}
pub fn get_node(&self, addr: nssa::Address) -> Option<&N> {
self.addr_map
.get(&addr)
pub fn get_node(&self, account_id: nssa::AccountId) -> Option<&N> {
self.account_id_map
.get(&account_id)
.and_then(|chain_id| self.key_map.get(chain_id))
}
pub fn get_node_mut(&mut self, addr: nssa::Address) -> Option<&mut N> {
self.addr_map
.get(&addr)
pub fn get_node_mut(&mut self, account_id: nssa::AccountId) -> Option<&mut N> {
self.account_id_map
.get(&account_id)
.and_then(|chain_id| self.key_map.get_mut(chain_id))
}
pub fn insert(&mut self, addr: nssa::Address, chain_index: ChainIndex, node: N) {
self.addr_map.insert(addr, chain_index.clone());
pub fn insert(&mut self, account_id: nssa::AccountId, chain_index: ChainIndex, node: N) {
self.account_id_map.insert(account_id, chain_index.clone());
self.key_map.insert(chain_index, node);
}
}
@@ -129,7 +135,7 @@ impl<N: KeyNode> KeyTree<N> {
mod tests {
use std::str::FromStr;
use nssa::Address;
use nssa::AccountId;
use super::*;
@@ -146,7 +152,7 @@ mod tests {
let tree = KeyTreePublic::new(&seed_holder);
assert!(tree.key_map.contains_key(&ChainIndex::root()));
assert!(tree.addr_map.contains_key(&Address::new([
assert!(tree.account_id_map.contains_key(&AccountId::new([
46, 223, 229, 177, 59, 18, 189, 219, 153, 31, 249, 90, 112, 230, 180, 164, 80, 25, 106,
159, 14, 238, 1, 192, 91, 8, 210, 165, 199, 41, 60, 104,
])));
@@ -10,5 +10,5 @@ pub trait KeyNode {
fn child_index(&self) -> Option<u32>;
fn address(&self) -> nssa::Address;
fn account_id(&self) -> nssa::AccountId;
}
+13 -11
View File
@@ -12,7 +12,7 @@ pub mod key_tree;
pub mod secret_holders;
#[derive(Serialize, Deserialize, Clone, Debug)]
///Entrypoint to key management
/// Entrypoint to key management
pub struct KeyChain {
pub secret_spending_key: SecretSpendingKey,
pub private_key_holder: PrivateKeyHolder,
@@ -22,8 +22,8 @@ pub struct KeyChain {
impl KeyChain {
pub fn new_os_random() -> Self {
//Currently dropping SeedHolder at the end of initialization.
//Now entirely sure if we need it in the future.
// Currently dropping SeedHolder at the end of initialization.
// Now entirely sure if we need it in the future.
let seed_holder = SeedHolder::new_os_random();
let secret_spending_key = seed_holder.produce_top_secret_key_holder();
@@ -76,8 +76,7 @@ impl KeyChain {
mod tests {
use aes_gcm::aead::OsRng;
use base58::ToBase58;
use k256::AffinePoint;
use k256::elliptic_curve::group::GroupEncoding;
use k256::{AffinePoint, elliptic_curve::group::GroupEncoding};
use rand::RngCore;
use super::*;
@@ -85,15 +84,18 @@ mod tests {
#[test]
fn test_new_os_random() {
// Ensure that a new KeyChain instance can be created without errors.
let address_key_holder = KeyChain::new_os_random();
let account_id_key_holder = KeyChain::new_os_random();
// Check that key holder fields are initialized with expected types
assert_ne!(address_key_holder.nullifer_public_key.as_ref(), &[0u8; 32]);
assert_ne!(
account_id_key_holder.nullifer_public_key.as_ref(),
&[0u8; 32]
);
}
#[test]
fn test_calculate_shared_secret_receiver() {
let address_key_holder = KeyChain::new_os_random();
let account_id_key_holder = KeyChain::new_os_random();
// Generate a random ephemeral public key sender
let mut scalar = [0; 32];
@@ -102,7 +104,7 @@ mod tests {
// Calculate shared secret
let _shared_secret =
address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender);
account_id_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender);
}
#[test]
@@ -119,7 +121,7 @@ mod tests {
let public_key = nssa::PublicKey::new_from_private_key(&pub_account_signing_key);
let address = nssa::Address::from(&public_key);
let account = nssa::AccountId::from(&public_key);
println!("======Prerequisites======");
println!();
@@ -140,7 +142,7 @@ mod tests {
println!("======Public data======");
println!();
println!("Address{:?}", address.value().to_base58());
println!("Account {:?}", account.value().to_base58());
println!(
"Nulifier public key {:?}",
hex::encode(nullifer_public_key.to_byte_array())
@@ -11,22 +11,23 @@ use sha2::{Digest, digest::FixedOutput};
const NSSA_ENTROPY_BYTES: [u8; 32] = [0; 32];
#[derive(Debug)]
///Seed holder. Non-clonable to ensure that different holders use different seeds.
/// Seed holder. Non-clonable to ensure that different holders use different seeds.
/// Produces `TopSecretKeyHolder` objects.
pub struct SeedHolder {
//ToDo: Needs to be vec as serde derives is not implemented for [u8; 64]
// 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 object. Can produce `PrivateKeyHolder` objects.
/// 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)]
///Private key holder. Produces public keys. Can produce address. Can produce shared secret for recepient.
/// Private key holder. Produces public keys. Can produce account_id. Can produce shared secret for
/// recepient.
pub struct PrivateKeyHolder {
pub nullifier_secret_key: NullifierSecretKey,
pub(crate) incoming_viewing_secret_key: IncomingViewingSecretKey,
@@ -64,7 +65,7 @@ impl SeedHolder {
hash = hmac_sha512::HMAC::mac(hash, "NSSA_seed");
}
//Safe unwrap
// Safe unwrap
*hash.first_chunk::<32>().unwrap()
}
+51 -42
View File
@@ -15,10 +15,10 @@ pub type PublicKey = AffinePoint;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NSSAUserData {
/// Default public accounts
pub default_pub_account_signing_keys: HashMap<nssa::Address, nssa::PrivateKey>,
pub default_pub_account_signing_keys: HashMap<nssa::AccountId, nssa::PrivateKey>,
/// Default private accounts
pub default_user_private_accounts:
HashMap<nssa::Address, (KeyChain, nssa_core::account::Account)>,
HashMap<nssa::AccountId, (KeyChain, nssa_core::account::Account)>,
/// Tree of public keys
pub public_key_tree: KeyTreePublic,
/// Tree of private keys
@@ -27,13 +27,14 @@ pub struct NSSAUserData {
impl NSSAUserData {
fn valid_public_key_transaction_pairing_check(
accounts_keys_map: &HashMap<nssa::Address, nssa::PrivateKey>,
accounts_keys_map: &HashMap<nssa::AccountId, nssa::PrivateKey>,
) -> bool {
let mut check_res = true;
for (addr, key) in accounts_keys_map {
let expected_addr = nssa::Address::from(&nssa::PublicKey::new_from_private_key(key));
if &expected_addr != addr {
println!("{}, {}", expected_addr, addr);
for (account_id, key) in accounts_keys_map {
let expected_account_id =
nssa::AccountId::from(&nssa::PublicKey::new_from_private_key(key));
if &expected_account_id != account_id {
println!("{}, {}", expected_account_id, account_id);
check_res = false;
}
}
@@ -41,13 +42,13 @@ impl NSSAUserData {
}
fn valid_private_key_transaction_pairing_check(
accounts_keys_map: &HashMap<nssa::Address, (KeyChain, nssa_core::account::Account)>,
accounts_keys_map: &HashMap<nssa::AccountId, (KeyChain, nssa_core::account::Account)>,
) -> bool {
let mut check_res = true;
for (addr, (key, _)) in accounts_keys_map {
let expected_addr = nssa::Address::from(&key.nullifer_public_key);
if expected_addr != *addr {
println!("{}, {}", expected_addr, addr);
for (account_id, (key, _)) in accounts_keys_map {
let expected_account_id = nssa::AccountId::from(&key.nullifer_public_key);
if expected_account_id != *account_id {
println!("{}, {}", expected_account_id, account_id);
check_res = false;
}
}
@@ -55,9 +56,9 @@ impl NSSAUserData {
}
pub fn new_with_accounts(
default_accounts_keys: HashMap<nssa::Address, nssa::PrivateKey>,
default_accounts_keys: HashMap<nssa::AccountId, nssa::PrivateKey>,
default_accounts_key_chains: HashMap<
nssa::Address,
nssa::AccountId,
(KeyChain, nssa_core::account::Account),
>,
public_key_tree: KeyTreePublic,
@@ -65,13 +66,13 @@ impl NSSAUserData {
) -> Result<Self> {
if !Self::valid_public_key_transaction_pairing_check(&default_accounts_keys) {
anyhow::bail!(
"Key transaction pairing check not satisfied, there is addresses, which is not derived from keys"
"Key transaction pairing check not satisfied, there is account_ids, which is not derived from keys"
);
}
if !Self::valid_private_key_transaction_pairing_check(&default_accounts_key_chains) {
anyhow::bail!(
"Key transaction pairing check not satisfied, there is addresses, which is not derived from keys"
"Key transaction pairing check not satisfied, there is account_ids, which is not derived from keys"
);
}
@@ -85,63 +86,65 @@ impl NSSAUserData {
/// Generated new private key for public transaction signatures
///
/// Returns the address of new account
/// Returns the account_id of new account
pub fn generate_new_public_transaction_private_key(
&mut self,
parent_cci: ChainIndex,
) -> nssa::Address {
) -> nssa::AccountId {
self.public_key_tree.generate_new_node(parent_cci).unwrap()
}
/// Returns the signing key for public transaction signatures
pub fn get_pub_account_signing_key(
&self,
address: &nssa::Address,
account_id: &nssa::AccountId,
) -> Option<&nssa::PrivateKey> {
//First seek in defaults
if let Some(key) = self.default_pub_account_signing_keys.get(address) {
// First seek in defaults
if let Some(key) = self.default_pub_account_signing_keys.get(account_id) {
Some(key)
//Then seek in tree
// Then seek in tree
} else {
self.public_key_tree.get_node(*address).map(Into::into)
self.public_key_tree.get_node(*account_id).map(Into::into)
}
}
/// Generated new private key for privacy preserving transactions
///
/// Returns the address of new account
/// Returns the account_id of new account
pub fn generate_new_privacy_preserving_transaction_key_chain(
&mut self,
parent_cci: ChainIndex,
) -> nssa::Address {
) -> nssa::AccountId {
self.private_key_tree.generate_new_node(parent_cci).unwrap()
}
/// Returns the signing key for public transaction signatures
pub fn get_private_account(
&self,
address: &nssa::Address,
account_id: &nssa::AccountId,
) -> Option<&(KeyChain, nssa_core::account::Account)> {
//First seek in defaults
if let Some(key) = self.default_user_private_accounts.get(address) {
// First seek in defaults
if let Some(key) = self.default_user_private_accounts.get(account_id) {
Some(key)
//Then seek in tree
// Then seek in tree
} else {
self.private_key_tree.get_node(*address).map(Into::into)
self.private_key_tree.get_node(*account_id).map(Into::into)
}
}
/// Returns the signing key for public transaction signatures
pub fn get_private_account_mut(
&mut self,
address: &nssa::Address,
account_id: &nssa::AccountId,
) -> Option<&mut (KeyChain, nssa_core::account::Account)> {
//First seek in defaults
if let Some(key) = self.default_user_private_accounts.get_mut(address) {
// First seek in defaults
if let Some(key) = self.default_user_private_accounts.get_mut(account_id) {
Some(key)
//Then seek in tree
// Then seek in tree
} else {
self.private_key_tree.get_node_mut(*address).map(Into::into)
self.private_key_tree
.get_node_mut(*account_id)
.map(Into::into)
}
}
}
@@ -166,21 +169,27 @@ mod tests {
fn test_new_account() {
let mut user_data = NSSAUserData::default();
let addr_pub = user_data.generate_new_public_transaction_private_key(ChainIndex::root());
let addr_private =
let account_id_pub =
user_data.generate_new_public_transaction_private_key(ChainIndex::root());
let account_id_private =
user_data.generate_new_privacy_preserving_transaction_key_chain(ChainIndex::root());
let is_private_key_generated = user_data.get_pub_account_signing_key(&addr_pub).is_some();
let is_private_key_generated = user_data
.get_pub_account_signing_key(&account_id_pub)
.is_some();
assert!(is_private_key_generated);
let is_key_chain_generated = user_data.get_private_account(&addr_private).is_some();
let is_key_chain_generated = user_data.get_private_account(&account_id_private).is_some();
assert!(is_key_chain_generated);
let addr_private_str = addr_private.to_string();
println!("{addr_private_str:#?}");
let key_chain = &user_data.get_private_account(&addr_private).unwrap().0;
let account_id_private_str = account_id_private.to_string();
println!("{account_id_private_str:#?}");
let key_chain = &user_data
.get_private_account(&account_id_private)
.unwrap()
.0;
println!("{key_chain:#?}");
}
}