From 14509a6ae0b1536b89e2f273b131a7ccad5c51c6 Mon Sep 17 00:00:00 2001 From: zer0 Date: Mon, 9 Sep 2024 10:54:53 +0200 Subject: [PATCH 1/9] wallet-core: Add compiler optimizations --- wallet-core/.cargo/config.toml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/wallet-core/.cargo/config.toml b/wallet-core/.cargo/config.toml index 14c5995198..35cadd1c7a 100644 --- a/wallet-core/.cargo/config.toml +++ b/wallet-core/.cargo/config.toml @@ -3,13 +3,16 @@ # # See: [alias] -wasm = "build --release --target wasm32-unknown-unknown -Z build-std=core,alloc,panic_abort -Z build-std-features=panic_immediate_abort" -wasm-debug = "build --release --target wasm32-unknown-unknown -Z build-std=core,alloc,panic_abort" +wasm = "build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort" +wasm-debug = "build --target wasm32-unknown-unknown -Z build-std=std,panic_abort" [profile.release] -codegen-units = 1 opt-level = "z" -lto = true -debug = false +lto = "fat" +codegen-units = 1 panic = "abort" -overflow-checks = true +overflow-checks = false +debug = false +strip = "symbols" +incremental = false +rpath = false From c4fae2108fe805c4e4e85ea3541b3fc8c4f6e34b Mon Sep 17 00:00:00 2001 From: zer0 Date: Mon, 9 Sep 2024 10:55:24 +0200 Subject: [PATCH 2/9] wallet-core: Add rkyv --- wallet-core/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wallet-core/Cargo.toml b/wallet-core/Cargo.toml index 2404d9932e..6e4d904167 100644 --- a/wallet-core/Cargo.toml +++ b/wallet-core/Cargo.toml @@ -16,11 +16,14 @@ rand = { version = "0.8", default-features = false } ff = { version = "0.13", default-features = false } poseidon-merkle = { version = "0.7", features = ["rkyv-impl"] } execution-core = { version = "0.1", path = "../execution-core/" } +rkyv = { version = "0.7", default-features = false, features = ["alloc"] } [target.'cfg(target_family = "wasm")'.dependencies] dlmalloc = { version = "0.2", features = ["global"] } [dev-dependencies] rand = "0.8" +rkyv = "0.7" +bytecheck = "0.6" [features] From be569d7f48c82f9a9d6c0871b23599ab99112df3 Mon Sep 17 00:00:00 2001 From: zer0 Date: Mon, 9 Sep 2024 12:19:32 +0200 Subject: [PATCH 3/9] wallet-core: add debugging capabilities - Add `dbg!` and `eprintln!` macros implementation for WASM host - Add panic handling for debug builds Resolves #2315 --- wallet-core/src/ffi/debug.rs | 90 ++++++++++++++++++++++++++++++++++++ wallet-core/src/ffi/panic.rs | 18 ++++++++ 2 files changed, 108 insertions(+) create mode 100644 wallet-core/src/ffi/debug.rs create mode 100644 wallet-core/src/ffi/panic.rs diff --git a/wallet-core/src/ffi/debug.rs b/wallet-core/src/ffi/debug.rs new file mode 100644 index 0000000000..60e2dafe81 --- /dev/null +++ b/wallet-core/src/ffi/debug.rs @@ -0,0 +1,90 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +//! Implements `dbg!` and `eprintln!` macros, similar to those inthe Rust +//! standard library, with adaptations for use in a WASM environment. +//! +//! The `dbg!` macro outputs the value of an expression along with file and line +//! number details, useful for debugging. The `eprintln!` macro sends error +//! messages to the host environment. +//! +//! Unlike their standard counterparts, these macros are designed to be no-ops +//! in release builds, where optimizations are applied. This means no code is +//! generated for them in release mode, which improves performance and avoids +//! generating unnecessary debug information, as the WASM host environment is +//! expected to handle errors by aborting on panic, rather than logging. + +#[cfg(debug_assertions)] +#[allow(unused_macros)] +#[macro_use] +pub mod enabled { + use alloc::vec::Vec; + + extern "C" { + fn sig(msg: *const u8); // Host function expects a pointer to a C-style string (null-terminated) + } + + // Converts a Rust string to a C-style string (null-terminated) + fn cstr(s: &str) -> Vec { + let mut bytes = Vec::with_capacity(s.len() + 1); // Allocate space for string + null terminator + bytes.extend_from_slice(s.as_bytes()); // Copy the string bytes + bytes.push(0); // Add the null terminator + bytes + } + + // Send a signal to the host environment + pub(crate) fn signal(message: &str) { + let c_string = cstr(message); // Convert to C-string + unsafe { + sig(c_string.as_ptr()); // Send the C-string to the host function + } + } + + macro_rules! eprintln { + // Match the format string with arguments (like the standard `println!`) + ($($arg:tt)*) => {{ + // Use `format!` to create the formatted string + let formatted = alloc::format!($($arg)*); + // Call the `signal` function with the resulting string + $crate::ffi::debug::enabled::signal(&formatted); + }}; + } + + macro_rules! dbg { + () => { + eprintln!("[{}:{}:{}]", file!(), line!(), column!()) + }; + ($val:expr $(,)?) => { + // Use of `match` here is intentional because it affects the lifetimes + // of temporaries - https://stackoverflow.com/a/48732525/1063961 + match $val { + tmp => { + eprintln!("[{}:{}:{}] {} = {:#?}", + file!(), line!(), column!(), stringify!($val), &tmp); + tmp + } + } + }; + ($($val:expr),+ $(,)?) => { + ($(dbg!($val)),+,) + }; + } +} + +#[cfg(not(debug_assertions))] +#[allow(unused_macros)] +#[macro_use] +pub mod disabled { + macro_rules! dbg { + ($val:expr) => { + $val + }; + ($($arg:tt)*) => {}; + } + macro_rules! eprintln { + ($($arg:tt)*) => {}; + } +} diff --git a/wallet-core/src/ffi/panic.rs b/wallet-core/src/ffi/panic.rs new file mode 100644 index 0000000000..aa5dc08a5e --- /dev/null +++ b/wallet-core/src/ffi/panic.rs @@ -0,0 +1,18 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +mod panic_handling { + use core::panic::PanicInfo; + + #[panic_handler] + #[allow(unused)] + fn panic(info: &PanicInfo) -> ! { + #[cfg(debug_assertions)] + eprintln!("{}", info); + + loop {} + } +} From 13b91c6af2ef2b2bd9f0359d684b27ed0c800965 Mon Sep 17 00:00:00 2001 From: zer0 Date: Mon, 9 Sep 2024 12:20:14 +0200 Subject: [PATCH 4/9] wallet-core: add FFI's ErrorCode enum and codes handling (requires `try_trait_v2`) --- wallet-core/src/ffi/error.rs | 59 ++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 wallet-core/src/ffi/error.rs diff --git a/wallet-core/src/ffi/error.rs b/wallet-core/src/ffi/error.rs new file mode 100644 index 0000000000..a67faff06c --- /dev/null +++ b/wallet-core/src/ffi/error.rs @@ -0,0 +1,59 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +//! Expose the `ErrorCode` enum to be used in FFI exported functions. +//! ErrorCode enum represents different error codes for FFI, mapped to u8 +//! values. +//! The Ok variant signifies successful execution. + +use core::ops::{ControlFlow, FromResidual, Try}; + +/// [`ErrorCode`] enum represents different error codes for FFI, mapped to +/// [`u8`] values. +/// The [`Ok`] variant signifies successful execution. +#[derive(Debug, Clone)] +#[repr(u8)] +pub enum ErrorCode { + // Archiving (rkyv serialization) error + ArchivingError = 255, + // Unarchiving (rkyv deserialization) error + UnarchivingError = 254, + // Success + Ok = 0, +} + +impl Try for ErrorCode { + type Output = ErrorCode; + type Residual = ErrorCode; + + fn from_output(_: Self::Output) -> Self { + ErrorCode::Ok + } + + fn branch(self) -> ControlFlow { + match self { + ErrorCode::Ok => ControlFlow::Continue(ErrorCode::Ok), /* Continue execution on success */ + _ => ControlFlow::Break(self), /* Return the error code early */ + } + } +} + +impl FromResidual for ErrorCode { + fn from_residual(residual: ErrorCode) -> Self { + residual // Simply return the error code as is + } +} + +impl FromResidual> for ErrorCode { + fn from_residual( + residual: Result, + ) -> Self { + match residual { + Err(e) => e, + _ => unreachable!(), + } + } +} From 202145bc4755dc3efc0766cc518754394f6f4752 Mon Sep 17 00:00:00 2001 From: zer0 Date: Mon, 9 Sep 2024 12:23:15 +0200 Subject: [PATCH 5/9] wallet-core: enable WASM host to calc balance for a profile - Add `map_owned` FFI - Add `balance` FFI - Add `OwnedList` serializable type - Add `ErrorCode` usage - Move `map_owned` in `notes` module with `OwnedList` Resolves #2317 --- wallet-core/src/ffi.rs | 119 ++++++++++++++++++++++++++++++++++----- wallet-core/src/keys.rs | 22 +++----- wallet-core/src/lib.rs | 38 ++++--------- wallet-core/src/notes.rs | 92 ++++++++++++++++++++++++++++++ 4 files changed, 215 insertions(+), 56 deletions(-) create mode 100644 wallet-core/src/notes.rs diff --git a/wallet-core/src/ffi.rs b/wallet-core/src/ffi.rs index f6e4a24147..f5b54637bb 100644 --- a/wallet-core/src/ffi.rs +++ b/wallet-core/src/ffi.rs @@ -4,15 +4,40 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use crate::keys::{derive_bls_pk, derive_phoenix_pk, RNG_SEED}; -use core::ptr; +//! This module provides the foreign function interface (FFI) for exposing +//! public functions from the `wallet-core` Rust library to a WASM runtime. +//! In addition to cryptographic operations, it offers memory management +//! functions, such as `malloc` and `free`, for interacting with the WASM +//! memory. +//! +//! This FFI allows seamless integration between Rust code and a WASM runtime +//! while ensuring efficient memory handling and secure key management. + +#[macro_use] +pub(crate) mod debug; + +pub mod error; +pub mod panic; + +use crate::keys::{ + derive_bls_pk, derive_phoenix_pk, derive_phoenix_sk, derive_phoenix_vk, +}; +use crate::notes; +use crate::phoenix_balance; +use crate::Seed; +use error::ErrorCode; + +use alloc::alloc::{alloc, dealloc, Layout}; +use alloc::vec::Vec; +use core::{ptr, slice}; use dusk_bytes::Serializable; use execution_core::{ signatures::bls::PublicKey as BlsPublicKey, - transfer::phoenix::PublicKey as PhoenixPublicKey, + transfer::phoenix::{NoteLeaf, PublicKey as PhoenixPublicKey}, }; +use zeroize::Zeroize; -use alloc::alloc::{alloc, dealloc, Layout}; +use rkyv::{from_bytes, to_bytes}; /// The alignment of the memory allocated by the FFI. /// @@ -39,12 +64,33 @@ pub fn free(ptr: u32, len: u32) { } } +/// Map a list of indexes into keys using the provided seed and callback. +unsafe fn indexes_into_keys( + seed: &Seed, + indexes: *const u8, + mut callback: F, +) -> Vec +where + F: FnMut(&Seed, u8) -> T, +{ + let len = *indexes as usize; + let slice = slice::from_raw_parts(indexes.add(1), len); + slice.iter().map(|&byte| callback(seed, byte)).collect() +} + +unsafe fn read_buffer(ptr: *const u8) -> Vec { + let len = slice::from_raw_parts(ptr, 4); + let len = u32::from_le_bytes(len.try_into().unwrap()) as usize; + slice::from_raw_parts(ptr.add(4), len).to_vec() +} + +/// Generate a profile (account / address pair) for the given seed and index. #[no_mangle] pub unsafe extern "C" fn generate_profile( - seed: &[u8; RNG_SEED], + seed: &Seed, index: u8, profile: *mut [u8; PhoenixPublicKey::SIZE + BlsPublicKey::SIZE], -) -> u8 { +) -> ErrorCode { let ppk = derive_phoenix_pk(seed, index).to_bytes(); let bpk = derive_bls_pk(seed, index).to_bytes(); @@ -60,16 +106,59 @@ pub unsafe extern "C" fn generate_profile( BlsPublicKey::SIZE, ); - 0 + ErrorCode::Ok } -// Currently we're not handling panic message in the WASM module; in the future -// we might want to enable it for `debug` releases. -mod panic_handling { - use core::panic::PanicInfo; +/// Filter all notes and their block height that are owned by the given keys, +/// mapped to their nullifiers. +#[no_mangle] +pub unsafe fn map_owned( + seed: &Seed, + indexes: *const u8, + notes_ptr: *mut u8, +) -> ErrorCode { + let keys = indexes_into_keys(seed, indexes, derive_phoenix_sk); + let notes = read_buffer(notes_ptr); + let notes: Vec = from_bytes::>(¬es) + .or(Err(ErrorCode::UnarchivingError))?; - #[panic_handler] - fn panic(_info: &PanicInfo) -> ! { - loop {} - } + let owned = notes::map_owned(&keys, notes); + + keys.into_iter().for_each(|mut sk| sk.zeroize()); + + let bytes = + to_bytes::<_, 4096>(&owned).or(Err(ErrorCode::ArchivingError))?; + + let len = bytes.len().to_le_bytes(); + + ptr::copy_nonoverlapping(len.as_ptr(), notes_ptr, 4); + ptr::copy_nonoverlapping(bytes.as_ptr(), notes_ptr.add(4), bytes.len()); + + ErrorCode::Ok +} + +/// Calculate the balance info for the phoenix address at the given index for +/// the given seed. +#[no_mangle] +pub unsafe fn balance( + seed: &Seed, + index: u8, + notes_ptr: *const u8, + balance_info_ptr: *mut [u8; 16], +) -> ErrorCode { + let vk = derive_phoenix_vk(seed, index); + + let notes = read_buffer(notes_ptr); + let notes: Vec = from_bytes::>(¬es) + .or(Err(ErrorCode::UnarchivingError))?; + + let info = phoenix_balance(&vk, notes.iter()); + + ptr::copy_nonoverlapping( + info.to_bytes().as_ptr(), + &mut (*balance_info_ptr)[0], + 16, + ); + + ErrorCode::Ok } diff --git a/wallet-core/src/keys.rs b/wallet-core/src/keys.rs index 1ce4714952..1314e73a55 100644 --- a/wallet-core/src/keys.rs +++ b/wallet-core/src/keys.rs @@ -21,17 +21,13 @@ use execution_core::{ }, }; -/// The seed bytes buffer which is used at multiple places -pub type Seed = [u8; RNG_SEED]; - -/// Length of the seed of the generated rng. -pub const RNG_SEED: usize = 64; +use crate::Seed; /// Generates a [`BlsSecretKey`] from a seed and index. /// /// The randomness is generated using [`rng_with_index`]. #[must_use] -pub fn derive_bls_sk(seed: &[u8; RNG_SEED], index: u8) -> BlsSecretKey { +pub fn derive_bls_sk(seed: &Seed, index: u8) -> BlsSecretKey { // note that if we change the string used for the rng, all previously // generated keys will become invalid // NOTE: When breaking the keys, we will want to change the string too @@ -42,7 +38,7 @@ pub fn derive_bls_sk(seed: &[u8; RNG_SEED], index: u8) -> BlsSecretKey { /// /// The randomness is generated using [`rng_with_index`]. #[must_use] -pub fn derive_bls_pk(seed: &[u8; RNG_SEED], index: u8) -> BlsPublicKey { +pub fn derive_bls_pk(seed: &Seed, index: u8) -> BlsPublicKey { let mut sk = derive_bls_sk(seed, index); let pk = BlsPublicKey::from(&sk); sk.zeroize(); @@ -54,7 +50,7 @@ pub fn derive_bls_pk(seed: &[u8; RNG_SEED], index: u8) -> BlsPublicKey { /// /// The randomness is generated using [`rng_with_index`]. #[must_use] -pub fn derive_phoenix_sk(seed: &[u8; RNG_SEED], index: u8) -> PhoenixSecretKey { +pub fn derive_phoenix_sk(seed: &Seed, index: u8) -> PhoenixSecretKey { // note that if we change the string used for the rng, all previously // generated keys will become invalid // NOTE: When breaking the keys, we will want to change the string too @@ -66,7 +62,7 @@ pub fn derive_phoenix_sk(seed: &[u8; RNG_SEED], index: u8) -> PhoenixSecretKey { /// The randomness is generated using [`rng_with_index`]. #[must_use] pub fn derive_multiple_phoenix_sk( - seed: &[u8; RNG_SEED], + seed: &Seed, index_range: Range, ) -> Vec { index_range @@ -74,13 +70,13 @@ pub fn derive_multiple_phoenix_sk( .collect() } -/// Generates a [`PheonixPublicKey`] from its seed and index. +/// Generates a [`PhoenixPublicKey`] from its seed and index. /// /// First the [`PhoenixSecretKey`] is derived with [`derive_phoenix_sk`], then /// the public key is generated from it and the secret key is erased from /// memory. #[must_use] -pub fn derive_phoenix_pk(seed: &[u8; RNG_SEED], index: u8) -> PhoenixPublicKey { +pub fn derive_phoenix_pk(seed: &Seed, index: u8) -> PhoenixPublicKey { let mut sk = derive_phoenix_sk(seed, index); let pk = PhoenixPublicKey::from(&sk); sk.zeroize(); @@ -93,7 +89,7 @@ pub fn derive_phoenix_pk(seed: &[u8; RNG_SEED], index: u8) -> PhoenixPublicKey { /// First the [`PhoenixSecretKey`] is derived with [`derive_phoenix_sk`], then /// the view key is generated from it and the secret key is erased from memory. #[must_use] -pub fn derive_phoenix_vk(seed: &[u8; RNG_SEED], index: u8) -> PhoenixViewKey { +pub fn derive_phoenix_vk(seed: &Seed, index: u8) -> PhoenixViewKey { let mut sk = derive_phoenix_sk(seed, index); let vk = PhoenixViewKey::from(&sk); sk.zeroize(); @@ -110,7 +106,7 @@ pub fn derive_phoenix_vk(seed: &[u8; RNG_SEED], index: u8) -> PhoenixViewKey { /// subsequently used to generate the key. #[must_use] pub fn rng_with_index( - seed: &[u8; RNG_SEED], + seed: &Seed, index: u8, termination: &[u8], ) -> ChaCha12Rng { diff --git a/wallet-core/src/lib.rs b/wallet-core/src/lib.rs index 610e6db1db..8d65e5d24a 100644 --- a/wallet-core/src/lib.rs +++ b/wallet-core/src/lib.rs @@ -10,6 +10,8 @@ #![deny(missing_docs)] #![deny(rustdoc::broken_intra_doc_links)] #![deny(clippy::pedantic)] +#![feature(try_trait_v2)] + #[cfg(target_family = "wasm")] #[global_allocator] static ALLOC: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc; @@ -17,50 +19,30 @@ static ALLOC: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc; extern crate alloc; #[cfg(target_family = "wasm")] +#[macro_use] mod ffi; pub mod input; pub mod keys; +pub mod notes; pub mod transaction; +/// The seed used to generate the entropy for the keys +pub type Seed = [u8; 64]; + pub mod prelude { //! Re-export of the most commonly used types and traits. pub use crate::keys; pub use crate::{input::MAX_INPUT_NOTES, keys::RNG_SEED}; } -use alloc::collections::btree_map::BTreeMap; use alloc::vec::Vec; use dusk_bytes::{DeserializableSlice, Serializable, Write}; -use execution_core::{ - transfer::phoenix::{ - Note, NoteLeaf, SecretKey as PhoenixSecretKey, - ViewKey as PhoenixViewKey, - }, - BlsScalar, -}; - -/// Filter all notes and their block height that are owned by the given keys, -/// mapped to their nullifiers. -pub fn map_owned( - keys: impl AsRef<[PhoenixSecretKey]>, - notes: impl AsRef<[NoteLeaf]>, -) -> BTreeMap { - notes - .as_ref() - .iter() - .fold(BTreeMap::new(), |mut notes_map, note_leaf| { - for sk in keys.as_ref() { - if sk.owns(note_leaf.note.stealth_address()) { - let nullifier = note_leaf.note.gen_nullifier(sk); - notes_map.insert(nullifier, note_leaf.clone()); - } - } - notes_map - }) -} +use execution_core::transfer::phoenix::{Note, ViewKey as PhoenixViewKey}; + +pub use notes::map_owned; /// Calculate the sum for all the given [`Note`]s that belong to the given /// [`PhoenixViewKey`]. diff --git a/wallet-core/src/notes.rs b/wallet-core/src/notes.rs new file mode 100644 index 0000000000..713b17cf47 --- /dev/null +++ b/wallet-core/src/notes.rs @@ -0,0 +1,92 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +//! Implementations of basic wallet functionalities to create transactions. + +use alloc::vec::Vec; +use core::ops::Index; +use execution_core::{ + transfer::phoenix::{NoteLeaf, SecretKey as PhoenixSecretKey}, + BlsScalar, +}; + +use rkyv::{Archive, Deserialize, Serialize}; + +/// A collection of notes stored as key-value pairs. +/// The key is a `BlsScalar` and the value is a `NoteLeaf`. +/// Duplicates are allowed. +#[derive(Default, Archive, Serialize, Deserialize, Debug)] +pub struct OwnedList { + /// The underlying storage of key-value pairs where + /// `BlsScalar` is the key and `NoteLeaf` is the value. + entries: Vec<(BlsScalar, NoteLeaf)>, +} + +impl OwnedList { + /// Inserts a new key-value pair into the collection. + pub fn insert(&mut self, key: BlsScalar, value: NoteLeaf) { + self.entries.push((key, value)); + } + + /// Returns the number of entries (key-value pairs) in the collection. + #[must_use] + pub fn len(&self) -> usize { + self.entries.len() + } + + /// Checks if the collection is empty. + #[must_use] + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + /// Retrieves the value (`NoteLeaf`) associated with a given key + #[must_use] + pub fn get(&self, key: &BlsScalar) -> Option<&NoteLeaf> { + self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v) + } + + /// Retrieves all keys in the collection. + #[must_use] + pub fn keys(&self) -> Vec { + self.entries.iter().map(|(k, _)| *k).collect() + } +} + +impl Index<&BlsScalar> for OwnedList { + type Output = NoteLeaf; + + /// Retrieves the value (`NoteLeaf`) associated with a given key + /// (`BlsScalar`). + /// + /// Panics if the key is not found in the collection. + fn index(&self, index: &BlsScalar) -> &Self::Output { + self.get(index).expect("key not found") + } +} + +/// Filter all notes and their block height that are owned by the given keys, +/// mapped to their nullifiers. +pub fn map_owned( + keys: impl AsRef<[PhoenixSecretKey]>, + notes: impl AsRef<[NoteLeaf]>, +) -> OwnedList { + notes.as_ref().iter().fold( + OwnedList::default(), + |mut notes_map, note_leaf| { + eprintln!("Printing note..."); + dbg!(note_leaf); + for sk in keys.as_ref() { + if sk.owns(note_leaf.note.stealth_address()) { + let nullifier = note_leaf.note.gen_nullifier(sk); + notes_map.insert(nullifier, note_leaf.clone()); + break; + } + } + notes_map + }, + ) +} From d5365aadefb4fa1f6f041216055c28bb2838c2d0 Mon Sep 17 00:00:00 2001 From: zer0 Date: Mon, 9 Sep 2024 12:28:25 +0200 Subject: [PATCH 6/9] wallet-core: change notes test to be close to SDK ones --- wallet-core/tests/notes.rs | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/wallet-core/tests/notes.rs b/wallet-core/tests/notes.rs index c7cff81fa5..3db5bec144 100644 --- a/wallet-core/tests/notes.rs +++ b/wallet-core/tests/notes.rs @@ -16,8 +16,8 @@ use execution_core::{ }; use wallet_core::{ - input::try_input_notes, keys::derive_multiple_phoenix_sk, map_owned, - phoenix_balance, BalanceInfo, + input::try_input_notes, keys::derive_multiple_phoenix_sk, + keys::derive_phoenix_sk, map_owned, phoenix_balance, BalanceInfo, Seed, }; /// Generate a note, useful for testing purposes @@ -50,36 +50,29 @@ pub fn gen_note( #[test] fn test_map_owned() { - // Assuming this set of notes where the number used as suffix is the - // "owner": - // notes := [A1, B1, C2, D2, E1, F3] - let mut rng = StdRng::seed_from_u64(0xdab); - const SEED_1: [u8; 64] = [1; 64]; - const SEED_2: [u8; 64] = [2; 64]; + const SEED: Seed = [1; 64]; - let owner_1_sks = derive_multiple_phoenix_sk(&SEED_1, 0..3); + let owner_1_sks = derive_multiple_phoenix_sk(&SEED, 0..3); let owner_1_pks = [ PhoenixPublicKey::from(&owner_1_sks[0]), PhoenixPublicKey::from(&owner_1_sks[1]), PhoenixPublicKey::from(&owner_1_sks[2]), ]; - let owner_2_sks = derive_multiple_phoenix_sk(&SEED_2, 0..2); + let owner_2_sks = derive_multiple_phoenix_sk(&SEED, 3..5); let owner_2_pks = [ PhoenixPublicKey::from(&owner_2_sks[0]), PhoenixPublicKey::from(&owner_2_sks[1]), ]; - let owner_3_pk = - PhoenixPublicKey::from(&PhoenixSecretKey::random(&mut rng)); - - let value = 42; - let note_leaves: Vec = vec![ - gen_note(&mut rng, true, &owner_1_pks[0], value), // owner 1 - gen_note(&mut rng, true, &owner_1_pks[1], value), // owner 1 - gen_note(&mut rng, true, &owner_2_pks[0], value), // owner 2 - gen_note(&mut rng, true, &owner_2_pks[1], value), // owner 2 - gen_note(&mut rng, true, &owner_1_pks[2], value), // owner 1 - gen_note(&mut rng, true, &owner_3_pk, value), // owner 3 + let owner_3_pk = PhoenixPublicKey::from(&derive_phoenix_sk(&SEED, 5)); + + let note_leaves = vec![ + gen_note(&mut rng, true, &owner_1_pks[0], 12), // owner 1 + gen_note(&mut rng, true, &owner_1_pks[1], 1), // owner 1 + gen_note(&mut rng, true, &owner_2_pks[0], 3), // owner 2 + gen_note(&mut rng, true, &owner_2_pks[1], 76), // owner 2 + gen_note(&mut rng, true, &owner_1_pks[2], 6), // owner 1 + gen_note(&mut rng, true, &owner_3_pk, 42), // owner 3 ]; let note_leaves: Vec = note_leaves From 44b8bfeef0d65c8c035492c49c9325f58a053a70 Mon Sep 17 00:00:00 2001 From: zer0 Date: Mon, 9 Sep 2024 16:11:16 +0200 Subject: [PATCH 7/9] wallet-core: Move some functionality from lib's root to `notes` module - Add `NOTES_BUFFER_SIZE` to indicate the scratch buffer size for notes - Add `MAX_INPUT_NOTES`, `phoenix_balance` and `BalanceInfo` under `notes` module - Remove `MAX_INPUT_NOTES`, `phoenix_balance` and `BalanceInfo` from lib.rs --- wallet-core/src/ffi.rs | 8 +++-- wallet-core/src/lib.rs | 62 ++---------------------------------- wallet-core/src/notes.rs | 69 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 74 insertions(+), 65 deletions(-) diff --git a/wallet-core/src/ffi.rs b/wallet-core/src/ffi.rs index f5b54637bb..c09459d4c2 100644 --- a/wallet-core/src/ffi.rs +++ b/wallet-core/src/ffi.rs @@ -39,6 +39,10 @@ use zeroize::Zeroize; use rkyv::{from_bytes, to_bytes}; +/// The size of the scratch buffer used for parsing the notes. +/// It can roughly contains less than 128 serialized notes. +const NOTES_BUFFER_SIZE: usize = 96 * 1024; + /// The alignment of the memory allocated by the FFI. /// /// This is 1 because we're not allocating any complex data structures, and @@ -126,8 +130,8 @@ pub unsafe fn map_owned( keys.into_iter().for_each(|mut sk| sk.zeroize()); - let bytes = - to_bytes::<_, 4096>(&owned).or(Err(ErrorCode::ArchivingError))?; + let bytes = to_bytes::<_, NOTES_BUFFER_SIZE>(&owned) + .or(Err(ErrorCode::ArchivingError))?; let len = bytes.len().to_le_bytes(); diff --git a/wallet-core/src/lib.rs b/wallet-core/src/lib.rs index 8d65e5d24a..bb939d060a 100644 --- a/wallet-core/src/lib.rs +++ b/wallet-core/src/lib.rs @@ -32,8 +32,8 @@ pub type Seed = [u8; 64]; pub mod prelude { //! Re-export of the most commonly used types and traits. + pub use crate::input::MAX_INPUT_NOTES; pub use crate::keys; - pub use crate::{input::MAX_INPUT_NOTES, keys::RNG_SEED}; } use alloc::vec::Vec; @@ -44,62 +44,4 @@ use execution_core::transfer::phoenix::{Note, ViewKey as PhoenixViewKey}; pub use notes::map_owned; -/// Calculate the sum for all the given [`Note`]s that belong to the given -/// [`PhoenixViewKey`]. -pub fn phoenix_balance( - phoenix_vk: &PhoenixViewKey, - notes: impl Iterator, -) -> BalanceInfo -where - T: AsRef, -{ - let mut values: Vec = notes - .filter_map(|note| note.as_ref().value(Some(phoenix_vk)).ok()) - .collect(); - - values.sort_by(|a, b| b.cmp(a)); - - let spendable = values.iter().take(input::MAX_INPUT_NOTES).sum(); - let value = - spendable + values.iter().skip(input::MAX_INPUT_NOTES).sum::(); - - BalanceInfo { value, spendable } -} - -/// Information about the balance of a particular key. -#[derive(Debug, Default, Hash, Clone, Copy, PartialEq, Eq)] -pub struct BalanceInfo { - /// The total value of the balance. - pub value: u64, - /// The maximum _spendable_ value in a single transaction. This is - /// different from `value` since there is a maximum number of notes one can - /// spend. - pub spendable: u64, -} - -impl Serializable<{ 2 * u64::SIZE }> for BalanceInfo { - type Error = dusk_bytes::Error; - - fn from_bytes(buf: &[u8; Self::SIZE]) -> Result - where - Self: Sized, - { - let mut reader = &buf[..]; - - let value = u64::from_reader(&mut reader)?; - let spendable = u64::from_reader(&mut reader)?; - - Ok(Self { value, spendable }) - } - - #[allow(unused_must_use)] - fn to_bytes(&self) -> [u8; Self::SIZE] { - let mut buf = [0u8; Self::SIZE]; - let mut writer = &mut buf[..]; - - writer.write(&self.value.to_bytes()); - writer.write(&self.spendable.to_bytes()); - - buf - } -} +pub use notes::{map_owned, phoenix_balance, BalanceInfo}; diff --git a/wallet-core/src/notes.rs b/wallet-core/src/notes.rs index 713b17cf47..54c7fcb3fb 100644 --- a/wallet-core/src/notes.rs +++ b/wallet-core/src/notes.rs @@ -4,10 +4,12 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -//! Implementations of basic wallet functionalities to create transactions. +//! Provides functions and types for interacting with notes. use alloc::vec::Vec; use core::ops::Index; +use dusk_bytes::{DeserializableSlice, Serializable, Write}; +use execution_core::transfer::phoenix::{Note, ViewKey as PhoenixViewKey}; use execution_core::{ transfer::phoenix::{NoteLeaf, SecretKey as PhoenixSecretKey}, BlsScalar, @@ -15,6 +17,10 @@ use execution_core::{ use rkyv::{Archive, Deserialize, Serialize}; +// The maximum amount of input notes that can be spend in one +// phoenix-transaction +const MAX_INPUT_NOTES: usize = 4; + /// A collection of notes stored as key-value pairs. /// The key is a `BlsScalar` and the value is a `NoteLeaf`. /// Duplicates are allowed. @@ -77,8 +83,6 @@ pub fn map_owned( notes.as_ref().iter().fold( OwnedList::default(), |mut notes_map, note_leaf| { - eprintln!("Printing note..."); - dbg!(note_leaf); for sk in keys.as_ref() { if sk.owns(note_leaf.note.stealth_address()) { let nullifier = note_leaf.note.gen_nullifier(sk); @@ -90,3 +94,62 @@ pub fn map_owned( }, ) } + +/// Calculate the sum for all the given [`Note`]s that belong to the given +/// [`PhoenixViewKey`]. +pub fn phoenix_balance( + phoenix_vk: &PhoenixViewKey, + notes: impl Iterator, +) -> BalanceInfo +where + T: AsRef, +{ + let mut values: Vec = notes + .filter_map(|note| note.as_ref().value(Some(phoenix_vk)).ok()) + .collect(); + + values.sort_by(|a, b| b.cmp(a)); + + let spendable = values.iter().take(MAX_INPUT_NOTES).sum(); + let value = spendable + values.iter().skip(MAX_INPUT_NOTES).sum::(); + + BalanceInfo { value, spendable } +} + +/// Information about the balance of a particular key. +#[derive(Debug, Default, Hash, Clone, Copy, PartialEq, Eq)] +pub struct BalanceInfo { + /// The total value of the balance. + pub value: u64, + /// The maximum _spendable_ value in a single transaction. This is + /// different from `value` since there is a maximum number of notes one can + /// spend. + pub spendable: u64, +} + +impl Serializable<{ 2 * u64::SIZE }> for BalanceInfo { + type Error = dusk_bytes::Error; + + fn from_bytes(buf: &[u8; Self::SIZE]) -> Result + where + Self: Sized, + { + let mut reader = &buf[..]; + + let value = u64::from_reader(&mut reader)?; + let spendable = u64::from_reader(&mut reader)?; + + Ok(Self { value, spendable }) + } + + #[allow(unused_must_use)] + fn to_bytes(&self) -> [u8; Self::SIZE] { + let mut buf = [0u8; Self::SIZE]; + let mut writer = &mut buf[..]; + + writer.write(&self.value.to_bytes()); + writer.write(&self.spendable.to_bytes()); + + buf + } +} From a2a0ae49cc60b2591a6a48830658549898fcbe40 Mon Sep 17 00:00:00 2001 From: zer0 Date: Mon, 9 Sep 2024 17:26:18 +0200 Subject: [PATCH 8/9] wallet-core: adjustment after rebasing with main branch --- wallet-core/src/input.rs | 3 +-- wallet-core/src/lib.rs | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/wallet-core/src/input.rs b/wallet-core/src/input.rs index 00fb51ed13..d9355a13b9 100644 --- a/wallet-core/src/input.rs +++ b/wallet-core/src/input.rs @@ -8,8 +8,7 @@ use alloc::vec::Vec; -use super::{alloc, Note}; - +use execution_core::transfer::phoenix::Note; use execution_core::BlsScalar; /// The maximum amount of input notes that can be spend in one diff --git a/wallet-core/src/lib.rs b/wallet-core/src/lib.rs index bb939d060a..0b6e80bac7 100644 --- a/wallet-core/src/lib.rs +++ b/wallet-core/src/lib.rs @@ -36,12 +36,4 @@ pub mod prelude { pub use crate::keys; } -use alloc::vec::Vec; - -use dusk_bytes::{DeserializableSlice, Serializable, Write}; - -use execution_core::transfer::phoenix::{Note, ViewKey as PhoenixViewKey}; - -pub use notes::map_owned; - pub use notes::{map_owned, phoenix_balance, BalanceInfo}; From 0e3edbebb714ce21d6eddc337001c8e001fc869d Mon Sep 17 00:00:00 2001 From: zer0 Date: Mon, 9 Sep 2024 17:27:09 +0200 Subject: [PATCH 9/9] rusk-wallet: remove unnecessary `Seed` structure --- rusk-wallet/src/dat.rs | 2 +- rusk-wallet/src/store.rs | 36 +++++++----------------------------- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/rusk-wallet/src/dat.rs b/rusk-wallet/src/dat.rs index 6ab92adaf0..285acf21cf 100644 --- a/rusk-wallet/src/dat.rs +++ b/rusk-wallet/src/dat.rs @@ -7,7 +7,7 @@ use std::fs; use std::io::Read; -use wallet_core::keys::Seed; +use wallet_core::Seed; use crate::crypto::decrypt; use crate::Error; diff --git a/rusk-wallet/src/store.rs b/rusk-wallet/src/store.rs index b5c31aec54..99944c112a 100644 --- a/rusk-wallet/src/store.rs +++ b/rusk-wallet/src/store.rs @@ -6,29 +6,7 @@ use crate::clients::State; -use dusk_bytes::{Error as BytesError, Serializable}; - -use wallet_core::keys::{self, RNG_SEED}; - -#[derive(Clone)] -pub struct Seed(keys::Seed); - -impl Default for Seed { - fn default() -> Self { - Self([0u8; RNG_SEED]) - } -} - -impl Serializable<64> for Seed { - type Error = BytesError; - - fn from_bytes(buff: &[u8; Seed::SIZE]) -> Result { - Ok(Self(*buff)) - } - fn to_bytes(&self) -> [u8; Seed::SIZE] { - self.0 - } -} +use wallet_core::Seed; /// Provides a valid wallet seed to dusk_wallet_core #[derive(Clone)] @@ -38,20 +16,20 @@ pub(crate) struct LocalStore { impl LocalStore { /// Retrieves the seed used to derive keys. - pub fn get_seed(&self) -> &[u8; Seed::SIZE] { - &self.seed.0 + pub fn get_seed(&self) -> &Seed { + &self.seed } } -impl From<[u8; Seed::SIZE]> for LocalStore { - fn from(seed: [u8; Seed::SIZE]) -> Self { - LocalStore { seed: Seed(seed) } +impl From for LocalStore { + fn from(seed: Seed) -> Self { + LocalStore { seed } } } impl State { /// Retrieves the seed used to derive keys. - pub fn get_seed(&self) -> &[u8; Seed::SIZE] { + pub fn get_seed(&self) -> &Seed { self.store().get_seed() } }