Embedded SDK
Embedded SDK
Loading...
Searching...
No Matches
Address Book API

Introduction

The Address Book feature allows a Ledger device to securely associate human-readable names with blockchain identifiers or Ledger-managed accounts. The device is stateless: nothing is stored persistently. On registration the device generates a random group_id (gid, 32 bytes), authenticates it as a group_handle (64 bytes), and returns two independent HMAC Proofs of Registration that the wallet stores alongside the contact record.

The feature provides:

  • Binding a contact name and scope to an external identifier (e.g. an Ethereum address) via the Register Identity flow
  • Linking multiple addresses to a single contact by re-calling Register Identity with an existing group_handle — all addresses then share the same name and rename atomically
  • Binding an account name to a BIP32 derivation path managed by the device via the Register Ledger Account flow
  • Updating existing bindings (name, identifier, scope) via dedicated Edit flows — each verified by presenting the stored group_handle and the relevant HMAC proof(s)
  • Cryptographic resistance to proof splicing: the wallet cannot mix HMAC proofs from different contacts because the device generates and authenticates gid itself
  • No plaintext secrets leave the device: the host receives only the HMAC proofs

Architecture

The Address Book implementation is split between the SDK and coin applications:

  • SDK Core: TLV parsing, group handle generation and verification, HMAC key derivation, HMAC computation and verification, Edit UI flows
  • Coin Application: identifier / account validation, blockchain-specific checks, Register Identity and Register Ledger Account UI, tag-value pairs for Edit Identifier

Sub-Commands

The Address Book APDU (CLA=0xB0, INS=0x10) dispatches on P1:

P1 Sub-command Struct type Guard
0x01 Register Identity 0x2d HAVE_ADDRESS_BOOK
0x02 Edit Contact Name 0x2e HAVE_ADDRESS_BOOK
0x03 Edit Identifier 0x31 HAVE_ADDRESS_BOOK
0x04 Edit Scope 0x32 HAVE_ADDRESS_BOOK
0x11 Register Ledger Account 0x2f HAVE_ADDRESS_BOOK_LEDGER_ACCOUNT
0x12 Edit Ledger Account 0x30 HAVE_ADDRESS_BOOK_LEDGER_ACCOUNT
0x20 Provide Contact 0x33 HAVE_ADDRESS_BOOK
0x21 Provide Ledger Account Contact 0x34 HAVE_ADDRESS_BOOK_LEDGER_ACCOUNT

Command Flows

Sequence diagrams for Register Identity (P1=0x01) and Register Ledger Account (P1=0x11) are available in Mermaid format in app_features/address_book/doc/address_book_spec.md (viewable on GitHub/GitLab).

Data Structures

TLV Tags

Tag Name Description Max size
0x01 STRUCT_TYPE Sub-command type discriminator (0x2d–0x33) 1 B
0x02 STRUCT_VERSION Always 0x01 1 B
0xf0 CONTACT_NAME Human-readable contact/account name 32 B
0xf1 SCOPE Scope/namespace for the identifier 32 B
0xf3 PREVIOUS_CONTACT_NAME old name (Edit Contact Name only) 32 B
0xf2 ACCOUNT_IDENTIFIER Opaque identifier (e.g. 20-byte Ethereum address) 80 B
0xf4 PREVIOUS_IDENTIFIER old identifier (Edit Identifier only) 80 B
0xf5 PREVIOUS_SCOPE old scope (Edit Scope only) 32 B
0xf6 GROUP_HANDLE Device-generated token: gid(32) + HMAC-SHA256(K_group, gid)(32) 64 B
0x21 DERIVATION_PATH BIP32 path for HMAC key derivation 43 B
0x23 CHAIN_ID Chain ID (mandatory for Ethereum family) 8 B
0x51 BLOCKCHAIN_FAMILY Blockchain family enum 1 B
0x29 HMAC_PROOF HMAC-SHA256 over gid + contact name 32 B
0xf7 HMAC_REST HMAC-SHA256 over gid + scope + identifier + network 32 B

Response Formats

// Register Identity (0x2d): 129 bytes
type(1) | group_handle(64) | hmac_proof(32) | hmac_rest(32)
// Edit Contact Name (0x2e): 33 bytes — new HMAC_PROOF
// Edit Identifier (0x31): 33 bytes — new HMAC_REST
// Edit Scope (0x32): 33 bytes — new HMAC_REST
type(1) | hmac(32)
// Register Ledger Account (0x2f): 33 bytes
// Edit Ledger Account (0x30): 33 bytes
type(1) | hmac_proof(32)
// Provide Contact (0x33): no data
// Provide Ledger Account Contact (0x34): no data
9000

Cryptographic Design

Group Handle

At registration the device generates a random 32-byte Group ID (gid) and authenticates it with a separate device key:

K_group = SHA256("AddressBook-Group" || BIP32_PRIVKEY.d)
group_handle = gid(32) || HMAC-SHA256(K_group, gid)(32)

The wallet stores group_handle opaquely. For each Edit operation the wallet re-sends the full group_handle; the device verifies the MAC in constant time and — only on success — extracts gid to authenticate the HMAC proofs.

