From fb4c5ce46d7d9345f0b32c9ae3546cfe440827df Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 20 May 2026 18:33:16 +0300 Subject: [PATCH 01/10] feat(wallet-ffi): exposed account identity, added resolve functions. --- .../src/bin/run_hello_world_private.rs | 4 +- ...n_hello_world_through_tail_call_private.rs | 4 +- .../bin/run_hello_world_with_move_function.rs | 8 +- integration_tests/src/setup.rs | 6 +- integration_tests/tests/private_pda.rs | 10 +- wallet-ffi/src/keys.rs | 84 ++++++ wallet-ffi/src/types.rs | 275 +++++++++++++++++- wallet-ffi/wallet_ffi.h | 66 +++++ wallet/src/account_manager.rs | 24 +- wallet/src/lib.rs | 20 +- wallet/src/program_facades/amm.rs | 72 ++--- wallet/src/program_facades/ata.rs | 32 +- .../native_token_transfer/deshielded.rs | 4 +- .../native_token_transfer/private.rs | 4 +- .../native_token_transfer/public.rs | 8 +- .../native_token_transfer/shielded.rs | 8 +- wallet/src/program_facades/pinata.rs | 8 +- wallet/src/program_facades/token.rs | 46 +-- 18 files changed, 553 insertions(+), 130 deletions(-) diff --git a/examples/program_deployment/src/bin/run_hello_world_private.rs b/examples/program_deployment/src/bin/run_hello_world_private.rs index 4fa149ea..725019f1 100644 --- a/examples/program_deployment/src/bin/run_hello_world_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_private.rs @@ -1,5 +1,5 @@ use nssa::{AccountId, program::Program}; -use wallet::{AccountManagerAccountIdentity, WalletCore}; +use wallet::{AccountIdentity, WalletCore}; // Before running this example, compile the `hello_world.rs` guest program with: // @@ -44,7 +44,7 @@ async fn main() { // Define the desired greeting in ASCII let greeting: Vec = vec![72, 111, 108, 97, 32, 109, 117, 110, 100, 111, 33]; - let accounts = vec![AccountManagerAccountIdentity::PrivateOwned(account_id)]; + let accounts = vec![AccountIdentity::PrivateOwned(account_id)]; // Construct and submit the privacy-preserving transaction wallet_core diff --git a/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs index 60d5df7c..d68e99dc 100644 --- a/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs @@ -4,7 +4,7 @@ use nssa::{ AccountId, ProgramId, privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program, }; -use wallet::{AccountManagerAccountIdentity, WalletCore}; +use wallet::{AccountIdentity, WalletCore}; // Before running this example, compile the `simple_tail_call.rs` guest program with: // @@ -51,7 +51,7 @@ async fn main() { std::iter::once((hello_world.id(), hello_world)).collect(); let program_with_dependencies = ProgramWithDependencies::new(simple_tail_call, dependencies); - let accounts = vec![AccountManagerAccountIdentity::PrivateOwned(account_id)]; + let accounts = vec![AccountIdentity::PrivateOwned(account_id)]; // Construct and submit the privacy-preserving transaction let instruction = (); diff --git a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs index e2056c4c..e6f667a6 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs @@ -2,7 +2,7 @@ use clap::{Parser, Subcommand}; use common::transaction::NSSATransaction; use nssa::{PublicTransaction, program::Program, public_transaction}; use sequencer_service_rpc::RpcClient as _; -use wallet::{AccountManagerAccountIdentity, WalletCore}; +use wallet::{AccountIdentity, WalletCore}; // Before running this example, compile the `hello_world_with_move_function.rs` guest program with: // @@ -99,7 +99,7 @@ async fn main() { } => { let instruction: Instruction = (WRITE_FUNCTION_ID, greeting.into_bytes()); let account_id = account_id.parse().unwrap(); - let accounts = vec![AccountManagerAccountIdentity::PrivateOwned(account_id)]; + let accounts = vec![AccountIdentity::PrivateOwned(account_id)]; wallet_core .send_privacy_preserving_tx( @@ -138,8 +138,8 @@ async fn main() { let to = to.parse().unwrap(); let accounts = vec![ - AccountManagerAccountIdentity::Public(from), - AccountManagerAccountIdentity::PrivateOwned(to), + AccountIdentity::Public(from), + AccountIdentity::PrivateOwned(to), ]; wallet_core diff --git a/integration_tests/src/setup.rs b/integration_tests/src/setup.rs index c1165285..8ec9c466 100644 --- a/integration_tests/src/setup.rs +++ b/integration_tests/src/setup.rs @@ -10,7 +10,7 @@ use sequencer_service_rpc::RpcClient as _; use tempfile::TempDir; use testcontainers::compose::DockerCompose; use wallet::{ - AccDecodeData::Decode, AccountManagerAccountIdentity, WalletCore, config::WalletConfigOverrides, + AccDecodeData::Decode, AccountIdentity, WalletCore, config::WalletConfigOverrides, }; use crate::{ @@ -293,8 +293,8 @@ async fn claim_funds_from_vault_to_private( let (tx_hash, mut secrets) = wallet .send_privacy_preserving_tx( vec![ - AccountManagerAccountIdentity::PrivateOwned(owner_id), - AccountManagerAccountIdentity::Public(owner_vault_id), + AccountIdentity::PrivateOwned(owner_id), + AccountIdentity::Public(owner_vault_id), ], instruction_data, &program_with_dependencies, diff --git a/integration_tests/tests/private_pda.rs b/integration_tests/tests/private_pda.rs index 4cbc07d1..0f8d7b8d 100644 --- a/integration_tests/tests/private_pda.rs +++ b/integration_tests/tests/private_pda.rs @@ -18,7 +18,7 @@ use nssa::{ use nssa_core::{NullifierPublicKey, encryption::ViewingPublicKey, program::PdaSeed}; use tokio::test; use wallet::{ - AccountManagerAccountIdentity, WalletCore, + AccountIdentity, WalletCore, cli::{Command, account::AccountSubcommand}, }; @@ -46,8 +46,8 @@ async fn fund_private_pda( wallet .send_privacy_preserving_tx( vec![ - AccountManagerAccountIdentity::Public(sender), - AccountManagerAccountIdentity::PrivatePdaForeign { + AccountIdentity::Public(sender), + AccountIdentity::PrivatePdaForeign { account_id: pda_account_id, npk, vpk, @@ -83,8 +83,8 @@ async fn spend_private_pda( wallet .send_privacy_preserving_tx( vec![ - AccountManagerAccountIdentity::PrivatePdaOwned(pda_account_id), - AccountManagerAccountIdentity::PrivateForeign { + AccountIdentity::PrivatePdaOwned(pda_account_id), + AccountIdentity::PrivateForeign { npk: recipient_npk, vpk: recipient_vpk, identifier: 0, diff --git a/wallet-ffi/src/keys.rs b/wallet-ffi/src/keys.rs index b676ffab..29fe8184 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,85 @@ 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`: does 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 `FfiAccountManagerAccountIdentity` 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 { + 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 wailed to lock wallet +/// - `AccountNotFound` if failed to found account +/// +/// # 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 `FfiAccountManagerAccountIdentity` 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 { + 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 resolved_account = match wallet.resolve_private_account(account_id) { + Some(v) => v, + None => { + print_error(format!("Account not found")); + return WalletFfiError::AccountNotFound; + } + }; + + unsafe { + *out_account_identity = resolved_account.into(); + } + + WalletFfiError::Success +} diff --git a/wallet-ffi/src/types.rs b/wallet-ffi/src/types.rs index b970a8d3..06833f74 100644 --- a/wallet-ffi/src/types.rs +++ b/wallet-ffi/src/types.rs @@ -4,7 +4,8 @@ use core::slice; use std::{ffi::c_char, ptr}; use nssa::Data; -use nssa_core::encryption::shared_key_derivation::Secp256k1Point; +use nssa_core::{encryption::shared_key_derivation::Secp256k1Point, NullifierPublicKey}; +use wallet::AccountIdentity; use crate::error::WalletFfiError; @@ -172,6 +173,45 @@ impl FfiPrivateAccountKeys { } } +/// Enumeration to represent kinds of FfiAccountManagerAccountIdentity +#[repr(C)] +pub enum FfiAccountIdentityKind { + Public = 0, + PublicNoSign = 1, + PrivateOwned = 2, + PrivateForeign = 3, + PrivatePdaOwned = 4, + PrivatePdaForeign = 5, + PrivateShared = 6, + PrivatePdaShared = 7, +} + +/// Struct representing of account identity, given to `AccountManager` at intialization +#[repr(C)] +pub struct FfiAccountIdentity { + 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 +232,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 +312,230 @@ 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::PrivateShared, + 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( + AccountIdentity::Public(value.account_id.into()), + ), + FfiAccountIdentityKind::PublicNoSign => Ok( + AccountIdentity::PublicNoSign(value.account_id.into()), + ), + FfiAccountIdentityKind::PrivateOwned => Ok( + AccountIdentity::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(AccountIdentity::PrivateForeign { + npk: NullifierPublicKey(value.nullifier_public_key.data), + vpk, + identifier: value.identifier.into(), + }) + } + FfiAccountIdentityKind::PrivatePdaOwned => Ok( + AccountIdentity::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(AccountIdentity::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(AccountIdentity::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(AccountIdentity::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(), + }) + } + } + } +} diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index adbb7b50..211d6513 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -109,6 +109,20 @@ typedef enum WalletFfiError { INTERNAL_ERROR = 99, } WalletFfiError; +/** + * Enumeration to represent kinds of FfiAccountManagerAccountIdentity + */ +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. * @@ -207,6 +221,19 @@ typedef struct FfiPublicAccountKey { struct FfiBytes32 public_key; } FfiPublicAccountKey; +/** + * Struct representing of 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; + /** * Result of a transfer operation. */ @@ -552,6 +579,45 @@ 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`: does 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 `FfiAccountManagerAccountIdentity` 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 wailed to lock wallet + * - `AccountNotFound` if failed to found account + * + * # 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 `FfiAccountManagerAccountIdentity` struct + */ +enum WalletFfiError wallet_ffi_resolve_private_account(struct WalletHandle *handle, + struct FfiBytes32 account_id, + struct FfiAccountIdentity *out_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 5a209d27..a088bcd6 100644 --- a/wallet/src/account_manager.rs +++ b/wallet/src/account_manager.rs @@ -11,7 +11,7 @@ use nssa_core::{ use crate::{ExecutionFailureKind, WalletCore}; #[derive(Clone)] -pub enum AccountManagerAccountIdentity { +pub enum AccountIdentity { Public(AccountId), /// A public account without signing. Would not try to sign, even if account is owned. PublicNoSign(AccountId), @@ -52,7 +52,7 @@ pub enum AccountManagerAccountIdentity { }, } -impl AccountManagerAccountIdentity { +impl AccountIdentity { #[must_use] pub const fn is_public(&self) -> bool { matches!(&self, Self::Public(_) | Self::PublicNoSign(_)) @@ -94,13 +94,13 @@ pub struct AccountManager { impl AccountManager { pub async fn new( wallet: &WalletCore, - accounts: Vec, + accounts: Vec, ) -> Result { let mut states = Vec::with_capacity(accounts.len()); for account in accounts { let state = match account { - AccountManagerAccountIdentity::Public(account_id) => { + AccountIdentity::Public(account_id) => { let acc = wallet .get_account_public(account_id) .await @@ -111,7 +111,7 @@ impl AccountManager { State::Public { account, sk } } - AccountManagerAccountIdentity::PublicNoSign(account_id) => { + AccountIdentity::PublicNoSign(account_id) => { let acc = wallet .get_account_public(account_id) .await @@ -122,12 +122,12 @@ impl AccountManager { State::Public { account, sk } } - AccountManagerAccountIdentity::PrivateOwned(account_id) => { + AccountIdentity::PrivateOwned(account_id) => { let pre = private_key_tree_acc_preparation(wallet, account_id, false).await?; State::Private(pre) } - AccountManagerAccountIdentity::PrivateForeign { + AccountIdentity::PrivateForeign { npk, vpk, identifier, @@ -151,11 +151,11 @@ impl AccountManager { State::Private(pre) } - AccountManagerAccountIdentity::PrivatePdaOwned(account_id) => { + AccountIdentity::PrivatePdaOwned(account_id) => { let pre = private_key_tree_acc_preparation(wallet, account_id, true).await?; State::Private(pre) } - AccountManagerAccountIdentity::PrivatePdaForeign { + AccountIdentity::PrivatePdaForeign { account_id, npk, vpk, @@ -179,7 +179,7 @@ impl AccountManager { }; State::Private(pre) } - AccountManagerAccountIdentity::PrivateShared { + AccountIdentity::PrivateShared { nsk, npk, vpk, @@ -193,7 +193,7 @@ impl AccountManager { State::Private(pre) } - AccountManagerAccountIdentity::PrivatePdaShared { + AccountIdentity::PrivatePdaShared { account_id, nsk, npk, @@ -423,7 +423,7 @@ mod tests { #[test] fn private_shared_is_private() { - let acc = AccountManagerAccountIdentity::PrivateShared { + let acc = AccountIdentity::PrivateShared { nsk: [0; 32], npk: NullifierPublicKey([1; 32]), vpk: ViewingPublicKey::from_scalar([2; 32]), diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index e1fec4d1..50fb993f 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -9,7 +9,7 @@ use std::path::PathBuf; -pub use account_manager::AccountManagerAccountIdentity; +pub use account_manager::AccountIdentity; use anyhow::{Context as _, Result}; use bip39::Mnemonic; use common::{HashType, transaction::NSSATransaction}; @@ -276,7 +276,7 @@ impl WalletCore { pub fn resolve_private_account( &self, account_id: nssa::AccountId, - ) -> Option { + ) -> Option { // Check key tree first if self .storage @@ -284,7 +284,7 @@ impl WalletCore { .private_account(account_id) .is_some() { - return Some(AccountManagerAccountIdentity::PrivateOwned(account_id)); + return Some(AccountIdentity::PrivateOwned(account_id)); } // Check shared private accounts @@ -299,7 +299,7 @@ impl WalletCore { if let (Some(pda_seed), Some(program_id)) = (entry.pda_seed, entry.pda_program_id) { let keys = holder.derive_keys_for_pda(&program_id, &pda_seed); - Some(AccountManagerAccountIdentity::PrivatePdaShared { + Some(AccountIdentity::PrivatePdaShared { account_id, nsk: keys.nullifier_secret_key, npk: keys.generate_nullifier_public_key(), @@ -316,7 +316,7 @@ impl WalletCore { result }; let keys = holder.derive_keys_for_shared_account(&derivation_seed); - Some(AccountManagerAccountIdentity::PrivateShared { + Some(AccountIdentity::PrivateShared { nsk: keys.nullifier_secret_key, npk: keys.generate_nullifier_public_key(), vpk: keys.generate_viewing_public_key(), @@ -541,7 +541,7 @@ impl WalletCore { pub async fn send_privacy_preserving_tx( &self, - accounts: Vec, + accounts: Vec, instruction_data: InstructionData, program: &ProgramWithDependencies, ) -> Result<(HashType, Vec), ExecutionFailureKind> { @@ -553,7 +553,7 @@ impl WalletCore { pub async fn send_privacy_preserving_tx_with_pre_check( &self, - accounts: Vec, + accounts: Vec, instruction_data: InstructionData, program: &ProgramWithDependencies, tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, @@ -612,7 +612,7 @@ impl WalletCore { pub async fn send_pub_tx( &self, - accounts: Vec, + accounts: Vec, instruction_data: InstructionData, program: &ProgramWithDependencies, ) -> Result { @@ -622,7 +622,7 @@ impl WalletCore { pub async fn send_pub_tx_with_pre_check( &self, - accounts: Vec, + accounts: Vec, instruction_data: InstructionData, program: &ProgramWithDependencies, tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, @@ -630,7 +630,7 @@ impl WalletCore { // Public transaction, all accounts must be public if accounts .iter() - .any(AccountManagerAccountIdentity::is_private) + .any(AccountIdentity::is_private) { return Err(ExecutionFailureKind::TransactionBuildError( nssa::error::NssaError::InvalidInput( diff --git a/wallet/src/program_facades/amm.rs b/wallet/src/program_facades/amm.rs index 43352386..2dbab6c5 100644 --- a/wallet/src/program_facades/amm.rs +++ b/wallet/src/program_facades/amm.rs @@ -3,7 +3,7 @@ use common::HashType; use nssa::{AccountId, program::Program}; use token_core::TokenHolding; -use crate::{AccountManagerAccountIdentity, ExecutionFailureKind, WalletCore}; +use crate::{AccountIdentity, ExecutionFailureKind, WalletCore}; pub struct Amm<'wallet>(pub &'wallet WalletCore); impl Amm<'_> { @@ -51,13 +51,13 @@ impl Amm<'_> { self.0 .send_pub_tx( vec![ - AccountManagerAccountIdentity::PublicNoSign(amm_pool), - AccountManagerAccountIdentity::PublicNoSign(vault_holding_a), - AccountManagerAccountIdentity::PublicNoSign(vault_holding_b), - AccountManagerAccountIdentity::PublicNoSign(pool_lp), - AccountManagerAccountIdentity::Public(user_holding_a), - AccountManagerAccountIdentity::Public(user_holding_b), - AccountManagerAccountIdentity::Public(user_holding_lp), + AccountIdentity::PublicNoSign(amm_pool), + AccountIdentity::PublicNoSign(vault_holding_a), + AccountIdentity::PublicNoSign(vault_holding_b), + AccountIdentity::PublicNoSign(pool_lp), + AccountIdentity::Public(user_holding_a), + AccountIdentity::Public(user_holding_b), + AccountIdentity::Public(user_holding_lp), ], instruction_data, &program.into(), @@ -114,23 +114,23 @@ impl Amm<'_> { } let user_a_signing_indentity = if token_definition_id_in == definition_token_a_id { - AccountManagerAccountIdentity::Public(user_holding_a) + AccountIdentity::Public(user_holding_a) } else { - AccountManagerAccountIdentity::PublicNoSign(user_holding_a) + AccountIdentity::PublicNoSign(user_holding_a) }; let user_b_signing_indentity = if token_definition_id_in == definition_token_b_id { - AccountManagerAccountIdentity::Public(user_holding_b) + AccountIdentity::Public(user_holding_b) } else { - AccountManagerAccountIdentity::PublicNoSign(user_holding_b) + AccountIdentity::PublicNoSign(user_holding_b) }; self.0 .send_pub_tx( vec![ - AccountManagerAccountIdentity::PublicNoSign(amm_pool), - AccountManagerAccountIdentity::PublicNoSign(vault_holding_a), - AccountManagerAccountIdentity::PublicNoSign(vault_holding_b), + AccountIdentity::PublicNoSign(amm_pool), + AccountIdentity::PublicNoSign(vault_holding_a), + AccountIdentity::PublicNoSign(vault_holding_b), user_a_signing_indentity, user_b_signing_indentity, ], @@ -189,23 +189,23 @@ impl Amm<'_> { } let user_a_signing_indentity = if token_definition_id_in == definition_token_a_id { - AccountManagerAccountIdentity::Public(user_holding_a) + AccountIdentity::Public(user_holding_a) } else { - AccountManagerAccountIdentity::PublicNoSign(user_holding_a) + AccountIdentity::PublicNoSign(user_holding_a) }; let user_b_signing_indentity = if token_definition_id_in == definition_token_b_id { - AccountManagerAccountIdentity::Public(user_holding_b) + AccountIdentity::Public(user_holding_b) } else { - AccountManagerAccountIdentity::PublicNoSign(user_holding_b) + AccountIdentity::PublicNoSign(user_holding_b) }; self.0 .send_pub_tx( vec![ - AccountManagerAccountIdentity::Public(amm_pool), - AccountManagerAccountIdentity::Public(vault_holding_a), - AccountManagerAccountIdentity::Public(vault_holding_b), + AccountIdentity::Public(amm_pool), + AccountIdentity::Public(vault_holding_a), + AccountIdentity::Public(vault_holding_b), user_a_signing_indentity, user_b_signing_indentity, ], @@ -260,13 +260,13 @@ impl Amm<'_> { self.0 .send_pub_tx( vec![ - AccountManagerAccountIdentity::PublicNoSign(amm_pool), - AccountManagerAccountIdentity::PublicNoSign(vault_holding_a), - AccountManagerAccountIdentity::PublicNoSign(vault_holding_b), - AccountManagerAccountIdentity::PublicNoSign(pool_lp), - AccountManagerAccountIdentity::Public(user_holding_a), - AccountManagerAccountIdentity::Public(user_holding_b), - AccountManagerAccountIdentity::PublicNoSign(user_holding_lp), + AccountIdentity::PublicNoSign(amm_pool), + AccountIdentity::PublicNoSign(vault_holding_a), + AccountIdentity::PublicNoSign(vault_holding_b), + AccountIdentity::PublicNoSign(pool_lp), + AccountIdentity::Public(user_holding_a), + AccountIdentity::Public(user_holding_b), + AccountIdentity::PublicNoSign(user_holding_lp), ], instruction_data, &program.into(), @@ -319,13 +319,13 @@ impl Amm<'_> { self.0 .send_pub_tx( vec![ - AccountManagerAccountIdentity::PublicNoSign(amm_pool), - AccountManagerAccountIdentity::PublicNoSign(vault_holding_a), - AccountManagerAccountIdentity::PublicNoSign(vault_holding_b), - AccountManagerAccountIdentity::PublicNoSign(pool_lp), - AccountManagerAccountIdentity::PublicNoSign(user_holding_a), - AccountManagerAccountIdentity::PublicNoSign(user_holding_b), - AccountManagerAccountIdentity::Public(user_holding_lp), + AccountIdentity::PublicNoSign(amm_pool), + AccountIdentity::PublicNoSign(vault_holding_a), + AccountIdentity::PublicNoSign(vault_holding_b), + AccountIdentity::PublicNoSign(pool_lp), + AccountIdentity::PublicNoSign(user_holding_a), + AccountIdentity::PublicNoSign(user_holding_b), + AccountIdentity::Public(user_holding_lp), ], instruction_data, &program.into(), diff --git a/wallet/src/program_facades/ata.rs b/wallet/src/program_facades/ata.rs index e7131b39..68d45bbe 100644 --- a/wallet/src/program_facades/ata.rs +++ b/wallet/src/program_facades/ata.rs @@ -7,7 +7,7 @@ use nssa::{ }; use nssa_core::SharedSecretKey; -use crate::{AccountManagerAccountIdentity, ExecutionFailureKind, WalletCore}; +use crate::{AccountIdentity, ExecutionFailureKind, WalletCore}; pub struct Ata<'wallet>(pub &'wallet WalletCore); @@ -30,9 +30,9 @@ impl Ata<'_> { self.0 .send_pub_tx( vec![ - AccountManagerAccountIdentity::Public(owner_id), - AccountManagerAccountIdentity::PublicNoSign(definition_id), - AccountManagerAccountIdentity::PublicNoSign(ata_id), + AccountIdentity::Public(owner_id), + AccountIdentity::PublicNoSign(definition_id), + AccountIdentity::PublicNoSign(ata_id), ], instruction_data, &program.into(), @@ -63,9 +63,9 @@ impl Ata<'_> { self.0 .send_pub_tx( vec![ - AccountManagerAccountIdentity::Public(owner_id), - AccountManagerAccountIdentity::PublicNoSign(sender_ata_id), - AccountManagerAccountIdentity::PublicNoSign(recipient_id), + AccountIdentity::Public(owner_id), + AccountIdentity::PublicNoSign(sender_ata_id), + AccountIdentity::PublicNoSign(recipient_id), ], instruction_data, &program.into(), @@ -95,9 +95,9 @@ impl Ata<'_> { self.0 .send_pub_tx( vec![ - AccountManagerAccountIdentity::Public(owner_id), - AccountManagerAccountIdentity::PublicNoSign(holder_ata_id), - AccountManagerAccountIdentity::PublicNoSign(definition_id), + AccountIdentity::Public(owner_id), + AccountIdentity::PublicNoSign(holder_ata_id), + AccountIdentity::PublicNoSign(definition_id), ], instruction_data, &program.into(), @@ -124,8 +124,8 @@ impl Ata<'_> { self.0 .resolve_private_account(owner_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - AccountManagerAccountIdentity::Public(definition_id), - AccountManagerAccountIdentity::Public(ata_id), + AccountIdentity::Public(definition_id), + AccountIdentity::Public(ata_id), ]; self.0 @@ -161,8 +161,8 @@ impl Ata<'_> { self.0 .resolve_private_account(owner_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - AccountManagerAccountIdentity::Public(sender_ata_id), - AccountManagerAccountIdentity::Public(recipient_id), + AccountIdentity::Public(sender_ata_id), + AccountIdentity::Public(recipient_id), ]; self.0 @@ -197,8 +197,8 @@ impl Ata<'_> { self.0 .resolve_private_account(owner_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - AccountManagerAccountIdentity::Public(holder_ata_id), - AccountManagerAccountIdentity::Public(definition_id), + AccountIdentity::Public(holder_ata_id), + AccountIdentity::Public(definition_id), ]; self.0 diff --git a/wallet/src/program_facades/native_token_transfer/deshielded.rs b/wallet/src/program_facades/native_token_transfer/deshielded.rs index 3cfaea1b..31374f99 100644 --- a/wallet/src/program_facades/native_token_transfer/deshielded.rs +++ b/wallet/src/program_facades/native_token_transfer/deshielded.rs @@ -2,7 +2,7 @@ use common::HashType; use nssa::AccountId; use super::{NativeTokenTransfer, auth_transfer_preparation}; -use crate::{AccountManagerAccountIdentity, ExecutionFailureKind}; +use crate::{AccountIdentity, ExecutionFailureKind}; impl NativeTokenTransfer<'_> { pub async fn send_deshielded_transfer( @@ -19,7 +19,7 @@ impl NativeTokenTransfer<'_> { self.0 .resolve_private_account(from) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - AccountManagerAccountIdentity::Public(to), + AccountIdentity::Public(to), ], instruction_data, &program.into(), diff --git a/wallet/src/program_facades/native_token_transfer/private.rs b/wallet/src/program_facades/native_token_transfer/private.rs index 508bb62f..481e4a5f 100644 --- a/wallet/src/program_facades/native_token_transfer/private.rs +++ b/wallet/src/program_facades/native_token_transfer/private.rs @@ -5,7 +5,7 @@ use nssa::{AccountId, program::Program}; use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey}; use super::{NativeTokenTransfer, auth_transfer_preparation}; -use crate::{AccountManagerAccountIdentity, ExecutionFailureKind}; +use crate::{AccountIdentity, ExecutionFailureKind}; impl NativeTokenTransfer<'_> { pub async fn register_account_private( @@ -49,7 +49,7 @@ impl NativeTokenTransfer<'_> { self.0 .resolve_private_account(from) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - AccountManagerAccountIdentity::PrivateForeign { + AccountIdentity::PrivateForeign { npk: to_npk, vpk: to_vpk, identifier: to_identifier, diff --git a/wallet/src/program_facades/native_token_transfer/public.rs b/wallet/src/program_facades/native_token_transfer/public.rs index 6054383c..03fccc9d 100644 --- a/wallet/src/program_facades/native_token_transfer/public.rs +++ b/wallet/src/program_facades/native_token_transfer/public.rs @@ -4,7 +4,7 @@ use nssa::{AccountId, program::Program}; use super::NativeTokenTransfer; use crate::{ - AccountManagerAccountIdentity, ExecutionFailureKind, + AccountIdentity, ExecutionFailureKind, program_facades::native_token_transfer::auth_transfer_preparation, }; @@ -20,8 +20,8 @@ impl NativeTokenTransfer<'_> { self.0 .send_pub_tx_with_pre_check( vec![ - AccountManagerAccountIdentity::Public(from), - AccountManagerAccountIdentity::Public(to), + AccountIdentity::Public(from), + AccountIdentity::Public(to), ], instruction_data, &program.into(), @@ -39,7 +39,7 @@ impl NativeTokenTransfer<'_> { self.0 .send_pub_tx( - vec![AccountManagerAccountIdentity::Public(from)], + vec![AccountIdentity::Public(from)], instruction_data, &program.into(), ) diff --git a/wallet/src/program_facades/native_token_transfer/shielded.rs b/wallet/src/program_facades/native_token_transfer/shielded.rs index 385187ce..44916529 100644 --- a/wallet/src/program_facades/native_token_transfer/shielded.rs +++ b/wallet/src/program_facades/native_token_transfer/shielded.rs @@ -3,7 +3,7 @@ use nssa::AccountId; use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey}; use super::{NativeTokenTransfer, auth_transfer_preparation}; -use crate::{AccountManagerAccountIdentity, ExecutionFailureKind}; +use crate::{AccountIdentity, ExecutionFailureKind}; impl NativeTokenTransfer<'_> { pub async fn send_shielded_transfer( @@ -17,7 +17,7 @@ impl NativeTokenTransfer<'_> { self.0 .send_privacy_preserving_tx_with_pre_check( vec![ - AccountManagerAccountIdentity::Public(from), + AccountIdentity::Public(from), self.0 .resolve_private_account(to) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, @@ -49,8 +49,8 @@ impl NativeTokenTransfer<'_> { self.0 .send_privacy_preserving_tx_with_pre_check( vec![ - AccountManagerAccountIdentity::Public(from), - AccountManagerAccountIdentity::PrivateForeign { + AccountIdentity::Public(from), + AccountIdentity::PrivateForeign { npk: to_npk, vpk: to_vpk, identifier: to_identifier, diff --git a/wallet/src/program_facades/pinata.rs b/wallet/src/program_facades/pinata.rs index 0c3a599b..2e40e78b 100644 --- a/wallet/src/program_facades/pinata.rs +++ b/wallet/src/program_facades/pinata.rs @@ -2,7 +2,7 @@ use common::HashType; use nssa::{AccountId, program::Program}; use nssa_core::{MembershipProof, SharedSecretKey}; -use crate::{AccountManagerAccountIdentity, ExecutionFailureKind, WalletCore}; +use crate::{AccountIdentity, ExecutionFailureKind, WalletCore}; pub struct Pinata<'wallet>(pub &'wallet WalletCore); @@ -21,8 +21,8 @@ impl Pinata<'_> { self.0 .send_pub_tx( vec![ - AccountManagerAccountIdentity::PublicNoSign(pinata_account_id), - AccountManagerAccountIdentity::PublicNoSign(winner_account_id), + AccountIdentity::PublicNoSign(pinata_account_id), + AccountIdentity::PublicNoSign(winner_account_id), ], instruction_data, &program.into(), @@ -55,7 +55,7 @@ impl Pinata<'_> { self.0 .send_privacy_preserving_tx( vec![ - AccountManagerAccountIdentity::Public(pinata_account_id), + AccountIdentity::Public(pinata_account_id), self.0 .resolve_private_account(winner_account_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, diff --git a/wallet/src/program_facades/token.rs b/wallet/src/program_facades/token.rs index 2de2b796..8e9b4f8f 100644 --- a/wallet/src/program_facades/token.rs +++ b/wallet/src/program_facades/token.rs @@ -3,7 +3,7 @@ use nssa::{AccountId, program::Program}; use nssa_core::{Identifier, NullifierPublicKey, SharedSecretKey, encryption::ViewingPublicKey}; use token_core::Instruction; -use crate::{AccountManagerAccountIdentity, ExecutionFailureKind, WalletCore}; +use crate::{AccountIdentity, ExecutionFailureKind, WalletCore}; pub struct Token<'wallet>(pub &'wallet WalletCore); @@ -23,8 +23,8 @@ impl Token<'_> { self.0 .send_pub_tx( vec![ - AccountManagerAccountIdentity::Public(definition_account_id), - AccountManagerAccountIdentity::Public(supply_account_id), + AccountIdentity::Public(definition_account_id), + AccountIdentity::Public(supply_account_id), ], instruction_data, &program.into(), @@ -46,7 +46,7 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - AccountManagerAccountIdentity::Public(definition_account_id), + AccountIdentity::Public(definition_account_id), self.0 .resolve_private_account(supply_account_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, @@ -81,7 +81,7 @@ impl Token<'_> { self.0 .resolve_private_account(definition_account_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - AccountManagerAccountIdentity::Public(supply_account_id), + AccountIdentity::Public(supply_account_id), ], instruction_data, &Program::token().into(), @@ -145,8 +145,8 @@ impl Token<'_> { self.0 .send_pub_tx( vec![ - AccountManagerAccountIdentity::Public(sender_account_id), - AccountManagerAccountIdentity::Public(recipient_account_id), + AccountIdentity::Public(sender_account_id), + AccountIdentity::Public(recipient_account_id), ], instruction_data, &program.into(), @@ -208,7 +208,7 @@ impl Token<'_> { self.0 .resolve_private_account(sender_account_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - AccountManagerAccountIdentity::PrivateForeign { + AccountIdentity::PrivateForeign { npk: recipient_npk, vpk: recipient_vpk, identifier: recipient_identifier, @@ -244,7 +244,7 @@ impl Token<'_> { self.0 .resolve_private_account(sender_account_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - AccountManagerAccountIdentity::Public(recipient_account_id), + AccountIdentity::Public(recipient_account_id), ], instruction_data, &Program::token().into(), @@ -274,7 +274,7 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - AccountManagerAccountIdentity::Public(sender_account_id), + AccountIdentity::Public(sender_account_id), self.0 .resolve_private_account(recipient_account_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, @@ -309,8 +309,8 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - AccountManagerAccountIdentity::Public(sender_account_id), - AccountManagerAccountIdentity::PrivateForeign { + AccountIdentity::Public(sender_account_id), + AccountIdentity::PrivateForeign { npk: recipient_npk, vpk: recipient_vpk, identifier: recipient_identifier, @@ -345,8 +345,8 @@ impl Token<'_> { self.0 .send_pub_tx( vec![ - AccountManagerAccountIdentity::PublicNoSign(definition_account_id), - AccountManagerAccountIdentity::Public(holder_account_id), + AccountIdentity::PublicNoSign(definition_account_id), + AccountIdentity::Public(holder_account_id), ], instruction_data, &program.into(), @@ -406,7 +406,7 @@ impl Token<'_> { self.0 .resolve_private_account(definition_account_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - AccountManagerAccountIdentity::Public(holder_account_id), + AccountIdentity::Public(holder_account_id), ], instruction_data, &Program::token().into(), @@ -436,7 +436,7 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - AccountManagerAccountIdentity::Public(definition_account_id), + AccountIdentity::Public(definition_account_id), self.0 .resolve_private_account(holder_account_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, @@ -470,8 +470,8 @@ impl Token<'_> { self.0 .send_pub_tx( vec![ - AccountManagerAccountIdentity::Public(definition_account_id), - AccountManagerAccountIdentity::Public(holder_account_id), + AccountIdentity::Public(definition_account_id), + AccountIdentity::Public(holder_account_id), ], instruction_data, &program.into(), @@ -533,7 +533,7 @@ impl Token<'_> { self.0 .resolve_private_account(definition_account_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - AccountManagerAccountIdentity::PrivateForeign { + AccountIdentity::PrivateForeign { npk: holder_npk, vpk: holder_vpk, identifier: holder_identifier, @@ -569,7 +569,7 @@ impl Token<'_> { self.0 .resolve_private_account(definition_account_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - AccountManagerAccountIdentity::Public(holder_account_id), + AccountIdentity::Public(holder_account_id), ], instruction_data, &Program::token().into(), @@ -599,7 +599,7 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - AccountManagerAccountIdentity::Public(definition_account_id), + AccountIdentity::Public(definition_account_id), self.0 .resolve_private_account(holder_account_id) .ok_or(ExecutionFailureKind::KeyNotFoundError)?, @@ -634,8 +634,8 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - AccountManagerAccountIdentity::Public(definition_account_id), - AccountManagerAccountIdentity::PrivateForeign { + AccountIdentity::Public(definition_account_id), + AccountIdentity::PrivateForeign { npk: holder_npk, vpk: holder_vpk, identifier: holder_identifier, From bc6ba30f6620b8eb938142202983cbfe7385bf86 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Thu, 21 May 2026 17:04:48 +0300 Subject: [PATCH 02/10] feat: generic public transactions --- Cargo.lock | 1 + wallet-ffi/Cargo.toml | 1 + wallet-ffi/src/account.rs | 28 ++- wallet-ffi/src/error.rs | 2 + wallet-ffi/src/generic_transaction.rs | 247 ++++++++++++++++++++++++++ wallet-ffi/src/lib.rs | 1 + wallet-ffi/wallet_ffi.h | 95 +++++++++- 7 files changed, 367 insertions(+), 8 deletions(-) create mode 100644 wallet-ffi/src/generic_transaction.rs diff --git a/Cargo.lock b/Cargo.lock index 52bb1ce8..91bbd3b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10240,6 +10240,7 @@ dependencies = [ "key_protocol", "nssa", "nssa_core", + "risc0-zkvm", "sequencer_service_rpc", "serde_json", "tempfile", 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/account.rs b/wallet-ffi/src/account.rs index ed27abe6..b4fb1e84 100644 --- a/wallet-ffi/src/account.rs +++ b/wallet-ffi/src/account.rs @@ -14,7 +14,7 @@ use crate::{ WalletHandle, }, wallet::get_wallet, - FfiU128, + FfiAccountIdentity, FfiU128, }; /// Create a new public account. @@ -653,3 +653,29 @@ 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/error.rs b/wallet-ffi/src/error.rs index 17b73075..35b084a9 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..1195c131 --- /dev/null +++ b/wallet-ffi/src/generic_transaction.rs @@ -0,0 +1,247 @@ +use std::{collections::HashMap, ffi::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, FfiTransferResult, WalletHandle, +}; + +#[repr(C)] +pub struct SerializationHelperResult { + pub instruction_words: *mut u32, + pub instruction_words_size: usize, + pub error: WalletFfiError, +} + +impl SerializationHelperResult { + fn from_err(error: WalletFfiError) -> Self { + Self { + instruction_words: std::ptr::null_mut(), + instruction_words_size: 0, + error, + } + } +} + +/// 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 = 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), + }; + 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, + } +} + +#[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 + }) + } +} + +#[repr(C)] +/// Intended to be created manually +pub struct FfiProgramWithDependencies { + pub program: FfiProgram, + pub deps: *const FfiProgram, + pub deps_size: usize, +} + +impl TryFrom 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, + }) + } +} + +#[repr(C)] +pub enum FfiExecutionFlow { + Public = 0, + PrivacyPreserving = 1, +} + +/// Send generic 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 `FfiTransferResult` +/// +/// # 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_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 FfiTransferResult, +) -> 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_pub_tx(accounts, instruction_data, &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) + } + } +} diff --git a/wallet-ffi/src/lib.rs b/wallet-ffi/src/lib.rs index 16943d3e..e28a0560 100644 --- a/wallet-ffi/src/lib.rs +++ b/wallet-ffi/src/lib.rs @@ -42,6 +42,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/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index 211d6513..86b50de9 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -103,6 +103,10 @@ typedef enum WalletFfiError { * Invalid Key value. */ INVALID_KEY_VALUE = 16, + /** + * Invalid program bytecode + */ + INVALID_BYTECODE = 17, /** * Internal error (catch-all). */ @@ -214,13 +218,6 @@ typedef struct FfiAccount { struct FfiU128 nonce; } FfiAccount; -/** - * Public key info for a public account. - */ -typedef struct FfiPublicAccountKey { - struct FfiBytes32 public_key; -} FfiPublicAccountKey; - /** * Struct representing of account identity, given to `AccountManager` at intialization */ @@ -234,6 +231,29 @@ 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 + */ +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 transfer operation. */ @@ -248,6 +268,13 @@ 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. * @@ -481,6 +508,60 @@ 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 + * + * # 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 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 `FfiTransferResult` + * + * # 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_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 FfiTransferResult *out_result); + /** * Get the public key for a public account. * From b80c89848cce59486e62799a87007893789f45d7 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Fri, 22 May 2026 16:50:55 +0300 Subject: [PATCH 03/10] feat(integration_tests): generic call of a transfer test added --- integration_tests/tests/wallet_ffi.rs | 114 +++++++++++++++++++++++++- nssa/src/program.rs | 4 +- wallet-ffi/src/generic_transaction.rs | 26 +++++- wallet-ffi/wallet_ffi.h | 16 ++-- 4 files changed, 145 insertions(+), 15 deletions(-) diff --git a/integration_tests/tests/wallet_ffi.rs b/integration_tests/tests/wallet_ffi.rs index 2677e10e..51de9b3f 100644 --- a/integration_tests/tests/wallet_ffi.rs +++ b/integration_tests/tests/wallet_ffi.rs @@ -21,13 +21,12 @@ 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 }; unsafe extern "C" { @@ -179,6 +178,22 @@ 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: FfiProgramWithDependencies, + out_result: *mut FfiTransferResult, + ) -> error::WalletFfiError; } fn new_wallet_ffi_with_test_context_config( @@ -1066,3 +1081,96 @@ 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 transfer_result = FfiTransferResult::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 = program.into(); + + 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 + ) + .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 { + wallet_ffi_free_transfer_result(&raw mut transfer_result); + wallet_ffi_destroy(wallet_ffi_handle); + } + + Ok(()) +} diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 1aff3bc9..ada2428f 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -21,8 +21,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/src/generic_transaction.rs b/wallet-ffi/src/generic_transaction.rs index 1195c131..ef4b77c2 100644 --- a/wallet-ffi/src/generic_transaction.rs +++ b/wallet-ffi/src/generic_transaction.rs @@ -96,6 +96,15 @@ impl TryFrom<&FfiProgram> for Program { } } +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 { @@ -128,13 +137,26 @@ impl TryFrom for ProgramWithDependencies { } } +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 } + } +} + #[repr(C)] pub enum FfiExecutionFlow { Public = 0, PrivacyPreserving = 1, } -/// Send generic transaction +/// Send generic public transaction /// /// # Parameters /// - `handle`: Valid pointer to wallet handle @@ -152,7 +174,7 @@ pub enum FfiExecutionFlow { /// - `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_transaction( +pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( handle: *mut WalletHandle, account_identities: *const FfiAccountIdentity, account_identities_size: usize, diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index 86b50de9..3c814c71 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -536,7 +536,7 @@ struct SerializationHelperResult wallet_ffi_serialization_helper(const uint8_t * uintptr_t input_instruction_data_size); /** - * Send generic transaction + * Send generic public transaction * * # Parameters * - `handle`: Valid pointer to wallet handle @@ -554,13 +554,13 @@ struct SerializationHelperResult wallet_ffi_serialization_helper(const uint8_t * * - `instruction_words` must be a valid pointer * - `out_result` must be a valid pointer */ -enum WalletFfiError wallet_ffi_send_generic_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 FfiTransferResult *out_result); +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, + struct FfiProgramWithDependencies program_with_dependencies, + struct FfiTransferResult *out_result); /** * Get the public key for a public account. From ac0c4363b6ccdd197fe3db73b049634707296f61 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Mon, 25 May 2026 14:31:44 +0300 Subject: [PATCH 04/10] feat(wallet_ffi): generic private transactions added --- integration_tests/tests/wallet_ffi.rs | 46 ++++--- wallet-ffi/src/account.rs | 28 +---- wallet-ffi/src/generic_transaction.rs | 166 ++++++++++++++++++++++++-- wallet-ffi/src/keys.rs | 35 ++++++ wallet-ffi/src/types.rs | 10 +- wallet-ffi/wallet_ffi.h | 96 +++++++++++---- 6 files changed, 289 insertions(+), 92 deletions(-) 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. * From 716c4eeded314144ffe70caede4e45eed6531c5f Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Fri, 29 May 2026 13:08:13 +0300 Subject: [PATCH 05/10] fix(integration_tests): integration test for private execution added --- .deny.toml | 1 - integration_tests/tests/wallet_ffi.rs | 151 +++++++++++++++++++++-- wallet-ffi/src/error.rs | 2 +- wallet-ffi/src/generic_transaction.rs | 170 ++++++++++++++++---------- wallet-ffi/src/keys.rs | 9 +- wallet-ffi/src/lib.rs | 1 + wallet-ffi/src/types.rs | 24 ++-- wallet-ffi/wallet_ffi.h | 31 +++-- 8 files changed, 283 insertions(+), 106 deletions(-) diff --git a/.deny.toml b/.deny.toml index fb1ce3cf..0a9a3df1 100644 --- a/.deny.toml +++ b/.deny.toml @@ -13,7 +13,6 @@ ignore = [ { id = "RUSTSEC-2025-0055", reason = "`tracing-subscriber` v0.2.25 pulled in by ark-relations v0.4.0 - will be addressed before mainnet" }, { id = "RUSTSEC-2025-0141", reason = "`bincode` is unmaintained but continuing to use it." }, { id = "RUSTSEC-2023-0089", reason = "atomic-polyfill is pulled transitively via risc0-zkvm; waiting on upstream fix (see https://github.com/risc0/risc0/issues/3453)" }, - { id = "RUSTSEC-2026-0097", reason = "`rand` v0.8.5 is present transitively from logos crates, modification may break integration" }, { id = "RUSTSEC-2026-0118", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" }, { id = "RUSTSEC-2026-0119", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" }, ] diff --git a/integration_tests/tests/wallet_ffi.rs b/integration_tests/tests/wallet_ffi.rs index 82ac6b52..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" )] @@ -31,7 +32,7 @@ use wallet::account::HumanReadableAccount; use wallet_ffi::{ FfiAccount, FfiAccountIdentity, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey, FfiTransferResult, FfiU128, WalletHandle, error, - generic_transaction::FfiProgramWithDependencies, + generic_transaction::{FfiProgramWithDependencies, FfiTransactionResult}, }; unsafe extern "C" { @@ -196,9 +197,29 @@ unsafe extern "C" { account_identities_size: usize, instruction_words: *const u32, instruction_words_size: usize, - program_with_dependencies: FfiProgramWithDependencies, - out_result: *mut FfiTransferResult, + 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( @@ -1096,7 +1117,7 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { let to: FfiBytes32 = ctx.ctx().existing_public_accounts()[1].into(); let amount = 100_u128; - let mut transfer_result = FfiTransferResult::default(); + let mut transaction_result = FfiTransactionResult::default(); let mut from_account_identity = FfiAccountIdentity::default(); let mut to_account_identity = FfiAccountIdentity::default(); @@ -1123,7 +1144,7 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { 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 = program.into(); + let program_with_dependencies: FfiProgramWithDependencies = program.into(); unsafe { wallet_ffi_send_generic_public_transaction( @@ -1132,8 +1153,8 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { account_identities_size, instruction_words, instruction_words_size, - program_with_dependencies, - &raw mut transfer_result, + &raw const program_with_dependencies, + &raw mut transaction_result, ) .unwrap(); } @@ -1164,7 +1185,121 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { assert_eq!(to_balance, 20100); unsafe { - wallet_ffi_free_transfer_result(&raw mut transfer_result); + 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); } diff --git a/wallet-ffi/src/error.rs b/wallet-ffi/src/error.rs index 35b084a9..e95b47a0 100644 --- a/wallet-ffi/src/error.rs +++ b/wallet-ffi/src/error.rs @@ -41,7 +41,7 @@ pub enum WalletFfiError { InvalidTypeConversion = 15, /// Invalid Key value. InvalidKeyValue = 16, - /// Invalid program bytecode + /// 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 index 193ccad9..1351a7be 100644 --- a/wallet-ffi/src/generic_transaction.rs +++ b/wallet-ffi/src/generic_transaction.rs @@ -1,9 +1,16 @@ -use std::{collections::HashMap, ffi::{CString, c_char}}; +use std::{ + collections::HashMap, + ffi::{c_char, CString}, +}; use nssa::{privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program}; use crate::{ - FfiAccountIdentity, FfiBytes32, WalletHandle, block_on, error::{WalletFfiError, print_error}, map_execution_error, wallet::get_wallet + block_on, + error::{print_error, WalletFfiError}, + map_execution_error, + wallet::get_wallet, + FfiAccountIdentity, FfiBytes32, WalletHandle, }; #[repr(C)] @@ -14,7 +21,7 @@ pub struct SerializationHelperResult { } impl SerializationHelperResult { - fn from_err(error: WalletFfiError) -> Self { + const fn from_err(error: WalletFfiError) -> Self { Self { instruction_words: std::ptr::null_mut(), instruction_words_size: 0, @@ -23,52 +30,8 @@ impl SerializationHelperResult { } } -/// 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 = 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), - }; - 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, - } -} - #[repr(C)] -/// Intended to be created manually +/// Intended to be created manually. pub struct FfiProgram { pub elf_data: *const u8, pub elf_size: usize, @@ -102,17 +65,17 @@ impl From for FfiProgram { } #[repr(C)] -/// Intended to be created manually +/// Intended to be created manually. pub struct FfiProgramWithDependencies { pub program: FfiProgram, pub deps: *const FfiProgram, pub deps_size: usize, } -impl TryFrom for ProgramWithDependencies { +impl TryFrom<&FfiProgramWithDependencies> for ProgramWithDependencies { type Error = WalletFfiError; - fn try_from(value: FfiProgramWithDependencies) -> Result { + fn try_from(value: &FfiProgramWithDependencies) -> Result { let mut program_map = HashMap::new(); let orig_program = (&value.program).try_into()?; @@ -163,7 +126,7 @@ pub struct FfiTransactionResult { /// Whether the transaction succeeded. pub success: bool, pub secrets_data: *const FfiBytes32, - /// Public transaction have 0 secrets + /// Public transaction have 0 secrets. pub secrets_size: usize, } @@ -178,7 +141,55 @@ impl Default for FfiTransactionResult { } } -/// Send generic public transaction +/// 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 @@ -202,7 +213,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( account_identities_size: usize, instruction_words: *const u32, instruction_words_size: usize, - program_with_dependencies: FfiProgramWithDependencies, + program_with_dependencies: *const FfiProgramWithDependencies, out_result: *mut FfiTransactionResult, ) -> WalletFfiError { let wrapper = match get_wallet(handle) { @@ -244,9 +255,9 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( { Ok(v) => v, Err(err) => { - print_error(format!( - "account_identities_size does not match actual size of account_identities" - )); + print_error( + "account_identities_size does not match actual size of account_identities", + ); return err; } } @@ -263,7 +274,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( instruction_data.push(unsafe { *instruction_words.add(i) }); } - let program = match program_with_dependencies.try_into() { + let program = match unsafe { &*program_with_dependencies }.try_into() { Ok(v) => v, Err(err) => return err, }; @@ -290,7 +301,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( } } -/// Send generic private transaction +/// Send generic private transaction. /// /// # Parameters /// - `handle`: Valid pointer to wallet handle @@ -314,7 +325,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( account_identities_size: usize, instruction_words: *const u32, instruction_words_size: usize, - program_with_dependencies: FfiProgramWithDependencies, + program_with_dependencies: *const FfiProgramWithDependencies, out_result: *mut FfiTransactionResult, ) -> WalletFfiError { let wrapper = match get_wallet(handle) { @@ -356,9 +367,9 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( { Ok(v) => v, Err(err) => { - print_error(format!( - "account_identities_size does not match actual size of account_identities" - )); + print_error( + "account_identities_size does not match actual size of account_identities", + ); return err; } } @@ -375,7 +386,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( instruction_data.push(unsafe { *instruction_words.add(i) }); } - let program = match program_with_dependencies.try_into() { + let program = match unsafe { &*program_with_dependencies }.try_into() { Ok(v) => v, Err(err) => return err, }; @@ -390,7 +401,11 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( (*out_result).success = true; let secrets_size = secrets.len(); - let boxed_slice = secrets.into_iter().map(Into::into).collect::>().into_boxed_slice(); + 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; @@ -407,3 +422,28 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( } } } + +/// 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 a0c90440..65aa5ef0 100644 --- a/wallet-ffi/src/keys.rs +++ b/wallet-ffi/src/keys.rs @@ -320,12 +320,9 @@ pub unsafe extern "C" fn wallet_ffi_resolve_private_account( let account_id = account_id.into(); - let resolved_account = match wallet.resolve_private_account(account_id) { - Some(v) => v, - None => { - print_error(format!("Account not found")); - return WalletFfiError::AccountNotFound; - } + let Some(resolved_account) = wallet.resolve_private_account(account_id) else { + print_error("Account not found"); + return WalletFfiError::AccountNotFound; }; unsafe { diff --git a/wallet-ffi/src/lib.rs b/wallet-ffi/src/lib.rs index e28a0560..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" )] diff --git a/wallet-ffi/src/types.rs b/wallet-ffi/src/types.rs index 960cba47..149b646b 100644 --- a/wallet-ffi/src/types.rs +++ b/wallet-ffi/src/types.rs @@ -179,7 +179,7 @@ impl FfiPrivateAccountKeys { } } -/// Enumeration to represent kinds of FfiAccountManagerAccountIdentity +/// Enumeration to represent kinds of `FfiAccountManagerAccountIdentity`. #[repr(C)] pub enum FfiAccountIdentityKind { Public = 0, @@ -192,7 +192,7 @@ pub enum FfiAccountIdentityKind { PrivatePdaShared = 7, } -/// Struct representing of account identity, given to `AccountManager` at intialization +/// Struct representing of account identity, given to `AccountManager` at intialization. #[repr(C)] pub struct FfiAccountIdentity { pub kind: FfiAccountIdentityKind, @@ -450,13 +450,9 @@ impl TryFrom<&FfiAccountIdentity> for AccountIdentity { fn try_from(value: &FfiAccountIdentity) -> Result { match value.kind { - FfiAccountIdentityKind::Public => Ok(AccountIdentity::Public(value.account_id.into())), - FfiAccountIdentityKind::PublicNoSign => { - Ok(AccountIdentity::PublicNoSign(value.account_id.into())) - } - FfiAccountIdentityKind::PrivateOwned => { - Ok(AccountIdentity::PrivateOwned(value.account_id.into())) - } + 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 { @@ -470,14 +466,14 @@ impl TryFrom<&FfiAccountIdentity> for AccountIdentity { Err(WalletFfiError::InvalidKeyValue) }?; - Ok(AccountIdentity::PrivateForeign { + Ok(Self::PrivateForeign { npk: NullifierPublicKey(value.nullifier_public_key.data), vpk, identifier: value.identifier.into(), }) } FfiAccountIdentityKind::PrivatePdaOwned => { - Ok(AccountIdentity::PrivatePdaOwned(value.account_id.into())) + Ok(Self::PrivatePdaOwned(value.account_id.into())) } FfiAccountIdentityKind::PrivatePdaForeign => { let vpk = if value.viewing_public_key_len == 33 { @@ -492,7 +488,7 @@ impl TryFrom<&FfiAccountIdentity> for AccountIdentity { Err(WalletFfiError::InvalidKeyValue) }?; - Ok(AccountIdentity::PrivatePdaForeign { + Ok(Self::PrivatePdaForeign { account_id: value.account_id.into(), npk: NullifierPublicKey(value.nullifier_public_key.data), vpk, @@ -512,7 +508,7 @@ impl TryFrom<&FfiAccountIdentity> for AccountIdentity { Err(WalletFfiError::InvalidKeyValue) }?; - Ok(AccountIdentity::PrivateShared { + Ok(Self::PrivateShared { nsk: value.nullifier_secret_key.data, npk: NullifierPublicKey(value.nullifier_public_key.data), vpk, @@ -532,7 +528,7 @@ impl TryFrom<&FfiAccountIdentity> for AccountIdentity { Err(WalletFfiError::InvalidKeyValue) }?; - Ok(AccountIdentity::PrivatePdaShared { + Ok(Self::PrivatePdaShared { account_id: value.account_id.into(), nsk: value.nullifier_secret_key.data, npk: NullifierPublicKey(value.nullifier_public_key.data), diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index d35463fc..03b139d7 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -104,7 +104,7 @@ typedef enum WalletFfiError { */ INVALID_KEY_VALUE = 16, /** - * Invalid program bytecode + * Invalid program bytecode. */ INVALID_BYTECODE = 17, /** @@ -114,7 +114,7 @@ typedef enum WalletFfiError { } WalletFfiError; /** - * Enumeration to represent kinds of FfiAccountManagerAccountIdentity + * Enumeration to represent kinds of `FfiAccountManagerAccountIdentity`. */ typedef enum FfiAccountIdentityKind { PUBLIC = 0, @@ -225,7 +225,7 @@ typedef struct SerializationHelperResult { } SerializationHelperResult; /** - * Struct representing of account identity, given to `AccountManager` at intialization + * Struct representing of account identity, given to `AccountManager` at intialization. */ typedef struct FfiAccountIdentity { enum FfiAccountIdentityKind kind; @@ -238,7 +238,7 @@ typedef struct FfiAccountIdentity { } FfiAccountIdentity; /** - * Intended to be created manually + * Intended to be created manually. */ typedef struct FfiProgram { const uint8_t *elf_data; @@ -246,7 +246,7 @@ typedef struct FfiProgram { } FfiProgram; /** - * Intended to be created manually + * Intended to be created manually. */ typedef struct FfiProgramWithDependencies { struct FfiProgram program; @@ -268,7 +268,7 @@ typedef struct FfiTransactionResult { bool success; const struct FfiBytes32 *secrets_data; /** - * Public transaction have 0 secrets + * Public transaction have 0 secrets. */ uintptr_t secrets_size; } FfiTransactionResult; @@ -528,7 +528,7 @@ enum WalletFfiError wallet_ffi_import_private_account(struct WalletHandle *handl const char *account_state_json); /** - * Serialize sequence of bytes into RISC0 readable words + * Serialize sequence of bytes into RISC0 readable words. * * # Parameters * - `input_instruction_data`: Valid pointer to a sequence of bytes @@ -545,7 +545,7 @@ struct SerializationHelperResult wallet_ffi_serialization_helper(const uint8_t * uintptr_t input_instruction_data_size); /** - * Send generic public transaction + * Send generic public transaction. * * # Parameters * - `handle`: Valid pointer to wallet handle @@ -568,11 +568,11 @@ enum WalletFfiError wallet_ffi_send_generic_public_transaction(struct WalletHand uintptr_t account_identities_size, const uint32_t *instruction_words, uintptr_t instruction_words_size, - struct FfiProgramWithDependencies program_with_dependencies, + const struct FfiProgramWithDependencies *program_with_dependencies, struct FfiTransactionResult *out_result); /** - * Send generic private transaction + * Send generic private transaction. * * # Parameters * - `handle`: Valid pointer to wallet handle @@ -595,9 +595,18 @@ enum WalletFfiError wallet_ffi_send_generic_private_transaction(struct WalletHan uintptr_t account_identities_size, const uint32_t *instruction_words, uintptr_t instruction_words_size, - struct FfiProgramWithDependencies program_with_dependencies, + 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. * From affc08128a4ee5b768d2dd6bbdf0fa68bbcb7a61 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Fri, 29 May 2026 13:17:33 +0300 Subject: [PATCH 06/10] fix(ci): deny fix --- .deny.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/.deny.toml b/.deny.toml index 0a9a3df1..fb1ce3cf 100644 --- a/.deny.toml +++ b/.deny.toml @@ -13,6 +13,7 @@ ignore = [ { id = "RUSTSEC-2025-0055", reason = "`tracing-subscriber` v0.2.25 pulled in by ark-relations v0.4.0 - will be addressed before mainnet" }, { id = "RUSTSEC-2025-0141", reason = "`bincode` is unmaintained but continuing to use it." }, { id = "RUSTSEC-2023-0089", reason = "atomic-polyfill is pulled transitively via risc0-zkvm; waiting on upstream fix (see https://github.com/risc0/risc0/issues/3453)" }, + { id = "RUSTSEC-2026-0097", reason = "`rand` v0.8.5 is present transitively from logos crates, modification may break integration" }, { id = "RUSTSEC-2026-0118", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" }, { id = "RUSTSEC-2026-0119", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration" }, ] From dd35c89ca1bd5b43c419c91b18f9693d5489a63d Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Mon, 1 Jun 2026 13:59:41 +0300 Subject: [PATCH 07/10] fix(wallet_ffi): suggestions fix 1 --- wallet-ffi/src/generic_transaction.rs | 87 ++++++------------ wallet-ffi/src/keys.rs | 20 +++-- wallet-ffi/src/types.rs | 125 +++++++++++++++++++++++++- wallet-ffi/wallet_ffi.h | 16 ++-- wallet/src/account_manager.rs | 2 +- 5 files changed, 175 insertions(+), 75 deletions(-) diff --git a/wallet-ffi/src/generic_transaction.rs b/wallet-ffi/src/generic_transaction.rs index 1351a7be..9ec3db33 100644 --- a/wallet-ffi/src/generic_transaction.rs +++ b/wallet-ffi/src/generic_transaction.rs @@ -126,7 +126,7 @@ pub struct FfiTransactionResult { /// Whether the transaction succeeded. pub success: bool, pub secrets_data: *const FfiBytes32, - /// Public transaction have 0 secrets. + /// Public transactions have 0 secrets. pub secrets_size: usize, } @@ -222,12 +222,12 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( }; if account_identities.is_null() { - print_error("Null output pointer for account identities list"); + print_error("Null input pointer for account identities list"); return WalletFfiError::NullPointer; } if instruction_words.is_null() { - print_error("Null output pointer for instruction data"); + print_error("Null input pointer for instruction data"); return WalletFfiError::NullPointer; } @@ -244,34 +244,19 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( } }; + 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); - 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( - "account_identities_size does not match actual size of account_identities", - ); - return err; - } + 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; } - .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 unsafe { &*program_with_dependencies }.try_into() { @@ -279,7 +264,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( Err(err) => return err, }; - match block_on(wallet.send_pub_tx(accounts, instruction_data, &program)) { + 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); @@ -334,12 +319,12 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( }; if account_identities.is_null() { - print_error("Null output pointer for account identities list"); + print_error("Null input pointer for account identities list"); return WalletFfiError::NullPointer; } if instruction_words.is_null() { - print_error("Null output pointer for instruction data"); + print_error("Null input pointer for instruction data"); return WalletFfiError::NullPointer; } @@ -356,34 +341,19 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( } }; + 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); - 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( - "account_identities_size does not match actual size of account_identities", - ); - return err; - } + 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; } - .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 unsafe { &*program_with_dependencies }.try_into() { @@ -391,7 +361,8 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( Err(err) => return err, }; - match block_on(wallet.send_privacy_preserving_tx(accounts, instruction_data, &program)) { + 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); @@ -414,7 +385,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction( WalletFfiError::Success } Err(e) => { - print_error(format!("Public send failed: {e:?}")); + print_error(format!("Private send failed: {e:?}")); unsafe { *out_result = FfiTransactionResult::default(); } diff --git a/wallet-ffi/src/keys.rs b/wallet-ffi/src/keys.rs index 65aa5ef0..ae7fdc36 100644 --- a/wallet-ffi/src/keys.rs +++ b/wallet-ffi/src/keys.rs @@ -257,20 +257,25 @@ pub unsafe extern "C" fn wallet_ffi_account_id_from_base58( /// /// # Parameters /// - `account_id`: 32 bytes of the public account ID -/// - `needs_sign`: does account needs signing +/// - `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 `FfiAccountManagerAccountIdentity` struct +/// - `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 { @@ -293,18 +298,23 @@ pub unsafe extern "C" fn wallet_ffi_resolve_public_account( /// /// # Returns /// - `Success` on successful retrieval -/// - `InternalError` if wailed to lock wallet -/// - `AccountNotFound` if failed to found account +/// - `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 `FfiAccountManagerAccountIdentity` struct +/// - `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, diff --git a/wallet-ffi/src/types.rs b/wallet-ffi/src/types.rs index 149b646b..4ad72285 100644 --- a/wallet-ffi/src/types.rs +++ b/wallet-ffi/src/types.rs @@ -179,8 +179,9 @@ impl FfiPrivateAccountKeys { } } -/// Enumeration to represent kinds of `FfiAccountManagerAccountIdentity`. +/// Enumeration to represent kinds of `FfiAccountIdentity`. #[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FfiAccountIdentityKind { Public = 0, PublicNoSign = 1, @@ -192,7 +193,7 @@ pub enum FfiAccountIdentityKind { PrivatePdaShared = 7, } -/// Struct representing of account identity, given to `AccountManager` at intialization. +/// Struct representing an account identity, given to `AccountManager` at intialization. #[repr(C)] pub struct FfiAccountIdentity { pub kind: FfiAccountIdentityKind, @@ -432,7 +433,7 @@ impl From for FfiAccountIdentity { }; Self { - kind: FfiAccountIdentityKind::PrivateShared, + kind: FfiAccountIdentityKind::PrivatePdaShared, account_id: account_id.into(), nullifier_secret_key: nsk.into(), nullifier_public_key: npk.0.into(), @@ -539,3 +540,121 @@ impl TryFrom<&FfiAccountIdentity> for AccountIdentity { } } } + +#[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 03b139d7..3b31516a 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -114,7 +114,7 @@ typedef enum WalletFfiError { } WalletFfiError; /** - * Enumeration to represent kinds of `FfiAccountManagerAccountIdentity`. + * Enumeration to represent kinds of `FfiAccountIdentity`. */ typedef enum FfiAccountIdentityKind { PUBLIC = 0, @@ -225,7 +225,7 @@ typedef struct SerializationHelperResult { } SerializationHelperResult; /** - * Struct representing of account identity, given to `AccountManager` at intialization. + * Struct representing an account identity, given to `AccountManager` at intialization. */ typedef struct FfiAccountIdentity { enum FfiAccountIdentityKind kind; @@ -268,7 +268,7 @@ typedef struct FfiTransactionResult { bool success; const struct FfiBytes32 *secrets_data; /** - * Public transaction have 0 secrets. + * Public transactions have 0 secrets. */ uintptr_t secrets_size; } FfiTransactionResult; @@ -710,14 +710,14 @@ enum WalletFfiError wallet_ffi_account_id_from_base58(const char *base58_str, * * # Parameters * - `account_id`: 32 bytes of the public account ID - * - `needs_sign`: does account needs signing + * - `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 `FfiAccountManagerAccountIdentity` struct + * - `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, @@ -733,12 +733,12 @@ enum WalletFfiError wallet_ffi_resolve_public_account(struct FfiBytes32 account_ * * # Returns * - `Success` on successful retrieval - * - `InternalError` if wailed to lock wallet - * - `AccountNotFound` if failed to found account + * - `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 `FfiAccountManagerAccountIdentity` struct + * - `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, 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. From 6b8265ed29b3d8ee26614def70072e0dc37e1fd4 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Mon, 1 Jun 2026 14:05:39 +0300 Subject: [PATCH 08/10] fix(ci): test without logos circuits --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49ceaab9..a36631c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,9 +87,9 @@ jobs: - uses: ./.github/actions/install-risc0 - - uses: ./.github/actions/install-logos-blockchain-circuits - with: - github-token: ${{ secrets.GITHUB_TOKEN }} + # - uses: ./.github/actions/install-logos-blockchain-circuits + # with: + # github-token: ${{ secrets.GITHUB_TOKEN }} - name: Install active toolchain run: rustup install From 077197aed482daa61b4c774da923088751e4191a Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Mon, 1 Jun 2026 14:10:59 +0300 Subject: [PATCH 09/10] fix(ci): pinned scripts fetch to a commit --- .github/actions/install-logos-blockchain-circuits/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 18e1bea512ab0c74e7ee701871628c93168b80ce Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Mon, 1 Jun 2026 14:30:12 +0300 Subject: [PATCH 10/10] fix(ci): ci fix 2 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a36631c6..49ceaab9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,9 +87,9 @@ jobs: - uses: ./.github/actions/install-risc0 - # - uses: ./.github/actions/install-logos-blockchain-circuits - # with: - # github-token: ${{ secrets.GITHUB_TOKEN }} + - uses: ./.github/actions/install-logos-blockchain-circuits + with: + github-token: ${{ secrets.GITHUB_TOKEN }} - name: Install active toolchain run: rustup install