diff --git a/Cargo.lock b/Cargo.lock index ee5c97f2..295797ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10765,6 +10765,7 @@ dependencies = [ name = "wallet-ffi" version = "0.1.0" dependencies = [ + "bip39", "cbindgen", "common", "key_protocol", diff --git a/integration_tests/tests/wallet_ffi.rs b/integration_tests/tests/wallet_ffi.rs index 1a648547..5eab3e0f 100644 --- a/integration_tests/tests/wallet_ffi.rs +++ b/integration_tests/tests/wallet_ffi.rs @@ -33,7 +33,7 @@ use wallet_ffi::{ FfiAccount, FfiAccountIdentity, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey, FfiTransferResult, FfiU128, WalletHandle, error, generic_transaction::{FfiProgramWithDependencies, FfiTransactionResult}, - wallet::FfiCreateWalletResult, + wallet::FfiCreateWalletOutput, }; unsafe extern "C" { @@ -41,7 +41,7 @@ unsafe extern "C" { config_path: *const c_char, storage_path: *const c_char, password: *const c_char, - ) -> FfiCreateWalletResult; + ) -> FfiCreateWalletOutput; fn wallet_ffi_open( config_path: *const c_char, @@ -241,7 +241,7 @@ unsafe extern "C" { fn new_wallet_ffi_with_test_context_config( ctx: &BlockingTestContext, home: &Path, -) -> Result { +) -> Result { let config_path = home.join("wallet_config.json"); let storage_path = home.join("storage.json"); let mut config = ctx.ctx().wallet().config().to_owned(); @@ -319,7 +319,7 @@ fn new_wallet_ffi_with_test_context_config( Ok(create_wallet_result) } -fn new_wallet_ffi_with_default_config(password: &str) -> Result { +fn new_wallet_ffi_with_default_config(password: &str) -> Result { let tempdir = tempdir()?; let config_path = tempdir.path().join("wallet_config.json"); let storage_path = tempdir.path().join("storage.json"); @@ -356,7 +356,7 @@ fn wallet_ffi_create_public_accounts() -> Result<()> { let new_public_account_ids_ffi = unsafe { let mut account_ids = Vec::new(); - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_default_config(password)?; @@ -395,7 +395,7 @@ fn wallet_ffi_create_private_accounts() -> Result<()> { let new_npks_ffi = unsafe { let mut npks = Vec::new(); - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_default_config(password)?; @@ -427,7 +427,7 @@ fn wallet_ffi_save_and_load_persistent_storage() -> Result<()> { let home = tempfile::tempdir()?; // Create a receiving key and save let first_npk = unsafe { - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -467,7 +467,7 @@ fn test_wallet_ffi_list_accounts() -> Result<()> { // Create the wallet FFI and track which account IDs were created as public/private let (wallet_ffi_handle, created_public_ids) = unsafe { - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: handle, mnemonic: _, } = new_wallet_ffi_with_default_config(password)?; @@ -535,7 +535,7 @@ fn test_wallet_ffi_get_balance_public() -> Result<()> { let ctx = BlockingTestContext::new()?; let account_id: AccountId = ctx.ctx().existing_public_accounts()[0]; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -568,7 +568,7 @@ fn test_wallet_ffi_get_account_public() -> Result<()> { let ctx = BlockingTestContext::new()?; let account_id: AccountId = ctx.ctx().existing_public_accounts()[0]; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -608,7 +608,7 @@ fn test_wallet_ffi_get_account_private() -> Result<()> { let ctx = BlockingTestContext::new()?; let account_id: AccountId = ctx.ctx().existing_private_accounts()[0]; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -647,7 +647,7 @@ fn test_wallet_ffi_get_public_account_keys() -> Result<()> { let ctx = BlockingTestContext::new()?; let account_id: AccountId = ctx.ctx().existing_public_accounts()[0]; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -689,7 +689,7 @@ fn test_wallet_ffi_get_private_account_keys() -> Result<()> { let ctx = BlockingTestContext::new()?; let account_id: AccountId = ctx.ctx().existing_private_accounts()[0]; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -774,7 +774,7 @@ fn wallet_ffi_base58_to_account_id() -> Result<()> { fn wallet_ffi_init_public_account_auth_transfer() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -840,7 +840,7 @@ fn wallet_ffi_init_public_account_auth_transfer() -> Result<()> { fn wallet_ffi_init_private_account_auth_transfer() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -900,7 +900,7 @@ fn wallet_ffi_init_private_account_auth_transfer() -> Result<()> { fn test_wallet_ffi_transfer_public() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -957,7 +957,7 @@ fn test_wallet_ffi_transfer_public() -> Result<()> { fn test_wallet_ffi_transfer_shielded() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -1036,7 +1036,7 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> { fn test_wallet_ffi_transfer_deshielded() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -1099,7 +1099,7 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> { fn test_wallet_ffi_transfer_private() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -1178,11 +1178,13 @@ fn test_wallet_ffi_transfer_private() -> Result<()> { fn restore_keys_from_seed_ffi() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let mnemonic = unsafe { CString::from_raw(mnemonic) }; + // Create 2 new private accounts let (private_account_id_1, private_account_1_keys) = unsafe { let mut out_keys = FfiPrivateAccountKeys::default(); @@ -1394,7 +1396,7 @@ fn restore_keys_from_seed_ffi() -> Result<()> { assert_eq!(public_account_id_2_balance, 103); unsafe { - wallet_ffi_restore_data(wallet_ffi_handle, *mnemonic, password.as_ptr()).unwrap(); + wallet_ffi_restore_data(wallet_ffi_handle, mnemonic.as_ptr(), password.as_ptr()).unwrap(); } // Sync private account local storage with onchain encrypted state @@ -1464,7 +1466,7 @@ fn restore_keys_from_seed_ffi() -> Result<()> { fn test_wallet_ffi_bridge_withdraw() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -1527,7 +1529,7 @@ fn test_wallet_ffi_bridge_withdraw() -> Result<()> { fn test_wallet_ffi_transfer_generic_public() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -1622,7 +1624,7 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { fn test_wallet_ffi_transfer_generic_private() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let FfiCreateWalletResult { + let FfiCreateWalletOutput { wallet: wallet_ffi_handle, mnemonic: _, } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; diff --git a/lez/wallet-ffi/Cargo.toml b/lez/wallet-ffi/Cargo.toml index 1e8b6395..0b54bc0f 100644 --- a/lez/wallet-ffi/Cargo.toml +++ b/lez/wallet-ffi/Cargo.toml @@ -21,6 +21,7 @@ tokio.workspace = true key_protocol.workspace = true serde_json.workspace = true risc0-zkvm.workspace = true +bip39.workspace = true [build-dependencies] cbindgen = "0.29" diff --git a/lez/wallet-ffi/src/wallet.rs b/lez/wallet-ffi/src/wallet.rs index f0f02afd..b19aaae5 100644 --- a/lez/wallet-ffi/src/wallet.rs +++ b/lez/wallet-ffi/src/wallet.rs @@ -4,9 +4,11 @@ use std::{ ffi::{c_char, CStr, CString}, path::PathBuf, ptr, + str::FromStr as _, sync::Mutex, }; +use bip39::Mnemonic; use wallet::{cli::execute_keys_restoration, WalletCore}; use crate::{ @@ -21,13 +23,13 @@ pub(crate) struct WalletWrapper { } #[repr(C)] -pub struct FfiCreateWalletResult { +pub struct FfiCreateWalletOutput { pub wallet: *mut WalletHandle, /// C compatible(null terminated) string. - pub mnemonic: *mut *const c_char, + pub mnemonic: *mut c_char, } -impl Default for FfiCreateWalletResult { +impl Default for FfiCreateWalletOutput { fn default() -> Self { Self { wallet: std::ptr::null_mut(), @@ -97,17 +99,17 @@ pub unsafe extern "C" fn wallet_ffi_create_new( config_path: *const c_char, storage_path: *const c_char, password: *const c_char, -) -> FfiCreateWalletResult { +) -> FfiCreateWalletOutput { let Ok(config_path) = c_str_to_path(config_path, "config_path") else { - return FfiCreateWalletResult::default(); + return FfiCreateWalletOutput::default(); }; let Ok(storage_path) = c_str_to_path(storage_path, "storage_path") else { - return FfiCreateWalletResult::default(); + return FfiCreateWalletOutput::default(); }; let Ok(password) = c_str_to_string(password, "password") else { - return FfiCreateWalletResult::default(); + return FfiCreateWalletOutput::default(); }; match WalletCore::new_init_storage(config_path, storage_path, None, &password) { @@ -118,21 +120,19 @@ pub unsafe extern "C" fn wallet_ffi_create_new( let handle = Box::into_raw(wrapper).cast::(); let Ok(c_mnemonic_string) = CString::new(mnemonic.to_string()) else { - return FfiCreateWalletResult::default(); + return FfiCreateWalletOutput::default(); }; let raw_pointer = CString::into_raw(c_mnemonic_string); - let boxed_mnemonic_string = Box::new(raw_pointer.cast_const()); - let raw_mnemonic_string_pointer = Box::into_raw(boxed_mnemonic_string); - FfiCreateWalletResult { + FfiCreateWalletOutput { wallet: handle, - mnemonic: raw_mnemonic_string_pointer, + mnemonic: raw_pointer, } } Err(e) => { print_error(format!("Failed to create wallet: {e}")); - FfiCreateWalletResult::default() + FfiCreateWalletOutput::default() } } } @@ -237,8 +237,9 @@ pub unsafe extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfi /// /// # Parameters /// - `handle`: Valid wallet handle -/// - `mnemonic`: Valid pointer to instance of `FfiMnemonic`, provided by `wallet_ffi_create_new` +/// - `mnemonic`: Valid pointer to instance of `* char`, provided by `wallet_ffi_create_new` /// - `password`: Valid pointer to C string. +/// - `depth`: Depth of a reconstructed tree /// /// # Returns /// - `Success` on successful restoration @@ -246,14 +247,16 @@ pub unsafe extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfi /// /// # Safety /// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` -/// - `mnemonic` must be a valid pointer to instance of `FfiMnemonic`, provided by +/// - `mnemonic` must be a valid pointer to instance of `* char`, provided by /// `wallet_ffi_create_new` /// - `password` must be a valid pointer to C string. +/// - `depth` parameter induces exponential growth in execution time, be aware of it. #[no_mangle] pub unsafe extern "C" fn wallet_ffi_restore_data( handle: *mut WalletHandle, mnemonic: *const c_char, password: *const c_char, + depth: u32, ) -> WalletFfiError { let wrapper = match get_wallet(handle) { Ok(w) => w, @@ -276,6 +279,14 @@ pub unsafe extern "C" fn wallet_ffi_restore_data( return WalletFfiError::NullPointer; }; + let mnemonic = match Mnemonic::from_str(&mnemonic) { + Ok(mn) => mn, + Err(e) => { + print_error(format!("Failed to parse mnemonic: {e}")); + return WalletFfiError::SerializationError; + } + }; + let res = match wallet.restore_storage(&mnemonic, &password) { Ok(()) => WalletFfiError::Success, Err(e) => { @@ -285,7 +296,7 @@ pub unsafe extern "C" fn wallet_ffi_restore_data( }; if res == WalletFfiError::Success { - match block_on(execute_keys_restoration(&mut wallet, 10)) { + match block_on(execute_keys_restoration(&mut wallet, depth)) { Ok(()) => WalletFfiError::Success, Err(err) => { print_error(format!("Failed to restore wallet data: {err}")); diff --git a/lez/wallet-ffi/wallet_ffi.h b/lez/wallet-ffi/wallet_ffi.h index d4237a50..18e1ad5b 100644 --- a/lez/wallet-ffi/wallet_ffi.h +++ b/lez/wallet-ffi/wallet_ffi.h @@ -299,13 +299,13 @@ typedef struct FfiPublicAccountKey { struct FfiBytes32 public_key; } FfiPublicAccountKey; -typedef struct FfiCreateWalletResult { +typedef struct FfiCreateWalletOutput { struct WalletHandle *wallet; /** * C compatible(null terminated) string. */ - const char **mnemonic; -} FfiCreateWalletResult; + char *mnemonic; +} FfiCreateWalletOutput; /** * Create a new public account. @@ -1387,7 +1387,7 @@ void wallet_ffi_free_transfer_result(struct FfiTransferResult *result); * # Safety * All string parameters must be valid null-terminated UTF-8 strings. */ -struct FfiCreateWalletResult wallet_ffi_create_new(const char *config_path, +struct FfiCreateWalletOutput wallet_ffi_create_new(const char *config_path, const char *storage_path, const char *password); @@ -1444,8 +1444,9 @@ enum WalletFfiError wallet_ffi_save(struct WalletHandle *handle); * * # Parameters * - `handle`: Valid wallet handle - * - `mnemonic`: Valid pointer to instance of `FfiMnemonic`, provided by `wallet_ffi_create_new` + * - `mnemonic`: Valid pointer to instance of `* char`, provided by `wallet_ffi_create_new` * - `password`: Valid pointer to C string. + * - `depth`: Depth of a reconstructed tree * * # Returns * - `Success` on successful restoration @@ -1453,13 +1454,15 @@ enum WalletFfiError wallet_ffi_save(struct WalletHandle *handle); * * # Safety * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` - * - `mnemonic` must be a valid pointer to instance of `FfiMnemonic`, provided by + * - `mnemonic` must be a valid pointer to instance of `* char`, provided by * `wallet_ffi_create_new` * - `password` must be a valid pointer to C string. + * - `depth` parameter induces exponential growth in execution time, be aware of it. */ enum WalletFfiError wallet_ffi_restore_data(struct WalletHandle *handle, const char *mnemonic, - const char *password); + const char *password, + uint32_t depth); /** * Get the sequencer address from the wallet configuration. diff --git a/lez/wallet/src/cli/mod.rs b/lez/wallet/src/cli/mod.rs index 76c69c8c..8a91d3ae 100644 --- a/lez/wallet/src/cli/mod.rs +++ b/lez/wallet/src/cli/mod.rs @@ -274,7 +274,7 @@ pub async fn execute_subcommand( Command::RestoreKeys { depth } => { let mnemonic = read_mnemonic_from_stdin()?; let password = read_password_from_stdin()?; - wallet_core.restore_storage(&mnemonic.to_string(), &password)?; + wallet_core.restore_storage(&mnemonic, &password)?; execute_keys_restoration(wallet_core, depth).await?; SubcommandReturnValue::Empty diff --git a/lez/wallet/src/lib.rs b/lez/wallet/src/lib.rs index 1850c33b..d8679dc1 100644 --- a/lez/wallet/src/lib.rs +++ b/lez/wallet/src/lib.rs @@ -199,8 +199,8 @@ impl WalletCore { } /// Restore storage from an existing mnemonic phrase. - pub fn restore_storage(&mut self, mnemonic: &str, password: &str) -> Result<()> { - self.storage.restore(&Mnemonic::parse(mnemonic)?, password) + pub fn restore_storage(&mut self, mnemonic: &Mnemonic, password: &str) -> Result<()> { + self.storage.restore(mnemonic, password) } /// Store persistent data at home. @@ -875,10 +875,7 @@ impl WalletCore { #[cfg(test)] mod tests { - use std::{ - ffi::{CStr, CString}, - str::FromStr as _, - }; + use std::{ffi::CString, str::FromStr as _}; use bip39::Mnemonic; @@ -888,14 +885,9 @@ mod tests { Mnemonic::from_entropy(&[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]).unwrap(); let c_mnemonic_string = CString::new(mnemonic.to_string()).unwrap(); - - let boxed_mnemonic_string = Box::new(c_mnemonic_string.as_ptr()); - let raw_mnemonic_string_pointer = Box::into_raw(boxed_mnemonic_string); - + let c_mnemonic_string_raw = c_mnemonic_string.into_raw(); // Safety: Will be safe, pointer is created from CString - let c_str_pointer = unsafe { *raw_mnemonic_string_pointer }; - // Safety: Will be safe, pointer is created from CString - let c_str = unsafe { CStr::from_ptr(c_str_pointer) }; + let c_str = unsafe { CString::from_raw(c_mnemonic_string_raw) }; let mn_string = c_str.to_str().unwrap(); let mn_ret = Mnemonic::from_str(mn_string).unwrap();