diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index be59c721a4..311e3c44e5 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -74,6 +74,7 @@ extern crate lazy_static; use core::ptr; use std::{ boxed::Box, + convert::TryFrom, ffi::{CStr, CString}, num::NonZeroU16, path::PathBuf, @@ -113,7 +114,18 @@ use tari_comms::{ types::{CommsPublicKey, CommsSecretKey}, }; use tari_comms_dht::{store_forward::SafConfig, DbConnectionUrl, DhtConfig}; -use tari_core::transactions::{tari_amount::MicroTari, CryptoFactories}; +use tari_core::transactions::{ + tari_amount::MicroTari, + transaction_components::{ + AssetOutputFeatures, + CommitteeDefinitionFeatures, + MintNonFungibleFeatures, + OutputFeaturesVersion, + OutputFlags, + SideChainCheckpointFeatures, + }, + CryptoFactories, +}; use tari_crypto::{ keys::{PublicKey as PublicKeyTrait, SecretKey}, tari_utilities::ByteArray, @@ -979,6 +991,138 @@ pub unsafe extern "C" fn commitment_signature_destroy(com_sig: *mut TariCommitme /// -------------------------------------------------------------------------------------------- /// +/// ---------------------------------- Output Features ------------------------------------------/// + +/// Creates a TariOutputFeatures from byte values +/// +/// ## Arguments +/// `version` - The encoded value of the version as a byte +/// `flags` - The encoded value of the flags as a byte +/// `maturity` - The encoded value maturity as bytes +/// `recovery_byte` - The encoded value of the recovery byte as a byte +/// `metadata` - The metadata componenet as a ByteVector. It cannot be null +/// `unique_id` - The unique id componenet as a ByteVector. It can be null +/// `mparent_public_key` - The parent public key component as a ByteVector. It can be null +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// +/// ## Returns +/// `TariOutputFeatures` - Returns an output features object. Note that it will be ptr::null_mut() if any mandatory +/// arguments are null or if there was an error with the contents of bytes +/// +/// # Safety +/// The ```output_features_destroy``` function must be called when finished with a TariOutputFeatures to +/// prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn output_features_create_from_bytes( + version: c_uchar, + flags: c_uchar, + maturity: c_ulonglong, + recovery_byte: c_uchar, + metadata: *const ByteVector, + unique_id: *const ByteVector, + parent_public_key: *const ByteVector, + error_out: *mut c_int, +) -> *mut TariOutputFeatures { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if metadata.is_null() { + error = LibWalletError::from(InterfaceError::NullError("metadata".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + + let decoded_version = match OutputFeaturesVersion::try_from(version) { + Ok(v) => v, + Err(message) => { + error!( + target: LOG_TARGET, + "Error creating a OutputFeaturesVersion: {:?}", message + ); + error = LibWalletError::from(InterfaceError::InvalidArgument("version".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + + let decoded_flags = match OutputFlags::from_bits(flags) { + Some(flags_value) => flags_value, + None => { + error!( + target: LOG_TARGET, + "Error creating a OutputFlags from bytes: {:?}", flags + ); + error = LibWalletError::from(InterfaceError::InvalidArgument("flags".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + + let decoded_metadata = (*metadata).0.clone(); + + let mut decoded_unique_id = None; + if !unique_id.is_null() { + decoded_unique_id = Some((*unique_id).0.clone()); + } + + let mut decoded_parent_public_key: Option = None; + if !parent_public_key.is_null() { + decoded_parent_public_key = match TariPublicKey::from_bytes(&(*parent_public_key).0.clone()) { + Ok(k) => Some(k), + Err(e) => { + error!( + target: LOG_TARGET, + "Error creating a Private Key (u) from bytes: {:?}", e + ); + error = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + } + + // DAN layer features are still a work in progress + // so, for now, we do not expose any of those fields + let asset: Option = None; + let mint_non_fungible: Option = None; + let sidechain_checkpoint: Option = None; + let committee_definition: Option = None; + + let output_features = TariOutputFeatures::new( + decoded_version, + decoded_flags, + maturity, + recovery_byte, + decoded_metadata, + decoded_unique_id, + decoded_parent_public_key, + asset, + mint_non_fungible, + sidechain_checkpoint, + committee_definition, + ); + Box::into_raw(Box::new(output_features)) +} + +/// Frees memory for a TariOutputFeatures +/// +/// ## Arguments +/// `output_features` - The pointer to a TariOutputFeatures +/// +/// ## Returns +/// `()` - Does not return a value, equivalent to void in C +/// +/// # Safety +/// None +#[no_mangle] +pub unsafe extern "C" fn output_features_destroy(output_features: *mut TariOutputFeatures) { + if !output_features.is_null() { + Box::from_raw(output_features); + } +} + +/// -------------------------------------------------------------------------------------------- /// + /// ----------------------------------- Seed Words ----------------------------------------------/// /// Create an empty instance of TariSeedWords @@ -6950,6 +7094,102 @@ mod test { } } + #[test] + fn test_output_features_create_empty() { + unsafe { + let mut error = 0; + let error_ptr = &mut error as *mut c_int; + + let version: c_uchar = 0; + let flags: c_uchar = 0; + let maturity: c_ulonglong = 20; + let recovery_byte: c_uchar = 1; + let metadata = Box::into_raw(Box::new(ByteVector(Vec::new()))); + let unique_id = ptr::null_mut(); + let parent_public_key = ptr::null_mut(); + + let output_features = output_features_create_from_bytes( + version, + flags, + maturity, + recovery_byte, + metadata, + unique_id, + parent_public_key, + error_ptr, + ); + assert_eq!(error, 0); + assert_eq!((*output_features).version, OutputFeaturesVersion::V0); + assert_eq!((*output_features).flags, OutputFlags::from_bits(flags).unwrap()); + assert_eq!((*output_features).maturity, maturity); + assert_eq!((*output_features).recovery_byte, recovery_byte); + assert!((*output_features).metadata.is_empty()); + assert!((*output_features).unique_id.is_none()); + assert!((*output_features).parent_public_key.is_none()); + + // These are DAN layer fields, we omit them + assert!((*output_features).asset.is_none()); + assert!((*output_features).mint_non_fungible.is_none()); + assert!((*output_features).sidechain_checkpoint.is_none()); + assert!((*output_features).committee_definition.is_none()); + + output_features_destroy(output_features); + byte_vector_destroy(metadata); + } + } + + #[test] + fn test_output_features_create_filled() { + unsafe { + let mut error = 0; + let error_ptr = &mut error as *mut c_int; + + let version: c_uchar = OutputFeaturesVersion::V1.as_u8(); + let flags: c_uchar = OutputFlags::COINBASE_OUTPUT.bits(); + let maturity: c_ulonglong = 20; + let recovery_byte: c_uchar = 1; + + let expected_metadata = vec![1; 1024]; + let metadata = Box::into_raw(Box::new(ByteVector(expected_metadata.clone()))); + + let expected_unique_id = vec![0u8; 256]; + let unique_id = Box::into_raw(Box::new(ByteVector(expected_unique_id.clone()))); + + let (_, public_key) = PublicKey::random_keypair(&mut OsRng); + let parent_public_key = Box::into_raw(Box::new(ByteVector(public_key.to_vec()))); + + let output_features = output_features_create_from_bytes( + version, + flags, + maturity, + recovery_byte, + metadata, + unique_id, + parent_public_key, + error_ptr, + ); + assert_eq!(error, 0); + assert_eq!((*output_features).version, OutputFeaturesVersion::V1); + assert_eq!((*output_features).flags, OutputFlags::from_bits(flags).unwrap()); + assert_eq!((*output_features).maturity, maturity); + assert_eq!((*output_features).recovery_byte, recovery_byte); + assert_eq!((*output_features).metadata, expected_metadata); + assert_eq!((*output_features).unique_id, Some(expected_unique_id)); + assert_eq!((*output_features).parent_public_key, Some(public_key)); + + // These are DAN layer fields, we omit them + assert!((*output_features).asset.is_none()); + assert!((*output_features).mint_non_fungible.is_none()); + assert!((*output_features).sidechain_checkpoint.is_none()); + assert!((*output_features).committee_definition.is_none()); + + output_features_destroy(output_features); + byte_vector_destroy(metadata); + byte_vector_destroy(unique_id); + byte_vector_destroy(parent_public_key); + } + } + #[test] fn test_keys_dont_panic() { unsafe { diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 660a25f7e2..a973d97ce9 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -189,6 +189,23 @@ struct TariCommitmentSignature *commitment_signature_create_from_bytes( // Frees memory for a TariCommitmentSignature void commitment_signature_destroy(struct TariCommitmentSignature *com_sig); +/// -------------------------------- Output Features --------------------------------------------- /// + +// Creates a TariOutputFeatures from byte values +struct TariOutputFeatures *output_features_create_from_bytes( + unsigned char version, + unsigned char flags, + unsigned long long maturity, + unsigned char recovery_byte, + struct ByteVector *metadata, + struct ByteVector *unique_id, + struct ByteVector *parent_public_key, + int *error_out +); + +// Frees memory for a TariOutputFeatures +void output_features_destroy(struct TariOutputFeatures *output_features); + /// -------------------------------- Seed Words -------------------------------------------------- /// // Create an empty instance of TariSeedWords struct TariSeedWords *seed_words_create();