diff --git a/key-wallet-ffi/Cargo.toml b/key-wallet-ffi/Cargo.toml index 0b0fbd7fb..aa6f30083 100644 --- a/key-wallet-ffi/Cargo.toml +++ b/key-wallet-ffi/Cargo.toml @@ -13,8 +13,7 @@ name = "key_wallet_ffi" crate-type = ["cdylib", "staticlib", "lib"] [features] -default = ["bincode", "eddsa", "bls", "bip38"] -bip38 = ["key-wallet/bip38"] +default = ["bincode", "eddsa", "bls"] bincode = ["key-wallet-manager/bincode", "key-wallet/bincode"] eddsa = ["dashcore/eddsa", "key-wallet/eddsa"] bls = ["dashcore/bls", "key-wallet/bls"] diff --git a/key-wallet-ffi/include/key_wallet_ffi.h b/key-wallet-ffi/include/key_wallet_ffi.h index c45d2a740..e2cea150d 100644 --- a/key-wallet-ffi/include/key_wallet_ffi.h +++ b/key-wallet-ffi/include/key_wallet_ffi.h @@ -102,16 +102,6 @@ typedef enum { ASSET_LOCK_SHIELDED_ADDRESS_TOP_UP = 15, } FFIAccountType; -/* - FFI Network type (single network) - */ -typedef enum { - MAINNET = 0, - TESTNET = 1, - REGTEST = 2, - DEVNET = 3, -} FFINetwork; - /* FFI Error code */ @@ -131,6 +121,16 @@ typedef enum { INTERNAL_ERROR = 12, } FFIErrorCode; +/* + FFI Network type (single network) + */ +typedef enum { + MAINNET = 0, + TESTNET = 1, + REGTEST = 2, + DEVNET = 3, +} FFINetwork; + /* Address pool type */ @@ -256,11 +256,6 @@ typedef struct FFIExtendedPrivateKey FFIExtendedPrivateKey; */ typedef struct FFIExtendedPubKey FFIExtendedPubKey; -/* - Opaque type for an extended public key - */ -typedef struct FFIExtendedPublicKey FFIExtendedPublicKey; - /* Opaque managed account handle that wraps ManagedAccount */ @@ -271,32 +266,6 @@ typedef struct FFIManagedCoreAccount FFIManagedCoreAccount; */ typedef struct FFIManagedCoreAccountCollection FFIManagedCoreAccountCollection; -/* - Opaque managed platform account handle that wraps ManagedPlatformAccount - - This is different from FFIManagedCoreAccount because ManagedPlatformAccount - has a different structure optimized for Platform Payment accounts (DIP-17): - - Simple u64 credit balance instead of WalletCoreBalance - - Per-address balances tracked directly - - No transactions or UTXOs (Platform handles these) - */ -typedef struct FFIManagedPlatformAccount FFIManagedPlatformAccount; - -/* - Opaque type for a private key (SecretKey) - */ -typedef struct FFIPrivateKey FFIPrivateKey; - -/* - Opaque type for a public key - */ -typedef struct FFIPublicKey FFIPublicKey; - -/* - Opaque handle for a transaction - */ -typedef struct FFITransaction FFITransaction; - /* Opaque wallet handle */ @@ -588,24 +557,6 @@ typedef struct { bool is_ours; } FFITransactionRecord; -/* - FFI Result type for ManagedPlatformAccount operations - */ -typedef struct { - /* - The managed platform account handle if successful, NULL if error - */ - FFIManagedPlatformAccount *account; - /* - Error code (0 = success) - */ - int32_t error_code; - /* - Error message (NULL if success, must be freed by caller if not NULL) - */ - char *error_message; -} FFIManagedPlatformAccountResult; - /* C-compatible platform payment account key */ @@ -728,50 +679,6 @@ typedef struct { uint32_t affected_accounts_count; } FFITransactionCheckResult; -/* - FFI-compatible transaction input - */ -typedef struct { - /* - Transaction ID (32 bytes) - */ - uint8_t txid[32]; - /* - Output index - */ - uint32_t vout; - /* - Script signature length - */ - uint32_t script_sig_len; - /* - Script signature data pointer - */ - const uint8_t *script_sig; - /* - Sequence number - */ - uint32_t sequence; -} FFITxIn; - -/* - FFI-compatible transaction output - */ -typedef struct { - /* - Amount in duffs - */ - uint64_t amount; - /* - Script pubkey length - */ - uint32_t script_pubkey_len; - /* - Script pubkey data pointer - */ - const uint8_t *script_pubkey; -} FFITxOut; - /* UTXO structure for FFI */ @@ -960,896 +867,248 @@ FFIAccountResult wallet_get_top_up_account_with_registration_index(const FFIWall void account_result_free_error(FFIAccountResult *result) ; /* - Get the extended public key of an account as a string + Get number of accounts # Safety - - `account` must be a valid pointer to an FFIAccount instance - - The returned string must be freed by the caller using `string_free` - - Returns NULL if the account is null + - `wallet` must be a valid pointer to an FFIWallet instance + - `error` must be a valid pointer to an FFIError structure or null + - The caller must ensure both pointers remain valid for the duration of this call */ - char *account_get_extended_public_key_as_string(const FFIAccount *account) ; + unsigned int wallet_get_account_count(const FFIWallet *wallet, FFIError *error) ; /* - Get the network of an account + Get account collection for a specific network from wallet # Safety - - `account` must be a valid pointer to an FFIAccount instance - - Returns `FFINetwork::Mainnet` if the account is null + - `wallet` must be a valid pointer to an FFIWallet instance + - `error` must be a valid pointer to an FFIError structure or null + - The returned pointer must be freed with `account_collection_free` when no longer needed */ - FFINetwork account_get_network(const FFIAccount *account) ; + FFIAccountCollection *wallet_get_account_collection(const FFIWallet *wallet, FFIError *error) ; /* - Get the parent wallet ID of an account + Free an account collection handle # Safety - - `account` must be a valid pointer to an FFIAccount instance - - Returns a pointer to the 32-byte wallet ID, or NULL if not set or account is null - - The returned pointer is valid only as long as the account exists - - The caller should copy the data if needed for longer use + - `collection` must be a valid pointer to an FFIAccountCollection created by this library + - `collection` must not be used after calling this function */ - const uint8_t *account_get_parent_wallet_id(const FFIAccount *account) ; + void account_collection_free(FFIAccountCollection *collection) ; /* - Get the account type of an account + Get the provider operator keys account if it exists + Note: Returns null if the `bls` feature is not enabled # Safety - - `account` must be a valid pointer to an FFIAccount instance - - `out_index` must be a valid pointer to a c_uint where the index will be stored - - Returns FFIAccountType::StandardBIP44 with index 0 if the account is null + - `collection` must be a valid pointer to an FFIAccountCollection + - The returned pointer must be freed with `bls_account_free` when no longer needed (when BLS is enabled) */ - FFIAccountType account_get_account_type(const FFIAccount *account, unsigned int *out_index) ; - -/* - Check if an account is watch-only - - # Safety - - `account` must be a valid pointer to an FFIAccount instance - - Returns false if the account is null - */ - bool account_get_is_watch_only(const FFIAccount *account) ; +void *account_collection_get_provider_operator_keys(const FFIAccountCollection *collection) +; /* - Get the extended public key of a BLS account as a string - - # Safety - - - `account` must be a valid pointer to an FFIBLSAccount instance - - The returned string must be freed by the caller using `string_free` - - Returns NULL if the account is null - */ - char *bls_account_get_extended_public_key_as_string(const FFIBLSAccount *account) ; + Get structured account collection summary data -/* - Get the network of a BLS account + Returns a struct containing arrays of indices for each account type and boolean + flags for special accounts. This provides Swift with programmatic access to + account information. # Safety - - `account` must be a valid pointer to an FFIBLSAccount instance - - Returns `FFINetwork::Mainnet` if the account is null + - `collection` must be a valid pointer to an FFIAccountCollection + - The returned pointer must be freed with `account_collection_summary_free` when no longer needed + - Returns null if the collection pointer is null */ - FFINetwork bls_account_get_network(const FFIBLSAccount *account) ; - -/* - Get the parent wallet ID of a BLS account - - # Safety - - `account` must be a valid pointer to an FFIBLSAccount instance - - Returns a pointer to the 32-byte wallet ID, or NULL if not set or account is null - - The returned pointer is valid only as long as the account exists - - The caller should copy the data if needed for longer use - */ - const uint8_t *bls_account_get_parent_wallet_id(const FFIBLSAccount *account) ; +FFIAccountCollectionSummary *account_collection_summary_data(const FFIAccountCollection *collection) +; /* - Get the account type of a BLS account + Free an account collection summary and all its allocated memory # Safety - - `account` must be a valid pointer to an FFIBLSAccount instance - - `out_index` must be a valid pointer to a c_uint where the index will be stored - - Returns FFIAccountType::StandardBIP44 with index 0 if the account is null + - `summary` must be a valid pointer to an FFIAccountCollectionSummary created by `account_collection_summary_data` + - `summary` must not be used after calling this function */ -FFIAccountType bls_account_get_account_type(const FFIBLSAccount *account, - unsigned int *out_index) +void account_collection_summary_free(FFIAccountCollectionSummary *summary) ; /* - Check if a BLS account is watch-only + Derive a private key from an account at a given chain/index and return as WIF string. + Caller must free the returned string with `string_free`. # Safety - - - `account` must be a valid pointer to an FFIBLSAccount instance - - Returns false if the account is null + - `account` and `master_xpriv` must be valid pointers allocated by this library + - `error` must be a valid pointer to an FFIError or null */ - bool bls_account_get_is_watch_only(const FFIBLSAccount *account) ; - -/* - Get the extended public key of an EdDSA account as a string - - # Safety - - `account` must be a valid pointer to an FFIEdDSAAccount instance - - The returned string must be freed by the caller using `string_free` - - Returns NULL if the account is null - */ - char *eddsa_account_get_extended_public_key_as_string(const FFIEdDSAAccount *account) ; +char *account_derive_private_key_as_wif_at(const FFIAccount *account, + const FFIExtendedPrivateKey *master_xpriv, + unsigned int index, + FFIError *error) +; /* - Get the network of an EdDSA account + Free address string # Safety - - `account` must be a valid pointer to an FFIEdDSAAccount instance - - Returns `FFINetwork::Mainnet` if the account is null + - `address` must be a valid pointer created by address functions or null + - After calling this function, the pointer becomes invalid */ - FFINetwork eddsa_account_get_network(const FFIEdDSAAccount *account) ; + void address_free(char *address) ; /* - Get the parent wallet ID of an EdDSA account + Free address array # Safety - - `account` must be a valid pointer to an FFIEdDSAAccount instance - - Returns a pointer to the 32-byte wallet ID, or NULL if not set or account is null - - The returned pointer is valid only as long as the account exists - - The caller should copy the data if needed for longer use + - `addresses` must be a valid pointer to an array of address strings or null + - Each address in the array must be a valid C string pointer + - `count` must be the correct number of addresses in the array + - After calling this function, all pointers become invalid */ - const uint8_t *eddsa_account_get_parent_wallet_id(const FFIEdDSAAccount *account) ; + void address_array_free(char **addresses, size_t count) ; /* - Get the account type of an EdDSA account + Validate an address # Safety - - `account` must be a valid pointer to an FFIEdDSAAccount instance - - `out_index` must be a valid pointer to a c_uint where the index will be stored - - Returns FFIAccountType::StandardBIP44 with index 0 if the account is null + - `address` must be a valid null-terminated C string + - `error` must be a valid pointer to an FFIError */ - -FFIAccountType eddsa_account_get_account_type(const FFIEdDSAAccount *account, - unsigned int *out_index) -; + bool address_validate(const char *address, FFINetwork network, FFIError *error) ; /* - Check if an EdDSA account is watch-only + Get address type + + Returns: + - 0: P2PKH address + - 1: P2SH address + - 2: Other address type + - u8::MAX (255): Error occurred # Safety - - `account` must be a valid pointer to an FFIEdDSAAccount instance - - Returns false if the account is null + - `address` must be a valid null-terminated C string + - `error` must be a valid pointer to an FFIError */ - bool eddsa_account_get_is_watch_only(const FFIEdDSAAccount *account) ; + unsigned char address_get_type(const char *address, FFINetwork network, FFIError *error) ; /* - Get number of accounts + Free an address pool handle # Safety - - `wallet` must be a valid pointer to an FFIWallet instance - - `error` must be a valid pointer to an FFIError structure or null - - The caller must ensure both pointers remain valid for the duration of this call + - `pool` must be a valid pointer to an FFIAddressPool that was allocated by this library + - The pointer must not be used after calling this function + - This function must only be called once per allocation */ - unsigned int wallet_get_account_count(const FFIWallet *wallet, FFIError *error) ; + void address_pool_free(FFIAddressPool *pool) ; /* - Get account collection for a specific network from wallet + Get address pool information for an account # Safety - - `wallet` must be a valid pointer to an FFIWallet instance - - `error` must be a valid pointer to an FFIError structure or null - - The returned pointer must be freed with `account_collection_free` when no longer needed + - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo + - `info_out` must be a valid pointer to store the pool info + - `error` must be a valid pointer to an FFIError or null */ - FFIAccountCollection *wallet_get_account_collection(const FFIWallet *wallet, FFIError *error) ; + +bool managed_wallet_get_address_pool_info(const FFIManagedWalletInfo *managed_wallet, + FFIAccountType account_type, + unsigned int account_index, + FFIAddressPoolType pool_type, + FFIAddressPoolInfo *info_out, + FFIError *error) +; /* - Free an account collection handle + Set the gap limit for an address pool + + The gap limit determines how many unused addresses to maintain at the end + of the pool. This is important for wallet recovery and address discovery. # Safety - - `collection` must be a valid pointer to an FFIAccountCollection created by this library - - `collection` must not be used after calling this function + - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo + - `error` must be a valid pointer to an FFIError or null */ - void account_collection_free(FFIAccountCollection *collection) ; + +bool managed_wallet_set_gap_limit(FFIManagedWalletInfo *managed_wallet, + FFIAccountType account_type, + unsigned int account_index, + FFIAddressPoolType pool_type, + unsigned int gap_limit, + FFIError *error) +; /* - Get a BIP44 account by index from the collection + Generate addresses up to a specific index in a pool + + This ensures that addresses up to and including the specified index exist + in the pool. This is useful for wallet recovery or when specific indices + are needed. # Safety - - `collection` must be a valid pointer to an FFIAccountCollection - - The returned pointer must be freed with `account_free` when no longer needed + - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo + - `wallet` must be a valid pointer to an FFIWallet (for key derivation) + - `error` must be a valid pointer to an FFIError or null */ -FFIAccount *account_collection_get_bip44_account(const FFIAccountCollection *collection, - unsigned int index) +bool managed_wallet_generate_addresses_to_index(FFIManagedWalletInfo *managed_wallet, + const FFIWallet *wallet, + FFIAccountType account_type, + unsigned int account_index, + FFIAddressPoolType pool_type, + unsigned int target_index, + FFIError *error) ; /* - Get all BIP44 account indices + Mark an address as used in the pool + + This updates the pool's tracking of which addresses have been used, + which is important for gap limit management and wallet recovery. # Safety - - `collection` must be a valid pointer to an FFIAccountCollection - - `out_indices` must be a valid pointer to store the indices array - - `out_count` must be a valid pointer to store the count - - The returned array must be freed with `free_u32_array` when no longer needed + - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo + - `address` must be a valid C string + - `error` must be a valid pointer to an FFIError or null */ -bool account_collection_get_bip44_indices(const FFIAccountCollection *collection, - unsigned int **out_indices, - size_t *out_count) +bool managed_wallet_mark_address_used(FFIManagedWalletInfo *managed_wallet, + const char *address, + FFIError *error) ; /* - Get a BIP32 account by index from the collection + Get a single address info at a specific index from the pool + + Returns detailed information about the address at the given index, or NULL + if the index is out of bounds or not generated yet. # Safety - - `collection` must be a valid pointer to an FFIAccountCollection - - The returned pointer must be freed with `account_free` when no longer needed + - `pool` must be a valid pointer to an FFIAddressPool + - `error` must be a valid pointer to an FFIError or null + - The returned FFIAddressInfo must be freed using `address_info_free` */ -FFIAccount *account_collection_get_bip32_account(const FFIAccountCollection *collection, - unsigned int index) -; - -/* - Get all BIP32 account indices - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - - `out_indices` must be a valid pointer to store the indices array - - `out_count` must be a valid pointer to store the count - - The returned array must be freed with `free_u32_array` when no longer needed - */ - -bool account_collection_get_bip32_indices(const FFIAccountCollection *collection, - unsigned int **out_indices, - size_t *out_count) -; - -/* - Get a CoinJoin account by index from the collection - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - - The returned pointer must be freed with `account_free` when no longer needed - */ - -FFIAccount *account_collection_get_coinjoin_account(const FFIAccountCollection *collection, - unsigned int index) -; - -/* - Get all CoinJoin account indices - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - - `out_indices` must be a valid pointer to store the indices array - - `out_count` must be a valid pointer to store the count - - The returned array must be freed with `free_u32_array` when no longer needed - */ - -bool account_collection_get_coinjoin_indices(const FFIAccountCollection *collection, - unsigned int **out_indices, - size_t *out_count) -; - -/* - Get the identity registration account if it exists - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - - The returned pointer must be freed with `account_free` when no longer needed - */ - FFIAccount *account_collection_get_identity_registration(const FFIAccountCollection *collection) ; - -/* - Check if identity registration account exists - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - */ - bool account_collection_has_identity_registration(const FFIAccountCollection *collection) ; - -/* - Get an identity topup account by registration index - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - - The returned pointer must be freed with `account_free` when no longer needed - */ - -FFIAccount *account_collection_get_identity_topup(const FFIAccountCollection *collection, - unsigned int registration_index) -; - -/* - Get all identity topup registration indices - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - - `out_indices` must be a valid pointer to store the indices array - - `out_count` must be a valid pointer to store the count - - The returned array must be freed with `free_u32_array` when no longer needed - */ - -bool account_collection_get_identity_topup_indices(const FFIAccountCollection *collection, - unsigned int **out_indices, - size_t *out_count) -; - -/* - Get the identity topup not bound account if it exists - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - - The returned pointer must be freed with `account_free` when no longer needed - */ - -FFIAccount *account_collection_get_identity_topup_not_bound(const FFIAccountCollection *collection) -; - -/* - Check if identity topup not bound account exists - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - */ - bool account_collection_has_identity_topup_not_bound(const FFIAccountCollection *collection) ; - -/* - Get the identity invitation account if it exists - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - - The returned pointer must be freed with `account_free` when no longer needed - */ - FFIAccount *account_collection_get_identity_invitation(const FFIAccountCollection *collection) ; - -/* - Check if identity invitation account exists - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - */ - bool account_collection_has_identity_invitation(const FFIAccountCollection *collection) ; - -/* - Get the provider voting keys account if it exists - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - - The returned pointer must be freed with `account_free` when no longer needed - */ - FFIAccount *account_collection_get_provider_voting_keys(const FFIAccountCollection *collection) ; - -/* - Check if provider voting keys account exists - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - */ - bool account_collection_has_provider_voting_keys(const FFIAccountCollection *collection) ; - -/* - Get the provider owner keys account if it exists - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - - The returned pointer must be freed with `account_free` when no longer needed - */ - FFIAccount *account_collection_get_provider_owner_keys(const FFIAccountCollection *collection) ; - -/* - Check if provider owner keys account exists - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - */ - bool account_collection_has_provider_owner_keys(const FFIAccountCollection *collection) ; - -/* - Get the provider operator keys account if it exists - Note: Returns null if the `bls` feature is not enabled - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - - The returned pointer must be freed with `bls_account_free` when no longer needed (when BLS is enabled) - */ - -void *account_collection_get_provider_operator_keys(const FFIAccountCollection *collection) -; - -/* - Check if provider operator keys account exists - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - */ - bool account_collection_has_provider_operator_keys(const FFIAccountCollection *collection) ; - -/* - Get the provider platform keys account if it exists - Note: Returns null if the `eddsa` feature is not enabled - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - - The returned pointer must be freed with `eddsa_account_free` when no longer needed (when EdDSA is enabled) - */ - -void *account_collection_get_provider_platform_keys(const FFIAccountCollection *collection) -; - -/* - Check if provider platform keys account exists - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - */ - bool account_collection_has_provider_platform_keys(const FFIAccountCollection *collection) ; - -/* - Free a u32 array allocated by this library - - # Safety - - - `array` must be a valid pointer to an array allocated by this library - - `array` must not be used after calling this function - */ - void free_u32_array(unsigned int *array, size_t count) ; - -/* - Get the total number of accounts in the collection - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - */ - unsigned int account_collection_count(const FFIAccountCollection *collection) ; - -/* - Get a human-readable summary of all accounts in the collection - - Returns a formatted string showing all account types and their indices. - The format is designed to be clear and readable for end users. - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - - The returned string must be freed with `string_free` when no longer needed - - Returns null if the collection pointer is null - */ - char *account_collection_summary(const FFIAccountCollection *collection) ; - -/* - Get structured account collection summary data - - Returns a struct containing arrays of indices for each account type and boolean - flags for special accounts. This provides Swift with programmatic access to - account information. - - # Safety - - - `collection` must be a valid pointer to an FFIAccountCollection - - The returned pointer must be freed with `account_collection_summary_free` when no longer needed - - Returns null if the collection pointer is null - */ - -FFIAccountCollectionSummary *account_collection_summary_data(const FFIAccountCollection *collection) -; - -/* - Free an account collection summary and all its allocated memory - - # Safety - - - `summary` must be a valid pointer to an FFIAccountCollectionSummary created by `account_collection_summary_data` - - `summary` must not be used after calling this function - */ - -void account_collection_summary_free(FFIAccountCollectionSummary *summary) -; - -/* - Derive an extended private key from an account at a given index, using the provided master xpriv. - - Returns an opaque FFIExtendedPrivateKey pointer that must be freed with `extended_private_key_free`. - - Notes: - - This is chain-agnostic. For accounts with internal/external chains, this returns an error. - - For hardened-only account types (e.g., EdDSA), a hardened index is used. - - # Safety - - `account` and `master_xpriv` must be valid, non-null pointers allocated by this library. - - `error` must be a valid pointer to an FFIError or null. - - The caller must free the returned pointer with `extended_private_key_free`. - */ - -FFIExtendedPrivateKey *account_derive_extended_private_key_at(const FFIAccount *account, - const FFIExtendedPrivateKey *master_xpriv, - unsigned int index, - FFIError *error) -; - -/* - Derive a BLS private key from a raw seed buffer at the given index. - - Returns a newly allocated hex string of the 32-byte private key. The caller must free - it with `string_free`. - - Notes: - - Uses the account's network for master key creation. - - Chain-agnostic; may return an error for accounts with internal/external chains. - - # Safety - - `account` must be a valid, non-null pointer to an `FFIBLSAccount` (only when `bls` feature is enabled). - - `seed` must point to a readable buffer of length `seed_len` (1..=64 bytes expected). - - `error` must be a valid pointer to an FFIError or null. - - Returned string must be freed with `string_free`. - */ - -char *bls_account_derive_private_key_from_seed(const FFIBLSAccount *account, - const uint8_t *seed, - size_t seed_len, - unsigned int index, - FFIError *error) -; - -/* - Derive a BLS private key from a mnemonic + optional passphrase at the given index. - - Returns a newly allocated hex string of the 32-byte private key. The caller must free - it with `string_free`. - - Notes: - - Uses the English wordlist for parsing the mnemonic. - - Chain-agnostic; may return an error for accounts with internal/external chains. - - # Safety - - `account` must be a valid, non-null pointer to an `FFIBLSAccount` (only when `bls` feature is enabled). - - `mnemonic` must be a valid, null-terminated UTF-8 C string. - - `passphrase` may be null; if not null, must be a valid UTF-8 C string. - - `error` must be a valid pointer to an FFIError or null. - - Returned string must be freed with `string_free`. - */ - -char *bls_account_derive_private_key_from_mnemonic(const FFIBLSAccount *account, - const char *mnemonic, - const char *passphrase, - unsigned int index, - FFIError *error) -; - -/* - Derive an EdDSA (ed25519) private key from a raw seed buffer at the given index. - - Returns a newly allocated hex string of the 32-byte private key. The caller must free - it with `string_free`. - - Notes: - - EdDSA only supports hardened derivation; the index will be used accordingly. - - Chain-agnostic; EdDSA accounts typically do not have internal/external split. - - # Safety - - `account` must be a valid, non-null pointer to an `FFIEdDSAAccount` (only when `eddsa` feature is enabled). - - `seed` must point to a readable buffer of length `seed_len` (1..=64 bytes expected). - - `error` must be a valid pointer to an FFIError or null. - - Returned string must be freed with `string_free`. - */ - -char *eddsa_account_derive_private_key_from_seed(const FFIEdDSAAccount *account, - const uint8_t *seed, - size_t seed_len, - unsigned int index, - FFIError *error) -; - -/* - Derive an EdDSA (ed25519) private key from a mnemonic + optional passphrase at the given index. - - Returns a newly allocated hex string of the 32-byte private key. The caller must free - it with `string_free`. - - Notes: - - Uses the English wordlist for parsing the mnemonic. - - # Safety - - `account` must be a valid, non-null pointer to an `FFIEdDSAAccount` (only when `eddsa` feature is enabled). - - `mnemonic` must be a valid, null-terminated UTF-8 C string. - - `passphrase` may be null; if not null, must be a valid UTF-8 C string. - - `error` must be a valid pointer to an FFIError or null. - - Returned string must be freed with `string_free`. - */ - -char *eddsa_account_derive_private_key_from_mnemonic(const FFIEdDSAAccount *account, - const char *mnemonic, - const char *passphrase, - unsigned int index, - FFIError *error) -; - -/* - Derive a private key (secp256k1) from an account at a given chain/index, using the provided master xpriv. - Returns an opaque FFIPrivateKey pointer that must be freed with `private_key_free`. - - # Safety - - `account` and `master_xpriv` must be valid pointers allocated by this library - - `error` must be a valid pointer to an FFIError or null - */ - -FFIPrivateKey *account_derive_private_key_at(const FFIAccount *account, - const FFIExtendedPrivateKey *master_xpriv, - unsigned int index, - FFIError *error) -; - -/* - Derive a private key from an account at a given chain/index and return as WIF string. - Caller must free the returned string with `string_free`. - - # Safety - - `account` and `master_xpriv` must be valid pointers allocated by this library - - `error` must be a valid pointer to an FFIError or null - */ - -char *account_derive_private_key_as_wif_at(const FFIAccount *account, - const FFIExtendedPrivateKey *master_xpriv, - unsigned int index, - FFIError *error) -; - -/* - Derive an extended private key from a raw seed buffer at the given index. - Returns an opaque FFIExtendedPrivateKey pointer that must be freed with `extended_private_key_free`. - - # Safety - - `account` must be a valid pointer to an FFIAccount - - `seed` must point to a valid buffer of length `seed_len` - - `error` must be a valid pointer to an FFIError or null - */ - -FFIExtendedPrivateKey *account_derive_extended_private_key_from_seed(const FFIAccount *account, - const uint8_t *seed, - size_t seed_len, - unsigned int index, - FFIError *error) -; - -/* - Derive a private key from a raw seed buffer at the given index. - Returns an opaque FFIPrivateKey pointer that must be freed with `private_key_free`. - - # Safety - - `account` must be a valid pointer to an FFIAccount - - `seed` must point to a valid buffer of length `seed_len` - - `error` must be a valid pointer to an FFIError or null - */ - -FFIPrivateKey *account_derive_private_key_from_seed(const FFIAccount *account, - const uint8_t *seed, - size_t seed_len, - unsigned int index, - FFIError *error) -; - -/* - Derive an extended private key from a mnemonic + optional passphrase at the given index. - Returns an opaque FFIExtendedPrivateKey pointer that must be freed with `extended_private_key_free`. - - # Safety - - `account` must be a valid pointer to an FFIAccount - - `mnemonic` must be a valid, null-terminated C string - - `passphrase` may be null; if not null, must be a valid C string - - `error` must be a valid pointer to an FFIError or null - */ - -FFIExtendedPrivateKey *account_derive_extended_private_key_from_mnemonic(const FFIAccount *account, - const char *mnemonic, - const char *passphrase, - unsigned int index, - FFIError *error) -; - -/* - Derive a private key from a mnemonic + optional passphrase at the given index. - Returns an opaque FFIPrivateKey pointer that must be freed with `private_key_free`. - - # Safety - - `account` must be a valid pointer to an FFIAccount - - `mnemonic` must be a valid, null-terminated C string - - `passphrase` may be null; if not null, must be a valid C string - - `error` must be a valid pointer to an FFIError or null - */ - -FFIPrivateKey *account_derive_private_key_from_mnemonic(const FFIAccount *account, - const char *mnemonic, - const char *passphrase, - unsigned int index, - FFIError *error) -; - -/* - Free address string - - # Safety - - - `address` must be a valid pointer created by address functions or null - - After calling this function, the pointer becomes invalid - */ - void address_free(char *address) ; - -/* - Free address array - - # Safety - - - `addresses` must be a valid pointer to an array of address strings or null - - Each address in the array must be a valid C string pointer - - `count` must be the correct number of addresses in the array - - After calling this function, all pointers become invalid - */ - void address_array_free(char **addresses, size_t count) ; - -/* - Validate an address - - # Safety - - - `address` must be a valid null-terminated C string - - `error` must be a valid pointer to an FFIError - */ - bool address_validate(const char *address, FFINetwork network, FFIError *error) ; - -/* - Get address type - - Returns: - - 0: P2PKH address - - 1: P2SH address - - 2: Other address type - - u8::MAX (255): Error occurred - - # Safety - - - `address` must be a valid null-terminated C string - - `error` must be a valid pointer to an FFIError - */ - unsigned char address_get_type(const char *address, FFINetwork network, FFIError *error) ; - -/* - Free an address pool handle - - # Safety - - - `pool` must be a valid pointer to an FFIAddressPool that was allocated by this library - - The pointer must not be used after calling this function - - This function must only be called once per allocation - */ - void address_pool_free(FFIAddressPool *pool) ; - -/* - Get address pool information for an account - - # Safety - - - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - - `info_out` must be a valid pointer to store the pool info - - `error` must be a valid pointer to an FFIError or null - */ - -bool managed_wallet_get_address_pool_info(const FFIManagedWalletInfo *managed_wallet, - FFIAccountType account_type, - unsigned int account_index, - FFIAddressPoolType pool_type, - FFIAddressPoolInfo *info_out, - FFIError *error) -; - -/* - Set the gap limit for an address pool - - The gap limit determines how many unused addresses to maintain at the end - of the pool. This is important for wallet recovery and address discovery. - - # Safety - - - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - - `error` must be a valid pointer to an FFIError or null - */ - -bool managed_wallet_set_gap_limit(FFIManagedWalletInfo *managed_wallet, - FFIAccountType account_type, - unsigned int account_index, - FFIAddressPoolType pool_type, - unsigned int gap_limit, - FFIError *error) -; - -/* - Generate addresses up to a specific index in a pool - - This ensures that addresses up to and including the specified index exist - in the pool. This is useful for wallet recovery or when specific indices - are needed. - - # Safety - - - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - - `wallet` must be a valid pointer to an FFIWallet (for key derivation) - - `error` must be a valid pointer to an FFIError or null - */ - -bool managed_wallet_generate_addresses_to_index(FFIManagedWalletInfo *managed_wallet, - const FFIWallet *wallet, - FFIAccountType account_type, - unsigned int account_index, - FFIAddressPoolType pool_type, - unsigned int target_index, - FFIError *error) -; - -/* - Mark an address as used in the pool - - This updates the pool's tracking of which addresses have been used, - which is important for gap limit management and wallet recovery. - - # Safety - - - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - - `address` must be a valid C string - - `error` must be a valid pointer to an FFIError or null - */ - -bool managed_wallet_mark_address_used(FFIManagedWalletInfo *managed_wallet, - const char *address, - FFIError *error) -; - -/* - Get a single address info at a specific index from the pool - - Returns detailed information about the address at the given index, or NULL - if the index is out of bounds or not generated yet. - - # Safety - - - `pool` must be a valid pointer to an FFIAddressPool - - `error` must be a valid pointer to an FFIError or null - - The returned FFIAddressInfo must be freed using `address_info_free` - */ - -FFIAddressInfo *address_pool_get_address_at_index(const FFIAddressPool *pool, - uint32_t index, - FFIError *error) +FFIAddressInfo *address_pool_get_address_at_index(const FFIAddressPool *pool, + uint32_t index, + FFIError *error) ; /* @@ -1986,24 +1245,6 @@ bool derivation_identity_authentication_path(FFINetwork network, FFIError *error) ; -/* - Derive private key for a specific path from seed - - # Safety - - - `seed` must be a valid pointer to a byte array of `seed_len` length - - `path` must be a valid pointer to a null-terminated C string - - `error` must be a valid pointer to an FFIError structure or null - - The caller must ensure all pointers remain valid for the duration of this call - */ - -FFIExtendedPrivKey *derivation_derive_private_key_from_seed(const uint8_t *seed, - size_t seed_len, - const char *path, - FFINetwork network, - FFIError *error) -; - /* Derive public key from extended private key @@ -2085,55 +1326,6 @@ bool derivation_xpub_fingerprint(const FFIExtendedPubKey *xpub, */ void derivation_string_free(char *s) ; -/* - Derive an address from a private key - - # Safety - - `private_key` must be a valid pointer to 32 bytes - - `network` is the network for the address - - # Returns - - Pointer to C string with address (caller must free) - - NULL on error - */ - char *key_wallet_derive_address_from_key(const uint8_t *private_key, FFINetwork network) ; - -/* - Derive an address from a seed at a specific derivation path - - # Safety - - `seed` must be a valid pointer to 64 bytes - - `network` is the network for the address - - `path` must be a valid null-terminated C string (e.g., "m/44'/5'/0'/0/0") - - # Returns - - Pointer to C string with address (caller must free) - - NULL on error - */ - -char *key_wallet_derive_address_from_seed(const uint8_t *seed, - FFINetwork network, - const char *path) -; - -/* - Derive a private key from a seed at a specific derivation path - - # Safety - - `seed` must be a valid pointer to 64 bytes - - `path` must be a valid null-terminated C string (e.g., "m/44'/5'/0'/0/0") - - `key_out` must be a valid pointer to a buffer of at least 32 bytes - - # Returns - - 0 on success - - -1 on error - */ - -int32_t key_wallet_derive_private_key_from_seed(const uint8_t *seed, - const char *path, - uint8_t *key_out) -; - /* Free an error message @@ -2161,249 +1353,68 @@ char *wallet_get_account_xpriv(const FFIWallet *wallet, ; /* - Get extended public key for account - - # Safety - - - `wallet` must be a valid pointer to an FFIWallet - - `error` must be a valid pointer to an FFIError - - The returned string must be freed with `string_free` - */ - -char *wallet_get_account_xpub(const FFIWallet *wallet, - unsigned int account_index, - FFIError *error) -; - -/* - Derive private key at a specific path - Returns an opaque FFIPrivateKey pointer that must be freed with private_key_free - - # Safety - - - `wallet` must be a valid pointer to an FFIWallet - - `derivation_path` must be a valid null-terminated C string - - `error` must be a valid pointer to an FFIError - - The returned pointer must be freed with `private_key_free` - */ - -FFIPrivateKey *wallet_derive_private_key(const FFIWallet *wallet, - const char *derivation_path, - FFIError *error) -; - -/* - Derive extended private key at a specific path - Returns an opaque FFIExtendedPrivateKey pointer that must be freed with extended_private_key_free - - # Safety - - - `wallet` must be a valid pointer to an FFIWallet - - `derivation_path` must be a valid null-terminated C string - - `error` must be a valid pointer to an FFIError - - The returned pointer must be freed with `extended_private_key_free` - */ - -FFIExtendedPrivateKey *wallet_derive_extended_private_key(const FFIWallet *wallet, - const char *derivation_path, - FFIError *error) -; - -/* - Derive private key at a specific path and return as WIF string - - # Safety - - - `wallet` must be a valid pointer to an FFIWallet - - `derivation_path` must be a valid null-terminated C string - - `error` must be a valid pointer to an FFIError - - The returned string must be freed with `string_free` - */ - -char *wallet_derive_private_key_as_wif(const FFIWallet *wallet, - const char *derivation_path, - FFIError *error) -; - -/* - Free a private key - - # Safety - - - `key` must be a valid pointer created by private key functions or null - - After calling this function, the pointer becomes invalid - */ - void private_key_free(FFIPrivateKey *key) ; - -/* - Free an extended private key - - # Safety - - - `key` must be a valid pointer created by extended private key functions or null - - After calling this function, the pointer becomes invalid - */ - void extended_private_key_free(FFIExtendedPrivateKey *key) ; - -/* - Get extended private key as string (xprv format) - - Returns the extended private key in base58 format (xprv... for mainnet, tprv... for testnet) - - # Safety - - - `key` must be a valid pointer to an FFIExtendedPrivateKey - - `network` is ignored; the network is encoded in the extended key - - `error` must be a valid pointer to an FFIError - - The returned string must be freed with `string_free` - */ - -char *extended_private_key_to_string(const FFIExtendedPrivateKey *key, - FFINetwork network, - FFIError *error) -; - -/* - Get the private key from an extended private key - - Extracts the non-extended private key from an extended private key. - - # Safety - - - `extended_key` must be a valid pointer to an FFIExtendedPrivateKey - - `error` must be a valid pointer to an FFIError - - The returned FFIPrivateKey must be freed with `private_key_free` - */ - -FFIPrivateKey *extended_private_key_get_private_key(const FFIExtendedPrivateKey *extended_key, - FFIError *error) -; - -/* - Get private key as WIF string from FFIPrivateKey - - # Safety - - - `key` must be a valid pointer to an FFIPrivateKey - - `error` must be a valid pointer to an FFIError - - The returned string must be freed with `string_free` - */ - char *private_key_to_wif(const FFIPrivateKey *key, FFINetwork network, FFIError *error) ; - -/* - Derive public key at a specific path - Returns an opaque FFIPublicKey pointer that must be freed with public_key_free - - # Safety - - - `wallet` must be a valid pointer to an FFIWallet - - `derivation_path` must be a valid null-terminated C string - - `error` must be a valid pointer to an FFIError - - The returned pointer must be freed with `public_key_free` - */ - -FFIPublicKey *wallet_derive_public_key(const FFIWallet *wallet, - const char *derivation_path, - FFIError *error) -; - -/* - Derive extended public key at a specific path - Returns an opaque FFIExtendedPublicKey pointer that must be freed with extended_public_key_free - - # Safety - - - `wallet` must be a valid pointer to an FFIWallet - - `derivation_path` must be a valid null-terminated C string - - `error` must be a valid pointer to an FFIError - - The returned pointer must be freed with `extended_public_key_free` - */ - -FFIExtendedPublicKey *wallet_derive_extended_public_key(const FFIWallet *wallet, - const char *derivation_path, - FFIError *error) -; - -/* - Derive public key at a specific path and return as hex string - - # Safety - - - `wallet` must be a valid pointer to an FFIWallet - - `derivation_path` must be a valid null-terminated C string - - `error` must be a valid pointer to an FFIError - - The returned string must be freed with `string_free` - */ - -char *wallet_derive_public_key_as_hex(const FFIWallet *wallet, - const char *derivation_path, - FFIError *error) -; - -/* - Free a public key + Get extended public key for account # Safety - - `key` must be a valid pointer created by public key functions or null - - After calling this function, the pointer becomes invalid + - `wallet` must be a valid pointer to an FFIWallet + - `error` must be a valid pointer to an FFIError + - The returned string must be freed with `string_free` */ - void public_key_free(FFIPublicKey *key) ; - -/* - Free an extended public key - - # Safety - - `key` must be a valid pointer created by extended public key functions or null - - After calling this function, the pointer becomes invalid - */ - void extended_public_key_free(FFIExtendedPublicKey *key) ; +char *wallet_get_account_xpub(const FFIWallet *wallet, + unsigned int account_index, + FFIError *error) +; /* - Get extended public key as string (xpub format) - - Returns the extended public key in base58 format (xpub... for mainnet, tpub... for testnet) + Derive extended private key at a specific path + Returns an opaque FFIExtendedPrivateKey pointer that must be freed with extended_private_key_free # Safety - - `key` must be a valid pointer to an FFIExtendedPublicKey - - `network` is ignored; the network is encoded in the extended key + - `wallet` must be a valid pointer to an FFIWallet + - `derivation_path` must be a valid null-terminated C string - `error` must be a valid pointer to an FFIError - - The returned string must be freed with `string_free` + - The returned pointer must be freed with `extended_private_key_free` */ -char *extended_public_key_to_string(const FFIExtendedPublicKey *key, - FFINetwork network, - FFIError *error) +FFIExtendedPrivateKey *wallet_derive_extended_private_key(const FFIWallet *wallet, + const char *derivation_path, + FFIError *error) ; /* - Get the public key from an extended public key - - Extracts the non-extended public key from an extended public key. + Derive private key at a specific path and return as WIF string # Safety - - `extended_key` must be a valid pointer to an FFIExtendedPublicKey + - `wallet` must be a valid pointer to an FFIWallet + - `derivation_path` must be a valid null-terminated C string - `error` must be a valid pointer to an FFIError - - The returned FFIPublicKey must be freed with `public_key_free` + - The returned string must be freed with `string_free` */ -FFIPublicKey *extended_public_key_get_public_key(const FFIExtendedPublicKey *extended_key, - FFIError *error) +char *wallet_derive_private_key_as_wif(const FFIWallet *wallet, + const char *derivation_path, + FFIError *error) ; /* - Get public key as hex string from FFIPublicKey + Derive public key at a specific path and return as hex string # Safety - - `key` must be a valid pointer to an FFIPublicKey + - `wallet` must be a valid pointer to an FFIWallet + - `derivation_path` must be a valid null-terminated C string - `error` must be a valid pointer to an FFIError - The returned string must be freed with `string_free` */ - char *public_key_to_hex(const FFIPublicKey *key, FFIError *error) ; + +char *wallet_derive_public_key_as_hex(const FFIWallet *wallet, + const char *derivation_path, + FFIError *error) +; /* Convert derivation path string to indices @@ -2477,35 +1488,6 @@ FFIManagedCoreAccountResult managed_wallet_get_top_up_account_with_registration_ unsigned int registration_index) ; -/* - Get a managed DashPay receiving funds account by composite key - - # Safety - - `manager`, `wallet_id` must be valid - - `user_identity_id` and `friend_identity_id` must each point to 32 bytes - */ - -FFIManagedCoreAccountResult managed_wallet_get_dashpay_receiving_account(const FFIWalletManager *manager, - const uint8_t *wallet_id, - unsigned int account_index, - const uint8_t *user_identity_id, - const uint8_t *friend_identity_id) -; - -/* - Get a managed DashPay external account by composite key - - # Safety - - Pointers must be valid - */ - -FFIManagedCoreAccountResult managed_wallet_get_dashpay_external_account(const FFIWalletManager *manager, - const uint8_t *wallet_id, - unsigned int account_index, - const uint8_t *user_identity_id, - const uint8_t *friend_identity_id) -; - /* Get the network of a managed account @@ -2516,22 +1498,6 @@ FFIManagedCoreAccountResult managed_wallet_get_dashpay_external_account(const FF */ FFINetwork managed_core_account_get_network(const FFIManagedCoreAccount *account) ; -/* - Get the parent wallet ID of a managed account - - Note: ManagedAccount doesn't store the parent wallet ID directly. - The wallet ID is typically known from the context (e.g., when getting the account from a managed wallet). - - # Safety - - - `wallet_id` must be a valid pointer to a 32-byte wallet ID buffer that was provided by the caller - - The returned pointer is the same as the input pointer for convenience - - The caller must not free the returned pointer as it's the same as the input - */ - -const uint8_t *managed_core_account_get_parent_wallet_id(const uint8_t *wallet_id) -; - /* Get the account type of a managed account @@ -2637,22 +1603,6 @@ bool managed_core_account_get_transactions(const FFIManagedCoreAccount *account, */ void managed_core_account_result_free_error(FFIManagedCoreAccountResult *result) ; -/* - Get number of accounts in a managed wallet - - # Safety - - - `manager` must be a valid pointer to an FFIWalletManager instance - - `wallet_id` must be a valid pointer to a 32-byte wallet ID - - `error` must be a valid pointer to an FFIError structure or null - - The caller must ensure all pointers remain valid for the duration of this call - */ - -unsigned int managed_wallet_get_account_count(const FFIWalletManager *manager, - const uint8_t *wallet_id, - FFIError *error) -; - /* Get the account index from a managed account @@ -2714,146 +1664,6 @@ FFIAddressPool *managed_core_account_get_address_pool(const FFIManagedCoreAccoun FFIAddressPoolType pool_type) ; -/* - Get a managed platform payment account from a managed wallet - - Platform Payment accounts (DIP-17) are identified by account index and key_class. - Returns a platform account handle that wraps the ManagedPlatformAccount. - - # Safety - - - `manager` must be a valid pointer to an FFIWalletManager instance - - `wallet_id` must be a valid pointer to a 32-byte wallet ID - - The caller must ensure all pointers remain valid for the duration of this call - - The returned account must be freed with `managed_platform_account_free` when no longer needed - */ - -FFIManagedPlatformAccountResult managed_wallet_get_platform_payment_account(const FFIWalletManager *manager, - const uint8_t *wallet_id, - unsigned int account_index, - unsigned int key_class) -; - -/* - Get the network of a managed platform account - - # Safety - - - `account` must be a valid pointer to an FFIManagedPlatformAccount instance - - Returns `FFINetwork::Mainnet` if the account is null - */ - FFINetwork managed_platform_account_get_network(const FFIManagedPlatformAccount *account) ; - -/* - Get the account index of a managed platform account - - # Safety - - - `account` must be a valid pointer to an FFIManagedPlatformAccount instance - */ - unsigned int managed_platform_account_get_account_index(const FFIManagedPlatformAccount *account) ; - -/* - Get the key class of a managed platform account - - # Safety - - - `account` must be a valid pointer to an FFIManagedPlatformAccount instance - */ - unsigned int managed_platform_account_get_key_class(const FFIManagedPlatformAccount *account) ; - -/* - Get the total credit balance of a managed platform account - - Returns the balance in credits (1000 credits = 1 duff) - - # Safety - - - `account` must be a valid pointer to an FFIManagedPlatformAccount instance - */ - uint64_t managed_platform_account_get_credit_balance(const FFIManagedPlatformAccount *account) ; - -/* - Get the total balance in duffs of a managed platform account - - Returns the balance in duffs (credit_balance / 1000) - - # Safety - - - `account` must be a valid pointer to an FFIManagedPlatformAccount instance - */ - uint64_t managed_platform_account_get_duff_balance(const FFIManagedPlatformAccount *account) ; - -/* - Get the number of funded addresses in a managed platform account - - # Safety - - - `account` must be a valid pointer to an FFIManagedPlatformAccount instance - */ - -unsigned int managed_platform_account_get_funded_address_count(const FFIManagedPlatformAccount *account) -; - -/* - Get the total number of addresses in a managed platform account - - # Safety - - - `account` must be a valid pointer to an FFIManagedPlatformAccount instance - */ - -unsigned int managed_platform_account_get_total_address_count(const FFIManagedPlatformAccount *account) -; - -/* - Check if a managed platform account is watch-only - - # Safety - - - `account` must be a valid pointer to an FFIManagedPlatformAccount instance - */ - bool managed_platform_account_get_is_watch_only(const FFIManagedPlatformAccount *account) ; - -/* - Get the address pool from a managed platform account - - Platform accounts only have a single address pool. - - # Safety - - - `account` must be a valid pointer to an FFIManagedPlatformAccount instance - - The returned pool must be freed with `address_pool_free` when no longer needed - */ - -FFIAddressPool *managed_platform_account_get_address_pool(const FFIManagedPlatformAccount *account) -; - -/* - Free a managed platform account handle - - # Safety - - - `account` must be a valid pointer to an FFIManagedPlatformAccount that was allocated by this library - - The pointer must not be used after calling this function - - This function must only be called once per allocation - */ - -void managed_platform_account_free(FFIManagedPlatformAccount *account) -; - -/* - Free a managed platform account result's error message (if any) - Note: This does NOT free the account handle itself - use managed_platform_account_free for that - - # Safety - - - `result` must be a valid pointer to an FFIManagedPlatformAccountResult - - The error_message field must be either null or a valid CString allocated by this library - - The caller must ensure the result pointer remains valid for the duration of this call - */ - void managed_platform_account_result_free_error(FFIManagedPlatformAccountResult *result) ; - /* Get managed account collection for a specific network from wallet manager @@ -3074,188 +1884,93 @@ bool managed_account_collection_has_identity_invitation(const FFIManagedCoreAcco # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - - The returned pointer must be freed with `managed_core_account_free` when no longer needed - */ - -FFIManagedCoreAccount *managed_account_collection_get_provider_voting_keys(const FFIManagedCoreAccountCollection *collection) -; - -/* - Check if provider voting keys account exists in managed collection - - # Safety - - - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - */ - -bool managed_account_collection_has_provider_voting_keys(const FFIManagedCoreAccountCollection *collection) -; - -/* - Get the provider owner keys account if it exists in managed collection - - # Safety - - - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - - The returned pointer must be freed with `managed_core_account_free` when no longer needed - */ - -FFIManagedCoreAccount *managed_account_collection_get_provider_owner_keys(const FFIManagedCoreAccountCollection *collection) -; - -/* - Check if provider owner keys account exists in managed collection - - # Safety - - - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - */ - -bool managed_account_collection_has_provider_owner_keys(const FFIManagedCoreAccountCollection *collection) -; - -/* - Get the provider operator keys account if it exists in managed collection - Note: Returns null if the `bls` feature is not enabled - - # Safety - - - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - - The returned pointer must be freed with `managed_core_account_free` when no longer needed (when BLS is enabled) - */ - -void *managed_account_collection_get_provider_operator_keys(const FFIManagedCoreAccountCollection *collection) -; - -/* - Check if provider operator keys account exists in managed collection - - # Safety - - - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - */ - -bool managed_account_collection_has_provider_operator_keys(const FFIManagedCoreAccountCollection *collection) -; - -/* - Get the provider platform keys account if it exists in managed collection - Note: Returns null if the `eddsa` feature is not enabled - - # Safety - - - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - - The returned pointer must be freed with `managed_core_account_free` when no longer needed (when EdDSA is enabled) - */ - -void *managed_account_collection_get_provider_platform_keys(const FFIManagedCoreAccountCollection *collection) -; - -/* - Check if provider platform keys account exists in managed collection - - # Safety - - - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - The returned pointer must be freed with `managed_core_account_free` when no longer needed */ -bool managed_account_collection_has_provider_platform_keys(const FFIManagedCoreAccountCollection *collection) +FFIManagedCoreAccount *managed_account_collection_get_provider_voting_keys(const FFIManagedCoreAccountCollection *collection) ; /* - Get a Platform Payment account by account index and key class from the managed collection - - Platform Payment accounts (DIP-17) are identified by two indices: - - account_index: The account' level in the derivation path - - key_class: The key_class' level in the derivation path (typically 0) + Check if provider voting keys account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - - The returned pointer must be freed with `managed_platform_account_free` when no longer needed */ -FFIManagedPlatformAccount *managed_account_collection_get_platform_payment_account(const FFIManagedCoreAccountCollection *collection, - unsigned int account_index, - unsigned int key_class) +bool managed_account_collection_has_provider_voting_keys(const FFIManagedCoreAccountCollection *collection) ; /* - Get all Platform Payment account keys from managed collection - - Returns an array of FFIPlatformPaymentAccountKey structures. + Get the provider owner keys account if it exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - - `out_keys` must be a valid pointer to store the keys array - - `out_count` must be a valid pointer to store the count - - The returned array must be freed with `managed_account_collection_free_platform_payment_keys` when no longer needed + - The returned pointer must be freed with `managed_core_account_free` when no longer needed */ -bool managed_account_collection_get_platform_payment_keys(const FFIManagedCoreAccountCollection *collection, - FFIPlatformPaymentAccountKey **out_keys, - size_t *out_count) +FFIManagedCoreAccount *managed_account_collection_get_provider_owner_keys(const FFIManagedCoreAccountCollection *collection) ; /* - Free platform payment keys array returned by managed_account_collection_get_platform_payment_keys + Check if provider owner keys account exists in managed collection # Safety - - `keys` must be a pointer returned by `managed_account_collection_get_platform_payment_keys` - - `count` must be the count returned by `managed_account_collection_get_platform_payment_keys` - - This function must only be called once per allocation + - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection */ -void managed_account_collection_free_platform_payment_keys(FFIPlatformPaymentAccountKey *keys, - size_t count) +bool managed_account_collection_has_provider_owner_keys(const FFIManagedCoreAccountCollection *collection) ; /* - Check if there are any Platform Payment accounts in the managed collection + Get the provider operator keys account if it exists in managed collection + Note: Returns null if the `bls` feature is not enabled # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - The returned pointer must be freed with `managed_core_account_free` when no longer needed (when BLS is enabled) */ -bool managed_account_collection_has_platform_payment_accounts(const FFIManagedCoreAccountCollection *collection) +void *managed_account_collection_get_provider_operator_keys(const FFIManagedCoreAccountCollection *collection) ; /* - Get the number of Platform Payment accounts in the managed collection + Check if provider operator keys account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection */ -unsigned int managed_account_collection_platform_payment_count(const FFIManagedCoreAccountCollection *collection) +bool managed_account_collection_has_provider_operator_keys(const FFIManagedCoreAccountCollection *collection) ; /* - Get the total number of accounts in the managed collection + Get the provider platform keys account if it exists in managed collection + Note: Returns null if the `eddsa` feature is not enabled # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection + - The returned pointer must be freed with `managed_core_account_free` when no longer needed (when EdDSA is enabled) */ - unsigned int managed_account_collection_count(const FFIManagedCoreAccountCollection *collection) ; -/* - Get a human-readable summary of all accounts in the managed collection +void *managed_account_collection_get_provider_platform_keys(const FFIManagedCoreAccountCollection *collection) +; - Returns a formatted string showing all account types and their indices. - The format is designed to be clear and readable for end users. +/* + Check if provider platform keys account exists in managed collection # Safety - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection - - The returned string must be freed with `string_free` when no longer needed - - Returns null if the collection pointer is null */ - char *managed_account_collection_summary(const FFIManagedCoreAccountCollection *collection) ; + +bool managed_account_collection_has_provider_platform_keys(const FFIManagedCoreAccountCollection *collection) +; /* Get structured account collection summary data for managed collection @@ -3403,30 +2118,6 @@ bool managed_wallet_get_balance(const FFIManagedWalletInfo *managed_wallet, FFIError *error) ; -/* - Get current synced height from wallet info - - # Safety - - - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - - `error` must be a valid pointer to an FFIError structure or null - - The caller must ensure all pointers remain valid for the duration of this call - */ - -unsigned int managed_wallet_synced_height(const FFIManagedWalletInfo *managed_wallet, - FFIError *error) -; - -/* - Free managed wallet info - - # Safety - - - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo or null - - After calling this function, the pointer becomes invalid and must not be used - */ - void managed_wallet_free(FFIManagedWalletInfo *managed_wallet) ; - /* Free managed wallet info returned by wallet_manager_get_managed_wallet_info @@ -3437,11 +2128,6 @@ unsigned int managed_wallet_synced_height(const FFIManagedWalletInfo *managed_wa */ void managed_wallet_info_free(FFIManagedWalletInfo *wallet_info) ; -/* - Generate a new mnemonic with specified word count (12, 15, 18, 21, or 24) - */ - char *mnemonic_generate(unsigned int word_count, FFIError *error) ; - /* Generate a new mnemonic with specified language and word count */ @@ -3573,173 +2259,6 @@ bool wallet_check_transaction(FFIWallet *wallet, */ void transaction_bytes_free(uint8_t *tx_bytes) ; -/* - Create a new empty transaction - - # Returns - - Pointer to FFITransaction on success - - NULL on error - */ - FFITransaction *transaction_create(void) ; - -/* - Add an input to a transaction - - # Safety - - `tx` must be a valid pointer to an FFITransaction - - `input` must be a valid pointer to an FFITxIn - - # Returns - - 0 on success - - -1 on error - */ - int32_t transaction_add_input(FFITransaction *tx, const FFITxIn *input) ; - -/* - Add an output to a transaction - - # Safety - - `tx` must be a valid pointer to an FFITransaction - - `output` must be a valid pointer to an FFITxOut - - # Returns - - 0 on success - - -1 on error - */ - int32_t transaction_add_output(FFITransaction *tx, const FFITxOut *output) ; - -/* - Get the transaction ID - - # Safety - - `tx` must be a valid pointer to an FFITransaction - - `txid_out` must be a valid pointer to a buffer of at least 32 bytes - - # Returns - - 0 on success - - -1 on error - */ - int32_t transaction_get_txid(const FFITransaction *tx, uint8_t *txid_out) ; - -/* - Get transaction ID from raw transaction bytes - - # Safety - - `tx_bytes` must be a valid pointer to transaction bytes - - `tx_len` must be the correct length of the transaction - - `error` must be a valid pointer to an FFIError - - # Returns - - Pointer to null-terminated hex string of TXID (must be freed with string_free) - - NULL on error - */ - char *transaction_get_txid_from_bytes(const uint8_t *tx_bytes, size_t tx_len, FFIError *error) ; - -/* - Serialize a transaction - - # Safety - - `tx` must be a valid pointer to an FFITransaction - - `out_buf` can be NULL to get size only - - `out_len` must be a valid pointer to store the size - - # Returns - - 0 on success - - -1 on error - */ - int32_t transaction_serialize(const FFITransaction *tx, uint8_t *out_buf, uint32_t *out_len) ; - -/* - Deserialize a transaction - - # Safety - - `data` must be a valid pointer to serialized transaction data - - `len` must be the correct length of the data - - # Returns - - Pointer to FFITransaction on success - - NULL on error - */ - FFITransaction *transaction_deserialize(const uint8_t *data, uint32_t len) ; - -/* - Destroy a transaction - - # Safety - - `tx` must be a valid pointer to an FFITransaction created by transaction functions or null - - After calling this function, the pointer becomes invalid - */ - void transaction_destroy(FFITransaction *tx) ; - -/* - Calculate signature hash for an input - - # Safety - - `tx` must be a valid pointer to an FFITransaction - - `script_pubkey` must be a valid pointer to the script pubkey - - `hash_out` must be a valid pointer to a buffer of at least 32 bytes - - # Returns - - 0 on success - - -1 on error - */ - -int32_t transaction_sighash(const FFITransaction *tx, - uint32_t input_index, - const uint8_t *script_pubkey, - uint32_t script_pubkey_len, - uint32_t sighash_type, - uint8_t *hash_out) -; - -/* - Sign a transaction input - - # Safety - - `tx` must be a valid pointer to an FFITransaction - - `private_key` must be a valid pointer to a 32-byte private key - - `script_pubkey` must be a valid pointer to the script pubkey - - # Returns - - 0 on success - - -1 on error - */ - -int32_t transaction_sign_input(FFITransaction *tx, - uint32_t input_index, - const uint8_t *private_key, - const uint8_t *script_pubkey, - uint32_t script_pubkey_len, - uint32_t sighash_type) -; - -/* - Create a P2PKH script pubkey - - # Safety - - `pubkey_hash` must be a valid pointer to a 20-byte public key hash - - `out_buf` can be NULL to get size only - - `out_len` must be a valid pointer to store the size - - # Returns - - 0 on success - - -1 on error - */ - int32_t script_p2pkh(const uint8_t *pubkey_hash, uint8_t *out_buf, uint32_t *out_len) ; - -/* - Extract public key hash from P2PKH address - - # Safety - - `address` must be a valid pointer to a null-terminated C string - - `hash_out` must be a valid pointer to a buffer of at least 20 bytes - - # Returns - - 0 on success - - -1 on error - */ - int32_t address_to_pubkey_hash(const char *address, FFINetwork network, uint8_t *hash_out) ; - /* Create a managed wallet from a regular wallet @@ -3820,8 +2339,6 @@ bool managed_wallet_check_transaction(FFIManagedWalletInfo *managed_wallet, */ char *transaction_classify(const uint8_t *tx_bytes, size_t tx_len, FFIError *error) ; - const char *ffi_network_get_name(FFINetwork network) ; - /* Free a string @@ -3851,21 +2368,6 @@ bool managed_wallet_get_utxos(const FFIManagedWalletInfo *managed_info, FFIError *error) ; -/* - Get all UTXOs (deprecated - use managed_wallet_get_utxos instead) - - # Safety - - This function is deprecated and returns an empty list. - Use `managed_wallet_get_utxos` with a ManagedWalletInfo instead. - */ - -bool wallet_get_utxos(const FFIWallet *_wallet, - FFIUTXO **utxos_out, - size_t *count_out, - FFIError *error) -; - /* Free UTXO array @@ -4009,18 +2511,6 @@ FFIWallet *wallet_create_random_with_options(FFINetwork network, */ bool wallet_is_watch_only(const FFIWallet *wallet, FFIError *error) ; -/* - Get extended public key for account - - # Safety - - - `wallet` must be a valid pointer to an FFIWallet instance - - `error` must be a valid pointer to an FFIError structure or null - - The caller must ensure all pointers remain valid for the duration of this call - - The returned C string must be freed by the caller when no longer needed - */ - char *wallet_get_xpub(const FFIWallet *wallet, unsigned int account_index, FFIError *error) ; - /* Free a wallet @@ -4032,22 +2522,6 @@ FFIWallet *wallet_create_random_with_options(FFINetwork network, */ void wallet_free(FFIWallet *wallet) ; -/* - Free a const wallet handle - - This is a const-safe wrapper for wallet_free() that accepts a const pointer. - Use this function when you have a *const FFIWallet that needs to be freed, - such as wallets returned from wallet_manager_get_wallet(). - - # Safety - - - `wallet` must be a valid pointer created by wallet creation functions or null - - After calling this function, the pointer becomes invalid - - This function must only be called once per wallet - - The wallet must have been allocated by this library (not stack or static memory) - */ - void wallet_free_const(const FFIWallet *wallet) ; - /* Add an account to the wallet without xpub @@ -4071,62 +2545,6 @@ FFIAccountResult wallet_add_account(FFIWallet *wallet, unsigned int account_index) ; -/* - Add a DashPay receiving funds account - - # Safety - - `wallet` must be a valid pointer - - `user_identity_id` and `friend_identity_id` must each point to 32 bytes - */ - -FFIAccountResult wallet_add_dashpay_receiving_account(FFIWallet *wallet, - unsigned int account_index, - const uint8_t *user_identity_id, - const uint8_t *friend_identity_id) -; - -/* - Add a DashPay external (watch-only) account with xpub bytes - - # Safety - - `wallet` must be valid, `xpub_bytes` must point to `xpub_len` bytes - - `user_identity_id` and `friend_identity_id` must each point to 32 bytes - */ - -FFIAccountResult wallet_add_dashpay_external_account_with_xpub_bytes(FFIWallet *wallet, - unsigned int account_index, - const uint8_t *user_identity_id, - const uint8_t *friend_identity_id, - const uint8_t *xpub_bytes, - size_t xpub_len) -; - -/* - Add an account to the wallet with xpub as byte array - - # Safety - - This function dereferences raw pointers. - The caller must ensure that: - - The wallet pointer is either null or points to a valid FFIWallet - - The xpub_bytes pointer is either null or points to at least xpub_len bytes - - The FFIWallet remains valid for the duration of this call - - # Note - - This function does NOT support the following account types: - - `PlatformPayment`: Use `wallet_add_platform_payment_account()` instead - - `DashpayReceivingFunds`: Use `wallet_add_dashpay_receiving_account()` instead - - `DashpayExternalAccount`: Use `wallet_add_dashpay_external_account_with_xpub_bytes()` instead - */ - -FFIAccountResult wallet_add_account_with_xpub_bytes(FFIWallet *wallet, - FFIAccountType account_type, - unsigned int account_index, - const uint8_t *xpub_bytes, - size_t xpub_len) -; - /* Add an account to the wallet with xpub as string @@ -4152,51 +2570,6 @@ FFIAccountResult wallet_add_account_with_string_xpub(FFIWallet *wallet, const char *xpub_string) ; -/* - Add a Platform Payment account (DIP-17) to the wallet - - Platform Payment accounts use the derivation path: - `m/9'/coin_type'/17'/account'/key_class'/index` - - # Arguments - * `wallet` - Pointer to the wallet - * `account_index` - The account index (hardened) in the derivation path - * `key_class` - The key class (hardened) - typically 0' for main addresses - - # Safety - - This function dereferences a raw pointer to FFIWallet. - The caller must ensure that: - - The wallet pointer is either null or points to a valid FFIWallet - - The FFIWallet remains valid for the duration of this call - */ - -FFIAccountResult wallet_add_platform_payment_account(FFIWallet *wallet, - unsigned int account_index, - unsigned int key_class) -; - -/* - Describe the wallet manager for a given network and return a newly - allocated C string. - - # Safety - - `manager` must be a valid pointer to an `FFIWalletManager` - - Callers must free the returned string with `wallet_manager_free_string` - */ - char *wallet_manager_describe(const FFIWalletManager *manager, FFIError *error) ; - -/* - Free a string previously returned by wallet manager APIs. - - # Safety - - `value` must be either null or a pointer obtained from - `wallet_manager_describe` (or other wallet manager FFI helpers that - specify this free function). - - The pointer must not be used after this call returns. - */ - void wallet_manager_free_string(char *value) ; - /* Create a new wallet manager */ @@ -4450,17 +2823,6 @@ bool wallet_manager_process_transaction(FFIWalletManager *manager, */ size_t wallet_manager_wallet_count(const FFIWalletManager *manager, FFIError *error) ; -/* - Free wallet manager - - # Safety - - - `manager` must be a valid pointer to an FFIWalletManager that was created by this library - - The pointer must not be used after calling this function - - This function must only be called once per manager - */ - void wallet_manager_free(FFIWalletManager *manager) ; - /* Free wallet IDs buffer @@ -4473,50 +2835,6 @@ bool wallet_manager_process_transaction(FFIWalletManager *manager, */ void wallet_manager_free_wallet_ids(uint8_t *wallet_ids, size_t count) ; -/* - Free address array - - # Safety - - - `addresses` must be a valid pointer to an array of C string pointers allocated by this library - - `count` must match the original allocation size - - Each address pointer in the array must be either null or a valid C string allocated by this library - - The pointers must not be used after calling this function - - This function must only be called once per allocation - */ - -void wallet_manager_free_addresses(char **addresses, - size_t count) -; - -/* - Encrypt a private key with BIP38 - - # Safety - - This function is unsafe because it dereferences raw pointers: - - `private_key` must be a valid, null-terminated C string - - `passphrase` must be a valid, null-terminated C string - - `error` must be a valid pointer to an FFIError or null - */ - char *bip38_encrypt_private_key(const char *private_key, const char *passphrase, FFIError *error) ; - -/* - Decrypt a BIP38 encrypted private key - - # Safety - - This function is unsafe because it dereferences raw pointers: - - `encrypted_key` must be a valid, null-terminated C string - - `passphrase` must be a valid, null-terminated C string - - `error` must be a valid pointer to an FFIError or null - */ - -char *bip38_decrypt_private_key(const char *encrypted_key, - const char *passphrase, - FFIError *error) -; - #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/key-wallet-ffi/src/account.rs b/key-wallet-ffi/src/account.rs index 2138bf3ee..df7198e7c 100644 --- a/key-wallet-ffi/src/account.rs +++ b/key-wallet-ffi/src/account.rs @@ -4,7 +4,7 @@ use std::os::raw::c_uint; use std::sync::Arc; use crate::error::{FFIError, FFIErrorCode}; -use crate::types::{FFIAccountResult, FFIAccountType, FFINetwork, FFIWallet}; +use crate::types::{FFIAccountResult, FFIAccountType, FFIWallet}; #[cfg(feature = "bls")] use key_wallet::account::BLSAccount; #[cfg(feature = "eddsa")] @@ -202,346 +202,6 @@ pub unsafe extern "C" fn account_result_free_error(result: *mut FFIAccountResult } } -/// Get the extended public key of an account as a string -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIAccount instance -/// - The returned string must be freed by the caller using `string_free` -/// - Returns NULL if the account is null -#[no_mangle] -pub unsafe extern "C" fn account_get_extended_public_key_as_string( - account: *const FFIAccount, -) -> *mut std::os::raw::c_char { - if account.is_null() { - return std::ptr::null_mut(); - } - - let account = &*account; - let xpub = account.inner().extended_public_key(); - - match std::ffi::CString::new(xpub.to_string()) { - Ok(c_str) => c_str.into_raw(), - Err(_) => std::ptr::null_mut(), - } -} - -/// Get the network of an account -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIAccount instance -/// - Returns `FFINetwork::Mainnet` if the account is null -#[no_mangle] -pub unsafe extern "C" fn account_get_network(account: *const FFIAccount) -> FFINetwork { - if account.is_null() { - return FFINetwork::Mainnet; - } - - let account = &*account; - account.inner().network.into() -} - -/// Get the parent wallet ID of an account -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIAccount instance -/// - Returns a pointer to the 32-byte wallet ID, or NULL if not set or account is null -/// - The returned pointer is valid only as long as the account exists -/// - The caller should copy the data if needed for longer use -#[no_mangle] -pub unsafe extern "C" fn account_get_parent_wallet_id(account: *const FFIAccount) -> *const u8 { - if account.is_null() { - return std::ptr::null(); - } - - let account = &*account; - match account.inner().parent_wallet_id { - Some(ref id) => id.as_ptr(), - None => std::ptr::null(), - } -} - -/// Get the account type of an account -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIAccount instance -/// - `out_index` must be a valid pointer to a c_uint where the index will be stored -/// - Returns FFIAccountType::StandardBIP44 with index 0 if the account is null -#[no_mangle] -pub unsafe extern "C" fn account_get_account_type( - account: *const FFIAccount, - out_index: *mut c_uint, -) -> FFIAccountType { - if account.is_null() || out_index.is_null() { - if !out_index.is_null() { - *out_index = 0; - } - return FFIAccountType::StandardBIP44; - } - - let account = &*account; - let (account_type, index, registration_index) = - FFIAccountType::from_account_type(&account.inner().account_type); - - // For IdentityTopUp, the registration_index is the relevant index - *out_index = registration_index.unwrap_or(index); - - account_type -} - -/// Check if an account is watch-only -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIAccount instance -/// - Returns false if the account is null -#[no_mangle] -pub unsafe extern "C" fn account_get_is_watch_only(account: *const FFIAccount) -> bool { - if account.is_null() { - return false; - } - - let account = &*account; - account.inner().is_watch_only -} - -// BLS account getter functions -/// Get the extended public key of a BLS account as a string -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIBLSAccount instance -/// - The returned string must be freed by the caller using `string_free` -/// - Returns NULL if the account is null -#[cfg(feature = "bls")] -#[no_mangle] -pub unsafe extern "C" fn bls_account_get_extended_public_key_as_string( - account: *const FFIBLSAccount, -) -> *mut std::os::raw::c_char { - if account.is_null() { - return std::ptr::null_mut(); - } - - let account = &*account; - // For BLS accounts, we need to encode the extended public key bytes - // There's no standard string representation for BLS extended keys - let bytes = account.inner().bls_public_key.to_bytes(); - let hex_string = hex::encode(bytes); - - match std::ffi::CString::new(hex_string) { - Ok(c_str) => c_str.into_raw(), - Err(_) => std::ptr::null_mut(), - } -} - -/// Get the network of a BLS account -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIBLSAccount instance -/// - Returns `FFINetwork::Mainnet` if the account is null -#[cfg(feature = "bls")] -#[no_mangle] -pub unsafe extern "C" fn bls_account_get_network(account: *const FFIBLSAccount) -> FFINetwork { - if account.is_null() { - return FFINetwork::Mainnet; - } - - let account = &*account; - account.inner().network.into() -} - -/// Get the parent wallet ID of a BLS account -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIBLSAccount instance -/// - Returns a pointer to the 32-byte wallet ID, or NULL if not set or account is null -/// - The returned pointer is valid only as long as the account exists -/// - The caller should copy the data if needed for longer use -#[cfg(feature = "bls")] -#[no_mangle] -pub unsafe extern "C" fn bls_account_get_parent_wallet_id( - account: *const FFIBLSAccount, -) -> *const u8 { - if account.is_null() { - return std::ptr::null(); - } - - let account = &*account; - match &account.inner().parent_wallet_id { - Some(id) => id.as_ptr(), - None => std::ptr::null(), - } -} - -/// Get the account type of a BLS account -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIBLSAccount instance -/// - `out_index` must be a valid pointer to a c_uint where the index will be stored -/// - Returns FFIAccountType::StandardBIP44 with index 0 if the account is null -#[cfg(feature = "bls")] -#[no_mangle] -pub unsafe extern "C" fn bls_account_get_account_type( - account: *const FFIBLSAccount, - out_index: *mut c_uint, -) -> FFIAccountType { - if account.is_null() || out_index.is_null() { - if !out_index.is_null() { - *out_index = 0; - } - return FFIAccountType::StandardBIP44; - } - - let account = &*account; - let (account_type, index, registration_index) = - FFIAccountType::from_account_type(&account.inner().account_type); - - // For IdentityTopUp, the registration_index is the relevant index - *out_index = registration_index.unwrap_or(index); - - account_type -} - -/// Check if a BLS account is watch-only -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIBLSAccount instance -/// - Returns false if the account is null -#[cfg(feature = "bls")] -#[no_mangle] -pub unsafe extern "C" fn bls_account_get_is_watch_only(account: *const FFIBLSAccount) -> bool { - if account.is_null() { - return false; - } - - let account = &*account; - account.inner().is_watch_only -} - -// EdDSA account getter functions -/// Get the extended public key of an EdDSA account as a string -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIEdDSAAccount instance -/// - The returned string must be freed by the caller using `string_free` -/// - Returns NULL if the account is null -#[cfg(feature = "eddsa")] -#[no_mangle] -pub unsafe extern "C" fn eddsa_account_get_extended_public_key_as_string( - account: *const FFIEdDSAAccount, -) -> *mut std::os::raw::c_char { - if account.is_null() { - return std::ptr::null_mut(); - } - - let account = &*account; - // For EdDSA accounts, we need to encode the extended public key - // There's no standard string representation for Ed25519 extended keys - let bytes = account.inner().ed25519_public_key.encode(); - let hex_string = hex::encode(bytes); - - match std::ffi::CString::new(hex_string) { - Ok(c_str) => c_str.into_raw(), - Err(_) => std::ptr::null_mut(), - } -} - -/// Get the network of an EdDSA account -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIEdDSAAccount instance -/// - Returns `FFINetwork::Mainnet` if the account is null -#[cfg(feature = "eddsa")] -#[no_mangle] -pub unsafe extern "C" fn eddsa_account_get_network(account: *const FFIEdDSAAccount) -> FFINetwork { - if account.is_null() { - return FFINetwork::Mainnet; - } - - let account = &*account; - account.inner().network.into() -} - -/// Get the parent wallet ID of an EdDSA account -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIEdDSAAccount instance -/// - Returns a pointer to the 32-byte wallet ID, or NULL if not set or account is null -/// - The returned pointer is valid only as long as the account exists -/// - The caller should copy the data if needed for longer use -#[cfg(feature = "eddsa")] -#[no_mangle] -pub unsafe extern "C" fn eddsa_account_get_parent_wallet_id( - account: *const FFIEdDSAAccount, -) -> *const u8 { - if account.is_null() { - return std::ptr::null(); - } - - let account = &*account; - match &account.inner().parent_wallet_id { - Some(id) => id.as_ptr(), - None => std::ptr::null(), - } -} - -/// Get the account type of an EdDSA account -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIEdDSAAccount instance -/// - `out_index` must be a valid pointer to a c_uint where the index will be stored -/// - Returns FFIAccountType::StandardBIP44 with index 0 if the account is null -#[cfg(feature = "eddsa")] -#[no_mangle] -pub unsafe extern "C" fn eddsa_account_get_account_type( - account: *const FFIEdDSAAccount, - out_index: *mut c_uint, -) -> FFIAccountType { - if account.is_null() || out_index.is_null() { - if !out_index.is_null() { - *out_index = 0; - } - return FFIAccountType::StandardBIP44; - } - - let account = &*account; - let (account_type, index, registration_index) = - FFIAccountType::from_account_type(&account.inner().account_type); - - // For IdentityTopUp, the registration_index is the relevant index - *out_index = registration_index.unwrap_or(index); - - account_type -} - -/// Check if an EdDSA account is watch-only -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIEdDSAAccount instance -/// - Returns false if the account is null -#[cfg(feature = "eddsa")] -#[no_mangle] -pub unsafe extern "C" fn eddsa_account_get_is_watch_only(account: *const FFIEdDSAAccount) -> bool { - if account.is_null() { - return false; - } - - let account = &*account; - account.inner().is_watch_only -} - /// Get number of accounts /// /// # Safety diff --git a/key-wallet-ffi/src/account_collection.rs b/key-wallet-ffi/src/account_collection.rs index 29f55b233..2a826ec63 100644 --- a/key-wallet-ffi/src/account_collection.rs +++ b/key-wallet-ffi/src/account_collection.rs @@ -3,11 +3,9 @@ //! This module provides FFI-compatible account collection functionality that mirrors //! the AccountCollection structure from key-wallet but uses FFI-safe types. -use std::ffi::CString; -use std::os::raw::{c_char, c_uint}; +use std::os::raw::c_uint; use std::ptr; -use crate::account::FFIAccount; use crate::error::{FFIError, FFIErrorCode}; use crate::types::FFIWallet; @@ -109,478 +107,8 @@ pub unsafe extern "C" fn account_collection_free(collection: *mut FFIAccountColl } } -// Standard BIP44 accounts functions - -/// Get a BIP44 account by index from the collection -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - The returned pointer must be freed with `account_free` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn account_collection_get_bip44_account( - collection: *const FFIAccountCollection, - index: c_uint, -) -> *mut FFIAccount { - if collection.is_null() { - return ptr::null_mut(); - } - - let collection = &*collection; - match collection.collection.standard_bip44_accounts.get(&index) { - Some(account) => { - let ffi_account = FFIAccount::new(account); - Box::into_raw(Box::new(ffi_account)) - } - None => ptr::null_mut(), - } -} - -/// Get all BIP44 account indices -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - `out_indices` must be a valid pointer to store the indices array -/// - `out_count` must be a valid pointer to store the count -/// - The returned array must be freed with `free_u32_array` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn account_collection_get_bip44_indices( - collection: *const FFIAccountCollection, - out_indices: *mut *mut c_uint, - out_count: *mut usize, -) -> bool { - if collection.is_null() || out_indices.is_null() || out_count.is_null() { - return false; - } - - let collection = &*collection; - let mut indices: Vec = - collection.collection.standard_bip44_accounts.keys().copied().collect(); - - if indices.is_empty() { - *out_indices = ptr::null_mut(); - *out_count = 0; - return true; - } - - indices.sort(); - - let mut boxed_slice = indices.into_boxed_slice(); - let ptr = boxed_slice.as_mut_ptr(); - let len = boxed_slice.len(); - std::mem::forget(boxed_slice); - - *out_indices = ptr; - *out_count = len; - true -} - -// Standard BIP32 accounts functions - -/// Get a BIP32 account by index from the collection -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - The returned pointer must be freed with `account_free` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn account_collection_get_bip32_account( - collection: *const FFIAccountCollection, - index: c_uint, -) -> *mut FFIAccount { - if collection.is_null() { - return ptr::null_mut(); - } - - let collection = &*collection; - match collection.collection.standard_bip32_accounts.get(&index) { - Some(account) => { - let ffi_account = FFIAccount::new(account); - Box::into_raw(Box::new(ffi_account)) - } - None => ptr::null_mut(), - } -} - -/// Get all BIP32 account indices -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - `out_indices` must be a valid pointer to store the indices array -/// - `out_count` must be a valid pointer to store the count -/// - The returned array must be freed with `free_u32_array` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn account_collection_get_bip32_indices( - collection: *const FFIAccountCollection, - out_indices: *mut *mut c_uint, - out_count: *mut usize, -) -> bool { - if collection.is_null() || out_indices.is_null() || out_count.is_null() { - return false; - } - - let collection = &*collection; - let indices: Vec = - collection.collection.standard_bip32_accounts.keys().copied().collect(); - - if indices.is_empty() { - *out_indices = ptr::null_mut(); - *out_count = 0; - return true; - } - - let mut boxed_slice = indices.into_boxed_slice(); - let ptr = boxed_slice.as_mut_ptr(); - let len = boxed_slice.len(); - std::mem::forget(boxed_slice); - - *out_indices = ptr; - *out_count = len; - true -} - -// CoinJoin accounts functions - -/// Get a CoinJoin account by index from the collection -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - The returned pointer must be freed with `account_free` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn account_collection_get_coinjoin_account( - collection: *const FFIAccountCollection, - index: c_uint, -) -> *mut FFIAccount { - if collection.is_null() { - return ptr::null_mut(); - } - - let collection = &*collection; - match collection.collection.coinjoin_accounts.get(&index) { - Some(account) => { - let ffi_account = FFIAccount::new(account); - Box::into_raw(Box::new(ffi_account)) - } - None => ptr::null_mut(), - } -} - -/// Get all CoinJoin account indices -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - `out_indices` must be a valid pointer to store the indices array -/// - `out_count` must be a valid pointer to store the count -/// - The returned array must be freed with `free_u32_array` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn account_collection_get_coinjoin_indices( - collection: *const FFIAccountCollection, - out_indices: *mut *mut c_uint, - out_count: *mut usize, -) -> bool { - if collection.is_null() || out_indices.is_null() || out_count.is_null() { - return false; - } - - let collection = &*collection; - let mut indices: Vec = - collection.collection.coinjoin_accounts.keys().copied().collect(); - - if indices.is_empty() { - *out_indices = ptr::null_mut(); - *out_count = 0; - return true; - } - - indices.sort(); - - let mut boxed_slice = indices.into_boxed_slice(); - let ptr = boxed_slice.as_mut_ptr(); - let len = boxed_slice.len(); - std::mem::forget(boxed_slice); - - *out_indices = ptr; - *out_count = len; - true -} - -// Identity accounts functions - -/// Get the identity registration account if it exists -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - The returned pointer must be freed with `account_free` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn account_collection_get_identity_registration( - collection: *const FFIAccountCollection, -) -> *mut FFIAccount { - if collection.is_null() { - return ptr::null_mut(); - } - - let collection = &*collection; - match &collection.collection.identity_registration { - Some(account) => { - let ffi_account = FFIAccount::new(account); - Box::into_raw(Box::new(ffi_account)) - } - None => ptr::null_mut(), - } -} - -/// Check if identity registration account exists -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -#[no_mangle] -pub unsafe extern "C" fn account_collection_has_identity_registration( - collection: *const FFIAccountCollection, -) -> bool { - if collection.is_null() { - return false; - } - - let collection = &*collection; - collection.collection.identity_registration.is_some() -} - -/// Get an identity topup account by registration index -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - The returned pointer must be freed with `account_free` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn account_collection_get_identity_topup( - collection: *const FFIAccountCollection, - registration_index: c_uint, -) -> *mut FFIAccount { - if collection.is_null() { - return ptr::null_mut(); - } - - let collection = &*collection; - match collection.collection.identity_topup.get(®istration_index) { - Some(account) => { - let ffi_account = FFIAccount::new(account); - Box::into_raw(Box::new(ffi_account)) - } - None => ptr::null_mut(), - } -} - -/// Get all identity topup registration indices -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - `out_indices` must be a valid pointer to store the indices array -/// - `out_count` must be a valid pointer to store the count -/// - The returned array must be freed with `free_u32_array` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn account_collection_get_identity_topup_indices( - collection: *const FFIAccountCollection, - out_indices: *mut *mut c_uint, - out_count: *mut usize, -) -> bool { - if collection.is_null() || out_indices.is_null() || out_count.is_null() { - return false; - } - - let collection = &*collection; - let mut indices: Vec = collection.collection.identity_topup.keys().copied().collect(); - - if indices.is_empty() { - *out_indices = ptr::null_mut(); - *out_count = 0; - return true; - } - - indices.sort(); - - let mut boxed_slice = indices.into_boxed_slice(); - let ptr = boxed_slice.as_mut_ptr(); - let len = boxed_slice.len(); - std::mem::forget(boxed_slice); - - *out_indices = ptr; - *out_count = len; - true -} - -/// Get the identity topup not bound account if it exists -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - The returned pointer must be freed with `account_free` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn account_collection_get_identity_topup_not_bound( - collection: *const FFIAccountCollection, -) -> *mut FFIAccount { - if collection.is_null() { - return ptr::null_mut(); - } - - let collection = &*collection; - match &collection.collection.identity_topup_not_bound { - Some(account) => { - let ffi_account = FFIAccount::new(account); - Box::into_raw(Box::new(ffi_account)) - } - None => ptr::null_mut(), - } -} - -/// Check if identity topup not bound account exists -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -#[no_mangle] -pub unsafe extern "C" fn account_collection_has_identity_topup_not_bound( - collection: *const FFIAccountCollection, -) -> bool { - if collection.is_null() { - return false; - } - - let collection = &*collection; - collection.collection.identity_topup_not_bound.is_some() -} - -/// Get the identity invitation account if it exists -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - The returned pointer must be freed with `account_free` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn account_collection_get_identity_invitation( - collection: *const FFIAccountCollection, -) -> *mut FFIAccount { - if collection.is_null() { - return ptr::null_mut(); - } - - let collection = &*collection; - match &collection.collection.identity_invitation { - Some(account) => { - let ffi_account = FFIAccount::new(account); - Box::into_raw(Box::new(ffi_account)) - } - None => ptr::null_mut(), - } -} - -/// Check if identity invitation account exists -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -#[no_mangle] -pub unsafe extern "C" fn account_collection_has_identity_invitation( - collection: *const FFIAccountCollection, -) -> bool { - if collection.is_null() { - return false; - } - - let collection = &*collection; - collection.collection.identity_invitation.is_some() -} - // Provider accounts functions -/// Get the provider voting keys account if it exists -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - The returned pointer must be freed with `account_free` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn account_collection_get_provider_voting_keys( - collection: *const FFIAccountCollection, -) -> *mut FFIAccount { - if collection.is_null() { - return ptr::null_mut(); - } - - let collection = &*collection; - match &collection.collection.provider_voting_keys { - Some(account) => { - let ffi_account = FFIAccount::new(account); - Box::into_raw(Box::new(ffi_account)) - } - None => ptr::null_mut(), - } -} - -/// Check if provider voting keys account exists -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -#[no_mangle] -pub unsafe extern "C" fn account_collection_has_provider_voting_keys( - collection: *const FFIAccountCollection, -) -> bool { - if collection.is_null() { - return false; - } - - let collection = &*collection; - collection.collection.provider_voting_keys.is_some() -} - -/// Get the provider owner keys account if it exists -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - The returned pointer must be freed with `account_free` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn account_collection_get_provider_owner_keys( - collection: *const FFIAccountCollection, -) -> *mut FFIAccount { - if collection.is_null() { - return ptr::null_mut(); - } - - let collection = &*collection; - match &collection.collection.provider_owner_keys { - Some(account) => { - let ffi_account = FFIAccount::new(account); - Box::into_raw(Box::new(ffi_account)) - } - None => ptr::null_mut(), - } -} - -/// Check if provider owner keys account exists -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -#[no_mangle] -pub unsafe extern "C" fn account_collection_has_provider_owner_keys( - collection: *const FFIAccountCollection, -) -> bool { - if collection.is_null() { - return false; - } - - let collection = &*collection; - collection.collection.provider_owner_keys.is_some() -} - /// Get the provider operator keys account if it exists /// Note: Returns null if the `bls` feature is not enabled /// @@ -616,298 +144,8 @@ pub unsafe extern "C" fn account_collection_get_provider_operator_keys( } } -/// Check if provider operator keys account exists -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -#[no_mangle] -pub unsafe extern "C" fn account_collection_has_provider_operator_keys( - collection: *const FFIAccountCollection, -) -> bool { - if collection.is_null() { - return false; - } - - #[cfg(feature = "bls")] - { - let collection = &*collection; - collection.collection.provider_operator_keys.is_some() - } - - #[cfg(not(feature = "bls"))] - { - false - } -} - -/// Get the provider platform keys account if it exists -/// Note: Returns null if the `eddsa` feature is not enabled -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - The returned pointer must be freed with `eddsa_account_free` when no longer needed (when EdDSA is enabled) -#[no_mangle] -pub unsafe extern "C" fn account_collection_get_provider_platform_keys( - collection: *const FFIAccountCollection, -) -> *mut std::os::raw::c_void { - #[cfg(feature = "eddsa")] - { - if collection.is_null() { - return ptr::null_mut(); - } - - let collection = &*collection; - match &collection.collection.provider_platform_keys { - Some(account) => { - let ffi_account = crate::account::FFIEdDSAAccount::new(account); - Box::into_raw(Box::new(ffi_account)) as *mut std::os::raw::c_void - } - None => ptr::null_mut(), - } - } - - #[cfg(not(feature = "eddsa"))] - { - // EdDSA feature not enabled, always return null - let _ = collection; // Avoid unused parameter warning - ptr::null_mut() - } -} - -/// Check if provider platform keys account exists -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -#[no_mangle] -pub unsafe extern "C" fn account_collection_has_provider_platform_keys( - collection: *const FFIAccountCollection, -) -> bool { - if collection.is_null() { - return false; - } - - #[cfg(feature = "eddsa")] - { - let collection = &*collection; - collection.collection.provider_platform_keys.is_some() - } - - #[cfg(not(feature = "eddsa"))] - { - false - } -} - // Utility functions -/// Free a u32 array allocated by this library -/// -/// # Safety -/// -/// - `array` must be a valid pointer to an array allocated by this library -/// - `array` must not be used after calling this function -#[no_mangle] -pub unsafe extern "C" fn free_u32_array(array: *mut c_uint, count: usize) { - if !array.is_null() && count > 0 { - let _ = Vec::from_raw_parts(array, count, count); - } -} - -/// Get the total number of accounts in the collection -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -#[no_mangle] -pub unsafe extern "C" fn account_collection_count( - collection: *const FFIAccountCollection, -) -> c_uint { - if collection.is_null() { - return 0; - } - - let collection = &*collection; - let mut count = 0u32; - - count += collection.collection.standard_bip44_accounts.len() as u32; - count += collection.collection.standard_bip32_accounts.len() as u32; - count += collection.collection.coinjoin_accounts.len() as u32; - count += collection.collection.identity_topup.len() as u32; - - if collection.collection.identity_registration.is_some() { - count += 1; - } - if collection.collection.identity_topup_not_bound.is_some() { - count += 1; - } - if collection.collection.identity_invitation.is_some() { - count += 1; - } - if collection.collection.provider_voting_keys.is_some() { - count += 1; - } - if collection.collection.provider_owner_keys.is_some() { - count += 1; - } - - #[cfg(feature = "bls")] - if collection.collection.provider_operator_keys.is_some() { - count += 1; - } - - #[cfg(feature = "eddsa")] - if collection.collection.provider_platform_keys.is_some() { - count += 1; - } - - count -} - -/// Get a human-readable summary of all accounts in the collection -/// -/// Returns a formatted string showing all account types and their indices. -/// The format is designed to be clear and readable for end users. -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIAccountCollection -/// - The returned string must be freed with `string_free` when no longer needed -/// - Returns null if the collection pointer is null -#[no_mangle] -pub unsafe extern "C" fn account_collection_summary( - collection: *const FFIAccountCollection, -) -> *mut c_char { - if collection.is_null() { - return ptr::null_mut(); - } - - let collection = &*collection; - let mut summary_parts = Vec::new(); - - summary_parts.push("Account Summary:".to_string()); - - // BIP44 Accounts - if !collection.collection.standard_bip44_accounts.is_empty() { - let mut indices: Vec = - collection.collection.standard_bip44_accounts.keys().copied().collect(); - indices.sort(); - let count = indices.len(); - let indices_str = format!("{:?}", indices); - summary_parts.push(format!( - "• BIP44 Accounts: {} {} at indices {}", - count, - if count == 1 { - "account" - } else { - "accounts" - }, - indices_str - )); - } - - // BIP32 Accounts - if !collection.collection.standard_bip32_accounts.is_empty() { - let mut indices: Vec = - collection.collection.standard_bip32_accounts.keys().copied().collect(); - indices.sort(); - let count = indices.len(); - let indices_str = format!("{:?}", indices); - summary_parts.push(format!( - "• BIP32 Accounts: {} {} at indices {}", - count, - if count == 1 { - "account" - } else { - "accounts" - }, - indices_str - )); - } - - // CoinJoin Accounts - if !collection.collection.coinjoin_accounts.is_empty() { - let mut indices: Vec = - collection.collection.coinjoin_accounts.keys().copied().collect(); - indices.sort(); - let count = indices.len(); - let indices_str = format!("{:?}", indices); - summary_parts.push(format!( - "• CoinJoin Accounts: {} {} at indices {}", - count, - if count == 1 { - "account" - } else { - "accounts" - }, - indices_str - )); - } - - // Identity TopUp Accounts - if !collection.collection.identity_topup.is_empty() { - let mut indices: Vec = collection.collection.identity_topup.keys().copied().collect(); - indices.sort(); - let count = indices.len(); - let indices_str = format!("{:?}", indices); - summary_parts.push(format!( - "• Identity TopUp: {} {} at indices {}", - count, - if count == 1 { - "account" - } else { - "accounts" - }, - indices_str - )); - } - - // Special accounts (single instances) - if collection.collection.identity_registration.is_some() { - summary_parts.push("• Identity Registration Account".to_string()); - } - - if collection.collection.identity_topup_not_bound.is_some() { - summary_parts.push("• Identity TopUp Not Bound Account".to_string()); - } - - if collection.collection.identity_invitation.is_some() { - summary_parts.push("• Identity Invitation Account".to_string()); - } - - if collection.collection.provider_voting_keys.is_some() { - summary_parts.push("• Provider Voting Keys Account".to_string()); - } - - if collection.collection.provider_owner_keys.is_some() { - summary_parts.push("• Provider Owner Keys Account".to_string()); - } - - #[cfg(feature = "bls")] - if collection.collection.provider_operator_keys.is_some() { - summary_parts.push("• Provider Operator Keys Account (BLS)".to_string()); - } - - #[cfg(feature = "eddsa")] - if collection.collection.provider_platform_keys.is_some() { - summary_parts.push("• Provider Platform Keys Account (EdDSA)".to_string()); - } - - // If there are no accounts at all - if summary_parts.len() == 1 { - summary_parts.push("No accounts configured".to_string()); - } - - let summary = summary_parts.join("\n"); - - match CString::new(summary) { - Ok(c_str) => c_str.into_raw(), - Err(_) => ptr::null_mut(), - } -} - /// Get structured account collection summary data /// /// Returns a struct containing arrays of indices for each account type and boolean diff --git a/key-wallet-ffi/src/account_derivation.rs b/key-wallet-ffi/src/account_derivation.rs index 1bd7b86ec..ac734fbf3 100644 --- a/key-wallet-ffi/src/account_derivation.rs +++ b/key-wallet-ffi/src/account_derivation.rs @@ -1,423 +1,14 @@ //! Account-level derivation functions exposed over FFI use crate::account::FFIAccount; -#[cfg(feature = "bls")] -use crate::account::FFIBLSAccount; -#[cfg(feature = "eddsa")] -use crate::account::FFIEdDSAAccount; use crate::error::{FFIError, FFIErrorCode}; -use crate::keys::{FFIExtendedPrivateKey, FFIPrivateKey}; +use crate::keys::FFIExtendedPrivateKey; use key_wallet::account::derivation::AccountDerivation; use key_wallet::account::AccountTrait; use std::ffi::CString; use std::os::raw::{c_char, c_uint}; use std::ptr; -// No extra FFI enum for chain selection; account semantics decide path. - -/// Derive an extended private key from an account at a given index, using the provided master xpriv. -/// -/// Returns an opaque FFIExtendedPrivateKey pointer that must be freed with `extended_private_key_free`. -/// -/// Notes: -/// - This is chain-agnostic. For accounts with internal/external chains, this returns an error. -/// - For hardened-only account types (e.g., EdDSA), a hardened index is used. -/// -/// # Safety -/// - `account` and `master_xpriv` must be valid, non-null pointers allocated by this library. -/// - `error` must be a valid pointer to an FFIError or null. -/// - The caller must free the returned pointer with `extended_private_key_free`. -#[no_mangle] -pub unsafe extern "C" fn account_derive_extended_private_key_at( - account: *const FFIAccount, - master_xpriv: *const FFIExtendedPrivateKey, - index: c_uint, - error: *mut FFIError, -) -> *mut FFIExtendedPrivateKey { - if account.is_null() || master_xpriv.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let account = &*account; - let master_xpriv = &*master_xpriv; - - if account.inner().is_watch_only() { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "Account is watch-only; private derivation not allowed".to_string(), - ); - return ptr::null_mut(); - } - - match account.inner().derive_from_master_xpriv_extended_xpriv_at(master_xpriv.inner(), index) { - Ok(derived) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIExtendedPrivateKey::from_inner(derived))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive extended private key: {:?}", e), - ); - ptr::null_mut() - } - } -} - -// ========================= BLS (feature = "bls") ========================= -/// Derive a BLS private key from a raw seed buffer at the given index. -/// -/// Returns a newly allocated hex string of the 32-byte private key. The caller must free -/// it with `string_free`. -/// -/// Notes: -/// - Uses the account's network for master key creation. -/// - Chain-agnostic; may return an error for accounts with internal/external chains. -/// -/// # Safety -/// - `account` must be a valid, non-null pointer to an `FFIBLSAccount` (only when `bls` feature is enabled). -/// - `seed` must point to a readable buffer of length `seed_len` (1..=64 bytes expected). -/// - `error` must be a valid pointer to an FFIError or null. -/// - Returned string must be freed with `string_free`. -#[cfg(feature = "bls")] -#[no_mangle] -pub unsafe extern "C" fn bls_account_derive_private_key_from_seed( - account: *const FFIBLSAccount, - seed: *const u8, - seed_len: usize, - index: c_uint, - error: *mut FFIError, -) -> *mut c_char { - if account.is_null() || seed.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - let account = &*account; - if seed_len == 0 || seed_len > 64 { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Seed length must be between 1 and 64 bytes".to_string(), - ); - return ptr::null_mut(); - } - let seed_slice = std::slice::from_raw_parts(seed, seed_len); - match account.inner().derive_from_seed_private_key_at(seed_slice, index) { - Ok(sk) => { - // Return private key bytes as hex - let hex = hex::encode(sk.to_be_bytes()); - match CString::new(hex) { - Ok(s) => { - FFIError::set_success(error); - s.into_raw() - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Allocation failed".into(), - ); - ptr::null_mut() - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive BLS private key from seed: {:?}", e), - ); - ptr::null_mut() - } - } -} - -/// Derive a BLS private key from a mnemonic + optional passphrase at the given index. -/// -/// Returns a newly allocated hex string of the 32-byte private key. The caller must free -/// it with `string_free`. -/// -/// Notes: -/// - Uses the English wordlist for parsing the mnemonic. -/// - Chain-agnostic; may return an error for accounts with internal/external chains. -/// -/// # Safety -/// - `account` must be a valid, non-null pointer to an `FFIBLSAccount` (only when `bls` feature is enabled). -/// - `mnemonic` must be a valid, null-terminated UTF-8 C string. -/// - `passphrase` may be null; if not null, must be a valid UTF-8 C string. -/// - `error` must be a valid pointer to an FFIError or null. -/// - Returned string must be freed with `string_free`. -#[cfg(feature = "bls")] -#[no_mangle] -pub unsafe extern "C" fn bls_account_derive_private_key_from_mnemonic( - account: *const FFIBLSAccount, - mnemonic: *const c_char, - passphrase: *const c_char, - index: c_uint, - error: *mut FFIError, -) -> *mut c_char { - if account.is_null() || mnemonic.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - let account = &*account; - let mnemonic_str = match std::ffi::CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid mnemonic string".into(), - ); - return ptr::null_mut(); - } - }; - let passphrase_str = if passphrase.is_null() { - None - } else { - match std::ffi::CStr::from_ptr(passphrase).to_str() { - Ok(s) => Some(s), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid passphrase string".into(), - ); - return ptr::null_mut(); - } - } - }; - match account.inner().derive_from_mnemonic_private_key_at( - mnemonic_str, - passphrase_str, - key_wallet::mnemonic::Language::English, - index, - ) { - Ok(sk) => { - let hex = hex::encode(sk.to_be_bytes()); - match CString::new(hex) { - Ok(s) => { - FFIError::set_success(error); - s.into_raw() - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Allocation failed".into(), - ); - ptr::null_mut() - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive BLS private key from mnemonic: {:?}", e), - ); - ptr::null_mut() - } - } -} - -// ========================= EdDSA (feature = "eddsa") ========================= -/// Derive an EdDSA (ed25519) private key from a raw seed buffer at the given index. -/// -/// Returns a newly allocated hex string of the 32-byte private key. The caller must free -/// it with `string_free`. -/// -/// Notes: -/// - EdDSA only supports hardened derivation; the index will be used accordingly. -/// - Chain-agnostic; EdDSA accounts typically do not have internal/external split. -/// -/// # Safety -/// - `account` must be a valid, non-null pointer to an `FFIEdDSAAccount` (only when `eddsa` feature is enabled). -/// - `seed` must point to a readable buffer of length `seed_len` (1..=64 bytes expected). -/// - `error` must be a valid pointer to an FFIError or null. -/// - Returned string must be freed with `string_free`. -#[cfg(feature = "eddsa")] -#[no_mangle] -pub unsafe extern "C" fn eddsa_account_derive_private_key_from_seed( - account: *const FFIEdDSAAccount, - seed: *const u8, - seed_len: usize, - index: c_uint, - error: *mut FFIError, -) -> *mut c_char { - if account.is_null() || seed.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - let account = &*account; - let seed_slice = std::slice::from_raw_parts(seed, seed_len); - match account.inner().derive_from_seed_private_key_at(seed_slice, index) { - Ok(sk) => { - // Return 32-byte ed25519 seed/private key as hex - let hex = hex::encode(sk.to_bytes()); - match CString::new(hex) { - Ok(s) => { - FFIError::set_success(error); - s.into_raw() - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Allocation failed".into(), - ); - ptr::null_mut() - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive EdDSA private key from seed: {:?}", e), - ); - ptr::null_mut() - } - } -} - -/// Derive an EdDSA (ed25519) private key from a mnemonic + optional passphrase at the given index. -/// -/// Returns a newly allocated hex string of the 32-byte private key. The caller must free -/// it with `string_free`. -/// -/// Notes: -/// - Uses the English wordlist for parsing the mnemonic. -/// -/// # Safety -/// - `account` must be a valid, non-null pointer to an `FFIEdDSAAccount` (only when `eddsa` feature is enabled). -/// - `mnemonic` must be a valid, null-terminated UTF-8 C string. -/// - `passphrase` may be null; if not null, must be a valid UTF-8 C string. -/// - `error` must be a valid pointer to an FFIError or null. -/// - Returned string must be freed with `string_free`. -#[cfg(feature = "eddsa")] -#[no_mangle] -pub unsafe extern "C" fn eddsa_account_derive_private_key_from_mnemonic( - account: *const FFIEdDSAAccount, - mnemonic: *const c_char, - passphrase: *const c_char, - index: c_uint, - error: *mut FFIError, -) -> *mut c_char { - if account.is_null() || mnemonic.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - let account = &*account; - let mnemonic_str = match std::ffi::CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid mnemonic string".into(), - ); - return ptr::null_mut(); - } - }; - let passphrase_str = if passphrase.is_null() { - None - } else { - match std::ffi::CStr::from_ptr(passphrase).to_str() { - Ok(s) => Some(s), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid passphrase string".into(), - ); - return ptr::null_mut(); - } - } - }; - match account.inner().derive_from_mnemonic_private_key_at( - mnemonic_str, - passphrase_str, - key_wallet::mnemonic::Language::English, - index, - ) { - Ok(sk) => { - let hex = hex::encode(sk.to_bytes()); - match CString::new(hex) { - Ok(s) => { - FFIError::set_success(error); - s.into_raw() - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Allocation failed".into(), - ); - ptr::null_mut() - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive EdDSA private key from mnemonic: {:?}", e), - ); - ptr::null_mut() - } - } -} - -/// Derive a private key (secp256k1) from an account at a given chain/index, using the provided master xpriv. -/// Returns an opaque FFIPrivateKey pointer that must be freed with `private_key_free`. -/// -/// # Safety -/// - `account` and `master_xpriv` must be valid pointers allocated by this library -/// - `error` must be a valid pointer to an FFIError or null -#[no_mangle] -pub unsafe extern "C" fn account_derive_private_key_at( - account: *const FFIAccount, - master_xpriv: *const FFIExtendedPrivateKey, - index: c_uint, - error: *mut FFIError, -) -> *mut FFIPrivateKey { - if account.is_null() || master_xpriv.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let account = &*account; - let master_xpriv = &*master_xpriv; - - if account.inner().is_watch_only() { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "Account is watch-only; private derivation not allowed".to_string(), - ); - return ptr::null_mut(); - } - - match account.inner().derive_from_master_xpriv_extended_xpriv_at(master_xpriv.inner(), index) { - Ok(derived) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIPrivateKey::from_secret(derived.private_key))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive private key: {:?}", e), - ); - ptr::null_mut() - } - } -} - /// Derive a private key from an account at a given chain/index and return as WIF string. /// Caller must free the returned string with `string_free`. /// @@ -481,221 +72,3 @@ pub unsafe extern "C" fn account_derive_private_key_as_wif_at( } } } - -/// Derive an extended private key from a raw seed buffer at the given index. -/// Returns an opaque FFIExtendedPrivateKey pointer that must be freed with `extended_private_key_free`. -/// -/// # Safety -/// - `account` must be a valid pointer to an FFIAccount -/// - `seed` must point to a valid buffer of length `seed_len` -/// - `error` must be a valid pointer to an FFIError or null -#[no_mangle] -pub unsafe extern "C" fn account_derive_extended_private_key_from_seed( - account: *const FFIAccount, - seed: *const u8, - seed_len: usize, - index: c_uint, - error: *mut FFIError, -) -> *mut FFIExtendedPrivateKey { - if account.is_null() || seed.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let account = &*account; - let seed_slice = std::slice::from_raw_parts(seed, seed_len); - - match account.inner().derive_from_seed_extended_xpriv_at(seed_slice, index) { - Ok(derived) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIExtendedPrivateKey::from_inner(derived))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive extended private key from seed: {:?}", e), - ); - ptr::null_mut() - } - } -} - -/// Derive a private key from a raw seed buffer at the given index. -/// Returns an opaque FFIPrivateKey pointer that must be freed with `private_key_free`. -/// -/// # Safety -/// - `account` must be a valid pointer to an FFIAccount -/// - `seed` must point to a valid buffer of length `seed_len` -/// - `error` must be a valid pointer to an FFIError or null -#[no_mangle] -pub unsafe extern "C" fn account_derive_private_key_from_seed( - account: *const FFIAccount, - seed: *const u8, - seed_len: usize, - index: c_uint, - error: *mut FFIError, -) -> *mut FFIPrivateKey { - if account.is_null() || seed.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let account = &*account; - let seed_slice = std::slice::from_raw_parts(seed, seed_len); - - match account.inner().derive_from_seed_extended_xpriv_at(seed_slice, index) { - Ok(derived) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIPrivateKey::from_secret(derived.private_key))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive private key from seed: {:?}", e), - ); - ptr::null_mut() - } - } -} - -/// Derive an extended private key from a mnemonic + optional passphrase at the given index. -/// Returns an opaque FFIExtendedPrivateKey pointer that must be freed with `extended_private_key_free`. -/// -/// # Safety -/// - `account` must be a valid pointer to an FFIAccount -/// - `mnemonic` must be a valid, null-terminated C string -/// - `passphrase` may be null; if not null, must be a valid C string -/// - `error` must be a valid pointer to an FFIError or null -#[no_mangle] -pub unsafe extern "C" fn account_derive_extended_private_key_from_mnemonic( - account: *const FFIAccount, - mnemonic: *const c_char, - passphrase: *const c_char, - index: c_uint, - error: *mut FFIError, -) -> *mut FFIExtendedPrivateKey { - if account.is_null() || mnemonic.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let account = &*account; - let mnemonic_str = match std::ffi::CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid mnemonic string".to_string(), - ); - return ptr::null_mut(); - } - }; - let passphrase_str = if passphrase.is_null() { - None - } else { - match std::ffi::CStr::from_ptr(passphrase).to_str() { - Ok(s) => Some(s), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid passphrase string".to_string(), - ); - return ptr::null_mut(); - } - } - }; - - match account.inner().derive_from_mnemonic_extended_xpriv_at( - mnemonic_str, - passphrase_str, - key_wallet::mnemonic::Language::English, - index, - ) { - Ok(derived) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIExtendedPrivateKey::from_inner(derived))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive extended private key from mnemonic: {:?}", e), - ); - ptr::null_mut() - } - } -} - -/// Derive a private key from a mnemonic + optional passphrase at the given index. -/// Returns an opaque FFIPrivateKey pointer that must be freed with `private_key_free`. -/// -/// # Safety -/// - `account` must be a valid pointer to an FFIAccount -/// - `mnemonic` must be a valid, null-terminated C string -/// - `passphrase` may be null; if not null, must be a valid C string -/// - `error` must be a valid pointer to an FFIError or null -#[no_mangle] -pub unsafe extern "C" fn account_derive_private_key_from_mnemonic( - account: *const FFIAccount, - mnemonic: *const c_char, - passphrase: *const c_char, - index: c_uint, - error: *mut FFIError, -) -> *mut FFIPrivateKey { - if account.is_null() || mnemonic.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let account = &*account; - let mnemonic_str = match std::ffi::CStr::from_ptr(mnemonic).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid mnemonic string".to_string(), - ); - return ptr::null_mut(); - } - }; - let passphrase_str = if passphrase.is_null() { - None - } else { - match std::ffi::CStr::from_ptr(passphrase).to_str() { - Ok(s) => Some(s), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid passphrase string".to_string(), - ); - return ptr::null_mut(); - } - } - }; - - match account.inner().derive_from_mnemonic_extended_xpriv_at( - mnemonic_str, - passphrase_str, - key_wallet::mnemonic::Language::English, - index, - ) { - Ok(derived) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIPrivateKey::from_secret(derived.private_key))) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive private key from mnemonic: {:?}", e), - ); - ptr::null_mut() - } - } -} diff --git a/key-wallet-ffi/src/bip38.rs b/key-wallet-ffi/src/bip38.rs deleted file mode 100644 index 583704abe..000000000 --- a/key-wallet-ffi/src/bip38.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! BIP38 encryption support - -use std::ffi::CStr; -use std::os::raw::c_char; -use std::ptr; - -use crate::error::{FFIError, FFIErrorCode}; - -/// Encrypt a private key with BIP38 -/// -/// # Safety -/// -/// This function is unsafe because it dereferences raw pointers: -/// - `private_key` must be a valid, null-terminated C string -/// - `passphrase` must be a valid, null-terminated C string -/// - `error` must be a valid pointer to an FFIError or null -#[no_mangle] -pub unsafe extern "C" fn bip38_encrypt_private_key( - private_key: *const c_char, - passphrase: *const c_char, - error: *mut FFIError, -) -> *mut c_char { - #[cfg(feature = "bip38")] - { - if private_key.is_null() || passphrase.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Null pointer provided".to_string(), - ); - return ptr::null_mut(); - } - - let _privkey_str = match CStr::from_ptr(private_key).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in private key".to_string(), - ); - return ptr::null_mut(); - } - }; - - let _passphrase_str = match CStr::from_ptr(passphrase).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in passphrase".to_string(), - ); - return ptr::null_mut(); - } - }; - - // Note: key_wallet doesn't have built-in BIP38 support - // This would need to be implemented using a BIP38 library - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "BIP38 encryption not yet implemented".to_string(), - ); - ptr::null_mut() - } - #[cfg(not(feature = "bip38"))] - { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "BIP38 support not enabled".to_string(), - ); - ptr::null_mut() - } -} - -/// Decrypt a BIP38 encrypted private key -/// -/// # Safety -/// -/// This function is unsafe because it dereferences raw pointers: -/// - `encrypted_key` must be a valid, null-terminated C string -/// - `passphrase` must be a valid, null-terminated C string -/// - `error` must be a valid pointer to an FFIError or null -#[no_mangle] -pub unsafe extern "C" fn bip38_decrypt_private_key( - encrypted_key: *const c_char, - passphrase: *const c_char, - error: *mut FFIError, -) -> *mut c_char { - #[cfg(feature = "bip38")] - { - if encrypted_key.is_null() || passphrase.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Null pointer provided".to_string(), - ); - return ptr::null_mut(); - } - - let _encrypted_str = match CStr::from_ptr(encrypted_key).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in encrypted key".to_string(), - ); - return ptr::null_mut(); - } - }; - - let _passphrase_str = match CStr::from_ptr(passphrase).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in passphrase".to_string(), - ); - return ptr::null_mut(); - } - }; - - // Note: key_wallet doesn't have built-in BIP38 support - // This would need to be implemented using a BIP38 library - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "BIP38 decryption not yet implemented".to_string(), - ); - ptr::null_mut() - } - #[cfg(not(feature = "bip38"))] - { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - "BIP38 support not enabled".to_string(), - ); - ptr::null_mut() - } -} diff --git a/key-wallet-ffi/src/derivation.rs b/key-wallet-ffi/src/derivation.rs index 1bfca0daf..9c13eab86 100644 --- a/key-wallet-ffi/src/derivation.rs +++ b/key-wallet-ffi/src/derivation.rs @@ -2,10 +2,7 @@ use crate::error::{FFIError, FFIErrorCode}; use crate::types::FFINetwork; -use dashcore::Network; -use key_wallet::{ExtendedPrivKey, ExtendedPubKey}; -use secp256k1::Secp256k1; -use std::ffi::{CStr, CString}; +use std::ffi::CString; use std::os::raw::{c_char, c_uint}; use std::ptr; use std::slice; @@ -424,89 +421,6 @@ pub extern "C" fn derivation_identity_authentication_path( true } -/// Derive private key for a specific path from seed -/// -/// # Safety -/// -/// - `seed` must be a valid pointer to a byte array of `seed_len` length -/// - `path` must be a valid pointer to a null-terminated C string -/// - `error` must be a valid pointer to an FFIError structure or null -/// - The caller must ensure all pointers remain valid for the duration of this call -#[no_mangle] -pub unsafe extern "C" fn derivation_derive_private_key_from_seed( - seed: *const u8, - seed_len: usize, - path: *const c_char, - network: FFINetwork, - error: *mut FFIError, -) -> *mut FFIExtendedPrivKey { - if seed.is_null() || path.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let seed_slice = slice::from_raw_parts(seed, seed_len); - let network_rust: Network = network.into(); - - let path_str = match CStr::from_ptr(path).to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in path".to_string(), - ); - return ptr::null_mut(); - } - }; - - use key_wallet::bip32::{DerivationPath, ExtendedPrivKey}; - use secp256k1::Secp256k1; - use std::str::FromStr; - - let derivation_path = match DerivationPath::from_str(path_str) { - Ok(p) => p, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidDerivationPath, - format!("Invalid derivation path: {:?}", e), - ); - return ptr::null_mut(); - } - }; - - let secp = Secp256k1::new(); - let master = match ExtendedPrivKey::new_master(network_rust, seed_slice) { - Ok(m) => m, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to create master key: {:?}", e), - ); - return ptr::null_mut(); - } - }; - - match master.derive_priv(&secp, &derivation_path) { - Ok(xpriv) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIExtendedPrivKey { - inner: xpriv, - })) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive private key: {:?}", e), - ); - ptr::null_mut() - } - } -} - /// Derive public key from extended private key /// /// # Safety @@ -699,168 +613,6 @@ pub unsafe extern "C" fn derivation_string_free(s: *mut c_char) { } } -// MARK: - Simplified Derivation Functions - -/// Derive an address from a private key -/// -/// # Safety -/// - `private_key` must be a valid pointer to 32 bytes -/// - `network` is the network for the address -/// -/// # Returns -/// - Pointer to C string with address (caller must free) -/// - NULL on error -#[no_mangle] -pub unsafe extern "C" fn key_wallet_derive_address_from_key( - private_key: *const u8, - network: FFINetwork, -) -> *mut c_char { - if private_key.is_null() { - return ptr::null_mut(); - } - - let key_slice = slice::from_raw_parts(private_key, 32); - - // Create a secp256k1 private key - let secp = Secp256k1::new(); - let secret_key = match secp256k1::SecretKey::from_slice(key_slice) { - Ok(sk) => sk, - Err(_) => return ptr::null_mut(), - }; - - // Get public key - let public_key = secp256k1::PublicKey::from_secret_key(&secp, &secret_key); - - // Convert to dashcore PublicKey - let dash_pubkey = dashcore::PublicKey::new(public_key); - - // Convert to Dash address - let dash_network: key_wallet::Network = network.into(); - let address = key_wallet::Address::p2pkh(&dash_pubkey, dash_network); - - match CString::new(address.to_string()) { - Ok(c_str) => c_str.into_raw(), - Err(_) => ptr::null_mut(), - } -} - -/// Derive an address from a seed at a specific derivation path -/// -/// # Safety -/// - `seed` must be a valid pointer to 64 bytes -/// - `network` is the network for the address -/// - `path` must be a valid null-terminated C string (e.g., "m/44'/5'/0'/0/0") -/// -/// # Returns -/// - Pointer to C string with address (caller must free) -/// - NULL on error -#[no_mangle] -pub unsafe extern "C" fn key_wallet_derive_address_from_seed( - seed: *const u8, - network: FFINetwork, - path: *const c_char, -) -> *mut c_char { - if seed.is_null() || path.is_null() { - return ptr::null_mut(); - } - - let seed_slice = slice::from_raw_parts(seed, 64); - let dash_network: key_wallet::Network = network.into(); - - // Parse derivation path - let path_str = match CStr::from_ptr(path).to_str() { - Ok(s) => s, - Err(_) => return ptr::null_mut(), - }; - - use std::str::FromStr; - let derivation_path = match key_wallet::DerivationPath::from_str(path_str) { - Ok(dp) => dp, - Err(_) => return ptr::null_mut(), - }; - - // Create master key from seed - let master_key = match ExtendedPrivKey::new_master(dash_network, seed_slice) { - Ok(xprv) => xprv, - Err(_) => return ptr::null_mut(), - }; - - // Derive at path - let secp = Secp256k1::new(); - let derived_key = match master_key.derive_priv(&secp, &derivation_path) { - Ok(xprv) => xprv, - Err(_) => return ptr::null_mut(), - }; - - // Get public key - let extended_pubkey = ExtendedPubKey::from_priv(&secp, &derived_key); - - // Convert secp256k1::PublicKey to dashcore::PublicKey - let dash_pubkey = dashcore::PublicKey::new(extended_pubkey.public_key); - - // Convert to address - let address = key_wallet::Address::p2pkh(&dash_pubkey, dash_network); - - match CString::new(address.to_string()) { - Ok(c_str) => c_str.into_raw(), - Err(_) => ptr::null_mut(), - } -} - -/// Derive a private key from a seed at a specific derivation path -/// -/// # Safety -/// - `seed` must be a valid pointer to 64 bytes -/// - `path` must be a valid null-terminated C string (e.g., "m/44'/5'/0'/0/0") -/// - `key_out` must be a valid pointer to a buffer of at least 32 bytes -/// -/// # Returns -/// - 0 on success -/// - -1 on error -#[no_mangle] -pub unsafe extern "C" fn key_wallet_derive_private_key_from_seed( - seed: *const u8, - path: *const c_char, - key_out: *mut u8, -) -> i32 { - if seed.is_null() || path.is_null() || key_out.is_null() { - return -1; - } - - let seed_slice = slice::from_raw_parts(seed, 64); - - // Parse derivation path - let path_str = match CStr::from_ptr(path).to_str() { - Ok(s) => s, - Err(_) => return -1, - }; - - use std::str::FromStr; - let derivation_path = match key_wallet::DerivationPath::from_str(path_str) { - Ok(dp) => dp, - Err(_) => return -1, - }; - - // Create master key from seed (use testnet as default, doesn't affect key derivation) - let master_key = match ExtendedPrivKey::new_master(key_wallet::Network::Testnet, seed_slice) { - Ok(xprv) => xprv, - Err(_) => return -1, - }; - - // Derive at path - let secp = Secp256k1::new(); - let derived_key = match master_key.derive_priv(&secp, &derivation_path) { - Ok(xprv) => xprv, - Err(_) => return -1, - }; - - // Copy private key bytes - let key_bytes = derived_key.private_key.secret_bytes(); - ptr::copy_nonoverlapping(key_bytes.as_ptr(), key_out, 32); - - 0 -} - #[cfg(test)] #[path = "derivation_tests.rs"] mod tests; diff --git a/key-wallet-ffi/src/keys.rs b/key-wallet-ffi/src/keys.rs index 8741a9e13..6e06794ca 100644 --- a/key-wallet-ffi/src/keys.rs +++ b/key-wallet-ffi/src/keys.rs @@ -1,7 +1,7 @@ //! Key derivation and management use crate::error::{FFIError, FFIErrorCode}; -use crate::types::{FFINetwork, FFIWallet}; +use crate::types::FFIWallet; use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_uint}; use std::ptr; @@ -31,22 +31,6 @@ impl FFIExtendedPrivateKey { pub(crate) fn inner(&self) -> &key_wallet::bip32::ExtendedPrivKey { &self.inner } - - #[inline] - pub(crate) fn from_inner(inner: key_wallet::bip32::ExtendedPrivKey) -> Self { - FFIExtendedPrivateKey { - inner, - } - } -} - -impl FFIPrivateKey { - #[inline] - pub(crate) fn from_secret(inner: secp256k1::SecretKey) -> Self { - FFIPrivateKey { - inner, - } - } } /// Get extended private key for account @@ -140,74 +124,6 @@ pub unsafe extern "C" fn wallet_get_account_xpub( } } -/// Derive private key at a specific path -/// Returns an opaque FFIPrivateKey pointer that must be freed with private_key_free -/// -/// # Safety -/// -/// - `wallet` must be a valid pointer to an FFIWallet -/// - `derivation_path` must be a valid null-terminated C string -/// - `error` must be a valid pointer to an FFIError -/// - The returned pointer must be freed with `private_key_free` -#[no_mangle] -pub unsafe extern "C" fn wallet_derive_private_key( - wallet: *const FFIWallet, - derivation_path: *const c_char, - error: *mut FFIError, -) -> *mut FFIPrivateKey { - if wallet.is_null() || derivation_path.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let path_str = match unsafe { CStr::from_ptr(derivation_path) }.to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in derivation path".to_string(), - ); - return ptr::null_mut(); - } - }; - - // Parse the derivation path - use key_wallet::DerivationPath; - use std::str::FromStr; - let path = match DerivationPath::from_str(path_str) { - Ok(p) => p, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Invalid derivation path: {}", e), - ); - return ptr::null_mut(); - } - }; - - let wallet = unsafe { &*wallet }; - - // Use the new wallet method to derive the private key - match wallet.inner().derive_private_key(&path) { - Ok(private_key) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIPrivateKey { - inner: private_key, - })) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive private key: {:?}", e), - ); - ptr::null_mut() - } - } -} - /// Derive extended private key at a specific path /// Returns an opaque FFIExtendedPrivateKey pointer that must be freed with extended_private_key_free /// @@ -351,295 +267,6 @@ pub unsafe extern "C" fn wallet_derive_private_key_as_wif( } } -/// Free a private key -/// -/// # Safety -/// -/// - `key` must be a valid pointer created by private key functions or null -/// - After calling this function, the pointer becomes invalid -#[no_mangle] -pub unsafe extern "C" fn private_key_free(key: *mut FFIPrivateKey) { - if !key.is_null() { - let _ = unsafe { Box::from_raw(key) }; - } -} - -/// Free an extended private key -/// -/// # Safety -/// -/// - `key` must be a valid pointer created by extended private key functions or null -/// - After calling this function, the pointer becomes invalid -#[no_mangle] -pub unsafe extern "C" fn extended_private_key_free(key: *mut FFIExtendedPrivateKey) { - if !key.is_null() { - let _ = unsafe { Box::from_raw(key) }; - } -} - -/// Get extended private key as string (xprv format) -/// -/// Returns the extended private key in base58 format (xprv... for mainnet, tprv... for testnet) -/// -/// # Safety -/// -/// - `key` must be a valid pointer to an FFIExtendedPrivateKey -/// - `network` is ignored; the network is encoded in the extended key -/// - `error` must be a valid pointer to an FFIError -/// - The returned string must be freed with `string_free` -#[no_mangle] -pub unsafe extern "C" fn extended_private_key_to_string( - key: *const FFIExtendedPrivateKey, - network: FFINetwork, - error: *mut FFIError, -) -> *mut c_char { - if key.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Extended private key is null".to_string(), - ); - return ptr::null_mut(); - } - - let key = unsafe { &*key }; - let _ = network; // Network is already encoded in the extended key - - // Convert to string - the network is already encoded in the extended key - let key_string = key.inner.to_string(); - - FFIError::set_success(error); - match CString::new(key_string) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } -} - -/// Get the private key from an extended private key -/// -/// Extracts the non-extended private key from an extended private key. -/// -/// # Safety -/// -/// - `extended_key` must be a valid pointer to an FFIExtendedPrivateKey -/// - `error` must be a valid pointer to an FFIError -/// - The returned FFIPrivateKey must be freed with `private_key_free` -#[no_mangle] -pub unsafe extern "C" fn extended_private_key_get_private_key( - extended_key: *const FFIExtendedPrivateKey, - error: *mut FFIError, -) -> *mut FFIPrivateKey { - if extended_key.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Extended private key is null".to_string(), - ); - return ptr::null_mut(); - } - - let extended = unsafe { &*extended_key }; - - // Extract the private key - let private_key = FFIPrivateKey { - inner: extended.inner.private_key, - }; - - FFIError::set_success(error); - Box::into_raw(Box::new(private_key)) -} - -/// Get private key as WIF string from FFIPrivateKey -/// -/// # Safety -/// -/// - `key` must be a valid pointer to an FFIPrivateKey -/// - `error` must be a valid pointer to an FFIError -/// - The returned string must be freed with `string_free` -#[no_mangle] -pub unsafe extern "C" fn private_key_to_wif( - key: *const FFIPrivateKey, - network: FFINetwork, - error: *mut FFIError, -) -> *mut c_char { - if key.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Private key is null".to_string()); - return ptr::null_mut(); - } - - let key = unsafe { &*key }; - let network_rust: key_wallet::Network = network.into(); - - // Convert to WIF format - use dashcore::PrivateKey as DashPrivateKey; - let dash_key = DashPrivateKey { - compressed: true, - network: network_rust, - inner: key.inner, - }; - - let wif = dash_key.to_wif(); - FFIError::set_success(error); - match CString::new(wif) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } -} - -/// Derive public key at a specific path -/// Returns an opaque FFIPublicKey pointer that must be freed with public_key_free -/// -/// # Safety -/// -/// - `wallet` must be a valid pointer to an FFIWallet -/// - `derivation_path` must be a valid null-terminated C string -/// - `error` must be a valid pointer to an FFIError -/// - The returned pointer must be freed with `public_key_free` -#[no_mangle] -pub unsafe extern "C" fn wallet_derive_public_key( - wallet: *const FFIWallet, - derivation_path: *const c_char, - error: *mut FFIError, -) -> *mut FFIPublicKey { - if wallet.is_null() || derivation_path.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let path_str = match unsafe { CStr::from_ptr(derivation_path) }.to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in derivation path".to_string(), - ); - return ptr::null_mut(); - } - }; - - // Parse the derivation path - use key_wallet::DerivationPath; - use std::str::FromStr; - let path = match DerivationPath::from_str(path_str) { - Ok(p) => p, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Invalid derivation path: {}", e), - ); - return ptr::null_mut(); - } - }; - - unsafe { - let wallet = &*wallet; - - // Use the new wallet method to derive the public key - match wallet.inner().derive_public_key(&path) { - Ok(public_key) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIPublicKey { - inner: public_key, - })) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive public key: {:?}", e), - ); - ptr::null_mut() - } - } - } -} - -/// Derive extended public key at a specific path -/// Returns an opaque FFIExtendedPublicKey pointer that must be freed with extended_public_key_free -/// -/// # Safety -/// -/// - `wallet` must be a valid pointer to an FFIWallet -/// - `derivation_path` must be a valid null-terminated C string -/// - `error` must be a valid pointer to an FFIError -/// - The returned pointer must be freed with `extended_public_key_free` -#[no_mangle] -pub unsafe extern "C" fn wallet_derive_extended_public_key( - wallet: *const FFIWallet, - derivation_path: *const c_char, - error: *mut FFIError, -) -> *mut FFIExtendedPublicKey { - if wallet.is_null() || derivation_path.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let path_str = match unsafe { CStr::from_ptr(derivation_path) }.to_str() { - Ok(s) => s, - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in derivation path".to_string(), - ); - return ptr::null_mut(); - } - }; - - // Parse the derivation path - use key_wallet::DerivationPath; - use std::str::FromStr; - let path = match DerivationPath::from_str(path_str) { - Ok(p) => p, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Invalid derivation path: {}", e), - ); - return ptr::null_mut(); - } - }; - - unsafe { - let wallet = &*wallet; - - // Use the new wallet method to derive the extended public key - match wallet.inner().derive_extended_public_key(&path) { - Ok(extended_public_key) => { - FFIError::set_success(error); - Box::into_raw(Box::new(FFIExtendedPublicKey { - inner: extended_public_key, - })) - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::WalletError, - format!("Failed to derive extended public key: {:?}", e), - ); - ptr::null_mut() - } - } - } -} - /// Derive public key at a specific path and return as hex string /// /// # Safety @@ -717,150 +344,6 @@ pub unsafe extern "C" fn wallet_derive_public_key_as_hex( } } -/// Free a public key -/// -/// # Safety -/// -/// - `key` must be a valid pointer created by public key functions or null -/// - After calling this function, the pointer becomes invalid -#[no_mangle] -pub unsafe extern "C" fn public_key_free(key: *mut FFIPublicKey) { - if !key.is_null() { - unsafe { - let _ = Box::from_raw(key); - } - } -} - -/// Free an extended public key -/// -/// # Safety -/// -/// - `key` must be a valid pointer created by extended public key functions or null -/// - After calling this function, the pointer becomes invalid -#[no_mangle] -pub unsafe extern "C" fn extended_public_key_free(key: *mut FFIExtendedPublicKey) { - if !key.is_null() { - unsafe { - let _ = Box::from_raw(key); - } - } -} - -/// Get extended public key as string (xpub format) -/// -/// Returns the extended public key in base58 format (xpub... for mainnet, tpub... for testnet) -/// -/// # Safety -/// -/// - `key` must be a valid pointer to an FFIExtendedPublicKey -/// - `network` is ignored; the network is encoded in the extended key -/// - `error` must be a valid pointer to an FFIError -/// - The returned string must be freed with `string_free` -#[no_mangle] -pub unsafe extern "C" fn extended_public_key_to_string( - key: *const FFIExtendedPublicKey, - network: FFINetwork, - error: *mut FFIError, -) -> *mut c_char { - if key.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Extended public key is null".to_string(), - ); - return ptr::null_mut(); - } - - let key = unsafe { &*key }; - let _ = network; // Network is already encoded in the extended key - - // Convert to string - the network is already encoded in the extended key - let key_string = key.inner.to_string(); - - FFIError::set_success(error); - match CString::new(key_string) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } -} - -/// Get the public key from an extended public key -/// -/// Extracts the non-extended public key from an extended public key. -/// -/// # Safety -/// -/// - `extended_key` must be a valid pointer to an FFIExtendedPublicKey -/// - `error` must be a valid pointer to an FFIError -/// - The returned FFIPublicKey must be freed with `public_key_free` -#[no_mangle] -pub unsafe extern "C" fn extended_public_key_get_public_key( - extended_key: *const FFIExtendedPublicKey, - error: *mut FFIError, -) -> *mut FFIPublicKey { - if extended_key.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Extended public key is null".to_string(), - ); - return ptr::null_mut(); - } - - let extended = unsafe { &*extended_key }; - - // Extract the public key - let public_key = FFIPublicKey { - inner: extended.inner.public_key, - }; - - FFIError::set_success(error); - Box::into_raw(Box::new(public_key)) -} - -/// Get public key as hex string from FFIPublicKey -/// -/// # Safety -/// -/// - `key` must be a valid pointer to an FFIPublicKey -/// - `error` must be a valid pointer to an FFIError -/// - The returned string must be freed with `string_free` -#[no_mangle] -pub unsafe extern "C" fn public_key_to_hex( - key: *const FFIPublicKey, - error: *mut FFIError, -) -> *mut c_char { - if key.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Public key is null".to_string()); - return ptr::null_mut(); - } - - let key = unsafe { &*key }; - let bytes = key.inner.serialize(); - let hex = hex::encode(bytes); - - FFIError::set_success(error); - match CString::new(hex) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } -} - /// Convert derivation path string to indices /// /// # Safety diff --git a/key-wallet-ffi/src/lib.rs b/key-wallet-ffi/src/lib.rs index d79ec1870..78a5bacd0 100644 --- a/key-wallet-ffi/src/lib.rs +++ b/key-wallet-ffi/src/lib.rs @@ -24,9 +24,6 @@ pub mod utxo; pub mod wallet; pub mod wallet_manager; -#[cfg(feature = "bip38")] -pub mod bip38; - // Test modules are now included in each source file // Re-export main types for convenience @@ -34,8 +31,7 @@ pub use error::{FFIError, FFIErrorCode}; pub use types::{FFIBalance, FFINetwork, FFIWallet}; pub use utxo::FFIUTXO; pub use wallet_manager::{ - wallet_manager_create, wallet_manager_describe, wallet_manager_free, - wallet_manager_free_string, wallet_manager_free_wallet_ids, wallet_manager_get_wallet, + wallet_manager_create, wallet_manager_free_wallet_ids, wallet_manager_get_wallet, wallet_manager_get_wallet_balance, wallet_manager_get_wallet_ids, wallet_manager_wallet_count, FFIWalletManager, }; diff --git a/key-wallet-ffi/src/managed_account.rs b/key-wallet-ffi/src/managed_account.rs index 9d5dd42e3..a6a07e063 100644 --- a/key-wallet-ffi/src/managed_account.rs +++ b/key-wallet-ffi/src/managed_account.rs @@ -14,7 +14,7 @@ use crate::error::{FFIError, FFIErrorCode}; use crate::types::FFIAccountType; use crate::wallet_manager::FFIWalletManager; use crate::FFINetwork; -use key_wallet::account::account_collection::{DashpayAccountKey, PlatformPaymentAccountKey}; +use key_wallet::account::account_collection::PlatformPaymentAccountKey; use key_wallet::managed_account::address_pool::AddressPool; use key_wallet::managed_account::managed_platform_account::ManagedPlatformAccount; use key_wallet::managed_account::ManagedCoreAccount; @@ -354,129 +354,6 @@ pub unsafe extern "C" fn managed_wallet_get_top_up_account_with_registration_ind result } -/// Get a managed DashPay receiving funds account by composite key -/// -/// # Safety -/// - `manager`, `wallet_id` must be valid -/// - `user_identity_id` and `friend_identity_id` must each point to 32 bytes -#[no_mangle] -pub unsafe extern "C" fn managed_wallet_get_dashpay_receiving_account( - manager: *const FFIWalletManager, - wallet_id: *const u8, - account_index: c_uint, - user_identity_id: *const u8, - friend_identity_id: *const u8, -) -> FFIManagedCoreAccountResult { - if manager.is_null() - || wallet_id.is_null() - || user_identity_id.is_null() - || friend_identity_id.is_null() - { - return FFIManagedCoreAccountResult::error( - FFIErrorCode::InvalidInput, - "Null pointer provided".to_string(), - ); - } - let mut user_id = [0u8; 32]; - let mut friend_id = [0u8; 32]; - core::ptr::copy_nonoverlapping(user_identity_id, user_id.as_mut_ptr(), 32); - core::ptr::copy_nonoverlapping(friend_identity_id, friend_id.as_mut_ptr(), 32); - let key = DashpayAccountKey { - index: account_index, - user_identity_id: user_id, - friend_identity_id: friend_id, - }; - - let mut error = FFIError::success(); - let managed_wallet_ptr = crate::wallet_manager::wallet_manager_get_managed_wallet_info( - manager, wallet_id, &mut error, - ); - if managed_wallet_ptr.is_null() { - return FFIManagedCoreAccountResult::error( - error.code, - if error.message.is_null() { - "Failed to get managed wallet info".to_string() - } else { - std::ffi::CStr::from_ptr(error.message).to_string_lossy().to_string() - }, - ); - } - let managed_wallet = &*managed_wallet_ptr; - - let result = match managed_wallet.inner().accounts.dashpay_receival_accounts.get(&key) { - Some(account) => FFIManagedCoreAccountResult::success(Box::into_raw(Box::new( - FFIManagedCoreAccount::new(account), - ))), - None => FFIManagedCoreAccountResult::error( - FFIErrorCode::NotFound, - "Account not found".to_string(), - ), - }; - crate::managed_wallet::managed_wallet_info_free(managed_wallet_ptr); - result -} - -/// Get a managed DashPay external account by composite key -/// -/// # Safety -/// - Pointers must be valid -#[no_mangle] -pub unsafe extern "C" fn managed_wallet_get_dashpay_external_account( - manager: *const FFIWalletManager, - wallet_id: *const u8, - account_index: c_uint, - user_identity_id: *const u8, - friend_identity_id: *const u8, -) -> FFIManagedCoreAccountResult { - if manager.is_null() - || wallet_id.is_null() - || user_identity_id.is_null() - || friend_identity_id.is_null() - { - return FFIManagedCoreAccountResult::error( - FFIErrorCode::InvalidInput, - "Null pointer provided".to_string(), - ); - } - let mut user_id = [0u8; 32]; - let mut friend_id = [0u8; 32]; - core::ptr::copy_nonoverlapping(user_identity_id, user_id.as_mut_ptr(), 32); - core::ptr::copy_nonoverlapping(friend_identity_id, friend_id.as_mut_ptr(), 32); - let key = DashpayAccountKey { - index: account_index, - user_identity_id: user_id, - friend_identity_id: friend_id, - }; - - let mut error = FFIError::success(); - let managed_wallet_ptr = crate::wallet_manager::wallet_manager_get_managed_wallet_info( - manager, wallet_id, &mut error, - ); - if managed_wallet_ptr.is_null() { - return FFIManagedCoreAccountResult::error( - error.code, - if error.message.is_null() { - "Failed to get managed wallet info".to_string() - } else { - std::ffi::CStr::from_ptr(error.message).to_string_lossy().to_string() - }, - ); - } - let managed_wallet = &*managed_wallet_ptr; - - let result = match managed_wallet.inner().accounts.dashpay_external_accounts.get(&key) { - Some(account) => FFIManagedCoreAccountResult::success(Box::into_raw(Box::new( - FFIManagedCoreAccount::new(account), - ))), - None => FFIManagedCoreAccountResult::error( - FFIErrorCode::NotFound, - "Account not found".to_string(), - ), - }; - crate::managed_wallet::managed_wallet_info_free(managed_wallet_ptr); - result -} - /// Get the network of a managed account /// /// # Safety @@ -495,25 +372,6 @@ pub unsafe extern "C" fn managed_core_account_get_network( account.inner().network.into() } -/// Get the parent wallet ID of a managed account -/// -/// Note: ManagedAccount doesn't store the parent wallet ID directly. -/// The wallet ID is typically known from the context (e.g., when getting the account from a managed wallet). -/// -/// # Safety -/// -/// - `wallet_id` must be a valid pointer to a 32-byte wallet ID buffer that was provided by the caller -/// - The returned pointer is the same as the input pointer for convenience -/// - The caller must not free the returned pointer as it's the same as the input -#[no_mangle] -pub unsafe extern "C" fn managed_core_account_get_parent_wallet_id( - wallet_id: *const u8, -) -> *const u8 { - // Simply return the wallet_id that was passed in - // This function exists for API consistency but ManagedAccount doesn't store parent wallet ID - wallet_id -} - /// Get the account type of a managed account /// /// # Safety @@ -810,49 +668,6 @@ pub unsafe extern "C" fn managed_core_account_result_free_error( } } -/// Get number of accounts in a managed wallet -/// -/// # Safety -/// -/// - `manager` must be a valid pointer to an FFIWalletManager instance -/// - `wallet_id` must be a valid pointer to a 32-byte wallet ID -/// - `error` must be a valid pointer to an FFIError structure or null -/// - The caller must ensure all pointers remain valid for the duration of this call -#[no_mangle] -pub unsafe extern "C" fn managed_wallet_get_account_count( - manager: *const FFIWalletManager, - wallet_id: *const u8, - error: *mut FFIError, -) -> c_uint { - if manager.is_null() || wallet_id.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return 0; - } - - // Get the wallet from the manager - let wallet_ptr = crate::wallet_manager::wallet_manager_get_wallet(manager, wallet_id, error); - - if wallet_ptr.is_null() { - // Error already set by wallet_manager_get_wallet - return 0; - } - - let wallet = &*wallet_ptr; - - FFIError::set_success(error); - let accounts = &wallet.inner().accounts; - let count = accounts.standard_bip44_accounts.len() - + accounts.standard_bip32_accounts.len() - + accounts.coinjoin_accounts.len() - + accounts.identity_registration.is_some() as usize - + accounts.identity_topup.len(); - - // Clean up the wallet pointer - crate::wallet::wallet_free_const(wallet_ptr); - - count as c_uint -} - // Note: BLS and EdDSA accounts are handled through regular FFIManagedCoreAccount // since ManagedAccountCollection stores all accounts as ManagedAccount type @@ -1074,286 +889,6 @@ pub unsafe extern "C" fn managed_core_account_get_address_pool( } } -// ==================== Platform Payment Account Functions ==================== - -/// Get a managed platform payment account from a managed wallet -/// -/// Platform Payment accounts (DIP-17) are identified by account index and key_class. -/// Returns a platform account handle that wraps the ManagedPlatformAccount. -/// -/// # Safety -/// -/// - `manager` must be a valid pointer to an FFIWalletManager instance -/// - `wallet_id` must be a valid pointer to a 32-byte wallet ID -/// - The caller must ensure all pointers remain valid for the duration of this call -/// - The returned account must be freed with `managed_platform_account_free` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn managed_wallet_get_platform_payment_account( - manager: *const FFIWalletManager, - wallet_id: *const u8, - account_index: c_uint, - key_class: c_uint, -) -> FFIManagedPlatformAccountResult { - if manager.is_null() { - return FFIManagedPlatformAccountResult::error( - FFIErrorCode::InvalidInput, - "Manager is null".to_string(), - ); - } - - if wallet_id.is_null() { - return FFIManagedPlatformAccountResult::error( - FFIErrorCode::InvalidInput, - "Wallet ID is null".to_string(), - ); - } - - // Get the managed wallet info from the manager - let mut error = FFIError::success(); - let managed_wallet_ptr = crate::wallet_manager::wallet_manager_get_managed_wallet_info( - manager, wallet_id, &mut error, - ); - - if managed_wallet_ptr.is_null() { - return FFIManagedPlatformAccountResult::error( - error.code, - if error.message.is_null() { - "Failed to get managed wallet info".to_string() - } else { - let c_str = std::ffi::CStr::from_ptr(error.message); - c_str.to_string_lossy().to_string() - }, - ); - } - - let managed_wallet = &*managed_wallet_ptr; - let key = PlatformPaymentAccountKey { - account: account_index, - key_class, - }; - - let result = match managed_wallet.inner().accounts.platform_payment_accounts.get(&key) { - Some(account) => { - let ffi_account = FFIManagedPlatformAccount::new(account); - FFIManagedPlatformAccountResult::success(Box::into_raw(Box::new(ffi_account))) - } - None => FFIManagedPlatformAccountResult::error( - FFIErrorCode::NotFound, - format!( - "Platform Payment account (account: {}, key_class: {}) not found", - account_index, key_class - ), - ), - }; - - // Clean up the managed wallet pointer - crate::managed_wallet::managed_wallet_info_free(managed_wallet_ptr); - - result -} - -/// Get the network of a managed platform account -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance -/// - Returns `FFINetwork::Mainnet` if the account is null -#[no_mangle] -pub unsafe extern "C" fn managed_platform_account_get_network( - account: *const FFIManagedPlatformAccount, -) -> FFINetwork { - if account.is_null() { - return FFINetwork::Mainnet; - } - - let account = &*account; - account.inner().network.into() -} - -/// Get the account index of a managed platform account -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance -#[no_mangle] -pub unsafe extern "C" fn managed_platform_account_get_account_index( - account: *const FFIManagedPlatformAccount, -) -> c_uint { - if account.is_null() { - return 0; - } - - let account = &*account; - account.inner().account -} - -/// Get the key class of a managed platform account -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance -#[no_mangle] -pub unsafe extern "C" fn managed_platform_account_get_key_class( - account: *const FFIManagedPlatformAccount, -) -> c_uint { - if account.is_null() { - return 0; - } - - let account = &*account; - account.inner().key_class -} - -/// Get the total credit balance of a managed platform account -/// -/// Returns the balance in credits (1000 credits = 1 duff) -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance -#[no_mangle] -pub unsafe extern "C" fn managed_platform_account_get_credit_balance( - account: *const FFIManagedPlatformAccount, -) -> u64 { - if account.is_null() { - return 0; - } - - let account = &*account; - account.inner().total_credit_balance() -} - -/// Get the total balance in duffs of a managed platform account -/// -/// Returns the balance in duffs (credit_balance / 1000) -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance -#[no_mangle] -pub unsafe extern "C" fn managed_platform_account_get_duff_balance( - account: *const FFIManagedPlatformAccount, -) -> u64 { - if account.is_null() { - return 0; - } - - let account = &*account; - account.inner().duff_balance() -} - -/// Get the number of funded addresses in a managed platform account -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance -#[no_mangle] -pub unsafe extern "C" fn managed_platform_account_get_funded_address_count( - account: *const FFIManagedPlatformAccount, -) -> c_uint { - if account.is_null() { - return 0; - } - - let account = &*account; - account.inner().funded_address_count() as c_uint -} - -/// Get the total number of addresses in a managed platform account -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance -#[no_mangle] -pub unsafe extern "C" fn managed_platform_account_get_total_address_count( - account: *const FFIManagedPlatformAccount, -) -> c_uint { - if account.is_null() { - return 0; - } - - let account = &*account; - account.inner().total_address_count() as c_uint -} - -/// Check if a managed platform account is watch-only -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance -#[no_mangle] -pub unsafe extern "C" fn managed_platform_account_get_is_watch_only( - account: *const FFIManagedPlatformAccount, -) -> bool { - if account.is_null() { - return false; - } - - let account = &*account; - account.inner().is_watch_only -} - -/// Get the address pool from a managed platform account -/// -/// Platform accounts only have a single address pool. -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIManagedPlatformAccount instance -/// - The returned pool must be freed with `address_pool_free` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn managed_platform_account_get_address_pool( - account: *const FFIManagedPlatformAccount, -) -> *mut FFIAddressPool { - if account.is_null() { - return std::ptr::null_mut(); - } - - let account = &*account; - let pool_ref = &account.inner().addresses; - - let ffi_pool = FFIAddressPool { - pool: pool_ref as *const AddressPool as *mut AddressPool, - pool_type: FFIAddressPoolType::Single, - }; - Box::into_raw(Box::new(ffi_pool)) -} - -/// Free a managed platform account handle -/// -/// # Safety -/// -/// - `account` must be a valid pointer to an FFIManagedPlatformAccount that was allocated by this library -/// - The pointer must not be used after calling this function -/// - This function must only be called once per allocation -#[no_mangle] -pub unsafe extern "C" fn managed_platform_account_free(account: *mut FFIManagedPlatformAccount) { - if !account.is_null() { - let _ = Box::from_raw(account); - } -} - -/// Free a managed platform account result's error message (if any) -/// Note: This does NOT free the account handle itself - use managed_platform_account_free for that -/// -/// # Safety -/// -/// - `result` must be a valid pointer to an FFIManagedPlatformAccountResult -/// - The error_message field must be either null or a valid CString allocated by this library -/// - The caller must ensure the result pointer remains valid for the duration of this call -#[no_mangle] -pub unsafe extern "C" fn managed_platform_account_result_free_error( - result: *mut FFIManagedPlatformAccountResult, -) { - if !result.is_null() { - let result = &mut *result; - if !result.error_message.is_null() { - let _ = std::ffi::CString::from_raw(result.error_message); - result.error_message = std::ptr::null_mut(); - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/key-wallet-ffi/src/managed_account_collection.rs b/key-wallet-ffi/src/managed_account_collection.rs index 1d4107c82..a9822f82f 100644 --- a/key-wallet-ffi/src/managed_account_collection.rs +++ b/key-wallet-ffi/src/managed_account_collection.rs @@ -5,8 +5,7 @@ //! of account_collection.rs but accesses accounts through the wallet manager's //! wallet reference. -use std::ffi::CString; -use std::os::raw::{c_char, c_uint}; +use std::os::raw::c_uint; use std::ptr; use crate::error::{FFIError, FFIErrorCode}; @@ -730,356 +729,8 @@ pub unsafe extern "C" fn managed_account_collection_has_provider_platform_keys( } } -// Platform Payment accounts functions - -/// Get a Platform Payment account by account index and key class from the managed collection -/// -/// Platform Payment accounts (DIP-17) are identified by two indices: -/// - account_index: The account' level in the derivation path -/// - key_class: The key_class' level in the derivation path (typically 0) -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection -/// - The returned pointer must be freed with `managed_platform_account_free` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn managed_account_collection_get_platform_payment_account( - collection: *const FFIManagedCoreAccountCollection, - account_index: c_uint, - key_class: c_uint, -) -> *mut crate::managed_account::FFIManagedPlatformAccount { - if collection.is_null() { - return ptr::null_mut(); - } - - let collection = &*collection; - let key = key_wallet::account::account_collection::PlatformPaymentAccountKey { - account: account_index, - key_class, - }; - - match collection.collection.platform_payment_accounts.get(&key) { - Some(account) => { - let ffi_account = crate::managed_account::FFIManagedPlatformAccount::new(account); - Box::into_raw(Box::new(ffi_account)) - } - None => ptr::null_mut(), - } -} - -/// Get all Platform Payment account keys from managed collection -/// -/// Returns an array of FFIPlatformPaymentAccountKey structures. -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection -/// - `out_keys` must be a valid pointer to store the keys array -/// - `out_count` must be a valid pointer to store the count -/// - The returned array must be freed with `managed_account_collection_free_platform_payment_keys` when no longer needed -#[no_mangle] -pub unsafe extern "C" fn managed_account_collection_get_platform_payment_keys( - collection: *const FFIManagedCoreAccountCollection, - out_keys: *mut *mut crate::managed_account::FFIPlatformPaymentAccountKey, - out_count: *mut usize, -) -> bool { - if collection.is_null() || out_keys.is_null() || out_count.is_null() { - return false; - } - - let collection = &*collection; - let keys: Vec = collection - .collection - .platform_payment_accounts - .keys() - .map(crate::managed_account::FFIPlatformPaymentAccountKey::from) - .collect(); - - if keys.is_empty() { - *out_keys = ptr::null_mut(); - *out_count = 0; - return true; - } - - let mut boxed_slice = keys.into_boxed_slice(); - let ptr = boxed_slice.as_mut_ptr(); - let len = boxed_slice.len(); - std::mem::forget(boxed_slice); - - *out_keys = ptr; - *out_count = len; - true -} - -/// Free platform payment keys array returned by managed_account_collection_get_platform_payment_keys -/// -/// # Safety -/// -/// - `keys` must be a pointer returned by `managed_account_collection_get_platform_payment_keys` -/// - `count` must be the count returned by `managed_account_collection_get_platform_payment_keys` -/// - This function must only be called once per allocation -#[no_mangle] -pub unsafe extern "C" fn managed_account_collection_free_platform_payment_keys( - keys: *mut crate::managed_account::FFIPlatformPaymentAccountKey, - count: usize, -) { - if !keys.is_null() && count > 0 { - let _ = Vec::from_raw_parts(keys, count, count); - } -} - -/// Check if there are any Platform Payment accounts in the managed collection -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection -#[no_mangle] -pub unsafe extern "C" fn managed_account_collection_has_platform_payment_accounts( - collection: *const FFIManagedCoreAccountCollection, -) -> bool { - if collection.is_null() { - return false; - } - - let collection = &*collection; - !collection.collection.platform_payment_accounts.is_empty() -} - -/// Get the number of Platform Payment accounts in the managed collection -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection -#[no_mangle] -pub unsafe extern "C" fn managed_account_collection_platform_payment_count( - collection: *const FFIManagedCoreAccountCollection, -) -> c_uint { - if collection.is_null() { - return 0; - } - - let collection = &*collection; - collection.collection.platform_payment_accounts.len() as c_uint -} - // Utility functions -/// Get the total number of accounts in the managed collection -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection -#[no_mangle] -pub unsafe extern "C" fn managed_account_collection_count( - collection: *const FFIManagedCoreAccountCollection, -) -> c_uint { - if collection.is_null() { - return 0; - } - - let collection = &*collection; - let mut count = 0u32; - - count += collection.collection.standard_bip44_accounts.len() as u32; - count += collection.collection.standard_bip32_accounts.len() as u32; - count += collection.collection.coinjoin_accounts.len() as u32; - count += collection.collection.identity_topup.len() as u32; - - if collection.collection.identity_registration.is_some() { - count += 1; - } - if collection.collection.identity_topup_not_bound.is_some() { - count += 1; - } - if collection.collection.identity_invitation.is_some() { - count += 1; - } - if collection.collection.provider_voting_keys.is_some() { - count += 1; - } - if collection.collection.provider_owner_keys.is_some() { - count += 1; - } - - #[cfg(feature = "bls")] - if collection.collection.provider_operator_keys.is_some() { - count += 1; - } - - #[cfg(feature = "eddsa")] - if collection.collection.provider_platform_keys.is_some() { - count += 1; - } - - // Platform payment accounts - count += collection.collection.platform_payment_accounts.len() as u32; - - count -} - -/// Get a human-readable summary of all accounts in the managed collection -/// -/// Returns a formatted string showing all account types and their indices. -/// The format is designed to be clear and readable for end users. -/// -/// # Safety -/// -/// - `collection` must be a valid pointer to an FFIManagedCoreAccountCollection -/// - The returned string must be freed with `string_free` when no longer needed -/// - Returns null if the collection pointer is null -#[no_mangle] -pub unsafe extern "C" fn managed_account_collection_summary( - collection: *const FFIManagedCoreAccountCollection, -) -> *mut c_char { - if collection.is_null() { - return ptr::null_mut(); - } - - let collection = &*collection; - let mut summary_parts = Vec::new(); - - summary_parts.push("Managed Account Summary:".to_string()); - - // BIP44 Accounts - if !collection.collection.standard_bip44_accounts.is_empty() { - let mut indices: Vec = - collection.collection.standard_bip44_accounts.keys().copied().collect(); - indices.sort(); - let count = indices.len(); - let indices_str = format!("{:?}", indices); - summary_parts.push(format!( - "• BIP44 Accounts: {} {} at indices {}", - count, - if count == 1 { - "account" - } else { - "accounts" - }, - indices_str - )); - } - - // BIP32 Accounts - if !collection.collection.standard_bip32_accounts.is_empty() { - let mut indices: Vec = - collection.collection.standard_bip32_accounts.keys().copied().collect(); - indices.sort(); - let count = indices.len(); - let indices_str = format!("{:?}", indices); - summary_parts.push(format!( - "• BIP32 Accounts: {} {} at indices {}", - count, - if count == 1 { - "account" - } else { - "accounts" - }, - indices_str - )); - } - - // CoinJoin Accounts - if !collection.collection.coinjoin_accounts.is_empty() { - let mut indices: Vec = - collection.collection.coinjoin_accounts.keys().copied().collect(); - indices.sort(); - let count = indices.len(); - let indices_str = format!("{:?}", indices); - summary_parts.push(format!( - "• CoinJoin Accounts: {} {} at indices {}", - count, - if count == 1 { - "account" - } else { - "accounts" - }, - indices_str - )); - } - - // Identity TopUp Accounts - if !collection.collection.identity_topup.is_empty() { - let mut indices: Vec = collection.collection.identity_topup.keys().copied().collect(); - indices.sort(); - let count = indices.len(); - let indices_str = format!("{:?}", indices); - summary_parts.push(format!( - "• Identity TopUp: {} {} at indices {}", - count, - if count == 1 { - "account" - } else { - "accounts" - }, - indices_str - )); - } - - // Special accounts (single instances) - if collection.collection.identity_registration.is_some() { - summary_parts.push("• Identity Registration Account".to_string()); - } - - if collection.collection.identity_topup_not_bound.is_some() { - summary_parts.push("• Identity TopUp Not Bound Account".to_string()); - } - - if collection.collection.identity_invitation.is_some() { - summary_parts.push("• Identity Invitation Account".to_string()); - } - - if collection.collection.provider_voting_keys.is_some() { - summary_parts.push("• Provider Voting Keys Account".to_string()); - } - - if collection.collection.provider_owner_keys.is_some() { - summary_parts.push("• Provider Owner Keys Account".to_string()); - } - - #[cfg(feature = "bls")] - if collection.collection.provider_operator_keys.is_some() { - summary_parts.push("• Provider Operator Keys Account (BLS)".to_string()); - } - - #[cfg(feature = "eddsa")] - if collection.collection.provider_platform_keys.is_some() { - summary_parts.push("• Provider Platform Keys Account (EdDSA)".to_string()); - } - - // Platform Payment Accounts - if !collection.collection.platform_payment_accounts.is_empty() { - let count = collection.collection.platform_payment_accounts.len(); - let keys: Vec = collection - .collection - .platform_payment_accounts - .keys() - .map(|k| format!("({},{})", k.account, k.key_class)) - .collect(); - summary_parts.push(format!( - "• Platform Payment: {} {} at keys {}", - count, - if count == 1 { - "account" - } else { - "accounts" - }, - keys.join(", ") - )); - } - - // If there are no accounts at all - if summary_parts.len() == 1 { - summary_parts.push("No accounts configured".to_string()); - } - - let summary = summary_parts.join("\n"); - - match CString::new(summary) { - Ok(c_str) => c_str.into_raw(), - Err(_) => ptr::null_mut(), - } -} - /// Get structured account collection summary data for managed collection /// /// Returns a struct containing arrays of indices for each account type and boolean diff --git a/key-wallet-ffi/src/managed_wallet.rs b/key-wallet-ffi/src/managed_wallet.rs index cc3927e6a..05cd9746f 100644 --- a/key-wallet-ffi/src/managed_wallet.rs +++ b/key-wallet-ffi/src/managed_wallet.rs @@ -5,13 +5,12 @@ //! use std::ffi::CString; -use std::os::raw::{c_char, c_uint}; +use std::os::raw::c_char; use std::ptr; use crate::error::{FFIError, FFIErrorCode}; use crate::types::FFIWallet; use key_wallet::managed_account::address_pool::KeySource; -use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use std::ffi::c_void; @@ -590,48 +589,6 @@ pub unsafe extern "C" fn managed_wallet_get_balance( true } -/// Get current synced height from wallet info -/// -/// # Safety -/// -/// - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo -/// - `error` must be a valid pointer to an FFIError structure or null -/// - The caller must ensure all pointers remain valid for the duration of this call -#[no_mangle] -pub unsafe extern "C" fn managed_wallet_synced_height( - managed_wallet: *const FFIManagedWalletInfo, - error: *mut FFIError, -) -> c_uint { - if managed_wallet.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Managed wallet is null".to_string(), - ); - return 0; - } - let managed_wallet = unsafe { &*managed_wallet }; - FFIError::set_success(error); - managed_wallet.inner().synced_height() -} - -/// Free managed wallet info -/// -/// # Safety -/// -/// - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo or null -/// - After calling this function, the pointer becomes invalid and must not be used -#[no_mangle] -pub unsafe extern "C" fn managed_wallet_free(managed_wallet: *mut FFIManagedWalletInfo) { - if !managed_wallet.is_null() { - // Reclaim outer struct, then free inner if present - let wrapper = Box::from_raw(managed_wallet); - if !wrapper.inner.is_null() { - let _ = Box::from_raw(wrapper.inner as *mut ManagedWalletInfo); - } - } -} - /// Free managed wallet info returned by wallet_manager_get_managed_wallet_info /// /// # Safety diff --git a/key-wallet-ffi/src/mnemonic.rs b/key-wallet-ffi/src/mnemonic.rs index 6bf6ea8f1..b86097312 100644 --- a/key-wallet-ffi/src/mnemonic.rs +++ b/key-wallet-ffi/src/mnemonic.rs @@ -68,60 +68,6 @@ impl From for FFILanguage { } } -/// Generate a new mnemonic with specified word count (12, 15, 18, 21, or 24) -#[no_mangle] -pub extern "C" fn mnemonic_generate(word_count: c_uint, error: *mut FFIError) -> *mut c_char { - let entropy_bits = match word_count { - 12 => 128, - 15 => 160, - 18 => 192, - 21 => 224, - 24 => 256, - _ => { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - format!("Invalid word count: {}. Must be 12, 15, 18, 21, or 24", word_count), - ); - return ptr::null_mut(); - } - }; - - use key_wallet::mnemonic::Language; - let word_count = match entropy_bits { - 128 => 12, - 160 => 15, - 192 => 18, - 224 => 21, - 256 => 24, - _ => 12, - }; - match Mnemonic::generate(word_count, Language::English) { - Ok(mnemonic) => { - FFIError::set_success(error); - match CString::new(mnemonic.to_string()) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidMnemonic, - format!("Failed to generate mnemonic: {}", e), - ); - ptr::null_mut() - } - } -} - /// Generate a new mnemonic with specified language and word count #[no_mangle] pub extern "C" fn mnemonic_generate_with_language( diff --git a/key-wallet-ffi/src/transaction.rs b/key-wallet-ffi/src/transaction.rs index 531d4ced8..008a61ece 100644 --- a/key-wallet-ffi/src/transaction.rs +++ b/key-wallet-ffi/src/transaction.rs @@ -1,29 +1,19 @@ //! Transaction building and management -use std::ffi::{CStr, CString}; +use std::ffi::CStr; use std::os::raw::c_char; -use std::ptr; use std::slice; -use dashcore::{ - consensus, hashes::Hash, sighash::SighashCache, EcdsaSighashType, Network, OutPoint, Script, - ScriptBuf, Transaction, TxIn, TxOut, Txid, -}; +use dashcore::consensus; use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; use key_wallet_manager::FeeRate; -use secp256k1::{Message, Secp256k1, SecretKey}; use crate::error::{FFIError, FFIErrorCode}; -use crate::types::{FFINetwork, FFITransactionContext, FFIWallet}; +use crate::types::{FFITransactionContext, FFIWallet}; use crate::FFIWalletManager; // MARK: - Transaction Types -/// Opaque handle for a transaction -pub struct FFITransaction { - inner: Transaction, -} - /// FFI-compatible transaction input #[repr(C)] pub struct FFITxIn { @@ -518,487 +508,3 @@ pub unsafe extern "C" fn transaction_bytes_free(tx_bytes: *mut u8) { } } } - -// MARK: - Transaction Creation - -/// Create a new empty transaction -/// -/// # Returns -/// - Pointer to FFITransaction on success -/// - NULL on error -#[no_mangle] -pub extern "C" fn transaction_create() -> *mut FFITransaction { - let tx = Transaction { - version: 2, - lock_time: 0, - input: vec![], - output: vec![], - special_transaction_payload: None, - }; - - Box::into_raw(Box::new(FFITransaction { - inner: tx, - })) -} - -/// Add an input to a transaction -/// -/// # Safety -/// - `tx` must be a valid pointer to an FFITransaction -/// - `input` must be a valid pointer to an FFITxIn -/// -/// # Returns -/// - 0 on success -/// - -1 on error -#[no_mangle] -pub unsafe extern "C" fn transaction_add_input( - tx: *mut FFITransaction, - input: *const FFITxIn, -) -> i32 { - if tx.is_null() || input.is_null() { - return -1; - } - - let tx = &mut *tx; - let input = &*input; - - // Convert txid - let txid = match Txid::from_slice(&input.txid) { - Ok(txid) => txid, - Err(_) => { - return -1; - } - }; - - // Convert script - let script_sig = if input.script_sig.is_null() || input.script_sig_len == 0 { - ScriptBuf::new() - } else { - let script_slice = slice::from_raw_parts(input.script_sig, input.script_sig_len as usize); - ScriptBuf::from(script_slice.to_vec()) - }; - - let tx_in = TxIn { - previous_output: OutPoint { - txid, - vout: input.vout, - }, - script_sig, - sequence: input.sequence, - witness: Default::default(), - }; - - tx.inner.input.push(tx_in); - 0 -} - -/// Add an output to a transaction -/// -/// # Safety -/// - `tx` must be a valid pointer to an FFITransaction -/// - `output` must be a valid pointer to an FFITxOut -/// -/// # Returns -/// - 0 on success -/// - -1 on error -#[no_mangle] -pub unsafe extern "C" fn transaction_add_output( - tx: *mut FFITransaction, - output: *const FFITxOut, -) -> i32 { - if tx.is_null() || output.is_null() { - return -1; - } - - let tx = &mut *tx; - let output = &*output; - - // Convert script - let script_pubkey = if output.script_pubkey.is_null() || output.script_pubkey_len == 0 { - return -1; - } else { - let script_slice = - slice::from_raw_parts(output.script_pubkey, output.script_pubkey_len as usize); - ScriptBuf::from(script_slice.to_vec()) - }; - - let tx_out = TxOut { - value: output.amount, - script_pubkey, - }; - - tx.inner.output.push(tx_out); - 0 -} - -/// Get the transaction ID -/// -/// # Safety -/// - `tx` must be a valid pointer to an FFITransaction -/// - `txid_out` must be a valid pointer to a buffer of at least 32 bytes -/// -/// # Returns -/// - 0 on success -/// - -1 on error -#[no_mangle] -pub unsafe extern "C" fn transaction_get_txid(tx: *const FFITransaction, txid_out: *mut u8) -> i32 { - if tx.is_null() || txid_out.is_null() { - return -1; - } - - let tx = &*tx; - let txid = tx.inner.txid(); - - let txid_bytes = txid.as_byte_array(); - ptr::copy_nonoverlapping(txid_bytes.as_ptr(), txid_out, 32); - 0 -} - -/// Get transaction ID from raw transaction bytes -/// -/// # Safety -/// - `tx_bytes` must be a valid pointer to transaction bytes -/// - `tx_len` must be the correct length of the transaction -/// - `error` must be a valid pointer to an FFIError -/// -/// # Returns -/// - Pointer to null-terminated hex string of TXID (must be freed with string_free) -/// - NULL on error -#[no_mangle] -pub unsafe extern "C" fn transaction_get_txid_from_bytes( - tx_bytes: *const u8, - tx_len: usize, - error: *mut FFIError, -) -> *mut c_char { - if tx_bytes.is_null() { - FFIError::set_error( - error, - FFIErrorCode::InvalidInput, - "Transaction bytes is null".to_string(), - ); - return ptr::null_mut(); - } - - let tx_slice = slice::from_raw_parts(tx_bytes, tx_len); - - // Deserialize the transaction - let tx: Transaction = match consensus::deserialize(tx_slice) { - Ok(t) => t, - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::SerializationError, - format!("Failed to deserialize transaction: {}", e), - ); - return ptr::null_mut(); - } - }; - - // Get TXID and convert to hex string - let txid = tx.txid(); - let txid_hex = txid.to_string(); - - match CString::new(txid_hex) { - Ok(c_str) => { - FFIError::set_success(error); - c_str.into_raw() - } - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::SerializationError, - "Failed to convert TXID to C string".to_string(), - ); - ptr::null_mut() - } - } -} - -/// Serialize a transaction -/// -/// # Safety -/// - `tx` must be a valid pointer to an FFITransaction -/// - `out_buf` can be NULL to get size only -/// - `out_len` must be a valid pointer to store the size -/// -/// # Returns -/// - 0 on success -/// - -1 on error -#[no_mangle] -pub unsafe extern "C" fn transaction_serialize( - tx: *const FFITransaction, - out_buf: *mut u8, - out_len: *mut u32, -) -> i32 { - if tx.is_null() || out_len.is_null() { - return -1; - } - - let tx = &*tx; - let serialized = consensus::serialize(&tx.inner); - let size = serialized.len() as u32; - - if out_buf.is_null() { - // Just return size - *out_len = size; - return 0; - } - - let provided_size = *out_len; - if provided_size < size { - *out_len = size; - return -1; - } - - ptr::copy_nonoverlapping(serialized.as_ptr(), out_buf, serialized.len()); - *out_len = size; - 0 -} - -/// Deserialize a transaction -/// -/// # Safety -/// - `data` must be a valid pointer to serialized transaction data -/// - `len` must be the correct length of the data -/// -/// # Returns -/// - Pointer to FFITransaction on success -/// - NULL on error -#[no_mangle] -pub unsafe extern "C" fn transaction_deserialize(data: *const u8, len: u32) -> *mut FFITransaction { - if data.is_null() { - return ptr::null_mut(); - } - - let slice = slice::from_raw_parts(data, len as usize); - - match consensus::deserialize::(slice) { - Ok(tx) => Box::into_raw(Box::new(FFITransaction { - inner: tx, - })), - Err(_) => ptr::null_mut(), - } -} - -/// Destroy a transaction -/// -/// # Safety -/// - `tx` must be a valid pointer to an FFITransaction created by transaction functions or null -/// - After calling this function, the pointer becomes invalid -#[no_mangle] -pub unsafe extern "C" fn transaction_destroy(tx: *mut FFITransaction) { - if !tx.is_null() { - let _ = Box::from_raw(tx); - } -} - -// MARK: - Transaction Signing - -/// Calculate signature hash for an input -/// -/// # Safety -/// - `tx` must be a valid pointer to an FFITransaction -/// - `script_pubkey` must be a valid pointer to the script pubkey -/// - `hash_out` must be a valid pointer to a buffer of at least 32 bytes -/// -/// # Returns -/// - 0 on success -/// - -1 on error -#[no_mangle] -pub unsafe extern "C" fn transaction_sighash( - tx: *const FFITransaction, - input_index: u32, - script_pubkey: *const u8, - script_pubkey_len: u32, - sighash_type: u32, - hash_out: *mut u8, -) -> i32 { - if tx.is_null() || script_pubkey.is_null() || hash_out.is_null() { - return -1; - } - - let tx = &*tx; - let script_slice = slice::from_raw_parts(script_pubkey, script_pubkey_len as usize); - let script = Script::from_bytes(script_slice); - - let sighash_type = EcdsaSighashType::from_consensus(sighash_type); - let cache = SighashCache::new(&tx.inner); - - match cache.legacy_signature_hash(input_index as usize, script, sighash_type.to_u32()) { - Ok(hash) => { - let hash_bytes: &[u8] = hash.as_ref(); - ptr::copy_nonoverlapping(hash_bytes.as_ptr(), hash_out, 32); - 0 - } - Err(_) => -1, - } -} - -/// Sign a transaction input -/// -/// # Safety -/// - `tx` must be a valid pointer to an FFITransaction -/// - `private_key` must be a valid pointer to a 32-byte private key -/// - `script_pubkey` must be a valid pointer to the script pubkey -/// -/// # Returns -/// - 0 on success -/// - -1 on error -#[no_mangle] -pub unsafe extern "C" fn transaction_sign_input( - tx: *mut FFITransaction, - input_index: u32, - private_key: *const u8, - script_pubkey: *const u8, - script_pubkey_len: u32, - sighash_type: u32, -) -> i32 { - if tx.is_null() || private_key.is_null() || script_pubkey.is_null() { - return -1; - } - - let tx = &mut *tx; - let input_index = input_index as usize; - - if input_index >= tx.inner.input.len() { - return -1; - } - - // Calculate sighash - let mut sighash = [0u8; 32]; - if transaction_sighash( - tx as *const FFITransaction, - input_index as u32, - script_pubkey, - script_pubkey_len, - sighash_type, - sighash.as_mut_ptr(), - ) != 0 - { - return -1; - } - - // Parse private key - let privkey_slice = slice::from_raw_parts(private_key, 32); - let privkey = match SecretKey::from_slice(privkey_slice) { - Ok(k) => k, - Err(_) => { - return -1; - } - }; - - // Sign - let secp = Secp256k1::new(); - let message = Message::from_digest(sighash); - let sig = secp.sign_ecdsa(&message, &privkey); - - // Build signature script (simplified P2PKH) - let mut sig_bytes = sig.serialize_der().to_vec(); - sig_bytes.push(sighash_type as u8); - - let pubkey = secp256k1::PublicKey::from_secret_key(&secp, &privkey); - let pubkey_bytes = pubkey.serialize(); - - let mut script_sig = vec![]; - script_sig.push(sig_bytes.len() as u8); - script_sig.extend_from_slice(&sig_bytes); - script_sig.push(pubkey_bytes.len() as u8); - script_sig.extend_from_slice(&pubkey_bytes); - - tx.inner.input[input_index].script_sig = ScriptBuf::from(script_sig); - 0 -} - -// MARK: - Script Utilities - -/// Create a P2PKH script pubkey -/// -/// # Safety -/// - `pubkey_hash` must be a valid pointer to a 20-byte public key hash -/// - `out_buf` can be NULL to get size only -/// - `out_len` must be a valid pointer to store the size -/// -/// # Returns -/// - 0 on success -/// - -1 on error -#[no_mangle] -pub unsafe extern "C" fn script_p2pkh( - pubkey_hash: *const u8, - out_buf: *mut u8, - out_len: *mut u32, -) -> i32 { - if pubkey_hash.is_null() || out_len.is_null() { - return -1; - } - - let hash_slice = slice::from_raw_parts(pubkey_hash, 20); - - // Build P2PKH script: OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG - let mut script = vec![0x76, 0xa9, 0x14]; // OP_DUP OP_HASH160 PUSH(20) - script.extend_from_slice(hash_slice); - script.extend_from_slice(&[0x88, 0xac]); // OP_EQUALVERIFY OP_CHECKSIG - - let size = script.len() as u32; - - if out_buf.is_null() { - *out_len = size; - return 0; - } - - let provided_size = *out_len; - if provided_size < size { - *out_len = size; - return -1; - } - - ptr::copy_nonoverlapping(script.as_ptr(), out_buf, script.len()); - *out_len = size; - 0 -} - -/// Extract public key hash from P2PKH address -/// -/// # Safety -/// - `address` must be a valid pointer to a null-terminated C string -/// - `hash_out` must be a valid pointer to a buffer of at least 20 bytes -/// -/// # Returns -/// - 0 on success -/// - -1 on error -#[no_mangle] -pub unsafe extern "C" fn address_to_pubkey_hash( - address: *const c_char, - network: FFINetwork, - hash_out: *mut u8, -) -> i32 { - if address.is_null() || hash_out.is_null() { - return -1; - } - - let address_str = match CStr::from_ptr(address).to_str() { - Ok(s) => s, - Err(_) => { - return -1; - } - }; - - let expected_network: Network = network.into(); - - match address_str.parse::>() { - Ok(addr) => { - if *addr.network() != expected_network { - return -1; - } - - match addr.payload() { - dashcore::address::Payload::PubkeyHash(hash) => { - let hash_bytes = hash.as_byte_array(); - ptr::copy_nonoverlapping(hash_bytes.as_ptr(), hash_out, 20); - 0 - } - _ => -1, - } - } - Err(_) => -1, - } -} diff --git a/key-wallet-ffi/src/types.rs b/key-wallet-ffi/src/types.rs index cc626cf05..64f4389c4 100644 --- a/key-wallet-ffi/src/types.rs +++ b/key-wallet-ffi/src/types.rs @@ -14,16 +14,6 @@ pub enum FFINetwork { Devnet = 3, } -#[no_mangle] -pub extern "C" fn ffi_network_get_name(network: FFINetwork) -> *const c_char { - match network { - FFINetwork::Mainnet => c"mainnet".as_ptr() as *const c_char, - FFINetwork::Testnet => c"testnet".as_ptr() as *const c_char, - FFINetwork::Regtest => c"regtest".as_ptr() as *const c_char, - FFINetwork::Devnet => c"devnet".as_ptr() as *const c_char, - } -} - impl From for Network { fn from(net: FFINetwork) -> Self { match net { @@ -269,100 +259,6 @@ impl FFIAccountType { } } } - - /// Convert from AccountType to FFI representation - /// - /// Returns: (FFIAccountType, primary_index, optional_secondary_index) - /// - /// # Panics - /// - /// Panics when attempting to convert DashPay account types (DashpayReceivingFunds, - /// DashpayExternalAccount) because they contain 32-byte identity IDs that cannot be - /// represented in the current FFI tuple format. This prevents silent data loss. - /// - /// TODO: Extend the return type or create separate FFI functions that can return - /// the full DashPay account information including identity IDs. - pub fn from_account_type(account_type: &key_wallet::AccountType) -> (Self, u32, Option) { - use key_wallet::account::account_type::StandardAccountType; - match account_type { - key_wallet::AccountType::Standard { - index, - standard_account_type, - } => match standard_account_type { - StandardAccountType::BIP44Account => (FFIAccountType::StandardBIP44, *index, None), - StandardAccountType::BIP32Account => (FFIAccountType::StandardBIP32, *index, None), - }, - key_wallet::AccountType::CoinJoin { - index, - } => (FFIAccountType::CoinJoin, *index, None), - key_wallet::AccountType::IdentityRegistration => { - (FFIAccountType::IdentityRegistration, 0, None) - } - key_wallet::AccountType::IdentityTopUp { - registration_index, - } => (FFIAccountType::IdentityTopUp, 0, Some(*registration_index)), - key_wallet::AccountType::IdentityTopUpNotBoundToIdentity => { - (FFIAccountType::IdentityTopUpNotBoundToIdentity, 0, None) - } - key_wallet::AccountType::IdentityInvitation => { - (FFIAccountType::IdentityInvitation, 0, None) - } - key_wallet::AccountType::AssetLockAddressTopUp => { - (FFIAccountType::AssetLockAddressTopUp, 0, None) - } - key_wallet::AccountType::AssetLockShieldedAddressTopUp => { - (FFIAccountType::AssetLockShieldedAddressTopUp, 0, None) - } - key_wallet::AccountType::ProviderVotingKeys => { - (FFIAccountType::ProviderVotingKeys, 0, None) - } - key_wallet::AccountType::ProviderOwnerKeys => { - (FFIAccountType::ProviderOwnerKeys, 0, None) - } - key_wallet::AccountType::ProviderOperatorKeys => { - (FFIAccountType::ProviderOperatorKeys, 0, None) - } - key_wallet::AccountType::ProviderPlatformKeys => { - (FFIAccountType::ProviderPlatformKeys, 0, None) - } - key_wallet::AccountType::DashpayReceivingFunds { - index, - user_identity_id, - friend_identity_id, - } => { - // Cannot convert DashPay accounts to FFI without losing identity ID information - panic!( - "Cannot convert AccountType::DashpayReceivingFunds (index={}, user_id={:?}, friend_id={:?}) \ - to FFI representation. The current FFI tuple format (FFIAccountType, u32, Option) \ - cannot represent the two 32-byte identity IDs required by DashPay accounts. \ - This would result in silent data loss. A dedicated FFI API for DashPay accounts is needed.", - index, - &user_identity_id[..8], // Show first 8 bytes for debugging - &friend_identity_id[..8] - ); - } - key_wallet::AccountType::DashpayExternalAccount { - index, - user_identity_id, - friend_identity_id, - } => { - // Cannot convert DashPay accounts to FFI without losing identity ID information - panic!( - "Cannot convert AccountType::DashpayExternalAccount (index={}, user_id={:?}, friend_id={:?}) \ - to FFI representation. The current FFI tuple format (FFIAccountType, u32, Option) \ - cannot represent the two 32-byte identity IDs required by DashPay accounts. \ - This would result in silent data loss. A dedicated FFI API for DashPay accounts is needed.", - index, - &user_identity_id[..8], // Show first 8 bytes for debugging - &friend_identity_id[..8] - ); - } - key_wallet::AccountType::PlatformPayment { - account, - key_class, - } => (FFIAccountType::PlatformPayment, *account, Some(*key_class)), - } - } } #[cfg(test)] diff --git a/key-wallet-ffi/src/utils.rs b/key-wallet-ffi/src/utils.rs index 42a535f79..dec06b134 100644 --- a/key-wallet-ffi/src/utils.rs +++ b/key-wallet-ffi/src/utils.rs @@ -29,19 +29,3 @@ pub fn rust_string_to_c(s: String) -> *mut c_char { Err(_) => std::ptr::null_mut(), } } - -/// Helper function to convert C string to Rust string -/// -/// # Safety -/// -/// - `s` must be a valid null-terminated C string or null -/// - The string must remain valid for the duration of this function call -pub unsafe fn c_string_to_rust(s: *const c_char) -> Result { - use std::ffi::CStr; - - if s.is_null() { - return Ok(String::new()); - } - - CStr::from_ptr(s).to_str().map(|s| s.to_string()) -} diff --git a/key-wallet-ffi/src/utxo.rs b/key-wallet-ffi/src/utxo.rs index 5fd757be2..0ce84eba5 100644 --- a/key-wallet-ffi/src/utxo.rs +++ b/key-wallet-ffi/src/utxo.rs @@ -146,33 +146,6 @@ pub unsafe extern "C" fn managed_wallet_get_utxos( true } -/// Get all UTXOs (deprecated - use managed_wallet_get_utxos instead) -/// -/// # Safety -/// -/// This function is deprecated and returns an empty list. -/// Use `managed_wallet_get_utxos` with a ManagedWalletInfo instead. -#[no_mangle] -#[deprecated(note = "Use managed_wallet_get_utxos with ManagedWalletInfo instead")] -pub unsafe extern "C" fn wallet_get_utxos( - _wallet: *const crate::types::FFIWallet, - utxos_out: *mut *mut FFIUTXO, - count_out: *mut usize, - error: *mut FFIError, -) -> bool { - if utxos_out.is_null() || count_out.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return false; - } - - // Return empty list for backwards compatibility - *count_out = 0; - *utxos_out = ptr::null_mut(); - - FFIError::set_success(error); - true -} - /// Free UTXO array /// /// # Safety diff --git a/key-wallet-ffi/src/wallet.rs b/key-wallet-ffi/src/wallet.rs index 5c082e470..d0996e8fb 100644 --- a/key-wallet-ffi/src/wallet.rs +++ b/key-wallet-ffi/src/wallet.rs @@ -4,12 +4,11 @@ #[path = "wallet_tests.rs"] mod tests; -use std::ffi::{CStr, CString}; +use std::ffi::CStr; use std::os::raw::{c_char, c_uint}; use std::ptr; use std::slice; -use crate::types::FFIAccountResult; use key_wallet::wallet::initialization::WalletAccountCreationOptions; use key_wallet::{Mnemonic, Seed, Wallet}; @@ -371,52 +370,6 @@ pub unsafe extern "C" fn wallet_is_watch_only( } } -/// Get extended public key for account -/// -/// # Safety -/// -/// - `wallet` must be a valid pointer to an FFIWallet instance -/// - `error` must be a valid pointer to an FFIError structure or null -/// - The caller must ensure all pointers remain valid for the duration of this call -/// - The returned C string must be freed by the caller when no longer needed -#[no_mangle] -pub unsafe extern "C" fn wallet_get_xpub( - wallet: *const FFIWallet, - account_index: c_uint, - error: *mut FFIError, -) -> *mut c_char { - if wallet.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Wallet is null".to_string()); - return ptr::null_mut(); - } - - unsafe { - let wallet = &*wallet; - - match wallet.inner().get_bip44_account(account_index) { - Some(account) => { - let xpub = account.extended_public_key(); - FFIError::set_success(error); - match CString::new(xpub.to_string()) { - Ok(c_str) => c_str.into_raw(), - Err(_) => { - FFIError::set_error( - error, - FFIErrorCode::AllocationFailed, - "Failed to allocate string".to_string(), - ); - ptr::null_mut() - } - } - } - None => { - FFIError::set_error(error, FFIErrorCode::NotFound, "Account not found".to_string()); - ptr::null_mut() - } - } - } -} - /// Free a wallet /// /// # Safety @@ -433,29 +386,6 @@ pub unsafe extern "C" fn wallet_free(wallet: *mut FFIWallet) { } } -/// Free a const wallet handle -/// -/// This is a const-safe wrapper for wallet_free() that accepts a const pointer. -/// Use this function when you have a *const FFIWallet that needs to be freed, -/// such as wallets returned from wallet_manager_get_wallet(). -/// -/// # Safety -/// -/// - `wallet` must be a valid pointer created by wallet creation functions or null -/// - After calling this function, the pointer becomes invalid -/// - This function must only be called once per wallet -/// - The wallet must have been allocated by this library (not stack or static memory) -#[no_mangle] -pub unsafe extern "C" fn wallet_free_const(wallet: *const FFIWallet) { - if !wallet.is_null() { - unsafe { - // Cast away const and free - this is safe because we know the wallet - // was originally allocated as mutable memory by Box::into_raw - let _ = Box::from_raw(wallet as *mut FFIWallet); - } - } -} - /// Add an account to the wallet without xpub /// /// # Safety @@ -549,254 +479,6 @@ pub unsafe extern "C" fn wallet_add_account( } } -/// Add a DashPay receiving funds account -/// -/// # Safety -/// - `wallet` must be a valid pointer -/// - `user_identity_id` and `friend_identity_id` must each point to 32 bytes -#[no_mangle] -pub unsafe extern "C" fn wallet_add_dashpay_receiving_account( - wallet: *mut FFIWallet, - account_index: c_uint, - user_identity_id: *const u8, - friend_identity_id: *const u8, -) -> FFIAccountResult { - use key_wallet::account::AccountType; - if wallet.is_null() || user_identity_id.is_null() || friend_identity_id.is_null() { - return FFIAccountResult::error( - crate::error::FFIErrorCode::InvalidInput, - "Null pointer provided".to_string(), - ); - } - let w = &mut *wallet; - let wallet_mut = match w.inner_mut() { - Some(w) => w, - None => { - return FFIAccountResult::error( - crate::error::FFIErrorCode::InvalidInput, - "Wallet is immutable".to_string(), - ) - } - }; - let mut user_id = [0u8; 32]; - let mut friend_id = [0u8; 32]; - core::ptr::copy_nonoverlapping(user_identity_id, user_id.as_mut_ptr(), 32); - core::ptr::copy_nonoverlapping(friend_identity_id, friend_id.as_mut_ptr(), 32); - - let acct = AccountType::DashpayReceivingFunds { - index: account_index, - user_identity_id: user_id, - friend_identity_id: friend_id, - }; - match wallet_mut.add_account(acct, None) { - Ok(()) => { - if let Some(account) = wallet_mut.accounts.account_of_type(acct) { - let ffi_account = crate::types::FFIAccount::new(account); - return FFIAccountResult::success(Box::into_raw(Box::new(ffi_account))); - } - FFIAccountResult::error( - crate::error::FFIErrorCode::WalletError, - "Failed to retrieve account after adding".to_string(), - ) - } - Err(e) => FFIAccountResult::error(crate::error::FFIErrorCode::InvalidInput, e.to_string()), - } -} - -/// Add a DashPay external (watch-only) account with xpub bytes -/// -/// # Safety -/// - `wallet` must be valid, `xpub_bytes` must point to `xpub_len` bytes -/// - `user_identity_id` and `friend_identity_id` must each point to 32 bytes -#[no_mangle] -pub unsafe extern "C" fn wallet_add_dashpay_external_account_with_xpub_bytes( - wallet: *mut FFIWallet, - account_index: c_uint, - user_identity_id: *const u8, - friend_identity_id: *const u8, - xpub_bytes: *const u8, - xpub_len: usize, -) -> FFIAccountResult { - use key_wallet::account::AccountType; - use key_wallet::bip32::ExtendedPubKey; - if wallet.is_null() - || user_identity_id.is_null() - || friend_identity_id.is_null() - || xpub_bytes.is_null() - { - return FFIAccountResult::error( - crate::error::FFIErrorCode::InvalidInput, - "Null pointer provided".to_string(), - ); - } - let w = &mut *wallet; - let wallet_mut = match w.inner_mut() { - Some(w) => w, - None => { - return FFIAccountResult::error( - crate::error::FFIErrorCode::InvalidInput, - "Wallet is immutable".to_string(), - ) - } - }; - let mut user_id = [0u8; 32]; - let mut friend_id = [0u8; 32]; - core::ptr::copy_nonoverlapping(user_identity_id, user_id.as_mut_ptr(), 32); - core::ptr::copy_nonoverlapping(friend_identity_id, friend_id.as_mut_ptr(), 32); - let xpub_slice = core::slice::from_raw_parts(xpub_bytes, xpub_len); - let xpub = match ExtendedPubKey::decode(xpub_slice) { - Ok(x) => x, - Err(_) => { - return FFIAccountResult::error( - crate::error::FFIErrorCode::InvalidInput, - "Invalid xpub bytes".to_string(), - ) - } - }; - let acct = AccountType::DashpayExternalAccount { - index: account_index, - user_identity_id: user_id, - friend_identity_id: friend_id, - }; - match wallet_mut.add_account(acct, Some(xpub)) { - Ok(()) => { - if let Some(account) = wallet_mut.accounts.account_of_type(acct) { - let ffi_account = crate::types::FFIAccount::new(account); - return FFIAccountResult::success(Box::into_raw(Box::new(ffi_account))); - } - FFIAccountResult::error( - crate::error::FFIErrorCode::WalletError, - "Failed to retrieve account after adding".to_string(), - ) - } - Err(e) => FFIAccountResult::error(crate::error::FFIErrorCode::InvalidInput, e.to_string()), - } -} - -/// Add an account to the wallet with xpub as byte array -/// -/// # Safety -/// -/// This function dereferences raw pointers. -/// The caller must ensure that: -/// - The wallet pointer is either null or points to a valid FFIWallet -/// - The xpub_bytes pointer is either null or points to at least xpub_len bytes -/// - The FFIWallet remains valid for the duration of this call -/// -/// # Note -/// -/// This function does NOT support the following account types: -/// - `PlatformPayment`: Use `wallet_add_platform_payment_account()` instead -/// - `DashpayReceivingFunds`: Use `wallet_add_dashpay_receiving_account()` instead -/// - `DashpayExternalAccount`: Use `wallet_add_dashpay_external_account_with_xpub_bytes()` instead -#[no_mangle] -pub unsafe extern "C" fn wallet_add_account_with_xpub_bytes( - wallet: *mut FFIWallet, - account_type: crate::types::FFIAccountType, - account_index: c_uint, - xpub_bytes: *const u8, - xpub_len: usize, -) -> crate::types::FFIAccountResult { - use crate::types::FFIAccountType; - - if wallet.is_null() { - return crate::types::FFIAccountResult::error( - FFIErrorCode::InvalidInput, - "Wallet is null".to_string(), - ); - } - - if xpub_bytes.is_null() { - return crate::types::FFIAccountResult::error( - FFIErrorCode::InvalidInput, - "Xpub bytes are null".to_string(), - ); - } - - // Check for account types that require special handling - match account_type { - FFIAccountType::PlatformPayment => { - return crate::types::FFIAccountResult::error( - FFIErrorCode::InvalidInput, - "PlatformPayment accounts require account and key_class indices. \ - Use wallet_add_platform_payment_account() instead." - .to_string(), - ); - } - FFIAccountType::DashpayReceivingFunds => { - return crate::types::FFIAccountResult::error( - FFIErrorCode::InvalidInput, - "DashpayReceivingFunds accounts require identity IDs. \ - Use wallet_add_dashpay_receiving_account() instead." - .to_string(), - ); - } - FFIAccountType::DashpayExternalAccount => { - return crate::types::FFIAccountResult::error( - FFIErrorCode::InvalidInput, - "DashpayExternalAccount accounts require identity IDs. \ - Use wallet_add_dashpay_external_account_with_xpub_bytes() instead." - .to_string(), - ); - } - _ => {} // Other types are supported - } - - let wallet = &mut *wallet; - - use key_wallet::ExtendedPubKey; - - let account_type_rust = account_type.to_account_type(account_index); - - // Parse the xpub from bytes (assuming it's a string representation) - let xpub_slice = slice::from_raw_parts(xpub_bytes, xpub_len); - let xpub_str = match std::str::from_utf8(xpub_slice) { - Ok(s) => s, - Err(_) => { - return crate::types::FFIAccountResult::error( - FFIErrorCode::InvalidInput, - "Invalid UTF-8 in xpub bytes".to_string(), - ); - } - }; - - let xpub = match xpub_str.parse::() { - Ok(xpub) => xpub, - Err(e) => { - return crate::types::FFIAccountResult::error( - FFIErrorCode::InvalidInput, - format!("Invalid extended public key: {}", e), - ); - } - }; - - match wallet.inner_mut() { - Some(w) => match w.add_account(account_type_rust, Some(xpub)) { - Ok(()) => { - // Get the account we just added - if let Some(account) = w.accounts.account_of_type(account_type_rust) { - let ffi_account = crate::types::FFIAccount::new(account); - return crate::types::FFIAccountResult::success(Box::into_raw(Box::new( - ffi_account, - ))); - } - crate::types::FFIAccountResult::error( - FFIErrorCode::WalletError, - "Failed to retrieve account after adding".to_string(), - ) - } - Err(e) => crate::types::FFIAccountResult::error( - FFIErrorCode::WalletError, - format!("Failed to add account with xpub: {}", e), - ), - }, - None => crate::types::FFIAccountResult::error( - FFIErrorCode::InvalidState, - "Cannot modify wallet".to_string(), - ), - } -} - /// Add an account to the wallet with xpub as string /// /// # Safety @@ -918,71 +600,3 @@ pub unsafe extern "C" fn wallet_add_account_with_string_xpub( ), } } - -/// Add a Platform Payment account (DIP-17) to the wallet -/// -/// Platform Payment accounts use the derivation path: -/// `m/9'/coin_type'/17'/account'/key_class'/index` -/// -/// # Arguments -/// * `wallet` - Pointer to the wallet -/// * `account_index` - The account index (hardened) in the derivation path -/// * `key_class` - The key class (hardened) - typically 0' for main addresses -/// -/// # Safety -/// -/// This function dereferences a raw pointer to FFIWallet. -/// The caller must ensure that: -/// - The wallet pointer is either null or points to a valid FFIWallet -/// - The FFIWallet remains valid for the duration of this call -#[no_mangle] -pub unsafe extern "C" fn wallet_add_platform_payment_account( - wallet: *mut FFIWallet, - account_index: c_uint, - key_class: c_uint, -) -> crate::types::FFIAccountResult { - use key_wallet::account::AccountType; - - if wallet.is_null() { - return crate::types::FFIAccountResult::error( - FFIErrorCode::InvalidInput, - "Wallet is null".to_string(), - ); - } - - let wallet = &mut *wallet; - - let account_type = AccountType::PlatformPayment { - account: account_index, - key_class, - }; - - match wallet.inner_mut() { - Some(w) => { - // Use the proper add_account method - match w.add_account(account_type, None) { - Ok(()) => { - // Get the account we just added - if let Some(account) = w.accounts.account_of_type(account_type) { - let ffi_account = crate::types::FFIAccount::new(account); - return crate::types::FFIAccountResult::success(Box::into_raw(Box::new( - ffi_account, - ))); - } - crate::types::FFIAccountResult::error( - FFIErrorCode::WalletError, - "Failed to retrieve account after adding".to_string(), - ) - } - Err(e) => crate::types::FFIAccountResult::error( - FFIErrorCode::WalletError, - format!("Failed to add platform payment account: {}", e), - ), - } - } - None => crate::types::FFIAccountResult::error( - FFIErrorCode::InvalidState, - "Cannot modify wallet".to_string(), - ), - } -} diff --git a/key-wallet-ffi/src/wallet_manager.rs b/key-wallet-ffi/src/wallet_manager.rs index 5369f6857..011578ede 100644 --- a/key-wallet-ffi/src/wallet_manager.rs +++ b/key-wallet-ffi/src/wallet_manager.rs @@ -8,7 +8,7 @@ mod tests; #[path = "wallet_manager_serialization_tests.rs"] mod serialization_tests; -use std::ffi::{CStr, CString}; +use std::ffi::CStr; use std::os::raw::{c_char, c_uint}; use std::ptr; use std::sync::Arc; @@ -54,63 +54,6 @@ impl FFIWalletManager { } } -/// Describe the wallet manager for a given network and return a newly -/// allocated C string. -/// -/// # Safety -/// - `manager` must be a valid pointer to an `FFIWalletManager` -/// - Callers must free the returned string with `wallet_manager_free_string` -#[no_mangle] -pub unsafe extern "C" fn wallet_manager_describe( - manager: *const FFIWalletManager, - error: *mut FFIError, -) -> *mut c_char { - if manager.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Null pointer provided".to_string()); - return ptr::null_mut(); - } - - let manager_ref = &*manager; - let runtime = manager_ref.runtime.clone(); - let manager_arc = manager_ref.manager.clone(); - - let description = runtime.block_on(async { - let guard = manager_arc.read().await; - guard.describe().await - }); - - match CString::new(description) { - Ok(c_string) => { - FFIError::set_success(error); - c_string.into_raw() - } - Err(e) => { - FFIError::set_error( - error, - FFIErrorCode::InvalidState, - format!("Failed to create description string: {}", e), - ); - ptr::null_mut() - } - } -} - -/// Free a string previously returned by wallet manager APIs. -/// -/// # Safety -/// - `value` must be either null or a pointer obtained from -/// `wallet_manager_describe` (or other wallet manager FFI helpers that -/// specify this free function). -/// - The pointer must not be used after this call returns. -#[no_mangle] -pub unsafe extern "C" fn wallet_manager_free_string(value: *mut c_char) { - if value.is_null() { - return; - } - - drop(CString::from_raw(value)); -} - /// Create a new wallet manager #[no_mangle] pub extern "C" fn wallet_manager_create( @@ -868,22 +811,6 @@ pub unsafe extern "C" fn wallet_manager_wallet_count( } } -/// Free wallet manager -/// -/// # Safety -/// -/// - `manager` must be a valid pointer to an FFIWalletManager that was created by this library -/// - The pointer must not be used after calling this function -/// - This function must only be called once per manager -#[no_mangle] -pub unsafe extern "C" fn wallet_manager_free(manager: *mut FFIWalletManager) { - if !manager.is_null() { - unsafe { - let _ = Box::from_raw(manager); - } - } -} - /// Free wallet IDs buffer /// /// # Safety @@ -901,26 +828,3 @@ pub unsafe extern "C" fn wallet_manager_free_wallet_ids(wallet_ids: *mut u8, cou } } } - -/// Free address array -/// -/// # Safety -/// -/// - `addresses` must be a valid pointer to an array of C string pointers allocated by this library -/// - `count` must match the original allocation size -/// - Each address pointer in the array must be either null or a valid C string allocated by this library -/// - The pointers must not be used after calling this function -/// - This function must only be called once per allocation -#[no_mangle] -pub unsafe extern "C" fn wallet_manager_free_addresses(addresses: *mut *mut c_char, count: usize) { - if !addresses.is_null() { - let slice = std::slice::from_raw_parts_mut(addresses, count); - for addr in slice { - if !addr.is_null() { - let _ = CString::from_raw(*addr); - } - } - // Free the array itself (matches boxed slice allocation) - let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(addresses, count)); - } -}