mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-03-25 11:43:06 +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>
273 lines
7.9 KiB
Rust
273 lines
7.9 KiB
Rust
//! Wallet lifecycle management functions.
|
|
|
|
use std::{
|
|
ffi::{c_char, CStr},
|
|
path::PathBuf,
|
|
ptr,
|
|
sync::Mutex,
|
|
};
|
|
|
|
use wallet::WalletCore;
|
|
|
|
use crate::{
|
|
block_on,
|
|
error::{print_error, WalletFfiError},
|
|
types::WalletHandle,
|
|
};
|
|
|
|
/// Internal wrapper around `WalletCore` with mutex for thread safety.
|
|
pub(crate) struct WalletWrapper {
|
|
pub core: Mutex<WalletCore>,
|
|
}
|
|
|
|
/// Helper to get the wallet wrapper from an opaque handle.
|
|
pub(crate) fn get_wallet(
|
|
handle: *mut WalletHandle,
|
|
) -> Result<&'static WalletWrapper, WalletFfiError> {
|
|
if handle.is_null() {
|
|
print_error("Null wallet handle");
|
|
return Err(WalletFfiError::NullPointer);
|
|
}
|
|
Ok(unsafe { &*handle.cast::<WalletWrapper>() })
|
|
}
|
|
|
|
/// Helper to get a mutable reference to the wallet wrapper.
|
|
#[expect(dead_code, reason = "Maybe used later")]
|
|
pub(crate) fn get_wallet_mut(
|
|
handle: *mut WalletHandle,
|
|
) -> Result<&'static mut WalletWrapper, WalletFfiError> {
|
|
if handle.is_null() {
|
|
print_error("Null wallet handle");
|
|
return Err(WalletFfiError::NullPointer);
|
|
}
|
|
Ok(unsafe { &mut *handle.cast::<WalletWrapper>() })
|
|
}
|
|
|
|
/// Helper to convert a C string to a Rust `PathBuf`.
|
|
fn c_str_to_path(ptr: *const c_char, name: &str) -> Result<PathBuf, WalletFfiError> {
|
|
if ptr.is_null() {
|
|
print_error(format!("Null pointer for {name}"));
|
|
return Err(WalletFfiError::NullPointer);
|
|
}
|
|
|
|
let c_str = unsafe { CStr::from_ptr(ptr) };
|
|
match c_str.to_str() {
|
|
Ok(s) => Ok(PathBuf::from(s)),
|
|
Err(e) => {
|
|
print_error(format!("Invalid UTF-8 in {name}: {e}"));
|
|
Err(WalletFfiError::InvalidUtf8)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Helper to convert a C string to a Rust String.
|
|
fn c_str_to_string(ptr: *const c_char, name: &str) -> Result<String, WalletFfiError> {
|
|
if ptr.is_null() {
|
|
print_error(format!("Null pointer for {name}"));
|
|
return Err(WalletFfiError::NullPointer);
|
|
}
|
|
|
|
let c_str = unsafe { CStr::from_ptr(ptr) };
|
|
match c_str.to_str() {
|
|
Ok(s) => Ok(s.to_owned()),
|
|
Err(e) => {
|
|
print_error(format!("Invalid UTF-8 in {name}: {e}"));
|
|
Err(WalletFfiError::InvalidUtf8)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create a new wallet with fresh storage.
|
|
///
|
|
/// This initializes a new wallet with a new seed derived from the password.
|
|
/// Use this for first-time wallet creation.
|
|
///
|
|
/// # Parameters
|
|
/// - `config_path`: Path to the wallet configuration file (JSON)
|
|
/// - `storage_path`: Path where wallet data will be stored
|
|
/// - `password`: Password for encrypting the wallet seed
|
|
///
|
|
/// # Returns
|
|
/// - Opaque wallet handle on success
|
|
/// - Null pointer on error (call `wallet_ffi_get_last_error()` for details)
|
|
///
|
|
/// # Safety
|
|
/// All string parameters must be valid null-terminated UTF-8 strings.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn wallet_ffi_create_new(
|
|
config_path: *const c_char,
|
|
storage_path: *const c_char,
|
|
password: *const c_char,
|
|
) -> *mut WalletHandle {
|
|
let Ok(config_path) = c_str_to_path(config_path, "config_path") else {
|
|
return ptr::null_mut();
|
|
};
|
|
|
|
let Ok(storage_path) = c_str_to_path(storage_path, "storage_path") else {
|
|
return ptr::null_mut();
|
|
};
|
|
|
|
let Ok(password) = c_str_to_string(password, "password") else {
|
|
return ptr::null_mut();
|
|
};
|
|
|
|
match WalletCore::new_init_storage(config_path, storage_path, None, password) {
|
|
Ok((core, _mnemonic)) => {
|
|
let wrapper = Box::new(WalletWrapper {
|
|
core: Mutex::new(core),
|
|
});
|
|
Box::into_raw(wrapper).cast::<WalletHandle>()
|
|
}
|
|
Err(e) => {
|
|
print_error(format!("Failed to create wallet: {e}"));
|
|
ptr::null_mut()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Open an existing wallet from storage.
|
|
///
|
|
/// This loads a wallet that was previously created with `wallet_ffi_create_new()`.
|
|
///
|
|
/// # Parameters
|
|
/// - `config_path`: Path to the wallet configuration file (JSON)
|
|
/// - `storage_path`: Path where wallet data is stored
|
|
///
|
|
/// # Returns
|
|
/// - Opaque wallet handle on success
|
|
/// - Null pointer on error (call `wallet_ffi_get_last_error()` for details)
|
|
///
|
|
/// # Safety
|
|
/// All string parameters must be valid null-terminated UTF-8 strings.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn wallet_ffi_open(
|
|
config_path: *const c_char,
|
|
storage_path: *const c_char,
|
|
) -> *mut WalletHandle {
|
|
let Ok(config_path) = c_str_to_path(config_path, "config_path") else {
|
|
return ptr::null_mut();
|
|
};
|
|
|
|
let Ok(storage_path) = c_str_to_path(storage_path, "storage_path") else {
|
|
return ptr::null_mut();
|
|
};
|
|
|
|
match WalletCore::new_update_chain(config_path, storage_path, None) {
|
|
Ok(core) => {
|
|
let wrapper = Box::new(WalletWrapper {
|
|
core: Mutex::new(core),
|
|
});
|
|
Box::into_raw(wrapper).cast::<WalletHandle>()
|
|
}
|
|
Err(e) => {
|
|
print_error(format!("Failed to open wallet: {e}"));
|
|
ptr::null_mut()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Destroy a wallet handle and free its resources.
|
|
///
|
|
/// After calling this function, the handle is invalid and must not be used.
|
|
///
|
|
/// # Safety
|
|
/// - The handle must be either null or a valid handle from `wallet_ffi_create_new()` or
|
|
/// `wallet_ffi_open()`.
|
|
/// - The handle must not be used after this call.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn wallet_ffi_destroy(handle: *mut WalletHandle) {
|
|
if !handle.is_null() {
|
|
unsafe {
|
|
drop(Box::from_raw(handle.cast::<WalletWrapper>()));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Save wallet state to persistent storage.
|
|
///
|
|
/// This should be called periodically or after important operations to ensure
|
|
/// wallet data is persisted to disk.
|
|
///
|
|
/// # Parameters
|
|
/// - `handle`: Valid wallet handle
|
|
///
|
|
/// # Returns
|
|
/// - `Success` on successful save
|
|
/// - Error code on failure
|
|
///
|
|
/// # Safety
|
|
/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfiError {
|
|
let wrapper = match get_wallet(handle) {
|
|
Ok(w) => w,
|
|
Err(e) => return e,
|
|
};
|
|
|
|
let wallet = match wrapper.core.lock() {
|
|
Ok(w) => w,
|
|
Err(e) => {
|
|
print_error(format!("Failed to lock wallet: {e}"));
|
|
return WalletFfiError::InternalError;
|
|
}
|
|
};
|
|
|
|
match block_on(wallet.store_persistent_data()) {
|
|
Ok(()) => WalletFfiError::Success,
|
|
Err(e) => {
|
|
print_error(format!("Failed to save wallet: {e}"));
|
|
WalletFfiError::StorageError
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get the sequencer address from the wallet configuration.
|
|
///
|
|
/// # Parameters
|
|
/// - `handle`: Valid wallet handle
|
|
///
|
|
/// # Returns
|
|
/// - Pointer to null-terminated string on success (caller must free with
|
|
/// `wallet_ffi_free_string()`)
|
|
/// - Null pointer on error
|
|
///
|
|
/// # Safety
|
|
/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn wallet_ffi_get_sequencer_addr(handle: *mut WalletHandle) -> *mut c_char {
|
|
let Ok(wrapper) = get_wallet(handle) else {
|
|
return ptr::null_mut();
|
|
};
|
|
|
|
let wallet = match wrapper.core.lock() {
|
|
Ok(w) => w,
|
|
Err(e) => {
|
|
print_error(format!("Failed to lock wallet: {e}"));
|
|
return ptr::null_mut();
|
|
}
|
|
};
|
|
|
|
let addr = wallet.config().sequencer_addr.clone().to_string();
|
|
|
|
match std::ffi::CString::new(addr) {
|
|
Ok(s) => s.into_raw(),
|
|
Err(e) => {
|
|
print_error(format!("Invalid sequencer address: {e}"));
|
|
ptr::null_mut()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Free a string returned by wallet FFI functions.
|
|
///
|
|
/// # Safety
|
|
/// The pointer must be either null or a valid string returned by an FFI function.
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn wallet_ffi_free_string(ptr: *mut c_char) {
|
|
if !ptr.is_null() {
|
|
unsafe {
|
|
drop(std::ffi::CString::from_raw(ptr));
|
|
}
|
|
}
|
|
}
|