mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-06-26 08:59:45 +00:00
Merge pull request #491 from logos-blockchain/Pravdyvy/wallet-ffi-extension
feat(wallet_ffi): wallet ffi generic transactions
This commit is contained in:
commit
a8c81f5445
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -10915,6 +10915,7 @@ dependencies = [
|
||||
"key_protocol",
|
||||
"lee",
|
||||
"lee_core",
|
||||
"risc0-zkvm",
|
||||
"sequencer_service_rpc",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
|
||||
@ -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"
|
||||
)]
|
||||
|
||||
@ -20,14 +21,18 @@ use std::{
|
||||
|
||||
use anyhow::Result;
|
||||
use integration_tests::{BlockingTestContext, TIME_TO_WAIT_FOR_BLOCK_SECONDS};
|
||||
use lee::{Account, AccountId, PrivateKey, PublicKey, program::Program};
|
||||
use lee::{
|
||||
Account, AccountId, PrivateKey, PublicKey,
|
||||
privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program,
|
||||
};
|
||||
use lee_core::program::DEFAULT_PROGRAM_ID;
|
||||
use log::info;
|
||||
use tempfile::tempdir;
|
||||
use wallet::account::HumanReadableAccount;
|
||||
use wallet_ffi::{
|
||||
FfiAccount, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey,
|
||||
FfiTransferResult, FfiU128, WalletHandle, error,
|
||||
FfiAccount, FfiAccountIdentity, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys,
|
||||
FfiPublicAccountKey, FfiTransferResult, FfiU128, WalletHandle, error,
|
||||
generic_transaction::{FfiProgramWithDependencies, FfiTransactionResult},
|
||||
};
|
||||
|
||||
unsafe extern "C" {
|
||||
@ -180,6 +185,42 @@ unsafe extern "C" {
|
||||
handle: *mut WalletHandle,
|
||||
out_block_height: *mut u64,
|
||||
) -> error::WalletFfiError;
|
||||
|
||||
fn wallet_ffi_resolve_public_account(
|
||||
account_id: FfiBytes32,
|
||||
needs_sign: bool,
|
||||
out_account_identity: *mut FfiAccountIdentity,
|
||||
) -> error::WalletFfiError;
|
||||
|
||||
fn wallet_ffi_send_generic_public_transaction(
|
||||
handle: *mut WalletHandle,
|
||||
account_identities: *const FfiAccountIdentity,
|
||||
account_identities_size: usize,
|
||||
instruction_words: *const u32,
|
||||
instruction_words_size: usize,
|
||||
program_with_dependencies: *const FfiProgramWithDependencies,
|
||||
out_result: *mut FfiTransactionResult,
|
||||
) -> error::WalletFfiError;
|
||||
|
||||
fn wallet_ffi_resolve_private_account(
|
||||
handle: *mut WalletHandle,
|
||||
account_id: FfiBytes32,
|
||||
out_account_identity: *mut FfiAccountIdentity,
|
||||
) -> error::WalletFfiError;
|
||||
|
||||
fn wallet_ffi_send_generic_private_transaction(
|
||||
handle: *mut WalletHandle,
|
||||
account_identities: *const FfiAccountIdentity,
|
||||
account_identities_size: usize,
|
||||
instruction_words: *const u32,
|
||||
instruction_words_size: usize,
|
||||
program_with_dependencies: *const FfiProgramWithDependencies,
|
||||
out_result: *mut FfiTransactionResult,
|
||||
) -> error::WalletFfiError;
|
||||
|
||||
fn wallet_ffi_free_transaction_result(result: *mut FfiTransactionResult);
|
||||
|
||||
fn wallet_ffi_free_account_identity(account_identity: *mut FfiAccountIdentity);
|
||||
}
|
||||
|
||||
fn new_wallet_ffi_with_test_context_config(
|
||||
@ -1068,3 +1109,201 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_transfer_generic_public() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let from: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into();
|
||||
let to: FfiBytes32 = ctx.ctx().existing_public_accounts()[1].into();
|
||||
let amount = 100_u128;
|
||||
|
||||
let mut transaction_result = FfiTransactionResult::default();
|
||||
|
||||
let mut from_account_identity = FfiAccountIdentity::default();
|
||||
let mut to_account_identity = FfiAccountIdentity::default();
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_resolve_public_account(from, true, &raw mut from_account_identity).unwrap();
|
||||
}
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_resolve_public_account(to, true, &raw mut to_account_identity).unwrap();
|
||||
}
|
||||
|
||||
let ffi_accs = vec![from_account_identity, to_account_identity];
|
||||
let account_identities_size = ffi_accs.len();
|
||||
let account_identities =
|
||||
Box::into_raw(ffi_accs.into_boxed_slice()) as *const FfiAccountIdentity;
|
||||
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(authenticated_transfer_core::Instruction::Transfer {
|
||||
amount,
|
||||
})
|
||||
.unwrap();
|
||||
let instruction_words_size = instruction_data.len();
|
||||
let instruction_words = Box::into_raw(instruction_data.into_boxed_slice()) as *const u32;
|
||||
|
||||
let program: ProgramWithDependencies = Program::authenticated_transfer_program().into();
|
||||
let program_with_dependencies: FfiProgramWithDependencies = program.into();
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_send_generic_public_transaction(
|
||||
wallet_ffi_handle,
|
||||
account_identities,
|
||||
account_identities_size,
|
||||
instruction_words,
|
||||
instruction_words_size,
|
||||
&raw const program_with_dependencies,
|
||||
&raw mut transaction_result,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS));
|
||||
|
||||
let from_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
&raw const from,
|
||||
true,
|
||||
&raw mut out_balance,
|
||||
)
|
||||
.unwrap();
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
let to_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
wallet_ffi_get_balance(wallet_ffi_handle, &raw const to, true, &raw mut out_balance)
|
||||
.unwrap();
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
assert_eq!(from_balance, 9900);
|
||||
assert_eq!(to_balance, 20100);
|
||||
|
||||
unsafe {
|
||||
let account_identities_mut = account_identities.cast_mut();
|
||||
wallet_ffi_free_account_identity(account_identities_mut);
|
||||
wallet_ffi_free_account_identity(account_identities_mut.add(1));
|
||||
|
||||
let instruction_data =
|
||||
std::slice::from_raw_parts_mut(instruction_words.cast_mut(), instruction_words_size);
|
||||
drop(Box::from_raw(std::ptr::from_mut(instruction_data)));
|
||||
|
||||
wallet_ffi_free_transaction_result(&raw mut transaction_result);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wallet_ffi_transfer_generic_private() -> Result<()> {
|
||||
let ctx = BlockingTestContext::new()?;
|
||||
let home = tempfile::tempdir()?;
|
||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||
let from: FfiBytes32 = ctx.ctx().existing_private_accounts()[0].into();
|
||||
let to: FfiBytes32 = ctx.ctx().existing_private_accounts()[1].into();
|
||||
let amount = 100_u128;
|
||||
|
||||
let mut transaction_result = FfiTransactionResult::default();
|
||||
|
||||
let mut from_account_identity = FfiAccountIdentity::default();
|
||||
let mut to_account_identity = FfiAccountIdentity::default();
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_resolve_private_account(wallet_ffi_handle, from, &raw mut from_account_identity)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_resolve_private_account(wallet_ffi_handle, to, &raw mut to_account_identity)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let ffi_accs = vec![from_account_identity, to_account_identity];
|
||||
let account_identities_size = ffi_accs.len();
|
||||
let account_identities =
|
||||
Box::into_raw(ffi_accs.into_boxed_slice()) as *const FfiAccountIdentity;
|
||||
|
||||
let instruction_data =
|
||||
Program::serialize_instruction(authenticated_transfer_core::Instruction::Transfer {
|
||||
amount,
|
||||
})
|
||||
.unwrap();
|
||||
let instruction_words_size = instruction_data.len();
|
||||
let instruction_words = Box::into_raw(instruction_data.into_boxed_slice()) as *const u32;
|
||||
|
||||
let program: ProgramWithDependencies = Program::authenticated_transfer_program().into();
|
||||
let program_with_dependencies: FfiProgramWithDependencies = program.into();
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_send_generic_private_transaction(
|
||||
wallet_ffi_handle,
|
||||
account_identities,
|
||||
account_identities_size,
|
||||
instruction_words,
|
||||
instruction_words_size,
|
||||
&raw const program_with_dependencies,
|
||||
&raw mut transaction_result,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(transaction_result.secrets_size, 2);
|
||||
|
||||
info!("Waiting for next block creation");
|
||||
std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS));
|
||||
|
||||
// Sync private account local storage with onchain encrypted state
|
||||
unsafe {
|
||||
let mut current_height = 0;
|
||||
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap();
|
||||
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap();
|
||||
};
|
||||
|
||||
let from_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
&raw const from,
|
||||
false,
|
||||
&raw mut out_balance,
|
||||
);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
let to_balance = unsafe {
|
||||
let mut out_balance: [u8; 16] = [0; 16];
|
||||
let _result = wallet_ffi_get_balance(
|
||||
wallet_ffi_handle,
|
||||
&raw const to,
|
||||
false,
|
||||
&raw mut out_balance,
|
||||
);
|
||||
u128::from_le_bytes(out_balance)
|
||||
};
|
||||
|
||||
assert_eq!(from_balance, 9900);
|
||||
assert_eq!(to_balance, 20100);
|
||||
|
||||
unsafe {
|
||||
let account_identities_mut = account_identities.cast_mut();
|
||||
wallet_ffi_free_account_identity(account_identities_mut);
|
||||
wallet_ffi_free_account_identity(account_identities_mut.add(1));
|
||||
|
||||
let instruction_data =
|
||||
std::slice::from_raw_parts_mut(instruction_words.cast_mut(), instruction_words_size);
|
||||
drop(Box::from_raw(std::ptr::from_mut(instruction_data)));
|
||||
|
||||
wallet_ffi_free_transaction_result(&raw mut transaction_result);
|
||||
wallet_ffi_destroy(wallet_ffi_handle);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
//!
|
||||
//! Uses numeric error codes with error messages printed to stderr.
|
||||
|
||||
use std::str::Utf8Error;
|
||||
|
||||
/// Error codes returned by FFI functions.
|
||||
#[repr(C)]
|
||||
#[must_use]
|
||||
@ -41,10 +43,18 @@ pub enum WalletFfiError {
|
||||
InvalidTypeConversion = 15,
|
||||
/// Invalid Key value.
|
||||
InvalidKeyValue = 16,
|
||||
/// Invalid program bytecode.
|
||||
InvalidBytecode = 17,
|
||||
/// Internal error (catch-all).
|
||||
InternalError = 99,
|
||||
}
|
||||
|
||||
impl From<Utf8Error> for WalletFfiError {
|
||||
fn from(_value: Utf8Error) -> Self {
|
||||
Self::InvalidUtf8
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletFfiError {
|
||||
/// Check if it's [`WalletFfiError::Success`] or panic.
|
||||
pub fn unwrap(self) {
|
||||
|
||||
469
lez/wallet-ffi/src/generic_transaction.rs
Normal file
469
lez/wallet-ffi/src/generic_transaction.rs
Normal file
@ -0,0 +1,469 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::{c_char, CString},
|
||||
};
|
||||
|
||||
use lee::{privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program};
|
||||
|
||||
use crate::{
|
||||
block_on,
|
||||
error::{print_error, WalletFfiError},
|
||||
map_execution_error,
|
||||
wallet::get_wallet,
|
||||
FfiAccountIdentity, FfiBytes32, WalletHandle,
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct FfiInstructionWords {
|
||||
pub instruction_words: *mut u32,
|
||||
pub instruction_words_size: usize,
|
||||
pub error: WalletFfiError,
|
||||
}
|
||||
|
||||
impl FfiInstructionWords {
|
||||
const fn from_err(error: WalletFfiError) -> Self {
|
||||
Self {
|
||||
instruction_words: std::ptr::null_mut(),
|
||||
instruction_words_size: 0,
|
||||
error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
/// Intended to be created manually.
|
||||
pub struct FfiProgram {
|
||||
pub elf_data: *const u8,
|
||||
pub elf_size: usize,
|
||||
}
|
||||
|
||||
impl TryFrom<&FfiProgram> for Program {
|
||||
type Error = WalletFfiError;
|
||||
|
||||
fn try_from(value: &FfiProgram) -> Result<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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Program> for FfiProgram {
|
||||
fn from(value: Program) -> Self {
|
||||
let elf_clone = value.elf().to_vec();
|
||||
let elf_size = elf_clone.len();
|
||||
let elf_data = Box::into_raw(elf_clone.into_boxed_slice()) as *const u8;
|
||||
|
||||
Self { elf_data, elf_size }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
/// Intended to be created manually.
|
||||
pub struct FfiProgramWithDependencies {
|
||||
pub program: FfiProgram,
|
||||
pub deps: *const FfiProgram,
|
||||
pub deps_size: usize,
|
||||
}
|
||||
|
||||
impl TryFrom<&FfiProgramWithDependencies> for ProgramWithDependencies {
|
||||
type Error = WalletFfiError;
|
||||
|
||||
fn try_from(value: &FfiProgramWithDependencies) -> Result<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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProgramWithDependencies> for FfiProgramWithDependencies {
|
||||
fn from(value: ProgramWithDependencies) -> Self {
|
||||
let ffi_program = value.program.into();
|
||||
|
||||
let ffi_deps: Vec<FfiProgram> = value
|
||||
.dependencies
|
||||
.into_values()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let deps_size = ffi_deps.len();
|
||||
let deps = Box::into_raw(ffi_deps.into_boxed_slice()) as *const FfiProgram;
|
||||
|
||||
Self {
|
||||
program: ffi_program,
|
||||
deps,
|
||||
deps_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of a generic transaction operation.
|
||||
#[repr(C)]
|
||||
pub struct FfiTransactionResult {
|
||||
// TODO: Replace with HashType FFI representation
|
||||
/// Transaction hash (null-terminated string, or null on failure).
|
||||
pub tx_hash: *mut c_char,
|
||||
/// Whether the transaction succeeded.
|
||||
pub success: bool,
|
||||
pub secrets_data: *const FfiBytes32,
|
||||
/// Public transactions have 0 secrets.
|
||||
pub secrets_size: usize,
|
||||
}
|
||||
|
||||
impl Default for FfiTransactionResult {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tx_hash: std::ptr::null_mut(),
|
||||
success: false,
|
||||
secrets_data: std::ptr::null(),
|
||||
secrets_size: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize sequence of bytes into RISC0 readable words.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `input_instruction_data`: Valid pointer to a sequence of bytes
|
||||
/// - `input_instruction_data_size`: Size of `input_instruction_data`
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Success` on successful creation
|
||||
/// - Error code on failure
|
||||
///
|
||||
/// # Safety
|
||||
/// - `input_instruction_data` must be a valid pointer
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_serialization_helper(
|
||||
input_instruction_data: *const u8,
|
||||
input_instruction_data_size: usize,
|
||||
) -> FfiInstructionWords {
|
||||
if input_instruction_data.is_null() {
|
||||
print_error("Null input pointer for instruction_data");
|
||||
return FfiInstructionWords::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 FfiInstructionWords::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::<u32>();
|
||||
|
||||
FfiInstructionWords {
|
||||
instruction_words: res_ptr,
|
||||
instruction_words_size: res_len,
|
||||
error: WalletFfiError::Success,
|
||||
}
|
||||
}
|
||||
|
||||
/// Send generic public transaction.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `handle`: Valid pointer to wallet handle
|
||||
/// - `account_identities`: Valid pointer to list of `FfiAccountIdentity`
|
||||
/// - `instruction_words`: Valid pointer to instruction words
|
||||
/// - `out_result`: Valid pointer to `FfiTransactionResult`
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Success` on successful creation
|
||||
/// - Error code on failure
|
||||
///
|
||||
/// # Safety
|
||||
/// - `handle` must be a valid pointer
|
||||
/// - `account_identities` must be a valid pointer
|
||||
/// - `instruction_words` must be a valid pointer
|
||||
/// - `out_result` must be a valid pointer
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction(
|
||||
handle: *mut WalletHandle,
|
||||
account_identities: *const FfiAccountIdentity,
|
||||
account_identities_size: usize,
|
||||
instruction_words: *const u32,
|
||||
instruction_words_size: usize,
|
||||
program_with_dependencies: *const FfiProgramWithDependencies,
|
||||
out_result: *mut FfiTransactionResult,
|
||||
) -> WalletFfiError {
|
||||
let wrapper = match get_wallet(handle) {
|
||||
Ok(w) => w,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
if account_identities.is_null() {
|
||||
print_error("Null input pointer for account identities list");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
if instruction_words.is_null() {
|
||||
print_error("Null input pointer for instruction data");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
if out_result.is_null() {
|
||||
print_error("Null output pointer return hash");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to lock wallet: {e}"));
|
||||
return WalletFfiError::InternalError;
|
||||
}
|
||||
};
|
||||
|
||||
let accounts_ffi = std::slice::from_raw_parts(account_identities, account_identities_size);
|
||||
let instruction_data = std::slice::from_raw_parts(instruction_words, instruction_words_size);
|
||||
|
||||
let mut accounts = Vec::with_capacity(account_identities_size);
|
||||
|
||||
for ffi_acc in accounts_ffi {
|
||||
match ffi_acc.try_into() {
|
||||
Ok(v) => accounts.push(v),
|
||||
Err(err) => {
|
||||
print_error("Failed to convert FfiAccountIdentity into AccountIdentity");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let program = match unsafe { &*program_with_dependencies }.try_into() {
|
||||
Ok(v) => v,
|
||||
Err(err) => return err,
|
||||
};
|
||||
|
||||
match block_on(wallet.send_pub_tx(accounts, instruction_data.to_vec(), &program)) {
|
||||
Ok(tx_hash) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map_or(std::ptr::null_mut(), std::ffi::CString::into_raw);
|
||||
|
||||
unsafe {
|
||||
(*out_result).tx_hash = tx_hash;
|
||||
(*out_result).success = true;
|
||||
}
|
||||
WalletFfiError::Success
|
||||
}
|
||||
Err(e) => {
|
||||
print_error(format!("Public send failed: {e:?}"));
|
||||
unsafe {
|
||||
(*out_result).tx_hash = std::ptr::null_mut();
|
||||
(*out_result).success = false;
|
||||
}
|
||||
map_execution_error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Send generic private transaction.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `handle`: Valid pointer to wallet handle
|
||||
/// - `account_identities`: Valid pointer to list of `FfiAccountIdentity`
|
||||
/// - `instruction_words`: Valid pointer to instruction words
|
||||
/// - `out_result`: Valid pointer to `FfiTransactionResult`
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Success` on successful creation
|
||||
/// - Error code on failure
|
||||
///
|
||||
/// # Safety
|
||||
/// - `handle` must be a valid pointer
|
||||
/// - `account_identities` must be a valid pointer
|
||||
/// - `instruction_words` must be a valid pointer
|
||||
/// - `out_result` must be a valid pointer
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_send_generic_private_transaction(
|
||||
handle: *mut WalletHandle,
|
||||
account_identities: *const FfiAccountIdentity,
|
||||
account_identities_size: usize,
|
||||
instruction_words: *const u32,
|
||||
instruction_words_size: usize,
|
||||
program_with_dependencies: *const FfiProgramWithDependencies,
|
||||
out_result: *mut FfiTransactionResult,
|
||||
) -> WalletFfiError {
|
||||
let wrapper = match get_wallet(handle) {
|
||||
Ok(w) => w,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
if account_identities.is_null() {
|
||||
print_error("Null input pointer for account identities list");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
if instruction_words.is_null() {
|
||||
print_error("Null input pointer for instruction data");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
if out_result.is_null() {
|
||||
print_error("Null output pointer return hash");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to lock wallet: {e}"));
|
||||
return WalletFfiError::InternalError;
|
||||
}
|
||||
};
|
||||
|
||||
let accounts_ffi = std::slice::from_raw_parts(account_identities, account_identities_size);
|
||||
let instruction_data = std::slice::from_raw_parts(instruction_words, instruction_words_size);
|
||||
|
||||
let mut accounts = Vec::with_capacity(account_identities_size);
|
||||
|
||||
for ffi_acc in accounts_ffi {
|
||||
match ffi_acc.try_into() {
|
||||
Ok(v) => accounts.push(v),
|
||||
Err(err) => {
|
||||
print_error("Failed to convert FfiAccountIdentity into AccountIdentity");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let program = match unsafe { &*program_with_dependencies }.try_into() {
|
||||
Ok(v) => v,
|
||||
Err(err) => return err,
|
||||
};
|
||||
|
||||
match block_on(wallet.send_privacy_preserving_tx(accounts, instruction_data.to_vec(), &program))
|
||||
{
|
||||
Ok((tx_hash, secrets)) => {
|
||||
let tx_hash = CString::new(tx_hash.to_string())
|
||||
.map_or(std::ptr::null_mut(), std::ffi::CString::into_raw);
|
||||
|
||||
unsafe {
|
||||
(*out_result).tx_hash = tx_hash;
|
||||
(*out_result).success = true;
|
||||
|
||||
let secrets_size = secrets.len();
|
||||
let boxed_slice = secrets
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<FfiBytes32>>()
|
||||
.into_boxed_slice();
|
||||
let secrets_data = Box::into_raw(boxed_slice) as *const FfiBytes32;
|
||||
|
||||
(*out_result).secrets_size = secrets_size;
|
||||
(*out_result).secrets_data = secrets_data;
|
||||
}
|
||||
WalletFfiError::Success
|
||||
}
|
||||
Err(e) => {
|
||||
print_error(format!("Private send failed: {e:?}"));
|
||||
unsafe {
|
||||
*out_result = FfiTransactionResult::default();
|
||||
}
|
||||
map_execution_error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a transaction result returned by `wallet_ffi_send_generic_public_transaction` or
|
||||
/// `wallet_ffi_send_generic_private_transaction`.
|
||||
///
|
||||
/// # Safety
|
||||
/// The result must be either null or a valid result from a transaction function.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_free_transaction_result(result: *mut FfiTransactionResult) {
|
||||
if result.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let result = &*result;
|
||||
if !result.tx_hash.is_null() {
|
||||
drop(CString::from_raw(result.tx_hash));
|
||||
}
|
||||
|
||||
if !result.secrets_data.is_null() {
|
||||
let secrets =
|
||||
std::slice::from_raw_parts_mut(result.secrets_data.cast_mut(), result.secrets_size);
|
||||
drop(Box::from_raw(std::ptr::from_mut::<[FfiBytes32]>(secrets)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a instruction words returned by `wallet_ffi_serialization_helper`.
|
||||
///
|
||||
/// # Safety
|
||||
/// The result must be either null or a valid result from a serialization helper function.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_free_instruction_words(words: *mut FfiInstructionWords) {
|
||||
if words.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let words = &*words;
|
||||
|
||||
if !words.instruction_words.is_null() {
|
||||
let words = std::slice::from_raw_parts_mut(
|
||||
words.instruction_words,
|
||||
words.instruction_words_size,
|
||||
);
|
||||
drop(Box::from_raw(std::ptr::from_mut::<[u32]>(words)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use lee::program::Program;
|
||||
|
||||
use crate::generic_transaction::FfiProgram;
|
||||
|
||||
#[test]
|
||||
fn program_cast_consistency() {
|
||||
let prog = Program::amm();
|
||||
|
||||
let first_5_bytes = prog.elf()[..5].to_vec();
|
||||
|
||||
let ffi_prog: FfiProgram = prog.into();
|
||||
|
||||
assert!(!ffi_prog.elf_data.is_null());
|
||||
|
||||
let mut ffi_first_5_bytes = vec![];
|
||||
for i in 0..5 {
|
||||
ffi_first_5_bytes.push(unsafe { *ffi_prog.elf_data.add(i) });
|
||||
}
|
||||
|
||||
assert_eq!(ffi_first_5_bytes, first_5_bytes);
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,15 @@
|
||||
//! Key retrieval functions.
|
||||
|
||||
use std::ptr;
|
||||
use std::{ffi::CString, ptr};
|
||||
|
||||
use lee::{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,153 @@ pub unsafe extern "C" fn wallet_ffi_account_id_from_base58(
|
||||
|
||||
WalletFfiError::Success
|
||||
}
|
||||
|
||||
/// Resolve public account.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `account_id`: 32 bytes of the public account ID
|
||||
/// - `needs_sign`: whether the account needs signing
|
||||
/// - `out_account_identity`: valid pointer, where output will be written
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Success` on successful retrieval
|
||||
///
|
||||
/// # Safety
|
||||
/// - `out_account_identity` must be a valid pointer to a `FfiAccountIdentity` struct
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_resolve_public_account(
|
||||
account_id: FfiBytes32,
|
||||
needs_sign: bool,
|
||||
out_account_identity: *mut FfiAccountIdentity,
|
||||
) -> WalletFfiError {
|
||||
if out_account_identity.is_null() {
|
||||
print_error("Null pointer argument");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let resolved_account = if needs_sign {
|
||||
AccountIdentity::Public(account_id.into())
|
||||
} else {
|
||||
AccountIdentity::PublicNoSign(account_id.into())
|
||||
};
|
||||
|
||||
unsafe {
|
||||
*out_account_identity = resolved_account.into();
|
||||
}
|
||||
|
||||
WalletFfiError::Success
|
||||
}
|
||||
|
||||
/// Resolve private account.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `handle`: Valid wallet handle
|
||||
/// - `account_id`: 32 bytes of the public account ID
|
||||
/// - `out_account_identity`: valid pointer, where output will be written
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Success` on successful retrieval
|
||||
/// - `InternalError` if failed to lock wallet
|
||||
/// - `AccountNotFound` if the account is not found
|
||||
///
|
||||
/// # Safety
|
||||
/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
|
||||
/// - `out_account_identity` must be a valid pointer to a `FfiAccountIdentity` struct
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_resolve_private_account(
|
||||
handle: *mut WalletHandle,
|
||||
account_id: FfiBytes32,
|
||||
out_account_identity: *mut FfiAccountIdentity,
|
||||
) -> WalletFfiError {
|
||||
if out_account_identity.is_null() {
|
||||
print_error("Null pointer argument");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let wrapper = match get_wallet(handle) {
|
||||
Ok(w) => w,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to lock wallet: {e}"));
|
||||
return WalletFfiError::InternalError;
|
||||
}
|
||||
};
|
||||
|
||||
let account_id = account_id.into();
|
||||
|
||||
let Some(resolved_account) = wallet.resolve_private_account(account_id) else {
|
||||
print_error("Account not found");
|
||||
return WalletFfiError::AccountNotFound;
|
||||
};
|
||||
|
||||
unsafe {
|
||||
*out_account_identity = resolved_account.into();
|
||||
}
|
||||
|
||||
WalletFfiError::Success
|
||||
}
|
||||
|
||||
/// Free account identity returned by `wallet_ffi_resolve_private_account` or
|
||||
/// `wallet_ffi_resolve_public_account`.
|
||||
///
|
||||
/// # Safety
|
||||
/// The account must be either null or a valid account returned by
|
||||
/// `wallet_ffi_resolve_private_account` or `wallet_ffi_resolve_public_account`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_free_account_identity(
|
||||
account_identity: *mut FfiAccountIdentity,
|
||||
) {
|
||||
if account_identity.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let FfiAccountIdentity {
|
||||
kind: _,
|
||||
account_id: _,
|
||||
key_path,
|
||||
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)));
|
||||
}
|
||||
|
||||
if !key_path.is_null() {
|
||||
let key_path_cstring = CString::from_raw(key_path);
|
||||
drop(key_path_cstring);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use lee::AccountId;
|
||||
use wallet::AccountIdentity;
|
||||
|
||||
use crate::{keys::wallet_ffi_free_account_identity, FfiAccountIdentity};
|
||||
|
||||
#[test]
|
||||
fn acc_identity_correct_free() {
|
||||
let acc_identity = AccountIdentity::Public(AccountId::new([42; 32]));
|
||||
let mut ffi_acc_identity: FfiAccountIdentity = acc_identity.into();
|
||||
|
||||
unsafe {
|
||||
wallet_ffi_free_account_identity(&raw mut ffi_acc_identity);
|
||||
}
|
||||
|
||||
assert!(ffi_acc_identity.viewing_public_key.is_null());
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#![expect(
|
||||
clippy::undocumented_unsafe_blocks,
|
||||
clippy::multiple_unsafe_ops_per_block,
|
||||
clippy::as_conversions,
|
||||
reason = "TODO: fix later"
|
||||
)]
|
||||
|
||||
@ -42,6 +43,7 @@ use crate::error::print_error;
|
||||
|
||||
pub mod account;
|
||||
pub mod error;
|
||||
pub mod generic_transaction;
|
||||
pub mod keys;
|
||||
pub mod pinata;
|
||||
pub mod sync;
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
//! C-compatible type definitions for the FFI layer.
|
||||
|
||||
use core::slice;
|
||||
use std::{ffi::c_char, ptr};
|
||||
use std::{
|
||||
ffi::{c_char, CString},
|
||||
ptr,
|
||||
str::FromStr as _,
|
||||
};
|
||||
|
||||
use lee::Data;
|
||||
use lee::{Data, SharedSecretKey};
|
||||
use lee_core::{encryption::MlKem768EncapsulationKey, NullifierPublicKey};
|
||||
use wallet::AccountIdentity;
|
||||
|
||||
use crate::error::WalletFfiError;
|
||||
|
||||
@ -153,6 +159,12 @@ impl FfiBytes32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SharedSecretKey> for FfiBytes32 {
|
||||
fn from(value: SharedSecretKey) -> Self {
|
||||
Self { data: value.0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl FfiPrivateAccountKeys {
|
||||
#[must_use]
|
||||
pub const fn npk(&self) -> lee_core::NullifierPublicKey {
|
||||
@ -174,6 +186,50 @@ impl FfiPrivateAccountKeys {
|
||||
}
|
||||
}
|
||||
|
||||
/// Enumeration to represent kinds of `FfiAccountIdentity`.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FfiAccountIdentityKind {
|
||||
Public = 0,
|
||||
PublicNoSign = 1,
|
||||
PublicKeycard = 2,
|
||||
PrivateOwned = 3,
|
||||
PrivateForeign = 4,
|
||||
PrivatePdaOwned = 5,
|
||||
PrivatePdaForeign = 6,
|
||||
PrivateShared = 7,
|
||||
PrivatePdaShared = 8,
|
||||
}
|
||||
|
||||
/// Struct representing an account identity, given to `AccountManager` at intialization.
|
||||
#[repr(C)]
|
||||
pub struct FfiAccountIdentity {
|
||||
pub kind: FfiAccountIdentityKind,
|
||||
pub account_id: FfiBytes32,
|
||||
/// C-compatible string.
|
||||
pub key_path: *mut c_char,
|
||||
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(),
|
||||
key_path: std::ptr::null_mut(),
|
||||
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<u128> for FfiU128 {
|
||||
fn from(value: u128) -> Self {
|
||||
Self {
|
||||
@ -194,6 +250,12 @@ impl From<lee::AccountId> for FfiBytes32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for FfiBytes32 {
|
||||
fn from(value: [u8; 32]) -> Self {
|
||||
Self { data: value }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FfiBytes32> for lee::AccountId {
|
||||
fn from(bytes: FfiBytes32) -> Self {
|
||||
Self::new(bytes.data)
|
||||
@ -268,3 +330,384 @@ impl TryFrom<&FfiPublicAccountKey> for lee::PublicKey {
|
||||
Ok(public_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AccountIdentity> 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::PublicKeycard {
|
||||
account_id,
|
||||
key_path,
|
||||
} => Self {
|
||||
kind: FfiAccountIdentityKind::PublicKeycard,
|
||||
account_id: account_id.into(),
|
||||
key_path: CString::into_raw(
|
||||
CString::from_str(&key_path).expect("key_path should be a valid string"),
|
||||
),
|
||||
..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.to_bytes().to_vec();
|
||||
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.to_bytes().to_vec();
|
||||
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.to_bytes().to_vec();
|
||||
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.to_bytes().to_vec();
|
||||
let vpk_len = vpk_vec.len();
|
||||
let vpk_data = if vpk_len > 0 {
|
||||
let vpk_data_boxed = vpk_vec.into_boxed_slice();
|
||||
Box::into_raw(vpk_data_boxed) as *const u8
|
||||
} else {
|
||||
ptr::null()
|
||||
};
|
||||
|
||||
Self {
|
||||
kind: FfiAccountIdentityKind::PrivatePdaShared,
|
||||
account_id: account_id.into(),
|
||||
nullifier_secret_key: nsk.into(),
|
||||
nullifier_public_key: npk.0.into(),
|
||||
viewing_public_key: vpk_data,
|
||||
viewing_public_key_len: vpk_len,
|
||||
identifier: identifier.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&FfiAccountIdentity> for AccountIdentity {
|
||||
type Error = WalletFfiError;
|
||||
|
||||
#[expect(
|
||||
clippy::map_err_ignore,
|
||||
reason = "`WalletFfiError` must be a trivial enum for FFI"
|
||||
)]
|
||||
fn try_from(value: &FfiAccountIdentity) -> Result<Self, Self::Error> {
|
||||
match value.kind {
|
||||
FfiAccountIdentityKind::Public => Ok(Self::Public(value.account_id.into())),
|
||||
FfiAccountIdentityKind::PublicNoSign => Ok(Self::PublicNoSign(value.account_id.into())),
|
||||
FfiAccountIdentityKind::PublicKeycard => {
|
||||
let key_path = unsafe { CString::from_raw(value.key_path) }
|
||||
.to_str()?
|
||||
.to_owned();
|
||||
Ok(Self::PublicKeycard {
|
||||
account_id: value.account_id.into(),
|
||||
key_path,
|
||||
})
|
||||
}
|
||||
FfiAccountIdentityKind::PrivateOwned => Ok(Self::PrivateOwned(value.account_id.into())),
|
||||
FfiAccountIdentityKind::PrivateForeign => {
|
||||
let vpk = if value.viewing_public_key_len == 1184 {
|
||||
let slice = unsafe {
|
||||
slice::from_raw_parts(
|
||||
value.viewing_public_key,
|
||||
value.viewing_public_key_len,
|
||||
)
|
||||
};
|
||||
Ok(MlKem768EncapsulationKey::from_bytes(slice.to_vec())
|
||||
.map_err(|_| WalletFfiError::InvalidKeyValue)?)
|
||||
} else {
|
||||
Err(WalletFfiError::InvalidKeyValue)
|
||||
}?;
|
||||
|
||||
Ok(Self::PrivateForeign {
|
||||
npk: NullifierPublicKey(value.nullifier_public_key.data),
|
||||
vpk,
|
||||
identifier: value.identifier.into(),
|
||||
})
|
||||
}
|
||||
FfiAccountIdentityKind::PrivatePdaOwned => {
|
||||
Ok(Self::PrivatePdaOwned(value.account_id.into()))
|
||||
}
|
||||
FfiAccountIdentityKind::PrivatePdaForeign => {
|
||||
let vpk = if value.viewing_public_key_len == 1184 {
|
||||
let slice = unsafe {
|
||||
slice::from_raw_parts(
|
||||
value.viewing_public_key,
|
||||
value.viewing_public_key_len,
|
||||
)
|
||||
};
|
||||
Ok(MlKem768EncapsulationKey::from_bytes(slice.to_vec())
|
||||
.map_err(|_| WalletFfiError::InvalidKeyValue)?)
|
||||
} else {
|
||||
Err(WalletFfiError::InvalidKeyValue)
|
||||
}?;
|
||||
|
||||
Ok(Self::PrivatePdaForeign {
|
||||
account_id: value.account_id.into(),
|
||||
npk: NullifierPublicKey(value.nullifier_public_key.data),
|
||||
vpk,
|
||||
identifier: value.identifier.into(),
|
||||
})
|
||||
}
|
||||
FfiAccountIdentityKind::PrivateShared => {
|
||||
let vpk = if value.viewing_public_key_len == 1184 {
|
||||
let slice = unsafe {
|
||||
slice::from_raw_parts(
|
||||
value.viewing_public_key,
|
||||
value.viewing_public_key_len,
|
||||
)
|
||||
};
|
||||
Ok(MlKem768EncapsulationKey::from_bytes(slice.to_vec())
|
||||
.map_err(|_| WalletFfiError::InvalidKeyValue)?)
|
||||
} else {
|
||||
Err(WalletFfiError::InvalidKeyValue)
|
||||
}?;
|
||||
|
||||
Ok(Self::PrivateShared {
|
||||
nsk: value.nullifier_secret_key.data,
|
||||
npk: NullifierPublicKey(value.nullifier_public_key.data),
|
||||
vpk,
|
||||
identifier: value.identifier.into(),
|
||||
})
|
||||
}
|
||||
FfiAccountIdentityKind::PrivatePdaShared => {
|
||||
let vpk = if value.viewing_public_key_len == 1184 {
|
||||
let slice = unsafe {
|
||||
slice::from_raw_parts(
|
||||
value.viewing_public_key,
|
||||
value.viewing_public_key_len,
|
||||
)
|
||||
};
|
||||
Ok(MlKem768EncapsulationKey::from_bytes(slice.to_vec())
|
||||
.map_err(|_| WalletFfiError::InvalidKeyValue)?)
|
||||
} else {
|
||||
Err(WalletFfiError::InvalidKeyValue)
|
||||
}?;
|
||||
|
||||
Ok(Self::PrivatePdaShared {
|
||||
account_id: value.account_id.into(),
|
||||
nsk: value.nullifier_secret_key.data,
|
||||
npk: NullifierPublicKey(value.nullifier_public_key.data),
|
||||
vpk,
|
||||
identifier: value.identifier.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use lee::{AccountId, PrivateKey, PublicKey};
|
||||
use lee_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_seed(&[44; 32], &[54; 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_2_5 = AccountIdentity::PublicKeycard {
|
||||
account_id: pub_acc_id,
|
||||
key_path: "path/to/key".to_owned(),
|
||||
};
|
||||
|
||||
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_2_5: FfiAccountIdentity = acc_identity_2_5.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_2_5.kind,
|
||||
FfiAccountIdentityKind::PublicKeycard
|
||||
);
|
||||
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_2_5: AccountIdentity = (&ffi_acc_identity_2_5).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_2_5, acc_identity_2_5);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,12 +103,31 @@ typedef enum WalletFfiError {
|
||||
* Invalid Key value.
|
||||
*/
|
||||
INVALID_KEY_VALUE = 16,
|
||||
/**
|
||||
* Invalid program bytecode.
|
||||
*/
|
||||
INVALID_BYTECODE = 17,
|
||||
/**
|
||||
* Internal error (catch-all).
|
||||
*/
|
||||
INTERNAL_ERROR = 99,
|
||||
} WalletFfiError;
|
||||
|
||||
/**
|
||||
* Enumeration to represent kinds of `FfiAccountIdentity`.
|
||||
*/
|
||||
typedef enum FfiAccountIdentityKind {
|
||||
PUBLIC = 0,
|
||||
PUBLIC_NO_SIGN = 1,
|
||||
PUBLIC_KEYCARD = 2,
|
||||
PRIVATE_OWNED = 3,
|
||||
PRIVATE_FOREIGN = 4,
|
||||
PRIVATE_PDA_OWNED = 5,
|
||||
PRIVATE_PDA_FOREIGN = 6,
|
||||
PRIVATE_SHARED = 7,
|
||||
PRIVATE_PDA_SHARED = 8,
|
||||
} FfiAccountIdentityKind;
|
||||
|
||||
/**
|
||||
* Opaque pointer to the Wallet instance.
|
||||
*
|
||||
@ -200,6 +219,65 @@ typedef struct FfiAccount {
|
||||
struct FfiU128 nonce;
|
||||
} FfiAccount;
|
||||
|
||||
typedef struct FfiInstructionWords {
|
||||
uint32_t *instruction_words;
|
||||
uintptr_t instruction_words_size;
|
||||
enum WalletFfiError error;
|
||||
} FfiInstructionWords;
|
||||
|
||||
/**
|
||||
* Struct representing an account identity, given to `AccountManager` at intialization.
|
||||
*/
|
||||
typedef struct FfiAccountIdentity {
|
||||
enum FfiAccountIdentityKind kind;
|
||||
struct FfiBytes32 account_id;
|
||||
/**
|
||||
* C-compatible string.
|
||||
*/
|
||||
char *key_path;
|
||||
struct FfiBytes32 nullifier_secret_key;
|
||||
struct FfiBytes32 nullifier_public_key;
|
||||
const uint8_t *viewing_public_key;
|
||||
uintptr_t viewing_public_key_len;
|
||||
struct FfiU128 identifier;
|
||||
} FfiAccountIdentity;
|
||||
|
||||
/**
|
||||
* Intended to be created manually.
|
||||
*/
|
||||
typedef struct FfiProgram {
|
||||
const uint8_t *elf_data;
|
||||
uintptr_t elf_size;
|
||||
} FfiProgram;
|
||||
|
||||
/**
|
||||
* Intended to be created manually.
|
||||
*/
|
||||
typedef struct FfiProgramWithDependencies {
|
||||
struct FfiProgram program;
|
||||
const struct FfiProgram *deps;
|
||||
uintptr_t deps_size;
|
||||
} FfiProgramWithDependencies;
|
||||
|
||||
/**
|
||||
* Result of a generic transaction operation.
|
||||
*/
|
||||
typedef struct FfiTransactionResult {
|
||||
/**
|
||||
* Transaction hash (null-terminated string, or null on failure).
|
||||
*/
|
||||
char *tx_hash;
|
||||
/**
|
||||
* Whether the transaction succeeded.
|
||||
*/
|
||||
bool success;
|
||||
const struct FfiBytes32 *secrets_data;
|
||||
/**
|
||||
* Public transactions have 0 secrets.
|
||||
*/
|
||||
uintptr_t secrets_size;
|
||||
} FfiTransactionResult;
|
||||
|
||||
/**
|
||||
* Public key info for a public account.
|
||||
*/
|
||||
@ -454,6 +532,94 @@ enum WalletFfiError wallet_ffi_import_private_account(struct WalletHandle *handl
|
||||
const struct FfiU128 *identifier,
|
||||
const char *account_state_json);
|
||||
|
||||
/**
|
||||
* Serialize sequence of bytes into RISC0 readable words.
|
||||
*
|
||||
* # Parameters
|
||||
* - `input_instruction_data`: Valid pointer to a sequence of bytes
|
||||
* - `input_instruction_data_size`: Size of `input_instruction_data`
|
||||
*
|
||||
* # Returns
|
||||
* - `Success` on successful creation
|
||||
* - Error code on failure
|
||||
*
|
||||
* # Safety
|
||||
* - `input_instruction_data` must be a valid pointer
|
||||
*/
|
||||
struct FfiInstructionWords wallet_ffi_serialization_helper(const uint8_t *input_instruction_data,
|
||||
uintptr_t input_instruction_data_size);
|
||||
|
||||
/**
|
||||
* Send generic public transaction.
|
||||
*
|
||||
* # Parameters
|
||||
* - `handle`: Valid pointer to wallet handle
|
||||
* - `account_identities`: Valid pointer to list of `FfiAccountIdentity`
|
||||
* - `instruction_words`: Valid pointer to instruction words
|
||||
* - `out_result`: Valid pointer to `FfiTransactionResult`
|
||||
*
|
||||
* # Returns
|
||||
* - `Success` on successful creation
|
||||
* - Error code on failure
|
||||
*
|
||||
* # Safety
|
||||
* - `handle` must be a valid pointer
|
||||
* - `account_identities` must be a valid pointer
|
||||
* - `instruction_words` must be a valid pointer
|
||||
* - `out_result` must be a valid pointer
|
||||
*/
|
||||
enum WalletFfiError wallet_ffi_send_generic_public_transaction(struct WalletHandle *handle,
|
||||
const struct FfiAccountIdentity *account_identities,
|
||||
uintptr_t account_identities_size,
|
||||
const uint32_t *instruction_words,
|
||||
uintptr_t instruction_words_size,
|
||||
const struct FfiProgramWithDependencies *program_with_dependencies,
|
||||
struct FfiTransactionResult *out_result);
|
||||
|
||||
/**
|
||||
* Send generic private transaction.
|
||||
*
|
||||
* # Parameters
|
||||
* - `handle`: Valid pointer to wallet handle
|
||||
* - `account_identities`: Valid pointer to list of `FfiAccountIdentity`
|
||||
* - `instruction_words`: Valid pointer to instruction words
|
||||
* - `out_result`: Valid pointer to `FfiTransactionResult`
|
||||
*
|
||||
* # Returns
|
||||
* - `Success` on successful creation
|
||||
* - Error code on failure
|
||||
*
|
||||
* # Safety
|
||||
* - `handle` must be a valid pointer
|
||||
* - `account_identities` must be a valid pointer
|
||||
* - `instruction_words` must be a valid pointer
|
||||
* - `out_result` must be a valid pointer
|
||||
*/
|
||||
enum WalletFfiError wallet_ffi_send_generic_private_transaction(struct WalletHandle *handle,
|
||||
const struct FfiAccountIdentity *account_identities,
|
||||
uintptr_t account_identities_size,
|
||||
const uint32_t *instruction_words,
|
||||
uintptr_t instruction_words_size,
|
||||
const struct FfiProgramWithDependencies *program_with_dependencies,
|
||||
struct FfiTransactionResult *out_result);
|
||||
|
||||
/**
|
||||
* Free a transaction result returned by `wallet_ffi_send_generic_public_transaction` or
|
||||
* `wallet_ffi_send_generic_private_transaction`.
|
||||
*
|
||||
* # Safety
|
||||
* The result must be either null or a valid result from a transaction function.
|
||||
*/
|
||||
void wallet_ffi_free_transaction_result(struct FfiTransactionResult *result);
|
||||
|
||||
/**
|
||||
* Free a instruction words returned by `wallet_ffi_serialization_helper`.
|
||||
*
|
||||
* # Safety
|
||||
* The result must be either null or a valid result from a serialization helper function.
|
||||
*/
|
||||
void wallet_ffi_free_instruction_words(struct FfiInstructionWords *words);
|
||||
|
||||
/**
|
||||
* Get the public key for a public account.
|
||||
*
|
||||
@ -552,6 +718,55 @@ char *wallet_ffi_account_id_to_base58(const struct FfiBytes32 *account_id);
|
||||
enum WalletFfiError wallet_ffi_account_id_from_base58(const char *base58_str,
|
||||
struct FfiBytes32 *out_account_id);
|
||||
|
||||
/**
|
||||
* Resolve public account.
|
||||
*
|
||||
* # Parameters
|
||||
* - `account_id`: 32 bytes of the public account ID
|
||||
* - `needs_sign`: whether the account needs signing
|
||||
* - `out_account_identity`: valid pointer, where output will be written
|
||||
*
|
||||
* # Returns
|
||||
* - `Success` on successful retrieval
|
||||
*
|
||||
* # Safety
|
||||
* - `out_account_identity` must be a valid pointer to a `FfiAccountIdentity` struct
|
||||
*/
|
||||
enum WalletFfiError wallet_ffi_resolve_public_account(struct FfiBytes32 account_id,
|
||||
bool needs_sign,
|
||||
struct FfiAccountIdentity *out_account_identity);
|
||||
|
||||
/**
|
||||
* Resolve private account.
|
||||
*
|
||||
* # Parameters
|
||||
* - `handle`: Valid wallet handle
|
||||
* - `account_id`: 32 bytes of the public account ID
|
||||
* - `out_account_identity`: valid pointer, where output will be written
|
||||
*
|
||||
* # Returns
|
||||
* - `Success` on successful retrieval
|
||||
* - `InternalError` if failed to lock wallet
|
||||
* - `AccountNotFound` if the account is not found
|
||||
*
|
||||
* # Safety
|
||||
* - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
|
||||
* - `out_account_identity` must be a valid pointer to a `FfiAccountIdentity` struct
|
||||
*/
|
||||
enum WalletFfiError wallet_ffi_resolve_private_account(struct WalletHandle *handle,
|
||||
struct FfiBytes32 account_id,
|
||||
struct FfiAccountIdentity *out_account_identity);
|
||||
|
||||
/**
|
||||
* Free account identity returned by `wallet_ffi_resolve_private_account` or
|
||||
* `wallet_ffi_resolve_public_account`.
|
||||
*
|
||||
* # Safety
|
||||
* The account must be either null or a valid account returned by
|
||||
* `wallet_ffi_resolve_private_account` or `wallet_ffi_resolve_public_account`.
|
||||
*/
|
||||
void wallet_ffi_free_account_identity(struct FfiAccountIdentity *account_identity);
|
||||
|
||||
/**
|
||||
* Claim a pinata reward using a public transaction.
|
||||
*
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
use core::fmt;
|
||||
|
||||
use anyhow::Result;
|
||||
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
|
||||
use keycard_wallet::{KeycardWallet, python_path};
|
||||
@ -11,7 +13,7 @@ use lee_core::{
|
||||
|
||||
use crate::{ExecutionFailureKind, WalletCore};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum AccountIdentity {
|
||||
Public(AccountId),
|
||||
/// A public account without signing. Would not try to sign, even if account is owned.
|
||||
@ -58,6 +60,73 @@ pub enum AccountIdentity {
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Debug for AccountIdentity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Public(id) => f.debug_tuple("Public").field(id).finish(),
|
||||
Self::PublicNoSign(id) => f.debug_tuple("PublicNoSign").field(id).finish(),
|
||||
Self::PublicKeycard {
|
||||
account_id,
|
||||
key_path: _,
|
||||
} => f
|
||||
.debug_struct("PublicKeycard")
|
||||
.field("account_id", account_id)
|
||||
.field("key_path", &"<redacted>")
|
||||
.finish(),
|
||||
Self::PrivateOwned(id) => f.debug_tuple("PrivateOwned").field(id).finish(),
|
||||
Self::PrivateForeign {
|
||||
npk,
|
||||
vpk,
|
||||
identifier,
|
||||
} => f
|
||||
.debug_struct("PrivateForeign")
|
||||
.field("npk", npk)
|
||||
.field("vpk", vpk)
|
||||
.field("identifier", identifier)
|
||||
.finish(),
|
||||
Self::PrivatePdaOwned(id) => f.debug_tuple("PrivatePdaOwned").field(id).finish(),
|
||||
Self::PrivatePdaForeign {
|
||||
account_id,
|
||||
npk,
|
||||
vpk,
|
||||
identifier,
|
||||
} => f
|
||||
.debug_struct("PrivatePdaForeign")
|
||||
.field("account_id", account_id)
|
||||
.field("npk", npk)
|
||||
.field("vpk", vpk)
|
||||
.field("identifier", identifier)
|
||||
.finish(),
|
||||
Self::PrivateShared {
|
||||
npk,
|
||||
vpk,
|
||||
identifier,
|
||||
..
|
||||
} => f
|
||||
.debug_struct("PrivateShared")
|
||||
.field("nsk", &"<redacted>")
|
||||
.field("npk", npk)
|
||||
.field("vpk", vpk)
|
||||
.field("identifier", identifier)
|
||||
.finish(),
|
||||
Self::PrivatePdaShared {
|
||||
account_id,
|
||||
npk,
|
||||
vpk,
|
||||
identifier,
|
||||
..
|
||||
} => f
|
||||
.debug_struct("PrivatePdaShared")
|
||||
.field("account_id", account_id)
|
||||
.field("nsk", &"<redacted>")
|
||||
.field("npk", npk)
|
||||
.field("vpk", vpk)
|
||||
.field("identifier", identifier)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AccountIdentity {
|
||||
#[must_use]
|
||||
/// Note: `PublicNoSign` still counts as public, the variant just suppresses the signing-key
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user