Skip to content

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 = &params.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(&params.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
    }
}