Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,12 @@ else
DEFINES_LIB = USE_LIB_ETHEREUM
endif


########################################
# Application communication interfaces #
########################################
ENABLE_BLUETOOTH = 1
ENABLE_SWAP = 1
#ENABLE_NFC = 1
ENABLE_NBGL_FOR_NANO_DEVICES = 1

########################################
# NBGL custom features #
Expand All @@ -159,10 +157,13 @@ ENABLE_NBGL_QRCODE = 1
########################################
# Main app configuration #
########################################
ENABLE_NBGL_FOR_NANO_DEVICES = 1
ENABLE_PKI_LIBRARY = 1
ENABLE_DYNAMIC_ALLOC = 1
ENABLE_TLV_LIBRARY = 1
ENABLE_LISTS_LIBRARY = 1
ENABLE_ADDRESS_BOOK = 1
ENABLE_ADDRESS_BOOK_LEDGER_ACCOUNT = 1

DEFINES += APP_TICKER=\"$(TICKER)\" APP_CHAIN_ID=$(CHAIN_ID) APP_COIN_TYPE=$(COIN_TYPE)

Expand Down
486 changes: 486 additions & 0 deletions client/src/ledger_app_clients/ethereum/address_book.py

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion client/src/ledger_app_clients/ethereum/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from .gating import Gating
from .trusted_name import TrustedName, TrustedNameSource
from .token_info import TokenInfo
from .address_book import AddressBookClient, AddressBookSubCommand


class EIP712CalldataParamPresence(IntEnum):
Expand Down Expand Up @@ -503,7 +504,7 @@ def provide_safe_account(self, safe_params: SafeAccount):
self._exchange(chunk)
return self._exchange_async(chunks[-1])

def provide_gating(self, gating_descriptor: Gating):
def provide_gating(self, gating_descriptor: Gating) -> RAPDU:
# Send ledgerPKI certificate
self.send_pki_certificate(GATING_PARTNER)

Expand All @@ -520,3 +521,13 @@ def provide_token_info(self, token_info: TokenInfo) -> RAPDU:
for chunk in chunks[:-1]:
self._exchange(chunk)
return self._exchange(chunks[-1])

def provide_address_book(self,
addr_book: AddressBookClient,
payload: bytes,
subcommand: AddressBookSubCommand,
async_mode: bool = True) -> RAPDU:

if async_mode:
return addr_book.send_async_raw(subcommand, payload)
return addr_book.send_sync_raw(subcommand, payload)
10 changes: 10 additions & 0 deletions client/src/ledger_app_clients/ethereum/tlv.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@
class FieldTag(IntEnum):
STRUCT_TYPE = 0x01
STRUCT_VERSION = 0x02
CONTACT_NAME = 0xf0
SCOPE = 0xf1
PREVIOUS_CONTACT_NAME = 0xf3
ACCOUNT_IDENTIFIER = 0xf2
PREVIOUS_IDENTIFIER = 0xf4
PREVIOUS_SCOPE = 0xf5
GROUP_HANDLE = 0xf6
CHALLENGE = 0x12
DER_SIGNATURE = 0x15
DERIVATION_PATH = 0x21
ADDRESS = 0x22
CHAIN_ID = 0x23
TICKER = 0x24
Expand All @@ -16,6 +24,8 @@ class FieldTag(IntEnum):
BLOCKCHAIN_FAMILY = 0x51
NETWORK_NAME = 0x52
NETWORK_ICON_HASH = 0x53
HMAC_PROOF = 0x29
HMAC_REST = 0xf7
TX_CHECKS_NORMALIZED_RISK = 0x80
TX_CHECKS_NORMALIZED_CATEGORY = 0x81
MESSAGE = 0x82
Expand Down
81 changes: 81 additions & 0 deletions src/address_name_lookup.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include "address_name_lookup.h"
#ifdef HAVE_ADDRESS_BOOK
#include "handle_contacts.h"
#endif
#include "common_utils.h"
#include "os_print.h"

/**
* @brief Resolve a human-readable display name for an Ethereum address.
*
* Tries name sources in priority order:
* 1. Address Book (if compiled in and @p types contains TN_TYPE_ACCOUNT)
* 2. Trusted Name matching the given @p types / @p sources constraints
* 3. Raw checksummed hex address as fallback
*
* @param[in] addr Raw 20-byte Ethereum address to resolve
* @param[in] chain_id Chain ID used for both Address Book / Trusted Name lookups and hex
* formatting
* @param[in] type_count Number of entries in @p types
* @param[in] types Accepted name types (e.g. TN_TYPE_ACCOUNT)
* @param[in] source_count Number of entries in @p sources
* @param[in] sources Accepted name sources (e.g. TN_SOURCE_ENS)
* @param[out] buf Output buffer receiving the display string
* @param[in] buf_size Size of @p buf
* @param[out] name_source_out Which source produced the name; NULL to discard
* @param[out] extra_data_out Opaque pointer to the matched record
* (s_ab_contact * or s_trusted_name *), NULL to discard
* @return true on success, false if address hex formatting failed
*/
bool get_address_display_name(const uint8_t *addr,
uint64_t chain_id,
uint8_t type_count,
const e_name_type *types,
uint8_t source_count,
const e_name_source *sources,
char *buf,
size_t buf_size,
e_addr_name_source *name_source_out,
const void **extra_data_out) {
const void *extra_data = NULL;
e_addr_name_source name_source = ADDR_NAME_FROM_RAW;
bool found = false;
const s_trusted_name *tname =
get_trusted_name(type_count, types, source_count, sources, &chain_id, addr);

#ifdef HAVE_ADDRESS_BOOK
// Address Book contacts are only relevant for EOA / account addresses (TN_TYPE_ACCOUNT).
// If that type is requested, check the local contact list first; it takes priority over
// any Trusted Name for the same address.
for (uint8_t i = 0; i < type_count; i++) {
if (types[i] == TN_TYPE_ACCOUNT) {
const s_ab_contact *ab_contact = get_address_book_contact(chain_id, addr);
if (ab_contact != NULL) {
strlcpy(buf, ab_contact->contact_name, buf_size);
extra_data = ab_contact;
name_source = ADDR_NAME_FROM_ADDRESS_BOOK;
found = true;
}
break;
}
}
#endif // HAVE_ADDRESS_BOOK
// Fall back to a Trusted Name if no Address Book contact matched.
if (!found) {
if (tname != NULL) {
strlcpy(buf, tname->name, buf_size);
extra_data = tname;
name_source = ADDR_NAME_FROM_TRUSTED_NAME;
found = true;
}
}
// Last resort: format the raw address as a checksummed hex string.
if (!found) {
if (!getEthDisplayableAddress(addr, buf, buf_size, chain_id)) {
return false;
}
}
if (name_source_out != NULL) *name_source_out = name_source;
if (extra_data_out != NULL) *extra_data_out = extra_data;
return true;
}
25 changes: 25 additions & 0 deletions src/address_name_lookup.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include "trusted_name.h"

