diff --git a/integration_tests/tests/wallet_ffi.rs b/integration_tests/tests/wallet_ffi.rs index 51de9b3f..82ac6b52 100644 --- a/integration_tests/tests/wallet_ffi.rs +++ b/integration_tests/tests/wallet_ffi.rs @@ -21,12 +21,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, privacy_preserving_transaction::circuit::ProgramWithDependencies, 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, FfiAccountIdentity, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey, FfiTransferResult, FfiU128, WalletHandle, error, generic_transaction::FfiProgramWithDependencies + FfiAccount, FfiAccountIdentity, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys, + FfiPublicAccountKey, FfiTransferResult, FfiU128, WalletHandle, error, + generic_transaction::FfiProgramWithDependencies, }; unsafe extern "C" { @@ -1096,27 +1101,18 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { 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(from, true, &raw mut from_account_identity).unwrap(); } - unsafe{ - wallet_ffi_resolve_public_account( - to, - true, - &raw mut to_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 account_identities = + Box::into_raw(ffi_accs.into_boxed_slice()) as *const FfiAccountIdentity; let instruction_data = Program::serialize_instruction(authenticated_transfer_core::Instruction::Transfer { @@ -1129,15 +1125,15 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { let program: ProgramWithDependencies = Program::authenticated_transfer_program().into(); let program_with_dependencies = program.into(); - unsafe{ + unsafe { wallet_ffi_send_generic_public_transaction( - wallet_ffi_handle, - account_identities, - account_identities_size, - instruction_words, - instruction_words_size, - program_with_dependencies, - &raw mut transfer_result + wallet_ffi_handle, + account_identities, + account_identities_size, + instruction_words, + instruction_words_size, + program_with_dependencies, + &raw mut transfer_result, ) .unwrap(); } diff --git a/wallet-ffi/src/account.rs b/wallet-ffi/src/account.rs index b4fb1e84..ed27abe6 100644 --- a/wallet-ffi/src/account.rs +++ b/wallet-ffi/src/account.rs @@ -14,7 +14,7 @@ use crate::{ WalletHandle, }, wallet::get_wallet, - FfiAccountIdentity, FfiU128, + FfiU128, }; /// Create a new public account. @@ -653,29 +653,3 @@ pub unsafe extern "C" fn wallet_ffi_import_private_account( } } } - -/// 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 account_identity = &*account_identity; - if !account_identity.viewing_public_key.is_null() { - let slice = std::slice::from_raw_parts_mut( - account_identity.viewing_public_key.cast_mut(), - account_identity.viewing_public_key_len, - ); - drop(Box::from_raw(std::ptr::from_mut::<[u8]>(slice))); - } - } -} diff --git a/wallet-ffi/src/generic_transaction.rs b/wallet-ffi/src/generic_transaction.rs index ef4b77c2..193ccad9 100644 --- a/wallet-ffi/src/generic_transaction.rs +++ b/wallet-ffi/src/generic_transaction.rs @@ -1,13 +1,9 @@ -use std::{collections::HashMap, ffi::CString}; +use std::{collections::HashMap, ffi::{CString, c_char}}; use nssa::{privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program}; use crate::{ - block_on, - error::{print_error, WalletFfiError}, - map_execution_error, - wallet::get_wallet, - FfiAccountIdentity, FfiTransferResult, WalletHandle, + FfiAccountIdentity, FfiBytes32, WalletHandle, block_on, error::{WalletFfiError, print_error}, map_execution_error, wallet::get_wallet }; #[repr(C)] @@ -141,19 +137,45 @@ 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 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 } + Self { + program: ffi_program, + deps, + deps_size, + } } } +/// Result of a generic transaction operation. #[repr(C)] -pub enum FfiExecutionFlow { - Public = 0, - PrivacyPreserving = 1, +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 transaction 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, + } + } } /// Send generic public transaction @@ -162,7 +184,7 @@ pub enum FfiExecutionFlow { /// - `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 `FfiTransferResult` +/// - `out_result`: Valid pointer to `FfiTransactionResult` /// /// # Returns /// - `Success` on successful creation @@ -181,7 +203,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( instruction_words: *const u32, instruction_words_size: usize, program_with_dependencies: FfiProgramWithDependencies, - out_result: *mut FfiTransferResult, + out_result: *mut FfiTransactionResult, ) -> WalletFfiError { let wrapper = match get_wallet(handle) { Ok(w) => w, @@ -267,3 +289,121 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( } } } + +/// 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: 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 output pointer for account identities list"); + return WalletFfiError::NullPointer; + } + + if instruction_words.is_null() { + print_error("Null output 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 mut accounts = Vec::with_capacity(account_identities_size); + let mut instruction_data = Vec::with_capacity(instruction_words_size); + + // Alignment will be different, we need to read elements one-by-one + for i in 0..account_identities_size { + accounts.push( + match match unsafe { account_identities.add(i).as_ref() } + .ok_or(WalletFfiError::NullPointer) + { + Ok(v) => v, + Err(err) => { + print_error(format!( + "account_identities_size does not match actual size of account_identities" + )); + return err; + } + } + .try_into() + { + Ok(v) => v, + Err(err) => return err, + }, + ); + } + + // Alignment will be different, we need to read elements one-by-one + for i in 0..instruction_words_size { + instruction_data.push(unsafe { *instruction_words.add(i) }); + } + + let program = match program_with_dependencies.try_into() { + Ok(v) => v, + Err(err) => return err, + }; + + match block_on(wallet.send_privacy_preserving_tx(accounts, instruction_data, &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!("Public send failed: {e:?}")); + unsafe { + *out_result = FfiTransactionResult::default(); + } + map_execution_error(e) + } + } +} diff --git a/wallet-ffi/src/keys.rs b/wallet-ffi/src/keys.rs index 0bb38274..a0c90440 100644 --- a/wallet-ffi/src/keys.rs +++ b/wallet-ffi/src/keys.rs @@ -334,3 +334,38 @@ pub unsafe extern "C" fn wallet_ffi_resolve_private_account( 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/types.rs b/wallet-ffi/src/types.rs index da58e572..960cba47 100644 --- a/wallet-ffi/src/types.rs +++ b/wallet-ffi/src/types.rs @@ -3,7 +3,7 @@ use core::slice; use std::{ffi::c_char, ptr}; -use nssa::Data; +use nssa::{Data, SharedSecretKey}; use nssa_core::{encryption::shared_key_derivation::Secp256k1Point, NullifierPublicKey}; use wallet::AccountIdentity; @@ -155,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 { @@ -189,7 +195,7 @@ pub enum FfiAccountIdentityKind { /// Struct representing of account identity, given to `AccountManager` at intialization #[repr(C)] pub struct FfiAccountIdentity { - kind: FfiAccountIdentityKind, + pub kind: FfiAccountIdentityKind, pub account_id: FfiBytes32, pub nullifier_secret_key: FfiBytes32, pub nullifier_public_key: FfiBytes32, diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index 3c814c71..d35463fc 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -218,6 +218,12 @@ 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 of account identity, given to `AccountManager` at intialization */ @@ -231,12 +237,6 @@ typedef struct FfiAccountIdentity { struct FfiU128 identifier; } FfiAccountIdentity; -typedef struct SerializationHelperResult { - uint32_t *instruction_words; - uintptr_t instruction_words_size; - enum WalletFfiError error; -} SerializationHelperResult; - /** * Intended to be created manually */ @@ -254,6 +254,32 @@ typedef struct FfiProgramWithDependencies { 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 transaction have 0 secrets + */ + uintptr_t secrets_size; +} FfiTransactionResult; + +/** + * Public key info for a public account. + */ +typedef struct FfiPublicAccountKey { + struct FfiBytes32 public_key; +} FfiPublicAccountKey; + /** * Result of a transfer operation. */ @@ -268,13 +294,6 @@ typedef struct FfiTransferResult { bool success; } FfiTransferResult; -/** - * Public key info for a public account. - */ -typedef struct FfiPublicAccountKey { - struct FfiBytes32 public_key; -} FfiPublicAccountKey; - /** * Create a new public account. * @@ -508,16 +527,6 @@ enum WalletFfiError wallet_ffi_import_private_account(struct WalletHandle *handl const struct FfiU128 *identifier, const char *account_state_json); -/** - * 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); - /** * Serialize sequence of bytes into RISC0 readable words * @@ -542,7 +551,7 @@ struct SerializationHelperResult wallet_ffi_serialization_helper(const uint8_t * * - `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 `FfiTransferResult` + * - `out_result`: Valid pointer to `FfiTransactionResult` * * # Returns * - `Success` on successful creation @@ -560,7 +569,34 @@ enum WalletFfiError wallet_ffi_send_generic_public_transaction(struct WalletHand const uint32_t *instruction_words, uintptr_t instruction_words_size, struct FfiProgramWithDependencies program_with_dependencies, - struct FfiTransferResult *out_result); + 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, + struct FfiProgramWithDependencies program_with_dependencies, + struct FfiTransactionResult *out_result); /** * Get the public key for a public account. @@ -699,6 +735,16 @@ enum WalletFfiError wallet_ffi_resolve_private_account(struct WalletHandle *hand 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. *