mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-03-26 12:13:08 +00:00
The mnemonic/wallet generation was using a constant zero-byte array for entropy ([0u8; 32]), making all wallets deterministic based solely on the password. This commit introduces proper random entropy using OsRng and enables users to back up their recovery phrase. Changes: - SeedHolder::new_mnemonic() now uses OsRng for 256-bit random entropy and returns the generated mnemonic - Added SeedHolder::from_mnemonic() to recover a wallet from an existing mnemonic phrase - WalletChainStore::new_storage() returns the mnemonic for user backup - Added WalletChainStore::restore_storage() for recovery from a mnemonic - WalletCore::new_init_storage() now returns the mnemonic - Renamed reset_storage to restore_storage, which accepts a mnemonic for recovery - CLI displays the recovery phrase when a new wallet is created - RestoreKeys command now prompts for the mnemonic phrase via read_mnemonic_from_stdin() Note: The password parameter is retained for future storage encryption but is no longer used in seed derivation (empty passphrase is used instead). This means the mnemonic alone is sufficient to recover accounts. Usage: On first wallet initialization, users will see: IMPORTANT: Write down your recovery phrase and store it securely. This is the only way to recover your wallet if you lose access. Recovery phrase: word1 word2 word3 ... word24 To restore keys: wallet restore-keys --depth 5 Input recovery phrase: <24 words> Input password: <password>
200 lines
6.3 KiB
Rust
200 lines
6.3 KiB
Rust
use nssa_core::{
|
|
NullifierPublicKey, SharedSecretKey,
|
|
encryption::{EphemeralPublicKey, ViewingPublicKey},
|
|
};
|
|
use secret_holders::{PrivateKeyHolder, SecretSpendingKey, SeedHolder};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
pub mod ephemeral_key_holder;
|
|
pub mod key_tree;
|
|
pub mod secret_holders;
|
|
|
|
pub type PublicAccountSigningKey = [u8; 32];
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
/// Entrypoint to key management.
|
|
pub struct KeyChain {
|
|
pub secret_spending_key: SecretSpendingKey,
|
|
pub private_key_holder: PrivateKeyHolder,
|
|
pub nullifier_public_key: NullifierPublicKey,
|
|
pub viewing_public_key: ViewingPublicKey,
|
|
}
|
|
|
|
impl KeyChain {
|
|
#[must_use]
|
|
pub fn new_os_random() -> 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 secret_spending_key = seed_holder.produce_top_secret_key_holder();
|
|
|
|
let private_key_holder = secret_spending_key.produce_private_key_holder(None);
|
|
|
|
let nullifier_public_key = private_key_holder.generate_nullifier_public_key();
|
|
let viewing_public_key = private_key_holder.generate_viewing_public_key();
|
|
|
|
Self {
|
|
secret_spending_key,
|
|
private_key_holder,
|
|
nullifier_public_key,
|
|
viewing_public_key,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn new_mnemonic(passphrase: &str) -> (Self, bip39::Mnemonic) {
|
|
// Currently dropping SeedHolder at the end of initialization.
|
|
// Not entirely sure if we need it in the future.
|
|
let (seed_holder, mnemonic) = SeedHolder::new_mnemonic(passphrase);
|
|
let secret_spending_key = seed_holder.produce_top_secret_key_holder();
|
|
|
|
let private_key_holder = secret_spending_key.produce_private_key_holder(None);
|
|
|
|
let nullifier_public_key = private_key_holder.generate_nullifier_public_key();
|
|
let viewing_public_key = private_key_holder.generate_viewing_public_key();
|
|
|
|
(
|
|
Self {
|
|
secret_spending_key,
|
|
private_key_holder,
|
|
nullifier_public_key,
|
|
viewing_public_key,
|
|
},
|
|
mnemonic,
|
|
)
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn calculate_shared_secret_receiver(
|
|
&self,
|
|
ephemeral_public_key_sender: &EphemeralPublicKey,
|
|
index: Option<u32>,
|
|
) -> SharedSecretKey {
|
|
SharedSecretKey::new(
|
|
&self.secret_spending_key.generate_viewing_secret_key(index),
|
|
ephemeral_public_key_sender,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use aes_gcm::aead::OsRng;
|
|
use base58::ToBase58 as _;
|
|
use k256::{AffinePoint, elliptic_curve::group::GroupEncoding as _};
|
|
use rand::RngCore as _;
|
|
|
|
use super::*;
|
|
use crate::key_management::{
|
|
ephemeral_key_holder::EphemeralKeyHolder, key_tree::KeyTreePrivate,
|
|
};
|
|
|
|
#[test]
|
|
fn new_os_random() {
|
|
// Ensure that a new KeyChain instance can be created without errors.
|
|
let account_id_key_holder = KeyChain::new_os_random();
|
|
|
|
// Check that key holder fields are initialized with expected types
|
|
assert_ne!(
|
|
account_id_key_holder.nullifier_public_key.as_ref(),
|
|
&[0_u8; 32]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn calculate_shared_secret_receiver() {
|
|
let account_id_key_holder = KeyChain::new_os_random();
|
|
|
|
// Generate a random ephemeral public key sender
|
|
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 = account_id_key_holder
|
|
.calculate_shared_secret_receiver(&ephemeral_public_key_sender, None);
|
|
}
|
|
|
|
#[test]
|
|
fn key_generation_test() {
|
|
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_private_key_holder(None);
|
|
|
|
let nullifier_public_key = utxo_secret_key_holder.generate_nullifier_public_key();
|
|
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key();
|
|
|
|
let pub_account_signing_key = nssa::PrivateKey::new_os_random();
|
|
|
|
let public_key = nssa::PublicKey::new_from_private_key(&pub_account_signing_key);
|
|
|
|
let account = nssa::AccountId::from(&public_key);
|
|
|
|
println!("======Prerequisites======");
|
|
println!();
|
|
|
|
println!(
|
|
"Group generator {:?}",
|
|
hex::encode(AffinePoint::GENERATOR.to_bytes())
|
|
);
|
|
println!();
|
|
|
|
println!("======Holders======");
|
|
println!();
|
|
|
|
println!("{seed_holder:?}");
|
|
println!("{top_secret_key_holder:?}");
|
|
println!("{utxo_secret_key_holder:?}");
|
|
println!();
|
|
|
|
println!("======Public data======");
|
|
println!();
|
|
println!("Account {:?}", account.value().to_base58());
|
|
println!(
|
|
"Nulifier public key {:?}",
|
|
hex::encode(nullifier_public_key.to_byte_array())
|
|
);
|
|
println!(
|
|
"Viewing public key {:?}",
|
|
hex::encode(viewing_public_key.to_bytes())
|
|
);
|
|
}
|
|
|
|
fn account_with_chain_index_2_for_tests() -> KeyChain {
|
|
let seed = SeedHolder::new_os_random();
|
|
let mut key_tree_private = KeyTreePrivate::new(&seed);
|
|
|
|
// /0
|
|
key_tree_private.generate_new_node_layered().unwrap();
|
|
// /1
|
|
key_tree_private.generate_new_node_layered().unwrap();
|
|
// /0/0
|
|
key_tree_private.generate_new_node_layered().unwrap();
|
|
// /2
|
|
let (second_child_id, _) = key_tree_private.generate_new_node_layered().unwrap();
|
|
|
|
key_tree_private
|
|
.get_node(second_child_id)
|
|
.unwrap()
|
|
.value
|
|
.0
|
|
.clone()
|
|
}
|
|
|
|
#[test]
|
|
fn non_trivial_chain_index() {
|
|
let keys = account_with_chain_index_2_for_tests();
|
|
|
|
let eph_key_holder = EphemeralKeyHolder::new(&keys.nullifier_public_key);
|
|
|
|
let key_sender = eph_key_holder.calculate_shared_secret_sender(&keys.viewing_public_key);
|
|
let key_receiver = keys.calculate_shared_secret_receiver(
|
|
&eph_key_holder.generate_ephemeral_public_key(),
|
|
Some(2),
|
|
);
|
|
|
|
assert_eq!(key_sender.0, key_receiver.0);
|
|
}
|
|
}
|