feat(wallet_ffi): generic private transactions added

This commit is contained in:
Pravdyvy 2026-05-25 14:31:44 +03:00
parent b80c89848c
commit ac0c4363b6
6 changed files with 289 additions and 92 deletions

View File

@ -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();
}

View File

@ -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)));
}
}
}

View File

@ -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<ProgramWithDependencies> for FfiProgramWithDependencies {
fn from(value: ProgramWithDependencies) -> Self {
let ffi_program = value.program.into();
let ffi_deps: Vec<FfiProgram> = value.dependencies.into_values().map(Into::into).collect::<Vec<_>>();
let ffi_deps: Vec<FfiProgram> = value
.dependencies
.into_values()
.map(Into::into)
.collect::<Vec<_>>();
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::<Vec<FfiBytes32>>().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)
}
}
}

View File

@ -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)));
}
}
}

View File

@ -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<SharedSecretKey> 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,

View File

@ -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.
*