Skip to content

Commit

Permalink
Merge pull request #2308 from dusk-network/wallet-core-ffi
Browse files Browse the repository at this point in the history
wallet-core: work on FFI
  • Loading branch information
ZER0 authored Sep 9, 2024
2 parents e3374d6 + 0e3edbe commit 7a259e9
Show file tree
Hide file tree
Showing 13 changed files with 483 additions and 180 deletions.
2 changes: 1 addition & 1 deletion rusk-wallet/src/dat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
36 changes: 7 additions & 29 deletions rusk-wallet/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self, Self::Error> {
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)]
Expand All @@ -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<Seed> 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()
}
}
15 changes: 9 additions & 6 deletions wallet-core/.cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
#
# See: <https://github.com/rust-lang/cargo/issues/6784>
[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
3 changes: 3 additions & 0 deletions wallet-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
123 changes: 108 additions & 15 deletions wallet-core/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,44 @@
//
// 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 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.
///
Expand All @@ -39,12 +68,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<T, F>(
seed: &Seed,
indexes: *const u8,
mut callback: F,
) -> Vec<T>
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<u8> {
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();

Expand All @@ -60,16 +110,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<NoteLeaf> = from_bytes::<Vec<NoteLeaf>>(&notes)
.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::<_, NOTES_BUFFER_SIZE>(&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<NoteLeaf> = from_bytes::<Vec<NoteLeaf>>(&notes)
.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
}
90 changes: 90 additions & 0 deletions wallet-core/src/ffi/debug.rs
Original file line number Diff line number Diff line change
@@ -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<u8> {
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)*) => {};
}
}
Loading

0 comments on commit 7a259e9

Please sign in to comment.