CHECK_ADDRESS
This handle is called when the Exchange application wants to ensure that a given address belongs to the device.
If the address does belong to the device, result is set to 1. Otherwise it is set to 0.
C SDK API
ledger-secure-sdk/lib_standard_app/swap_entrypoints.h
// This callback must be defined in Coin Applications that use the SWAP feature.
void swap_handle_check_address(check_address_parameters_t *params);
ledger-secure-sdk/lib_standard_app/swap_lib_calls.h
typedef struct check_address_parameters_s {
// INPUTS //
// Additional data when dealing with tokens
// Content is coin application specific
uint8_t *coin_configuration;
uint8_t coin_configuration_length;
// serialized path, segwit, version prefix, hash used, dictionary etc.
// fields and serialization format are coin application specific
uint8_t *address_parameters;
uint8_t address_parameters_length;
// The address to check
char *address_to_check;
// Extra content that may be relevant depending on context: memo, calldata, ...
// Content is coin application specific
char *extra_id_to_check;
// OUTPUT //
// Set to 1 if the address belongs to the device. 0 otherwise.
int result;
} check_address_parameters_t;
Example of handle implementation in C Boilerplate
app-boilerplate/src/swap/handle_check_address.c
#ifdef HAVE_SWAP
#include "swap.h"
#include "buffer.h"
#include "bip32.h"
#include "crypto_helpers.h"
#include "cx.h"
#include "os.h"
#include "types.h"
#include "format.h"
#include "address.h"
#include "tx_types.h"
#include <string.h>
/* Check that the address used to receive funds is owned by the device
* check_address_parameters_t is defined in C SDK as:
* struct {
* // IN
* uint8_t *coin_configuration;
* uint8_t coin_configuration_length;
* // serialized path, segwit, version prefix, hash used, dictionary etc.
* // fields and serialization format depends on specific coin app
* uint8_t *address_parameters;
* uint8_t address_parameters_length;
* char *address_to_check;
* char *extra_id_to_check;
* // OUT
* int result;
* } check_address_parameters_t;
*/
void swap_handle_check_address(check_address_parameters_t *params) {
PRINTF("Inside swap_handle_check_address\n");
params->result = 0;
if (params->address_parameters == NULL) {
PRINTF("derivation path expected\n");
return;
}
PRINTF("address_parameters %.*H\n",
params->address_parameters_length,
params->address_parameters);
if (params->address_to_check == NULL) {
PRINTF("Address to check expected\n");
return;
}
PRINTF("Address to check %s\n", params->address_to_check);
if (strlen(params->address_to_check) != (ADDRESS_LEN * 2)) {
PRINTF("Address to check expected length %d, not %d\n",
ADDRESS_LEN * 2,
strlen(params->address_to_check));
return;
}
buffer_t buf = {.ptr = params->address_parameters,
.size = params->address_parameters_length,
.offset = 0};
uint8_t bip32_path_len;
uint32_t bip32_path[MAX_BIP32_PATH];
pubkey_ctx_t pk_info = {0};
buffer_read_u8(&buf, &bip32_path_len);
buffer_read_bip32_path(&buf, bip32_path, (size_t) bip32_path_len);
cx_err_t ret = bip32_derive_get_pubkey_256(CX_CURVE_256K1,
bip32_path,
bip32_path_len,
pk_info.raw_public_key,
pk_info.chain_code,
CX_SHA512);
if (ret != CX_OK) {
PRINTF("Failed to derive public key\n");
return;
}
uint8_t address[ADDRESS_LEN] = {0};
address_from_pubkey(pk_info.raw_public_key, address, sizeof(address));
char derived_address[41];
memset(derived_address, 0, sizeof(derived_address));
format_hex(address, sizeof(address), derived_address, sizeof(derived_address));
PRINTF("Derived address %s\n", derived_address);
PRINTF("Checked address %s\n", params->address_to_check);
if (strncmp(derived_address, params->address_to_check, sizeof(derived_address)) != 0) {
PRINTF("Addresses do not match\n");
} else {
PRINTF("Addresses match\n");
params->result = 1;
}
}
#endif // HAVE_SWAP
Rust SDK API
ledger-device-rust-sdk/ledger_device_sdk/src/libcall/swap.rs
/// Retrieves parameters for the `SwapCheckAddress` command.
///
/// This function parses the raw arguments provided by `os_lib_call` and populates
/// a `CheckAddressParams` struct.
///
/// # Arguments
///
/// * `arg0` - The argument passed to the main entry point by `os_lib_call`.
pub fn get_check_address_params<
const COIN_CONFIG_BUF_SIZE: usize,
const ADDRESS_BUF_SIZE: usize,
const ADDRESS_EXTRA_ID_BUF_SIZE: usize,
>(
arg0: u32,
) -> CheckAddressParams<COIN_CONFIG_BUF_SIZE, ADDRESS_BUF_SIZE, ADDRESS_EXTRA_ID_BUF_SIZE> {
/// Parameters for the `SwapCheckAddress` command.
///
/// This struct holds the data provided by the Exchange app to verify a destination address.
pub struct CheckAddressParams<
const COIN_CONFIG_BUF_SIZE: usize = DEFAULT_COIN_CONFIG_BUF_SIZE,
const ADDRESS_BUF_SIZE: usize = DEFAULT_ADDRESS_BUF_SIZE,
const ADDRESS_EXTRA_ID_BUF_SIZE: usize = DEFAULT_ADDRESS_EXTRA_ID_BUF_SIZE,
> {
/// Coin configuration (ticker, decimals, etc.)
pub coin_config: [u8; COIN_CONFIG_BUF_SIZE],
/// Length of the coin configuration
pub coin_config_len: usize,
/// BIP32 derivation path (raw bytes)
pub dpath: [u8; DPATH_STAGE_SIZE * 4],
/// Number of path components (u32)
pub dpath_len: usize,
/// Reference address provided by the Exchange app (as a string)
pub ref_address: [u8; ADDRESS_BUF_SIZE],
/// Length of the reference address
pub ref_address_len: usize,
/// Pointer to the result buffer (internal use)
pub result: *mut i32,
}
Example of handle implementation in Rust Boilerplate
app-boilerplate-rust/src/swap.rs
/// Verify that a given address belongs to this device.
///
/// The Exchange app calls this to ensure the user owns the destination address
/// before proceeding with the swap. This prevents sending funds to wrong addresses.
///
/// # Flow
///
/// 1. Parse BIP32 derivation path from params
/// 2. Derive public key from the path
/// 3. Compute address from public key (Keccak256 hash)
/// 4. Compare with reference address from Exchange
///
/// # Important Notes
///
/// - **No heap allocation**: Uses stack arrays only (BSS memory is shared with Exchange)
/// - **Hex string comparison**: Exchange sends address as hex string via C API,
/// so we convert our computed address to hex for comparison
/// - **Address format**: This app uses Ethereum-style addresses (last 20 bytes of
/// Keccak256 hash of pubkey). Adapt this for your blockchain's address format.
///
/// # Arguments
///
/// * `params` - Contains BIP32 path and reference address from Exchange
///
/// # Returns
///
/// * `1` if addresses match (valid)
/// * `0` if addresses don't match or error occurred
fn check_address(params: &CheckAddressParams) -> i32 {
// Parse BIP32 derivation path
// Note: params.dpath_len is the NUMBER of u32 path components (e.g., 5 for m/44'/1'/0'/0/0),
// not the byte length. Each component is 4 bytes (big-endian u32).
let path_bytes = ¶ms.dpath[..params.dpath_len * 4];
// Use stack-allocated array (no heap!) to store parsed path
let mut path: [u32; 10] = [0; 10]; // Max 10 derivation levels
if params.dpath_len > 10 {
debug_print("Path too long\n");
return 0;
}
// Convert big-endian bytes to u32 path components
for i in 0..params.dpath_len {
path[i] = u32::from_be_bytes([
path_bytes[i * 4],
path_bytes[i * 4 + 1],
path_bytes[i * 4 + 2],
path_bytes[i * 4 + 3],
]);
}
// Derive public key from path using the same logic as get_public_key handler
let (k, _) = Secp256k1::derive_from(&path[..params.dpath_len]);
let pubkey = match k.public_key() {
Ok(pk) => pk.pubkey,
Err(_) => {
debug_print("Key derivation failed\n");
return 0;
}
};
// Compute address: Keccak256 hash of pubkey (excluding first byte 0x04)
let address_hash = get_address_hash_from_pubkey(&pubkey);
// Take last 20 bytes as address (Ethereum-style)
let address = &address_hash[address_hash.len() - 20..];
// Exchange sends address bytes, but SDK's read_c_string() interprets them as
// a hex string. This is a quirk of the C API - the Exchange sends binary address
// bytes, but they're read as ASCII characters.
// Example: byte 0x04 becomes ASCII '0' (0x30) and '4' (0x34) = "04" in the string
let ref_hex = match core::str::from_utf8(¶ms.ref_address[..params.ref_address_len]) {
Ok(s) => s,
Err(_) => return 0,
};
// Convert our derived address to hex string for comparison
// Using ArrayString (stack-allocated) to avoid heap allocation
let mut our_hex = ArrayString::<40>::new(); // 20 bytes * 2 hex chars
for b in address {
let _ = write!(&mut our_hex, "{:02x}", b);
}
// Compare hex strings
if our_hex.as_str() == ref_hex {
debug_print("Check address successful, derived and received addresses match\n");
1 // Success
} else {
debug_print("Derived and received addresses do NOT match\n");
debug_hex("Derived address: ", address);
debug_print("Reference (hex): ");
debug_print(ref_hex);
debug_print("\n");
0 // Failure
}
}