Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion consensus/src/block/validity/block_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,6 @@ pub mod tests {

test_program_snapshot!(
BlockProgram,
"2d3fe8ddca93ac8be7f92a53c541f6dbb971ab2817cbc1743c8b5a3b3c99b6caf0102385258a828d"
"f74b4d364a4ec880057f0ca1cde0beff5904ff67d6e0530aa844f92112c6d0cad5ba6097f6732766"
);
}
4 changes: 1 addition & 3 deletions consensus/src/mutator_set/removal_record/chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ const LONG_LENGTH_FLAG: u32 = 1 << 11;

#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
pub(crate) enum ChunkUnpackError {
#[error(
"payload is too large -- packed chunk can never be more than {MAX_PACKED_LENGTH} u32s"
)]
#[error("payload is too large -- packed chunk can never be more than {MAX_PACKED_LENGTH} u32s")]
PayloadTooBig,

#[error("actual length is inconsistent relative to length indicator")]
Expand Down
13 changes: 6 additions & 7 deletions wallet/cli/src/core/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,20 +110,19 @@ impl KeysStore {
write_json(&self.path, data);
}

/// Returns the next derivation index (stored index + 1) for display.
pub fn next_index(&self, key: KeyType) -> u64 {
pub fn last_index(&self, key: KeyType) -> u64 {
let data = self.read();
match key {
KeyType::Generation => data.generation_index + 1,
KeyType::Symmetric => data.symmetric_index + 1,
KeyType::Generation => data.generation_index,
KeyType::Symmetric => data.symmetric_index,
}
}

pub fn increment(&self, key: KeyType) {
pub fn set_index(&self, key: KeyType, index: u64) {
let mut data = self.read();
match key {
KeyType::Generation => data.generation_index += 1,
KeyType::Symmetric => data.symmetric_index += 1,
KeyType::Generation => data.generation_index = index,
KeyType::Symmetric => data.symmetric_index = index,
}
self.write(&data);
}
Expand Down
8 changes: 8 additions & 0 deletions wallet/cli/src/core/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ pub async fn run(wallet: Wallet, storage: Storage) {
WalletEvent::UtxosOutgoing { id, utxos } => {
info!("{} UTXOs being spent on transaction {}.", utxos.len(), id);
}
WalletEvent::AddressGenerated {
key_type,
index,
address,
} => {
info!("Generated {key_type:?} wallet address: {address:?}.");
storage.keys.set_index(key_type, index);
}
}
}

Expand Down
10 changes: 9 additions & 1 deletion wallet/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use anyhow::Result;
use clap::Parser;
use nyks_consensus::network::Network;
use nyks_rpc_client::http::HttpClient;
use nyks_standards::wallet::keys::key::KeyType;
use nyks_wallet_core::entropy::wallet_entropy::WalletEntropy;
use nyks_wallet_sdk::wallet::Wallet;

Expand Down Expand Up @@ -54,7 +55,14 @@ async fn main() -> Result<()> {
None => setup::run(&storage)?,
};

let wallet = Wallet::new(rpc, entropy, storage.chain.get_height(), args.network);
let wallet = Wallet::new_with_indexes(
rpc,
entropy,
storage.chain.get_height(),
args.network,
storage.keys.last_index(KeyType::Generation),
storage.keys.last_index(KeyType::Symmetric),
);

// Import initial state of wallet.
wallet.import_utxos(storage.utxos.iter().collect()).await;
Expand Down
139 changes: 63 additions & 76 deletions wallet/sdk/src/state/address_book.rs
Original file line number Diff line number Diff line change
@@ -1,126 +1,113 @@
use std::collections::BTreeMap;
use std::collections::HashMap;

use nyks_standards::wallet::keys::address::Address;
use nyks_standards::wallet::keys::key::Key;
use nyks_standards::wallet::keys::key::KeyType;
use nyks_standards::wallet::keys::key::Spender;
use nyks_standards::wallet::keys::viewing_key::ViewingKey;

use nyks_wallet_core::entropy::wallet_entropy::WalletEntropy;

/// Owns the wallet's entropy and derives/tracks its addresses and keys.
///
/// Addresses are grouped by key type in derivation order - index `i` is
/// the `i`-th derived key of that type, with index 0 being the special key.
/// `view_keys` mirrors that same order and stays in sync as addresses are
/// added.
#[derive(Debug)]
pub(crate) struct AddressBook {
pub struct AddressBook {
entropy: WalletEntropy,
addresses: HashMap<KeyType, Vec<Address>>,
view_keys: Vec<ViewingKey>,
addresses: HashMap<KeyType, BTreeMap<u64, (Address, ViewingKey)>>,
}

impl AddressBook {
/// Seeds each key type with the special key and index-1 address.
pub(crate) fn new(entropy: WalletEntropy) -> Self {
let mut addresses: HashMap<KeyType, Vec<Address>> = HashMap::new();

addresses.insert(
KeyType::Generation,
vec![
entropy.special_generation_key().to_address().into(),
entropy.nth_generation_address(1).into(),
],
);
addresses.insert(
KeyType::Symmetric,
vec![
entropy.special_symmetric_key().to_address().into(),
entropy.nth_symmetric_address(1).into(),
],
);

let view_keys = vec![
entropy.special_generation_key().to_viewing_key().into(),
entropy.nth_generation_key(1).to_viewing_key().into(),
entropy.special_symmetric_key().to_viewing_key().into(),
entropy.nth_symmetric_key(1).to_viewing_key().into(),
];

AddressBook {
entropy,
addresses,
view_keys,
/// Tracks every used derivation index, plus the next unused address for
/// each key type. Index 0 is reserved for the special key and is included.
pub fn with_address_indexes(
entropy: WalletEntropy,
generation_index: u64,
symmetric_index: u64,
) -> Self {
let mut addresses = HashMap::new();

for (key_type, index) in [
(KeyType::Generation, generation_index),
(KeyType::Symmetric, symmetric_index),
] {
let mut by_index = BTreeMap::new();

for index in 0..=index + 1 {
by_index.insert(index, derive(&entropy, key_type, index));
}

addresses.insert(key_type, by_index);
}

AddressBook { entropy, addresses }
}

pub(crate) fn view_keys(&self) -> &[ViewingKey] {
&self.view_keys
pub fn viewing_keys(&self) -> impl Iterator<Item = &ViewingKey> {
self.addresses
.values()
.flat_map(|by_index| by_index.values().map(|(_, viewing_key)| viewing_key))
}

/// Escape hatch for entropy-derived stuff that isn't really about
/// addresses (e.g. sender randomness), so this struct doesn't need a
/// proxy method per use.
pub(crate) fn entropy(&self) -> &WalletEntropy {
&self.entropy
}

pub(crate) fn latest(&self, key_type: KeyType) -> Address {
/// Returns the highest tracked address, which is the wallet's current
/// unused address.
pub fn latest_address(&self, key_type: KeyType) -> Address {
self.addresses
.get(&key_type)
.and_then(|v| v.last())
.cloned()
.and_then(|by_index| by_index.last_key_value())
.map(|(_, (address, _))| address.clone())
.unwrap()
}

/// Derives the next address for a key type, registers it, and returns
/// it along with its viewing key.
pub(crate) fn next_address(&mut self, key_type: KeyType) -> (Address, ViewingKey) {
let next_index = self.next_index(key_type);

let (address, view_key): (Address, ViewingKey) = match key_type {
KeyType::Generation => {
let key = self.entropy.nth_generation_key(next_index);
(key.to_address().into(), key.to_viewing_key().into())
}
KeyType::Symmetric => {
let key = self.entropy.nth_symmetric_key(next_index);
(key.to_address().into(), key.to_viewing_key().into())
}
};

/// Derives and records a new address, then returns it with its index and
/// viewing key.
pub fn next_address(&mut self, key_type: KeyType) -> (Address, u64, ViewingKey) {
let index = self.addresses[&key_type]
.last_key_value()
.map(|(&index, _)| index + 1)
.unwrap_or(1);
let (address, viewing_key) = derive(&self.entropy, key_type, index);
self.addresses
.entry(key_type)
.or_default()
.push(address.clone());
self.view_keys.push(view_key.clone());
.insert(index, (address.clone(), viewing_key.clone()));

(address, view_key)
(address, index, viewing_key)
}

/// Derives the spending key for whichever address matches, if any.
pub(crate) fn spending_key(&self, matches: impl Fn(&Address) -> bool) -> Option<Key> {
pub fn spending_key(&self, matches: impl Fn(&Address) -> bool) -> Option<Key> {
let (key_type, index) = self.find(matches)?;

Some(match key_type {
KeyType::Generation => self.entropy.nth_generation_key(index as u64).into(),
KeyType::Symmetric => self.entropy.nth_symmetric_key(index as u64).into(),
KeyType::Generation => self.entropy.nth_generation_key(index).into(),
KeyType::Symmetric => self.entropy.nth_symmetric_key(index).into(),
})
}

fn next_index(&self, key_type: KeyType) -> u64 {
self.addresses.get(&key_type).map(|v| v.len()).unwrap_or(0) as u64
}

fn find(&self, matches: impl Fn(&Address) -> bool) -> Option<(KeyType, usize)> {
fn find(&self, matches: impl Fn(&Address) -> bool) -> Option<(KeyType, u64)> {
for (key_type, addrs) in self.addresses.iter() {
for (index, address) in addrs.iter().enumerate() {
if matches(address) {
for (&index, address) in addrs.iter() {
if matches(&address.0) {
return Some((*key_type, index));
}
}
}
None
}
}

fn derive(entropy: &WalletEntropy, key_type: KeyType, index: u64) -> (Address, ViewingKey) {
match key_type {
KeyType::Generation => {
let key = entropy.nth_generation_key(index);
(key.to_address().into(), key.to_viewing_key().into())
}
KeyType::Symmetric => {
let key = entropy.nth_symmetric_key(index);
(key.to_address().into(), key.to_viewing_key().into())
}
}
}
47 changes: 47 additions & 0 deletions wallet/sdk/src/wallet/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use nyks_consensus::transaction::transaction_kernel_id::TransactionKernelId;
use nyks_standards::wallet::keys::address::Address;
use nyks_standards::wallet::keys::key::KeyType;

use crate::state::utxos::MonitoredUtxo;
use crate::state::utxos::UtxoKey;

/// Events emitted by the wallet as a result of sync and scan activity.
#[derive(Debug, Clone)]
pub enum WalletEvent {
/// A new UTXO was discovered and added to the wallet's UTXO pool.
UtxoReceived { key: UtxoKey, utxo: MonitoredUtxo },

/// A previously-tracked UTXO was found to be spent (or otherwise
/// invalid) while syncing membership proofs, and was evicted from the
/// pool.
UtxoInvalidated { key: UtxoKey, utxo: MonitoredUtxo },

/// A mempool transaction was found to spend one or more of the
/// wallet's UTXOs. Emitted once per transaction, the first time it's
/// observed as relevant.
UtxosOutgoing {
id: TransactionKernelId,
utxos: Vec<UtxoKey>,
},

/// A new address was derived by the wallet.
AddressGenerated {
key_type: KeyType,
index: u64,
address: Address,
},
}

impl WalletEvent {
pub fn utxo_received(key: UtxoKey, utxo: MonitoredUtxo) -> Self {
WalletEvent::UtxoReceived { key, utxo }
}

pub fn utxo_invalidated(key: UtxoKey, utxo: MonitoredUtxo) -> Self {
WalletEvent::UtxoInvalidated { key, utxo }
}

pub fn utxos_outgoing(id: TransactionKernelId, utxos: Vec<UtxoKey>) -> Self {
WalletEvent::UtxosOutgoing { id, utxos }
}
}
Loading
Loading