ui: add the BIP-322 message review flow
What changed, and why it matters
This commit adds a new on-screen review flow for BIP-322 message signatures in the Ledger Bitcoin app. It only displays information to the user and asks for confirmation; it does not change how signatures are computed or how data is validated. The code is marked 'Not used yet,' meaning the new function is not wired into any signing handler. There is no security issue visible in the change itself.
No security action required. Treat as a normal UI feature addition. When the function is later wired into a handler, review that the caller validates the BIP-322 inputs and passes correct account/address/message values.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces ui_display_bip322_message_and_confirm() and the NBGL flow ui_display_bip322_message_flow() to review BIP-322 message-signing requests. It copies account/address/message into existing UI state buffers, formats an optional proof-of-funds amount, and presents a standard nbgl_useCaseReview() message-review screen. It also moves two processing-screen string declarations into a shared header. The function is declared but not called anywhere in the diff, and the commit message explicitly says ‘Not used yet.’
Changed components
src/ui/display.csrc/ui/display.hsrc/ui/display_nbgl.csrc/handler/sign_message.cInspect captured patch +107 / −2
### src/handler/sign_message.c
@@ -32,8 +32,6 @@
#include "menu.h"
#include "sw.h"
-extern const char GA_LOADING_MESSAGE[];
-
static unsigned char const BSM_SIGN_MAGIC[] = {'\x18', 'B', 'i', 't', 'c', 'o', 'i', 'n', ' ',
'S', 'i', 'g', 'n', 'e', 'd', ' ', 'M', 'e',
's', 's', 'a', 'g', 'e', ':', '\n'};
### src/ui/display.c
@@ -113,6 +113,40 @@ bool ui_display_message_and_confirm(dispatcher_context_t *context,
return io_ui_process(context);
}
+bool ui_display_bip322_message_and_confirm(dispatcher_context_t *context,
+ const char *account,
+ const char *address,
+ const char *message,
+ bool is_hash,
+ bool has_proven_funds,
+ uint64_t proven_amount) {
+#ifdef HAVE_AUTOAPPROVE_FOR_PERF_TESTS
+ return true;
+#endif
+
+ ui_bip322_message_state_t *state = (ui_bip322_message_state_t *) &g_ui_state;
+
+ copy_ui_string(state->message, message, sizeof(state->message));
+ if (account != NULL) {
+ strncpy(state->account, account, sizeof(state->account));
+ state->account[sizeof(state->account) - 1] = '\0';
+ } else {
+ state->account[0] = '\0';
+ }
+ strncpy(state->address, address, sizeof(state->address));
+ state->address[sizeof(state->address) - 1] = '\0';
+
+ if (has_proven_funds) {
+ format_sats_amount(COIN_COINID_SHORT, proven_amount, state->proven_amount);
+ } else {
+ state->proven_amount[0] = '\0';
+ }
+
+ ui_display_bip322_message_flow(account != NULL, is_hash, has_proven_funds);
+
+ return io_ui_process(context);
+}
+
bool ui_display_register_wallet_policy(
dispatcher_context_t *context,
const policy_map_wallet_header_t *wallet_header,
### src/ui/display.h
@@ -79,6 +79,14 @@ typedef struct {
char message[MAX_DISPLAYBLE_MESSAGE_LENGTH + 1];
} ui_path_and_message_state_t;
+// State for the BIP-322 message review.
+typedef struct {
+ char account[MAX_WALLET_NAME_LENGTH + 1];
+ char address[MAX_ADDRESS_LENGTH_STR + 1];
+ char proven_amount[MAX_AMOUNT_LENGTH + 1]; // total of the proof-of-funds inputs
+ char message[MAX_DISPLAYBLE_MESSAGE_LENGTH + 1];
+} ui_bip322_message_state_t;
+
typedef struct {
char wallet_name[MAX_WALLET_NAME_LENGTH + 1];
@@ -175,6 +183,7 @@ typedef union {
ui_path_and_pubkey_state_t path_and_pubkey;
ui_path_and_address_state_t path_and_address;
ui_path_and_message_state_t path_and_message;
+ ui_bip322_message_state_t bip322_message;
ui_wallet_state_t wallet;
ui_cosigner_pubkey_and_index_state_t cosigner_pubkey_and_index;
ui_register_wallet_policy_state_t register_wallet_policy;
@@ -201,6 +210,21 @@ bool ui_display_message_and_confirm(dispatcher_context_t *context,
const char *message,
bool is_hash);
+/**
+ * Shows the BIP-322 message review and asks for confirmation to sign.
+ * account (NULL to hide the row), address and message are copied into the UI state. If is_hash
+ * is true, message is the hex-encoded hash of the message, and is labeled as such.
+ * If has_proven_funds is true, a "Proving funds" row with the formatted proven_amount is
+ * shown (proof-of-funds variant).
+ */
+bool ui_display_bip322_message_and_confirm(dispatcher_context_t *context,
+ const char *account,
+ const char *address,
+ const char *message,
+ bool is_hash,
+ bool has_proven_funds,
+ uint64_t proven_amount);
+
// Reviews a wallet policy to register. Pass `descriptor_template == NULL` to
// hide the raw descriptor template (when the cleartext lines already fully
// capture the policy); otherwise it is shown after the cleartext block.
@@ -268,6 +292,8 @@ void ui_display_pubkey_flow(void);
void ui_sign_message_and_confirm_flow(bool is_hash);
+void ui_display_bip322_message_flow(bool has_account, bool is_hash, bool has_proven_funds);
+
void ui_display_receive_in_wallet_flow(void);
void ui_display_default_wallet_address_flow(void);
@@ -302,3 +328,7 @@ void ui_display_post_processing_confirm_transaction(bool success);
*/
char const *ui_get_processing_screen_text(void);
void ui_set_processing_screen_text(const char *text);
+
+// Processing screen texts for the message signing flows (SIGN_MESSAGE and BIP-322)
+extern const char GA_LOADING_MESSAGE[];
+extern const char GA_SIGNING_MESSAGE[];
### src/ui/display_nbgl.c
@@ -54,6 +54,7 @@ const char GA_REVIEW_MESSAGE[] = "Review message";
const char GA_LOADING_TRANSACTION[] = "Loading transaction";
const char GA_SIGNING_TRANSACTION[] = "Signing transaction";
const char GA_LOADING_MESSAGE[] = "Loading message";
+const char GA_SIGNING_MESSAGE[] = "Signing message";
// Non-default-sighash transaction summary labels (trustworthy-or-bust display)
const char GA_FEE_NOT_AVAILABLE[] = "Not available";
@@ -606,6 +607,48 @@ void ui_sign_message_and_confirm_flow(bool is_hash) {
start_processing_message_callback);
}
+// BIP-322 message review: account (optional), the address being proven, the total amount of
+// the coins being proven (proof-of-funds only), and the message.
+void ui_display_bip322_message_flow(bool has_account, bool is_hash, bool has_proven_funds) {
+ reset_flow_state();
+
+ unsigned int np = 0;
+
+ if (has_account) {
+ pairs[np++] =
+ (nbgl_layoutTagValue_t) {.item = "Account", .value = g_ui_state.bip322_message.account};
+ }
+
+ pairs[np++] =
+ (nbgl_layoutTagValue_t) {.item = "Address", .value = g_ui_state.bip322_message.address};
+
+ if (has_proven_funds) {
+ pairs[np++] = (nbgl_layoutTagValue_t) {.item = "Proving funds",
+ .value = g_ui_state.bip322_message.proven_amount};
+ }
+
+ const char *message_label;
+ if (!is_hash) {
+#ifdef SCREEN_SIZE_WALLET
+ message_label = "Message content";
+#else
+ message_label = "Message";
+#endif
+ } else {
+ message_label = "Message hash";
+ }
+ pairs[np++] =
+ (nbgl_layoutTagValue_t) {.item = message_label, .value = g_ui_state.bip322_message.message};
+
+ nbgl_useCaseReview(TYPE_MESSAGE,
+ make_pair_list(np, true),
+ &ICON_APP_ACTION,
+ GA_REVIEW_MESSAGE,
+ NULL,
+ GA_SIGN_MESSAGE,
+ start_processing_message_callback);
+}
+
// Address flow
void ui_display_default_wallet_address_flow(void) {
reset_flow_state();Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.