This closes the proof splicing attack: a wallet that registers two contacts cannot mix HMAC_PROOF from one with HMAC_REST from the other, because neither can fabricate a valid group_handle for an arbitrary gid.

Key Derivation

Three independent KDFs prevent cross-feature key reuse:

// Group handle MAC key
K_group = SHA256("AddressBook-Group" || BIP32_PRIVKEY.d)
// Identity HMAC key (Register Identity + all Identity Edit flows)
K_identity = SHA256("AddressBook-Identity" || BIP32_PRIVKEY.d)
// Ledger Account HMAC key (Register, Edit, and Provide Ledger Account Contact)
K_account = SHA256("AddressBook-LedgerAccount" || BIP32_PRIVKEY.d)

HMAC Message Formats

Identity — HMAC_PROOF (covers name only):

message = gid(32) | name_len(1) | contact_name
hmac_proof = HMAC-SHA256(K_identity, message)

Identity — HMAC_REST (covers scope, identifier, and network):

message = gid(32) | scope_len(1) | scope | id_len(1) | identifier | family(1) [| chain_id(8)]
hmac_rest = HMAC-SHA256(K_identity, message)

chain_id is included only when BLOCKCHAIN_FAMILY = 1 (Ethereum), encoded as 8 bytes big-endian.

Ledger Account — HMAC_PROOF:

message = name_len(1) | account_name | blockchain_family(1) [| chain_id(8)]
hmac_proof = HMAC-SHA256(K_account, message)

Key Design Decisions

  1. Device-generated gid: The device generates gid randomly rather than accepting a wallet-chosen contact ID. Combined with the authenticated group_handle, this prevents proof splicing attacks where a malicious wallet mixes proofs from different contacts.
  2. Two independent Identity HMACs: HMAC_PROOF covers only gid + name, while HMAC_REST covers gid + scope + identifier + network. This allows the wallet to rename a contact without re-presenting the identifier or scope, and vice-versa.
  3. Multi-address contacts: Register Identity can be called with an existing group_handle + HMAC_PROOF to link a second (scope, identifier) pair to the same gid. The device verifies ownership before issuing a new HMAC_REST. Because all addresses share the same gid, a single Edit Contact Name invalidates the HMAC_PROOF for every address in the group simultaneously.
  4. Triple KDF: Separate salts for Group Handle, Identity, and Ledger Account prevent cross-feature key reuse even at the same BIP32 derivation path.
  5. No secrets leave the device: The BIP32 private key, KDF keys, and gid are computed on-device. The host receives only the opaque group_handle and HMAC proofs.
  6. App validation: Coin-specific validation logic is delegated to the application via callbacks, maintaining separation of concerns.
  7. User confirmation: A mandatory review screen ensures users explicitly approve each registration before the HMAC proofs are sent.

API Reference

Application Callbacks

Applications must implement the following callbacks to support Address Book:

Register Identity:

bool handle_check_register_identity(identity_t *params);
nbgl_contentTagValue_t *get_register_identity_tagValue(nbgl_contentTagValue_t *pair, uint8_t index);
void finalize_ui_register_identity(void);
This structure contains a [tag,value] pair and possible extensions.

Edit Identifier:

bool handle_check_edit_identifier(edit_identifier_t *params);
nbgl_contentTagValue_t *get_edit_identifier_tagValue(nbgl_contentTagValue_t *pair, uint8_t index);
void finalize_ui_edit_identifier(void);

Edit Contact Name / Edit Scope (SDK handles display):

void finalize_ui_edit_contact_name(void);
void finalize_ui_edit_scope(void);

Register Ledger Account (requires HAVE_ADDRESS_BOOK_LEDGER_ACCOUNT):

bool handle_check_register_ledger_account(ledger_account_t *params);
void display_register_ledger_account_review(nbgl_choiceCallback_t choice_callback);
void finalize_ui_register_ledger_account(void);
void finalize_ui_edit_ledger_account(void);
void(* nbgl_choiceCallback_t)(bool confirm)
prototype of choice callback function
Data extracted from a Register Ledger Account TLV payload.

Provide Contact:

bool handle_provide_identity(const identity_t *contact);

Provide Ledger Account Contact (requires HAVE_ADDRESS_BOOK_LEDGER_ACCOUNT):

bool handle_provide_ledger_account(const ledger_account_t *account);

Core Crypto Functions

Key functions provided by the Address Book module:

  • address_book_generate_group_handle(): Generate gid and compute authenticated group_handle
  • address_book_verify_group_handle(): Verify group_handle MAC and extract gid
  • address_book_compute_hmac_proof(): Compute HMAC_PROOF over (gid, name)
  • address_book_verify_hmac_proof(): Verify HMAC_PROOF (constant-time)
  • address_book_compute_hmac_rest(): Compute HMAC_REST over (gid, scope, identifier, network)
  • address_book_verify_hmac_rest(): Verify HMAC_REST (constant-time)
  • address_book_send_register_identity_response(): Send type(1) | group_handle(64) | hmac_proof(32) | hmac_rest(32)
  • address_book_send_hmac_proof(): Send type(1) | hmac(32) for Edit responses

See the source files for detailed API documentation.