feat: generic public transactions

This commit is contained in:
Pravdyvy 2026-05-21 17:04:48 +03:00
parent 1c32b14053
commit bc6ba30f66
7 changed files with 367 additions and 8 deletions

1
Cargo.lock generated
View File

@ -10240,6 +10240,7 @@ dependencies = [
"key_protocol",
"nssa",
"nssa_core",
"risc0-zkvm",
"sequencer_service_rpc",
"serde_json",
"tempfile",

View File

@ -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"

View File

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

View File

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

View File

@ -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::<u32>();
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<Self, Self::Error> {
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<FfiProgramWithDependencies> for ProgramWithDependencies {
type Error = WalletFfiError;
fn try_from(value: FfiProgramWithDependencies) -> Result<Self, Self::Error> {
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)
}
}
}

View File

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

View File

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