// clang-format off
typedef enum {
ADDR_NAME_FROM_ADDRESS_BOOK,
ADDR_NAME_FROM_TRUSTED_NAME,
ADDR_NAME_FROM_RAW,
} e_addr_name_source;
// clang-format on

bool get_address_display_name(const uint8_t *addr,
uint64_t chain_id,
uint8_t type_count,
const e_name_type *types,
uint8_t source_count,
const e_name_source *sources,
char *buf,
size_t buf_size,
e_addr_name_source *name_source_out,
const void **extra_data_out);
10 changes: 10 additions & 0 deletions src/features/address_book/address_book_ctx.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* SPDX-FileCopyrightText: © 2026 Ledger SAS */
/* SPDX-License-Identifier: Apache-2.0 */

#include "address_book_ctx.h"

#if defined(HAVE_ADDRESS_BOOK)

ab_ui_ctx_t g_ab_ctx = {0};

#endif // HAVE_ADDRESS_BOOK
90 changes: 90 additions & 0 deletions src/features/address_book/address_book_ctx.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* SPDX-FileCopyrightText: © 2026 Ledger SAS */
/* SPDX-License-Identifier: Apache-2.0 */
/**
* @file address_book_ctx.h
* @brief Shared UI context for all Address Book flows.
*
* Identity and Ledger Account flows are mutually exclusive (only one UI flow
* runs at a time), so their per-flow context structs are kept in a union to
* minimise RAM usage.
*
* The unified contact list (g_ab_contact_list in handle_contacts.c) is
* intentionally NOT included here: it holds persistent state that survives
* across UI flows and must not be reset between operations.
*/

#pragma once

#if defined(HAVE_ADDRESS_BOOK)

#include "identity.h"
#include "nbgl_use_case.h" // nbgl_contentTagValue_t (via nbgl_content.h), nbgl_warningDetails_t

/* ---------------------------------------------------------------------------
* Identity UI contexts
* --------------------------------------------------------------------------- */

typedef struct {
identity_t *identity;
char *identifier_display;
char *network_display;
} register_identity_ctx_t;

typedef struct {
char *contact_name;
char *scope;
char *old_identifier;
char *new_identifier;
} edit_identifier_ctx_t;

typedef struct {
union {
register_identity_ctx_t register_identity;
edit_identifier_ctx_t edit_identifier;
};
nbgl_contentTagValue_t current_pair;
} identity_ui_ctx_t;

#if defined(HAVE_ADDRESS_BOOK_LEDGER_ACCOUNT)

#include "ledger_account.h"

/* ---------------------------------------------------------------------------
* Ledger Account UI contexts
* --------------------------------------------------------------------------- */

/** Number of hex chars shown on each side of the middle-truncated address */
#define ADDR_SHORT_HEX_LEN 4
/** "0x" + ADDR_SHORT_HEX_LEN + "..." + ADDR_SHORT_HEX_LEN + '\0' */
#define ADDR_DISPLAY_SHORT_SIZE (2 + ADDR_SHORT_HEX_LEN + 3 + ADDR_SHORT_HEX_LEN + 1)

typedef struct {
char *address_display;
char *network_display;
char address_display_short[ADDR_DISPLAY_SHORT_SIZE]; ///< "0x1234...5678"
const char *texts[1];
const char *subTexts[1];
nbgl_warningDetails_t details;
} register_ledger_account_ctx_t;

typedef struct {
ledger_account_t *ledger_account;
register_ledger_account_ctx_t register_account;
} ledger_account_ui_ctx_t;

#endif // HAVE_ADDRESS_BOOK_LEDGER_ACCOUNT

/* ---------------------------------------------------------------------------
* Shared UI context union — only one flow is active at a time
* --------------------------------------------------------------------------- */

typedef union {
identity_ui_ctx_t identity;
#if defined(HAVE_ADDRESS_BOOK_LEDGER_ACCOUNT)
ledger_account_ui_ctx_t ledger_account;
#endif
} ab_ui_ctx_t;

extern ab_ui_ctx_t g_ab_ctx;

#endif // HAVE_ADDRESS_BOOK
Loading
Loading