diff --git a/.github/actions/install-logos-blockchain-circuits/action.yaml b/.github/actions/install-logos-blockchain-circuits/action.yaml index e62aea6b..30c0972b 100644 --- a/.github/actions/install-logos-blockchain-circuits/action.yaml +++ b/.github/actions/install-logos-blockchain-circuits/action.yaml @@ -16,4 +16,4 @@ runs: env: GITHUB_TOKEN: ${{ inputs.github-token }} run: | - curl -sSL https://raw.githubusercontent.com/logos-blockchain/logos-blockchain/main/scripts/setup-logos-blockchain-circuits.sh | bash + curl -sSL https://raw.githubusercontent.com/logos-blockchain/logos-blockchain/dd055cc1ef7c130f710a52a190edd97bc7b0f71b/scripts/setup-logos-blockchain-circuits.sh | bash diff --git a/Cargo.lock b/Cargo.lock index 0bad6271..bcd49668 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10720,6 +10720,7 @@ dependencies = [ "key_protocol", "nssa", "nssa_core", + "risc0-zkvm", "sequencer_service_rpc", "serde_json", "tempfile", diff --git a/integration_tests/tests/wallet_ffi.rs b/integration_tests/tests/wallet_ffi.rs index 2677e10e..93939cd1 100644 --- a/integration_tests/tests/wallet_ffi.rs +++ b/integration_tests/tests/wallet_ffi.rs @@ -7,6 +7,7 @@ clippy::undocumented_unsafe_blocks, clippy::multiple_unsafe_ops_per_block, clippy::shadow_unrelated, + clippy::as_conversions, reason = "We don't care about these in tests" )] @@ -21,13 +22,17 @@ use std::{ use anyhow::Result; use integration_tests::{BlockingTestContext, TIME_TO_WAIT_FOR_BLOCK_SECONDS}; use log::info; -use nssa::{Account, AccountId, PrivateKey, PublicKey, program::Program}; +use nssa::{ + Account, AccountId, PrivateKey, PublicKey, + privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program, +}; use nssa_core::program::DEFAULT_PROGRAM_ID; use tempfile::tempdir; use wallet::account::HumanReadableAccount; use wallet_ffi::{ - FfiAccount, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey, - FfiTransferResult, FfiU128, WalletHandle, error, + FfiAccount, FfiAccountIdentity, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys, + FfiPublicAccountKey, FfiTransferResult, FfiU128, WalletHandle, error, + generic_transaction::{FfiProgramWithDependencies, FfiTransactionResult}, }; unsafe extern "C" { @@ -179,6 +184,42 @@ unsafe extern "C" { handle: *mut WalletHandle, out_block_height: *mut u64, ) -> error::WalletFfiError; + + fn wallet_ffi_resolve_public_account( + account_id: FfiBytes32, + needs_sign: bool, + out_account_identity: *mut FfiAccountIdentity, + ) -> error::WalletFfiError; + + fn wallet_ffi_send_generic_public_transaction( + handle: *mut WalletHandle, + account_identities: *const FfiAccountIdentity, + account_identities_size: usize, + instruction_words: *const u32, + instruction_words_size: usize, + program_with_dependencies: *const FfiProgramWithDependencies, + out_result: *mut FfiTransactionResult, + ) -> error::WalletFfiError; + + fn wallet_ffi_resolve_private_account( + handle: *mut WalletHandle, + account_id: FfiBytes32, + out_account_identity: *mut FfiAccountIdentity, + ) -> error::WalletFfiError; + + fn wallet_ffi_send_generic_private_transaction( + handle: *mut WalletHandle, + account_identities: *const FfiAccountIdentity, + account_identities_size: usize, + instruction_words: *const u32, + instruction_words_size: usize, + program_with_dependencies: *const FfiProgramWithDependencies, + out_result: *mut FfiTransactionResult, + ) -> error::WalletFfiError; + + fn wallet_ffi_free_transaction_result(result: *mut FfiTransactionResult); + + fn wallet_ffi_free_account_identity(account_identity: *mut FfiAccountIdentity); } fn new_wallet_ffi_with_test_context_config( @@ -1066,3 +1107,201 @@ fn test_wallet_ffi_transfer_private() -> Result<()> { Ok(()) } + +#[test] +fn test_wallet_ffi_transfer_generic_public() -> Result<()> { + let ctx = BlockingTestContext::new()?; + let home = tempfile::tempdir()?; + let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let from: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into(); + let to: FfiBytes32 = ctx.ctx().existing_public_accounts()[1].into(); + let amount = 100_u128; + + let mut transaction_result = FfiTransactionResult::default(); + + let mut from_account_identity = FfiAccountIdentity::default(); + let mut to_account_identity = FfiAccountIdentity::default(); + + unsafe { + wallet_ffi_resolve_public_account(from, true, &raw mut from_account_identity).unwrap(); + } + + unsafe { + wallet_ffi_resolve_public_account(to, true, &raw mut to_account_identity).unwrap(); + } + + let ffi_accs = vec![from_account_identity, to_account_identity]; + let account_identities_size = ffi_accs.len(); + let account_identities = + Box::into_raw(ffi_accs.into_boxed_slice()) as *const FfiAccountIdentity; + + let instruction_data = + Program::serialize_instruction(authenticated_transfer_core::Instruction::Transfer { + amount, + }) + .unwrap(); + let instruction_words_size = instruction_data.len(); + let instruction_words = Box::into_raw(instruction_data.into_boxed_slice()) as *const u32; + + let program: ProgramWithDependencies = Program::authenticated_transfer_program().into(); + let program_with_dependencies: FfiProgramWithDependencies = program.into(); + + unsafe { + wallet_ffi_send_generic_public_transaction( + wallet_ffi_handle, + account_identities, + account_identities_size, + instruction_words, + instruction_words_size, + &raw const program_with_dependencies, + &raw mut transaction_result, + ) + .unwrap(); + } + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + let from_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const from, + true, + &raw mut out_balance, + ) + .unwrap(); + u128::from_le_bytes(out_balance) + }; + + let to_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + wallet_ffi_get_balance(wallet_ffi_handle, &raw const to, true, &raw mut out_balance) + .unwrap(); + u128::from_le_bytes(out_balance) + }; + + assert_eq!(from_balance, 9900); + assert_eq!(to_balance, 20100); + + unsafe { + let account_identities_mut = account_identities.cast_mut(); + wallet_ffi_free_account_identity(account_identities_mut); + wallet_ffi_free_account_identity(account_identities_mut.add(1)); + + let instruction_data = + std::slice::from_raw_parts_mut(instruction_words.cast_mut(), instruction_words_size); + drop(Box::from_raw(std::ptr::from_mut(instruction_data))); + + wallet_ffi_free_transaction_result(&raw mut transaction_result); + wallet_ffi_destroy(wallet_ffi_handle); + } + + Ok(()) +} + +#[test] +fn test_wallet_ffi_transfer_generic_private() -> Result<()> { + let ctx = BlockingTestContext::new()?; + let home = tempfile::tempdir()?; + let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let from: FfiBytes32 = ctx.ctx().existing_private_accounts()[0].into(); + let to: FfiBytes32 = ctx.ctx().existing_private_accounts()[1].into(); + let amount = 100_u128; + + let mut transaction_result = FfiTransactionResult::default(); + + let mut from_account_identity = FfiAccountIdentity::default(); + let mut to_account_identity = FfiAccountIdentity::default(); + + unsafe { + wallet_ffi_resolve_private_account(wallet_ffi_handle, from, &raw mut from_account_identity) + .unwrap(); + } + + unsafe { + wallet_ffi_resolve_private_account(wallet_ffi_handle, to, &raw mut to_account_identity) + .unwrap(); + } + + let ffi_accs = vec![from_account_identity, to_account_identity]; + let account_identities_size = ffi_accs.len(); + let account_identities = + Box::into_raw(ffi_accs.into_boxed_slice()) as *const FfiAccountIdentity; + + let instruction_data = + Program::serialize_instruction(authenticated_transfer_core::Instruction::Transfer { + amount, + }) + .unwrap(); + let instruction_words_size = instruction_data.len(); + let instruction_words = Box::into_raw(instruction_data.into_boxed_slice()) as *const u32; + + let program: ProgramWithDependencies = Program::authenticated_transfer_program().into(); + let program_with_dependencies: FfiProgramWithDependencies = program.into(); + + unsafe { + wallet_ffi_send_generic_private_transaction( + wallet_ffi_handle, + account_identities, + account_identities_size, + instruction_words, + instruction_words_size, + &raw const program_with_dependencies, + &raw mut transaction_result, + ) + .unwrap(); + } + + assert_eq!(transaction_result.secrets_size, 2); + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + // Sync private account local storage with onchain encrypted state + unsafe { + let mut current_height = 0; + wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap(); + wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap(); + }; + + let from_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + let _result = wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const from, + false, + &raw mut out_balance, + ); + u128::from_le_bytes(out_balance) + }; + + let to_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + let _result = wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const to, + false, + &raw mut out_balance, + ); + u128::from_le_bytes(out_balance) + }; + + assert_eq!(from_balance, 9900); + assert_eq!(to_balance, 20100); + + unsafe { + let account_identities_mut = account_identities.cast_mut(); + wallet_ffi_free_account_identity(account_identities_mut); + wallet_ffi_free_account_identity(account_identities_mut.add(1)); + + let instruction_data = + std::slice::from_raw_parts_mut(instruction_words.cast_mut(), instruction_words_size); + drop(Box::from_raw(std::ptr::from_mut(instruction_data))); + + wallet_ffi_free_transaction_result(&raw mut transaction_result); + wallet_ffi_destroy(wallet_ffi_handle); + } + + Ok(()) +} diff --git a/nssa/src/program.rs b/nssa/src/program.rs index adc69b3f..c624af3b 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -22,8 +22,8 @@ const MAX_NUM_CYCLES_PUBLIC_EXECUTION: u64 = 1024 * 1024 * 32; // 32M cycles #[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Program { - id: ProgramId, - elf: Vec, + pub id: ProgramId, + pub elf: Vec, } impl Program { diff --git a/wallet-ffi/Cargo.toml b/wallet-ffi/Cargo.toml index 869845c8..9940607f 100644 --- a/wallet-ffi/Cargo.toml +++ b/wallet-ffi/Cargo.toml @@ -19,6 +19,7 @@ sequencer_service_rpc = { workspace = true, features = ["client"] } tokio.workspace = true key_protocol.workspace = true serde_json.workspace = true +risc0-zkvm.workspace = true [build-dependencies] cbindgen = "0.29" diff --git a/wallet-ffi/src/error.rs b/wallet-ffi/src/error.rs index 17b73075..e95b47a0 100644 --- a/wallet-ffi/src/error.rs +++ b/wallet-ffi/src/error.rs @@ -41,6 +41,8 @@ pub enum WalletFfiError { InvalidTypeConversion = 15, /// Invalid Key value. InvalidKeyValue = 16, + /// Invalid program bytecode. + InvalidBytecode = 17, /// Internal error (catch-all). InternalError = 99, } diff --git a/wallet-ffi/src/generic_transaction.rs b/wallet-ffi/src/generic_transaction.rs new file mode 100644 index 00000000..9ec3db33 --- /dev/null +++ b/wallet-ffi/src/generic_transaction.rs @@ -0,0 +1,420 @@ +use std::{ + collections::HashMap, + ffi::{c_char, CString}, +}; + +use nssa::{privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program}; + +use crate::{ + block_on, + error::{print_error, WalletFfiError}, + map_execution_error, + wallet::get_wallet, + FfiAccountIdentity, FfiBytes32, WalletHandle, +}; + +#[repr(C)] +pub struct SerializationHelperResult { + pub instruction_words: *mut u32, + pub instruction_words_size: usize, + pub error: WalletFfiError, +} + +impl SerializationHelperResult { + const fn from_err(error: WalletFfiError) -> Self { + Self { + instruction_words: std::ptr::null_mut(), + instruction_words_size: 0, + error, + } + } +} + +#[repr(C)] +/// Intended to be created manually. +pub struct FfiProgram { + pub elf_data: *const u8, + pub elf_size: usize, +} + +impl TryFrom<&FfiProgram> for Program { + type Error = WalletFfiError; + + fn try_from(value: &FfiProgram) -> Result { + let mut elf = Vec::with_capacity(value.elf_size); + + // Alignment will be different, we need to read elements one-by-one + for i in 0..value.elf_size { + elf.push(unsafe { *value.elf_data.add(i) }); + } + + Self::new(elf).map_err(|err| { + print_error(format!("Invalid program bytecode, err: {err}")); + WalletFfiError::InvalidBytecode + }) + } +} + +impl From for FfiProgram { + fn from(value: Program) -> Self { + let elf_size = value.elf.len(); + let elf_data = Box::into_raw(value.elf.into_boxed_slice()) as *const u8; + + Self { elf_data, elf_size } + } +} + +#[repr(C)] +/// Intended to be created manually. +pub struct FfiProgramWithDependencies { + pub program: FfiProgram, + pub deps: *const FfiProgram, + pub deps_size: usize, +} + +impl TryFrom<&FfiProgramWithDependencies> for ProgramWithDependencies { + type Error = WalletFfiError; + + fn try_from(value: &FfiProgramWithDependencies) -> Result { + let mut program_map = HashMap::new(); + + let orig_program = (&value.program).try_into()?; + + // Alignment will be different, we need to read elements one-by-one + for i in 0..value.deps_size { + let program_dep: Program = unsafe { value.deps.add(i).as_ref() } + .ok_or(WalletFfiError::NullPointer)? + .try_into()?; + + program_map.insert(program_dep.id(), program_dep); + } + + Ok(Self { + program: orig_program, + dependencies: program_map, + }) + } +} + +impl From for FfiProgramWithDependencies { + fn from(value: ProgramWithDependencies) -> Self { + let ffi_program = value.program.into(); + + let ffi_deps: Vec = value + .dependencies + .into_values() + .map(Into::into) + .collect::>(); + + let deps_size = ffi_deps.len(); + let deps = Box::into_raw(ffi_deps.into_boxed_slice()) as *const FfiProgram; + + Self { + program: ffi_program, + deps, + deps_size, + } + } +} + +/// Result of a generic transaction operation. +#[repr(C)] +pub struct FfiTransactionResult { + // TODO: Replace with HashType FFI representation + /// Transaction hash (null-terminated string, or null on failure). + pub tx_hash: *mut c_char, + /// Whether the transaction succeeded. + pub success: bool, + pub secrets_data: *const FfiBytes32, + /// Public transactions have 0 secrets. + pub secrets_size: usize, +} + +impl Default for FfiTransactionResult { + fn default() -> Self { + Self { + tx_hash: std::ptr::null_mut(), + success: false, + secrets_data: std::ptr::null(), + secrets_size: 0, + } + } +} + +/// Serialize sequence of bytes into RISC0 readable words. +/// +/// # Parameters +/// - `input_instruction_data`: Valid pointer to a sequence of bytes +/// - `input_instruction_data_size`: Size of `input_instruction_data` +/// +/// # Returns +/// - `Success` on successful creation +/// - Error code on failure +/// +/// # Safety +/// - `input_instruction_data` must be a valid pointer +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_serialization_helper( + input_instruction_data: *const u8, + input_instruction_data_size: usize, +) -> SerializationHelperResult { + if input_instruction_data.is_null() { + print_error("Null input pointer for instruction_data"); + return SerializationHelperResult::from_err(WalletFfiError::NullPointer); + } + + let input_slice = + unsafe { std::slice::from_raw_parts(input_instruction_data, input_instruction_data_size) }; + let res_vec_u32_with_prefix = match risc0_zkvm::serde::to_vec(input_slice).map_err(|err| { + print_error(format!( + "Failed to serialize input into words with err {err}" + )); + WalletFfiError::SerializationError + }) { + Ok(res) => res, + Err(err) => return SerializationHelperResult::from_err(err), + }; + + // The resulting vec contains len as prefix + let res_vec_u32 = res_vec_u32_with_prefix[1..].to_vec(); + + let res_len = res_vec_u32.len(); + let res_boxed = res_vec_u32.into_boxed_slice(); + let res_ptr = Box::into_raw(res_boxed).cast::(); + + SerializationHelperResult { + instruction_words: res_ptr, + instruction_words_size: res_len, + error: WalletFfiError::Success, + } +} + +/// Send generic public transaction. +/// +/// # Parameters +/// - `handle`: Valid pointer to wallet handle +/// - `account_identities`: Valid pointer to list of `FfiAccountIdentity` +/// - `instruction_words`: Valid pointer to instruction words +/// - `out_result`: Valid pointer to `FfiTransactionResult` +/// +/// # Returns +/// - `Success` on successful creation +/// - Error code on failure +/// +/// # Safety +/// - `handle` must be a valid pointer +/// - `account_identities` must be a valid pointer +/// - `instruction_words` must be a valid pointer +/// - `out_result` must be a valid pointer +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( + handle: *mut WalletHandle, + account_identities: *const FfiAccountIdentity, + account_identities_size: usize, + instruction_words: *const u32, + instruction_words_size: usize, + program_with_dependencies: *const FfiProgramWithDependencies, + out_result: *mut FfiTransactionResult, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if account_identities.is_null() { + print_error("Null input pointer for account identities list"); + return WalletFfiError::NullPointer; + } + + if instruction_words.is_null() { + print_error("Null input pointer for instruction data"); + return WalletFfiError::NullPointer; + } + + if out_result.is_null() { + print_error("Null output pointer return hash"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + print_error(format!("Failed to lock wallet: {e}")); + return WalletFfiError::InternalError; + } + }; + + let accounts_ffi = std::slice::from_raw_parts(account_identities, account_identities_size); + let instruction_data = std::slice::from_raw_parts(instruction_words, instruction_words_size); + + let mut accounts = Vec::with_capacity(account_identities_size); + + for ffi_acc in accounts_ffi { + match ffi_acc.try_into() { + Ok(v) => accounts.push(v), + Err(err) => { + print_error("Failed to convert FfiAccountIdentity into AccountIdentity"); + return err; + } + } + } + + let program = match unsafe { &*program_with_dependencies }.try_into() { + Ok(v) => v, + Err(err) => return err, + }; + + match block_on(wallet.send_pub_tx(accounts, instruction_data.to_vec(), &program)) { + Ok(tx_hash) => { + let tx_hash = CString::new(tx_hash.to_string()) + .map_or(std::ptr::null_mut(), std::ffi::CString::into_raw); + + unsafe { + (*out_result).tx_hash = tx_hash; + (*out_result).success = true; + } + WalletFfiError::Success + } + Err(e) => { + print_error(format!("Public send failed: {e:?}")); + unsafe { + (*out_result).tx_hash = std::ptr::null_mut(); + (*out_result).success = false; + } + map_execution_error(e) + } + } +} + +/// Send generic private transaction. +/// +/// # Parameters +/// - `handle`: Valid pointer to wallet handle +/// - `account_identities`: Valid pointer to list of `FfiAccountIdentity` +/// - `instruction_words`: Valid pointer to instruction words +/// - `out_result`: Valid pointer to `FfiTransactionResult` +/// +/// # Returns +/// - `Success` on successful creation +/// - Error code on failure +/// +/// # Safety +/// - `handle` must be a valid pointer +/// - `account_identities` must be a valid pointer +/// - `instruction_words` must be a valid pointer +/// - `out_result` must be a valid pointer +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( + handle: *mut WalletHandle, + account_identities: *const FfiAccountIdentity, + account_identities_size: usize, + instruction_words: *const u32, + instruction_words_size: usize, + program_with_dependencies: *const FfiProgramWithDependencies, + out_result: *mut FfiTransactionResult, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if account_identities.is_null() { + print_error("Null input pointer for account identities list"); + return WalletFfiError::NullPointer; + } + + if instruction_words.is_null() { + print_error("Null input pointer for instruction data"); + return WalletFfiError::NullPointer; + } + + if out_result.is_null() { + print_error("Null output pointer return hash"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + print_error(format!("Failed to lock wallet: {e}")); + return WalletFfiError::InternalError; + } + }; + + let accounts_ffi = std::slice::from_raw_parts(account_identities, account_identities_size); + let instruction_data = std::slice::from_raw_parts(instruction_words, instruction_words_size); + + let mut accounts = Vec::with_capacity(account_identities_size); + + for ffi_acc in accounts_ffi { + match ffi_acc.try_into() { + Ok(v) => accounts.push(v), + Err(err) => { + print_error("Failed to convert FfiAccountIdentity into AccountIdentity"); + return err; + } + } + } + + let program = match unsafe { &*program_with_dependencies }.try_into() { + Ok(v) => v, + Err(err) => return err, + }; + + match block_on(wallet.send_privacy_preserving_tx(accounts, instruction_data.to_vec(), &program)) + { + Ok((tx_hash, secrets)) => { + let tx_hash = CString::new(tx_hash.to_string()) + .map_or(std::ptr::null_mut(), std::ffi::CString::into_raw); + + unsafe { + (*out_result).tx_hash = tx_hash; + (*out_result).success = true; + + let secrets_size = secrets.len(); + let boxed_slice = secrets + .into_iter() + .map(Into::into) + .collect::>() + .into_boxed_slice(); + let secrets_data = Box::into_raw(boxed_slice) as *const FfiBytes32; + + (*out_result).secrets_size = secrets_size; + (*out_result).secrets_data = secrets_data; + } + WalletFfiError::Success + } + Err(e) => { + print_error(format!("Private send failed: {e:?}")); + unsafe { + *out_result = FfiTransactionResult::default(); + } + map_execution_error(e) + } + } +} + +/// Free a transaction result returned by `wallet_ffi_send_generic_public_transaction` or +/// `wallet_ffi_send_generic_private_transaction`. +/// +/// # Safety +/// The result must be either null or a valid result from a transaction function. +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_free_transaction_result(result: *mut FfiTransactionResult) { + if result.is_null() { + return; + } + + unsafe { + let result = &*result; + if !result.tx_hash.is_null() { + drop(CString::from_raw(result.tx_hash)); + } + + if !result.secrets_data.is_null() { + let secrets = + std::slice::from_raw_parts_mut(result.secrets_data.cast_mut(), result.secrets_size); + drop(Box::from_raw(std::ptr::from_mut::<[FfiBytes32]>(secrets))); + } + } +} diff --git a/wallet-ffi/src/keys.rs b/wallet-ffi/src/keys.rs index b676ffab..ae7fdc36 100644 --- a/wallet-ffi/src/keys.rs +++ b/wallet-ffi/src/keys.rs @@ -3,11 +3,13 @@ use std::ptr; use nssa::{AccountId, PublicKey}; +use wallet::AccountIdentity; use crate::{ error::{print_error, WalletFfiError}, types::{FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey, WalletHandle}, wallet::get_wallet, + FfiAccountIdentity, }; /// Get the public key for a public account. @@ -250,3 +252,127 @@ pub unsafe extern "C" fn wallet_ffi_account_id_from_base58( WalletFfiError::Success } + +/// Resolve public account. +/// +/// # Parameters +/// - `account_id`: 32 bytes of the public account ID +/// - `needs_sign`: whether the account needs signing +/// - `out_account_identity`: valid pointer, where output will be written +/// +/// # Returns +/// - `Success` on successful retrieval +/// +/// # Safety +/// - `out_account_identity` must be a valid pointer to a `FfiAccountIdentity` struct +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_resolve_public_account( + account_id: FfiBytes32, + needs_sign: bool, + out_account_identity: *mut FfiAccountIdentity, +) -> WalletFfiError { + if out_account_identity.is_null() { + print_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + let resolved_account = if needs_sign { + AccountIdentity::Public(account_id.into()) + } else { + AccountIdentity::PublicNoSign(account_id.into()) + }; + + unsafe { + *out_account_identity = resolved_account.into(); + } + + WalletFfiError::Success +} + +/// Resolve private account. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `account_id`: 32 bytes of the public account ID +/// - `out_account_identity`: valid pointer, where output will be written +/// +/// # Returns +/// - `Success` on successful retrieval +/// - `InternalError` if failed to lock wallet +/// - `AccountNotFound` if the account is not found +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `out_account_identity` must be a valid pointer to a `FfiAccountIdentity` struct +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_resolve_private_account( + handle: *mut WalletHandle, + account_id: FfiBytes32, + out_account_identity: *mut FfiAccountIdentity, +) -> WalletFfiError { + if out_account_identity.is_null() { + print_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + 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; + } + }; + + let account_id = account_id.into(); + + let Some(resolved_account) = wallet.resolve_private_account(account_id) else { + print_error("Account not found"); + return WalletFfiError::AccountNotFound; + }; + + unsafe { + *out_account_identity = resolved_account.into(); + } + + WalletFfiError::Success +} + +/// Free account identity returned by `wallet_ffi_resolve_private_account` or +/// `wallet_ffi_resolve_public_account`. +/// +/// # Safety +/// The account must be either null or a valid account returned by +/// `wallet_ffi_resolve_private_account` or `wallet_ffi_resolve_public_account`. +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_free_account_identity( + account_identity: *mut FfiAccountIdentity, +) { + if account_identity.is_null() { + return; + } + + unsafe { + let FfiAccountIdentity { + kind: _, + account_id: _, + nullifier_secret_key: _, + nullifier_public_key: _, + viewing_public_key, + viewing_public_key_len, + identifier: _, + } = *account_identity; + + if !viewing_public_key.is_null() { + let slice = std::slice::from_raw_parts_mut( + viewing_public_key.cast_mut(), + viewing_public_key_len, + ); + drop(Box::from_raw(std::ptr::from_mut::<[u8]>(slice))); + } + } +} diff --git a/wallet-ffi/src/lib.rs b/wallet-ffi/src/lib.rs index 16943d3e..9502d0e0 100644 --- a/wallet-ffi/src/lib.rs +++ b/wallet-ffi/src/lib.rs @@ -23,6 +23,7 @@ #![expect( clippy::undocumented_unsafe_blocks, clippy::multiple_unsafe_ops_per_block, + clippy::as_conversions, reason = "TODO: fix later" )] @@ -42,6 +43,7 @@ use crate::error::print_error; pub mod account; pub mod error; +pub mod generic_transaction; pub mod keys; pub mod pinata; pub mod sync; diff --git a/wallet-ffi/src/types.rs b/wallet-ffi/src/types.rs index b970a8d3..4ad72285 100644 --- a/wallet-ffi/src/types.rs +++ b/wallet-ffi/src/types.rs @@ -3,8 +3,9 @@ use core::slice; use std::{ffi::c_char, ptr}; -use nssa::Data; -use nssa_core::encryption::shared_key_derivation::Secp256k1Point; +use nssa::{Data, SharedSecretKey}; +use nssa_core::{encryption::shared_key_derivation::Secp256k1Point, NullifierPublicKey}; +use wallet::AccountIdentity; use crate::error::WalletFfiError; @@ -154,6 +155,12 @@ impl FfiBytes32 { } } +impl From for FfiBytes32 { + fn from(value: SharedSecretKey) -> Self { + Self { data: value.0 } + } +} + impl FfiPrivateAccountKeys { #[must_use] pub const fn npk(&self) -> nssa_core::NullifierPublicKey { @@ -172,6 +179,46 @@ impl FfiPrivateAccountKeys { } } +/// Enumeration to represent kinds of `FfiAccountIdentity`. +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FfiAccountIdentityKind { + Public = 0, + PublicNoSign = 1, + PrivateOwned = 2, + PrivateForeign = 3, + PrivatePdaOwned = 4, + PrivatePdaForeign = 5, + PrivateShared = 6, + PrivatePdaShared = 7, +} + +/// Struct representing an account identity, given to `AccountManager` at intialization. +#[repr(C)] +pub struct FfiAccountIdentity { + pub kind: FfiAccountIdentityKind, + pub account_id: FfiBytes32, + pub nullifier_secret_key: FfiBytes32, + pub nullifier_public_key: FfiBytes32, + pub viewing_public_key: *const u8, + pub viewing_public_key_len: usize, + pub identifier: FfiU128, +} + +impl Default for FfiAccountIdentity { + fn default() -> Self { + Self { + kind: FfiAccountIdentityKind::Public, + account_id: FfiBytes32::default(), + nullifier_secret_key: FfiBytes32::default(), + nullifier_public_key: FfiBytes32::default(), + viewing_public_key: std::ptr::null(), + viewing_public_key_len: 0, + identifier: FfiU128::default(), + } + } +} + impl From for FfiU128 { fn from(value: u128) -> Self { Self { @@ -192,6 +239,12 @@ impl From for FfiBytes32 { } } +impl From<[u8; 32]> for FfiBytes32 { + fn from(value: [u8; 32]) -> Self { + Self { data: value } + } +} + impl From for nssa::AccountId { fn from(bytes: FfiBytes32) -> Self { Self::new(bytes.data) @@ -266,3 +319,342 @@ impl TryFrom<&FfiPublicAccountKey> for nssa::PublicKey { Ok(public_key) } } + +impl From for FfiAccountIdentity { + fn from(value: AccountIdentity) -> Self { + match value { + AccountIdentity::Public(account_id) => Self { + kind: FfiAccountIdentityKind::Public, + account_id: account_id.into(), + ..Default::default() + }, + AccountIdentity::PublicNoSign(account_id) => Self { + kind: FfiAccountIdentityKind::PublicNoSign, + account_id: account_id.into(), + ..Default::default() + }, + AccountIdentity::PrivateOwned(account_id) => Self { + kind: FfiAccountIdentityKind::PrivateOwned, + account_id: account_id.into(), + ..Default::default() + }, + AccountIdentity::PrivateForeign { + npk, + vpk, + identifier, + } => { + let vpk_vec = vpk.0; + let vpk_len = vpk_vec.len(); + let vpk_data = if vpk_len > 0 { + let vpk_data_boxed = vpk_vec.into_boxed_slice(); + Box::into_raw(vpk_data_boxed) as *const u8 + } else { + ptr::null() + }; + + Self { + kind: FfiAccountIdentityKind::PrivateForeign, + nullifier_public_key: npk.0.into(), + viewing_public_key: vpk_data, + viewing_public_key_len: vpk_len, + identifier: identifier.into(), + ..Default::default() + } + } + AccountIdentity::PrivatePdaOwned(account_id) => Self { + kind: FfiAccountIdentityKind::PrivatePdaOwned, + account_id: account_id.into(), + ..Default::default() + }, + AccountIdentity::PrivatePdaForeign { + account_id, + npk, + vpk, + identifier, + } => { + let vpk_vec = vpk.0; + let vpk_len = vpk_vec.len(); + let vpk_data = if vpk_len > 0 { + let vpk_data_boxed = vpk_vec.into_boxed_slice(); + Box::into_raw(vpk_data_boxed) as *const u8 + } else { + ptr::null() + }; + + Self { + kind: FfiAccountIdentityKind::PrivatePdaForeign, + account_id: account_id.into(), + nullifier_public_key: npk.0.into(), + viewing_public_key: vpk_data, + viewing_public_key_len: vpk_len, + identifier: identifier.into(), + ..Default::default() + } + } + AccountIdentity::PrivateShared { + nsk, + npk, + vpk, + identifier, + } => { + let vpk_vec = vpk.0; + let vpk_len = vpk_vec.len(); + let vpk_data = if vpk_len > 0 { + let vpk_data_boxed = vpk_vec.into_boxed_slice(); + Box::into_raw(vpk_data_boxed) as *const u8 + } else { + ptr::null() + }; + + Self { + kind: FfiAccountIdentityKind::PrivateShared, + nullifier_secret_key: nsk.into(), + nullifier_public_key: npk.0.into(), + viewing_public_key: vpk_data, + viewing_public_key_len: vpk_len, + identifier: identifier.into(), + ..Default::default() + } + } + AccountIdentity::PrivatePdaShared { + account_id, + nsk, + npk, + vpk, + identifier, + } => { + let vpk_vec = vpk.0; + let vpk_len = vpk_vec.len(); + let vpk_data = if vpk_len > 0 { + let vpk_data_boxed = vpk_vec.into_boxed_slice(); + Box::into_raw(vpk_data_boxed) as *const u8 + } else { + ptr::null() + }; + + Self { + kind: FfiAccountIdentityKind::PrivatePdaShared, + account_id: account_id.into(), + nullifier_secret_key: nsk.into(), + nullifier_public_key: npk.0.into(), + viewing_public_key: vpk_data, + viewing_public_key_len: vpk_len, + identifier: identifier.into(), + } + } + } + } +} + +impl TryFrom<&FfiAccountIdentity> for AccountIdentity { + type Error = WalletFfiError; + + fn try_from(value: &FfiAccountIdentity) -> Result { + match value.kind { + FfiAccountIdentityKind::Public => Ok(Self::Public(value.account_id.into())), + FfiAccountIdentityKind::PublicNoSign => Ok(Self::PublicNoSign(value.account_id.into())), + FfiAccountIdentityKind::PrivateOwned => Ok(Self::PrivateOwned(value.account_id.into())), + FfiAccountIdentityKind::PrivateForeign => { + let vpk = if value.viewing_public_key_len == 33 { + let slice = unsafe { + slice::from_raw_parts( + value.viewing_public_key, + value.viewing_public_key_len, + ) + }; + Ok(Secp256k1Point(slice.to_vec())) + } else { + Err(WalletFfiError::InvalidKeyValue) + }?; + + Ok(Self::PrivateForeign { + npk: NullifierPublicKey(value.nullifier_public_key.data), + vpk, + identifier: value.identifier.into(), + }) + } + FfiAccountIdentityKind::PrivatePdaOwned => { + Ok(Self::PrivatePdaOwned(value.account_id.into())) + } + FfiAccountIdentityKind::PrivatePdaForeign => { + let vpk = if value.viewing_public_key_len == 33 { + let slice = unsafe { + slice::from_raw_parts( + value.viewing_public_key, + value.viewing_public_key_len, + ) + }; + Ok(Secp256k1Point(slice.to_vec())) + } else { + Err(WalletFfiError::InvalidKeyValue) + }?; + + Ok(Self::PrivatePdaForeign { + account_id: value.account_id.into(), + npk: NullifierPublicKey(value.nullifier_public_key.data), + vpk, + identifier: value.identifier.into(), + }) + } + FfiAccountIdentityKind::PrivateShared => { + let vpk = if value.viewing_public_key_len == 33 { + let slice = unsafe { + slice::from_raw_parts( + value.viewing_public_key, + value.viewing_public_key_len, + ) + }; + Ok(Secp256k1Point(slice.to_vec())) + } else { + Err(WalletFfiError::InvalidKeyValue) + }?; + + Ok(Self::PrivateShared { + nsk: value.nullifier_secret_key.data, + npk: NullifierPublicKey(value.nullifier_public_key.data), + vpk, + identifier: value.identifier.into(), + }) + } + FfiAccountIdentityKind::PrivatePdaShared => { + let vpk = if value.viewing_public_key_len == 33 { + let slice = unsafe { + slice::from_raw_parts( + value.viewing_public_key, + value.viewing_public_key_len, + ) + }; + Ok(Secp256k1Point(slice.to_vec())) + } else { + Err(WalletFfiError::InvalidKeyValue) + }?; + + Ok(Self::PrivatePdaShared { + account_id: value.account_id.into(), + nsk: value.nullifier_secret_key.data, + npk: NullifierPublicKey(value.nullifier_public_key.data), + vpk, + identifier: value.identifier.into(), + }) + } + } + } +} + +#[cfg(test)] +mod tests { + use nssa::{AccountId, PrivateKey, PublicKey}; + use nssa_core::{encryption::ViewingPublicKey, program::PdaSeed, PrivateAccountKind}; + use wallet::AccountIdentity; + + use crate::{FfiAccountIdentity, FfiAccountIdentityKind}; + + #[test] + fn account_identity_roundtrip() { + let private_key = PrivateKey::try_new([42; 32]).unwrap(); + let public_key = PublicKey::new_from_private_key(&private_key); + let pub_acc_id = (&public_key).into(); + + let nsk = [43; 32]; + let vpk = ViewingPublicKey::from_scalar([44; 32]); + let npk = (&nsk).into(); + let identifier = u128::from_le_bytes([45; 16]); + + let private_reg_acc_id = + AccountId::for_private_account(&npk, &PrivateAccountKind::Regular(identifier)); + let private_pda_acc_id = AccountId::for_private_account( + &npk, + &PrivateAccountKind::Pda { + program_id: [46; 8], + seed: PdaSeed::new([47; 32]), + identifier, + }, + ); + + let acc_identity_1 = AccountIdentity::Public(pub_acc_id); + let acc_identity_2 = AccountIdentity::PublicNoSign(pub_acc_id); + let acc_identity_3 = AccountIdentity::PrivateOwned(private_reg_acc_id); + let acc_identity_4 = AccountIdentity::PrivateForeign { + npk, + vpk: vpk.clone(), + identifier, + }; + let acc_identity_5 = AccountIdentity::PrivatePdaOwned(private_pda_acc_id); + let acc_identity_6 = AccountIdentity::PrivatePdaForeign { + account_id: private_pda_acc_id, + npk, + vpk: vpk.clone(), + identifier, + }; + let acc_identity_7 = AccountIdentity::PrivateShared { + nsk, + npk, + vpk: vpk.clone(), + identifier, + }; + let acc_identity_8 = AccountIdentity::PrivatePdaShared { + account_id: private_pda_acc_id, + nsk, + npk, + vpk, + identifier, + }; + + let ffi_acc_identity_1: FfiAccountIdentity = acc_identity_1.clone().into(); + let ffi_acc_identity_2: FfiAccountIdentity = acc_identity_2.clone().into(); + let ffi_acc_identity_3: FfiAccountIdentity = acc_identity_3.clone().into(); + let ffi_acc_identity_4: FfiAccountIdentity = acc_identity_4.clone().into(); + let ffi_acc_identity_5: FfiAccountIdentity = acc_identity_5.clone().into(); + let ffi_acc_identity_6: FfiAccountIdentity = acc_identity_6.clone().into(); + let ffi_acc_identity_7: FfiAccountIdentity = acc_identity_7.clone().into(); + let ffi_acc_identity_8: FfiAccountIdentity = acc_identity_8.clone().into(); + + assert_eq!(ffi_acc_identity_1.kind, FfiAccountIdentityKind::Public); + assert_eq!( + ffi_acc_identity_2.kind, + FfiAccountIdentityKind::PublicNoSign + ); + assert_eq!( + ffi_acc_identity_3.kind, + FfiAccountIdentityKind::PrivateOwned + ); + assert_eq!( + ffi_acc_identity_4.kind, + FfiAccountIdentityKind::PrivateForeign + ); + assert_eq!( + ffi_acc_identity_5.kind, + FfiAccountIdentityKind::PrivatePdaOwned + ); + assert_eq!( + ffi_acc_identity_6.kind, + FfiAccountIdentityKind::PrivatePdaForeign + ); + assert_eq!( + ffi_acc_identity_7.kind, + FfiAccountIdentityKind::PrivateShared + ); + assert_eq!( + ffi_acc_identity_8.kind, + FfiAccountIdentityKind::PrivatePdaShared + ); + + let acc_identity_res_1: AccountIdentity = (&ffi_acc_identity_1).try_into().unwrap(); + let acc_identity_res_2: AccountIdentity = (&ffi_acc_identity_2).try_into().unwrap(); + let acc_identity_res_3: AccountIdentity = (&ffi_acc_identity_3).try_into().unwrap(); + let acc_identity_res_4: AccountIdentity = (&ffi_acc_identity_4).try_into().unwrap(); + let acc_identity_res_5: AccountIdentity = (&ffi_acc_identity_5).try_into().unwrap(); + let acc_identity_res_6: AccountIdentity = (&ffi_acc_identity_6).try_into().unwrap(); + let acc_identity_res_7: AccountIdentity = (&ffi_acc_identity_7).try_into().unwrap(); + let acc_identity_res_8: AccountIdentity = (&ffi_acc_identity_8).try_into().unwrap(); + + assert_eq!(acc_identity_res_1, acc_identity_1); + assert_eq!(acc_identity_res_2, acc_identity_2); + assert_eq!(acc_identity_res_3, acc_identity_3); + assert_eq!(acc_identity_res_4, acc_identity_4); + assert_eq!(acc_identity_res_5, acc_identity_5); + assert_eq!(acc_identity_res_6, acc_identity_6); + assert_eq!(acc_identity_res_7, acc_identity_7); + assert_eq!(acc_identity_res_8, acc_identity_8); + } +} diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index adbb7b50..3b31516a 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -103,12 +103,30 @@ typedef enum WalletFfiError { * Invalid Key value. */ INVALID_KEY_VALUE = 16, + /** + * Invalid program bytecode. + */ + INVALID_BYTECODE = 17, /** * Internal error (catch-all). */ INTERNAL_ERROR = 99, } WalletFfiError; +/** + * Enumeration to represent kinds of `FfiAccountIdentity`. + */ +typedef enum FfiAccountIdentityKind { + PUBLIC = 0, + PUBLIC_NO_SIGN = 1, + PRIVATE_OWNED = 2, + PRIVATE_FOREIGN = 3, + PRIVATE_PDA_OWNED = 4, + PRIVATE_PDA_FOREIGN = 5, + PRIVATE_SHARED = 6, + PRIVATE_PDA_SHARED = 7, +} FfiAccountIdentityKind; + /** * Opaque pointer to the Wallet instance. * @@ -200,6 +218,61 @@ typedef struct FfiAccount { struct FfiU128 nonce; } FfiAccount; +typedef struct SerializationHelperResult { + uint32_t *instruction_words; + uintptr_t instruction_words_size; + enum WalletFfiError error; +} SerializationHelperResult; + +/** + * Struct representing an account identity, given to `AccountManager` at intialization. + */ +typedef struct FfiAccountIdentity { + enum FfiAccountIdentityKind kind; + struct FfiBytes32 account_id; + struct FfiBytes32 nullifier_secret_key; + struct FfiBytes32 nullifier_public_key; + const uint8_t *viewing_public_key; + uintptr_t viewing_public_key_len; + struct FfiU128 identifier; +} FfiAccountIdentity; + +/** + * Intended to be created manually. + */ +typedef struct FfiProgram { + const uint8_t *elf_data; + uintptr_t elf_size; +} FfiProgram; + +/** + * Intended to be created manually. + */ +typedef struct FfiProgramWithDependencies { + struct FfiProgram program; + const struct FfiProgram *deps; + uintptr_t deps_size; +} FfiProgramWithDependencies; + +/** + * Result of a generic transaction operation. + */ +typedef struct FfiTransactionResult { + /** + * Transaction hash (null-terminated string, or null on failure). + */ + char *tx_hash; + /** + * Whether the transaction succeeded. + */ + bool success; + const struct FfiBytes32 *secrets_data; + /** + * Public transactions have 0 secrets. + */ + uintptr_t secrets_size; +} FfiTransactionResult; + /** * Public key info for a public account. */ @@ -454,6 +527,86 @@ enum WalletFfiError wallet_ffi_import_private_account(struct WalletHandle *handl const struct FfiU128 *identifier, const char *account_state_json); +/** + * Serialize sequence of bytes into RISC0 readable words. + * + * # Parameters + * - `input_instruction_data`: Valid pointer to a sequence of bytes + * - `input_instruction_data_size`: Size of `input_instruction_data` + * + * # Returns + * - `Success` on successful creation + * - Error code on failure + * + * # Safety + * - `input_instruction_data` must be a valid pointer + */ +struct SerializationHelperResult wallet_ffi_serialization_helper(const uint8_t *input_instruction_data, + uintptr_t input_instruction_data_size); + +/** + * Send generic public transaction. + * + * # Parameters + * - `handle`: Valid pointer to wallet handle + * - `account_identities`: Valid pointer to list of `FfiAccountIdentity` + * - `instruction_words`: Valid pointer to instruction words + * - `out_result`: Valid pointer to `FfiTransactionResult` + * + * # Returns + * - `Success` on successful creation + * - Error code on failure + * + * # Safety + * - `handle` must be a valid pointer + * - `account_identities` must be a valid pointer + * - `instruction_words` must be a valid pointer + * - `out_result` must be a valid pointer + */ +enum WalletFfiError wallet_ffi_send_generic_public_transaction(struct WalletHandle *handle, + const struct FfiAccountIdentity *account_identities, + uintptr_t account_identities_size, + const uint32_t *instruction_words, + uintptr_t instruction_words_size, + const struct FfiProgramWithDependencies *program_with_dependencies, + struct FfiTransactionResult *out_result); + +/** + * Send generic private transaction. + * + * # Parameters + * - `handle`: Valid pointer to wallet handle + * - `account_identities`: Valid pointer to list of `FfiAccountIdentity` + * - `instruction_words`: Valid pointer to instruction words + * - `out_result`: Valid pointer to `FfiTransactionResult` + * + * # Returns + * - `Success` on successful creation + * - Error code on failure + * + * # Safety + * - `handle` must be a valid pointer + * - `account_identities` must be a valid pointer + * - `instruction_words` must be a valid pointer + * - `out_result` must be a valid pointer + */ +enum WalletFfiError wallet_ffi_send_generic_private_transaction(struct WalletHandle *handle, + const struct FfiAccountIdentity *account_identities, + uintptr_t account_identities_size, + const uint32_t *instruction_words, + uintptr_t instruction_words_size, + const struct FfiProgramWithDependencies *program_with_dependencies, + struct FfiTransactionResult *out_result); + +/** + * Free a transaction result returned by `wallet_ffi_send_generic_public_transaction` or + * `wallet_ffi_send_generic_private_transaction`. + * + * # Safety + * The result must be either null or a valid result from a transaction function. + */ +void wallet_ffi_free_transaction_result(struct FfiTransactionResult *result); + /** * Get the public key for a public account. * @@ -552,6 +705,55 @@ char *wallet_ffi_account_id_to_base58(const struct FfiBytes32 *account_id); enum WalletFfiError wallet_ffi_account_id_from_base58(const char *base58_str, struct FfiBytes32 *out_account_id); +/** + * Resolve public account. + * + * # Parameters + * - `account_id`: 32 bytes of the public account ID + * - `needs_sign`: whether the account needs signing + * - `out_account_identity`: valid pointer, where output will be written + * + * # Returns + * - `Success` on successful retrieval + * + * # Safety + * - `out_account_identity` must be a valid pointer to a `FfiAccountIdentity` struct + */ +enum WalletFfiError wallet_ffi_resolve_public_account(struct FfiBytes32 account_id, + bool needs_sign, + struct FfiAccountIdentity *out_account_identity); + +/** + * Resolve private account. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `account_id`: 32 bytes of the public account ID + * - `out_account_identity`: valid pointer, where output will be written + * + * # Returns + * - `Success` on successful retrieval + * - `InternalError` if failed to lock wallet + * - `AccountNotFound` if the account is not found + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `out_account_identity` must be a valid pointer to a `FfiAccountIdentity` struct + */ +enum WalletFfiError wallet_ffi_resolve_private_account(struct WalletHandle *handle, + struct FfiBytes32 account_id, + struct FfiAccountIdentity *out_account_identity); + +/** + * Free account identity returned by `wallet_ffi_resolve_private_account` or + * `wallet_ffi_resolve_public_account`. + * + * # Safety + * The account must be either null or a valid account returned by + * `wallet_ffi_resolve_private_account` or `wallet_ffi_resolve_public_account`. + */ +void wallet_ffi_free_account_identity(struct FfiAccountIdentity *account_identity); + /** * Claim a pinata reward using a public transaction. * diff --git a/wallet/src/account_manager.rs b/wallet/src/account_manager.rs index 5caf2b22..3934a2a5 100644 --- a/wallet/src/account_manager.rs +++ b/wallet/src/account_manager.rs @@ -10,7 +10,7 @@ use nssa_core::{ use crate::{ExecutionFailureKind, WalletCore}; -#[derive(Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum AccountIdentity { Public(AccountId), /// A public account without signing. Would not try to sign, even if account is owned.