wallet: allow skipping leading 'm' when converting bip32 paths
What changed, and why it matters
This commit changes a helper function that turns numeric Bitcoin key paths (like 0/1/2) into human-readable text. It adds an option to leave off the leading 'm/' so callers that only want the raw path can get it. All existing callers are updated to keep the old behavior (with the 'm/'), so there is no immediate change in behavior. It appears to be a code cleanup or preparation for a future feature rather than a fix for an active security problem.
No immediate security action is required. Treat as a normal code review item: verify that future callers using path_only=true handle the resulting bare path string safely and that users are not confused by displays that omit the 'm/' master-key indicator.
Security signals we found
No security-relevant keywords in commit title or message
All existing call sites pass the new parameter as false, preserving prior behavior
Function signature change requires matching header update, reducing risk of ABI mismatch in this compiled project
No changes to length checks, buffer sizes, or validation of path components
No references to CVEs, advisories, or security reports in commit materials
Evidence from the diff
wallet_bip32_path_as_str() gains a new boolean parameter path_only. When path_only is true, the function omits the leading ‘m’ and the first path component is printed without a leading slash. All nine call sites in the diff pass path_only = false, preserving the previous ‘m/a/b/c’ output format. The change is therefore backward-compatible at every current call site. No input validation, buffer sizing, or path-parsing logic is altered except the formatting of the first segment when path_only is true.
Changed components
main/wallet.cmain/wallet.hmain/multisig.cmain/process/get_receive_address.cmain/process/sign_message.cmain/process/sign_psbt.cmain/process/sign_tx.cmain/qrmode.cmain/ui/signer.cInspect captured patch +39 / −23
diff --git a/main/multisig.c b/main/multisig.c
index beb3e66..2ecd231 100644
--- a/main/multisig.c
+++ b/main/multisig.c
@@ -664,7 +664,8 @@ bool multisig_create_export_file(const char* multisig_name, const multisig_data_
}
// Derivation
- if (!wallet_bip32_path_as_str(signer->derivation, signer->derivation_len, buf, sizeof(buf))) {
+ const bool path_only = false;
+ if (!wallet_bip32_path_as_str(signer->derivation, signer->derivation_len, buf, sizeof(buf), path_only)) {
JADE_LOGE("Multisig signer derivation path error");
return false;
}
diff --git a/main/process/get_receive_address.c b/main/process/get_receive_address.c
index e2e26dc..90056c2 100644
--- a/main/process/get_receive_address.c
+++ b/main/process/get_receive_address.c
@@ -190,7 +190,8 @@ void get_receive_address_process(void* process_ptr)
is_change = wallet_is_expected_singlesig_path(network_id, script_variant, is_change, path, path_len);
char path_str[MAX_PATH_STR_LEN(MAX_PATH_LEN)];
- if (!wallet_bip32_path_as_str(path, path_len, path_str, sizeof(path_str))) {
+ const bool path_only = false;
+ if (!wallet_bip32_path_as_str(path, path_len, path_str, sizeof(path_str), path_only)) {
jade_process_reject_message(
process, CBOR_RPC_INTERNAL_ERROR, "Failed to convert path to string format");
goto cleanup;
diff --git a/main/process/sign_message.c b/main/process/sign_message.c
index 0498a51..a9621cb 100644
--- a/main/process/sign_message.c
+++ b/main/process/sign_message.c
@@ -94,7 +94,8 @@ int sign_message_file(const char* str, const size_t str_len, uint8_t* sig_output
}
char pathstr[MAX_PATH_STR_LEN(MAX_PATH_LEN)];
- if (!wallet_bip32_path_as_str(path, path_len, pathstr, sizeof(pathstr))) {
+ const bool path_only = false;
+ if (!wallet_bip32_path_as_str(path, path_len, pathstr, sizeof(pathstr), path_only)) {
*errmsg = "Invalid bip32 path";
return CBOR_RPC_BAD_PARAMETERS;
}
@@ -211,7 +212,8 @@ void sign_message_process(void* process_ptr)
}
char pathstr[MAX_PATH_STR_LEN(MAX_PATH_LEN)];
- if (!wallet_bip32_path_as_str(path, path_len, pathstr, sizeof(pathstr))) {
+ const bool path_only = false;
+ if (!wallet_bip32_path_as_str(path, path_len, pathstr, sizeof(pathstr), path_only)) {
jade_process_reject_message(process, CBOR_RPC_INTERNAL_ERROR, "Failed to convert path to string format");
goto cleanup;
}
diff --git a/main/process/sign_psbt.c b/main/process/sign_psbt.c
index 8c93f50..a1bcc99 100644
--- a/main/process/sign_psbt.c
+++ b/main/process/sign_psbt.c
@@ -596,7 +596,9 @@ static bool psbt_update_outputs(const network_t network_id, struct wally_psbt* p
if (!wallet_is_expected_singlesig_path(network_id, script_variant, is_change, path, path_len)) {
// Not our standard change path - add warning
char path_str[MAX_PATH_STR_LEN(MAX_PATH_LEN)];
- const bool have_path_str = wallet_bip32_path_as_str(path, path_len, path_str, sizeof(path_str));
+ const bool path_only = false;
+ const bool have_path_str
+ = wallet_bip32_path_as_str(path, path_len, path_str, sizeof(path_str), path_only);
const int ret = snprintf(outinfo->message, sizeof(outinfo->message), "Unusual receive path: %s",
have_path_str ? path_str : "too long");
JADE_ASSERT(ret > 0 && ret < sizeof(outinfo->message));
@@ -664,7 +666,9 @@ static bool psbt_update_outputs(const network_t network_id, struct wally_psbt* p
if (!wallet_is_expected_multisig_path(iter.key_index, is_change, &path[path_tail_start], path_tail_len)) {
// Not our standard change path - add warning
char path_str[MAX_PATH_STR_LEN(MAX_PATH_LEN)];
- const bool have_path_str = wallet_bip32_path_as_str(path, path_len, path_str, sizeof(path_str));
+ const bool path_only = false;
+ const bool have_path_str
+ = wallet_bip32_path_as_str(path, path_len, path_str, sizeof(path_str), path_only);
const int ret = snprintf(outinfo->message, sizeof(outinfo->message), "Unusual change path suffix: %s",
have_path_str ? path_str : "too long");
JADE_ASSERT(ret > 0 && ret < sizeof(outinfo->message));
diff --git a/main/process/sign_tx.c b/main/process/sign_tx.c
index 26011a2..3582f06 100644
--- a/main/process/sign_tx.c
+++ b/main/process/sign_tx.c
@@ -255,7 +255,8 @@ static bool params_signing_outputs(jade_process_t* process, const CborValue* par
// If paths not as expected show a warning message and ask the user to confirm
if (!wallet_is_expected_singlesig_path(network_id, script_variant, is_change, path, path_len)) {
char path_str[MAX_PATH_STR_LEN(MAX_PATH_LEN)];
- if (!wallet_bip32_path_as_str(path, path_len, path_str, sizeof(path_str))) {
+ const bool path_only = false;
+ if (!wallet_bip32_path_as_str(path, path_len, path_str, sizeof(path_str), path_only)) {
errmsg = "Failed to convert path to string format";
goto cleanup;
}
diff --git a/main/qrmode.c b/main/qrmode.c
index 069dc49..8e516cd 100644
--- a/main/qrmode.c
+++ b/main/qrmode.c
@@ -250,7 +250,8 @@ static gui_activity_t* create_display_xpub_qr_activity(const uint32_t qr_flags)
// Create xpub activity for those icons
char pathstr[MAX_PATH_STR_LEN(EXPORT_XPUB_PATH_LEN)];
- const bool ret = wallet_bip32_path_as_str(path, path_len, pathstr, sizeof(pathstr));
+ const bool path_only = false;
+ const bool ret = wallet_bip32_path_as_str(path, path_len, pathstr, sizeof(pathstr), path_only);
JADE_ASSERT(ret);
const char* label = contains_flags(qr_flags, QR_XPUB_MULTISIG) ? "Multisig" : "Singlesig";
const uint8_t frames_per_qr = qr_framerate_from_flags(QR_SPEED_LOW); // always use slow framerate for xpub export
@@ -484,7 +485,8 @@ static void get_singlesig_search_root(const script_variant_t variant, const uint
JADE_ASSERT(ret);
// Use the root bip32 path as the label
- ret = wallet_bip32_path_as_str(path, path_len, pathstr, pathstr_len);
+ const bool path_only = false;
+ ret = wallet_bip32_path_as_str(path, path_len, pathstr, pathstr_len, path_only);
JADE_ASSERT(ret);
}
diff --git a/main/ui/signer.c b/main/ui/signer.c
index 2a8f082..51ec599 100644
--- a/main/ui/signer.c
+++ b/main/ui/signer.c
@@ -51,10 +51,11 @@ static gui_activity_t* make_signer_activities(const signer_t* signer, const size
gui_set_align(derivation, GUI_ALIGN_RIGHT, GUI_ALIGN_MIDDLE);
gui_set_parent(derivation, splitderivation);
+ const bool path_only = false;
if (signer->derivation_len == 0) {
strcpy(display_str, "<None>");
} else if (!wallet_bip32_path_as_str(
- signer->derivation, signer->derivation_len, display_str, sizeof(display_str))) {
+ signer->derivation, signer->derivation_len, display_str, sizeof(display_str), path_only)) {
strcpy(display_str, "[too long]");
}
gui_make_text(&derivation, display_str, TFT_WHITE);
@@ -121,7 +122,7 @@ static gui_activity_t* make_signer_activities(const signer_t* signer, const size
} else if (signer->path_is_string) {
JADE_ASSERT(signer->path_len < sizeof(display_str));
strcpy(display_str, signer->path_str);
- } else if (!wallet_bip32_path_as_str(signer->path, signer->path_len, display_str, sizeof(display_str))) {
+ } else if (!wallet_bip32_path_as_str(signer->path, signer->path_len, display_str, sizeof(display_str), path_only)) {
strcpy(display_str, "[too long]");
}
gui_make_text(&path, display_str, TFT_WHITE);
diff --git a/main/wallet.c b/main/wallet.c
index 5b1e8af..a82cb08 100644
--- a/main/wallet.c
+++ b/main/wallet.c
@@ -116,26 +116,29 @@ void wallet_init(void)
}
// Outputs eg. "m/a'/b'/c/d" - ie. uses m/ as master, and ' as hardened indicator
-bool wallet_bip32_path_as_str(const uint32_t* parts, const size_t num_parts, char* output, const size_t output_len)
+bool wallet_bip32_path_as_str(
+ const uint32_t* parts, const size_t num_parts, char* output, const size_t output_len, const bool path_only)
{
JADE_ASSERT(parts);
JADE_ASSERT(output);
JADE_ASSERT(output_len > 16);
- output[0] = 'm';
- output[1] = '\0';
-
- for (size_t pos = 1, i = 0; i < num_parts; ++i) {
- uint32_t val = parts[i];
- const char* fmt = "/%u";
+ size_t pos = 0;
+ if (!path_only) {
+ output[pos++] = 'm'; // Add leading 'm' master key indicator
+ }
+ output[pos] = '\0';
- if (ishardened(val)) {
- val = unharden(val);
- fmt = "/%u'"; // hardened
+ for (size_t i = 0; i < num_parts; ++i) {
+ const char* fmt;
+ if (ishardened(parts[i])) {
+ fmt = path_only && i == 0 ? "%u'" : "/%u'"; // Add hardened indicator
+ } else {
+ fmt = path_only && i == 0 ? "%u" : "/%u";
}
const size_t freespace = output_len - pos;
- const int nchars = snprintf(output + pos, freespace, fmt, val);
+ const int nchars = snprintf(output + pos, freespace, fmt, unharden(parts[i]));
if (nchars < 0 || nchars > freespace) {
return false;
}
diff --git a/main/wallet.h b/main/wallet.h
index 48c3683..b4208d9 100644
--- a/main/wallet.h
+++ b/main/wallet.h
@@ -56,7 +56,8 @@ typedef enum { GREEN, P2PKH, P2WPKH, P2WPKH_P2SH, MULTI_P2WSH, MULTI_P2SH, MULTI
void wallet_init(void);
-bool wallet_bip32_path_as_str(const uint32_t parts[], size_t num_parts, char* output, size_t output_len);
+bool wallet_bip32_path_as_str(
+ const uint32_t parts[], size_t num_parts, char* output, size_t output_len, bool path_only);
bool wallet_bip32_path_from_str(const char* pathstr, size_t str_len, uint32_t* path, size_t path_len, size_t* written);
bool wallet_derive_pubkey(const uint8_t* serialised_key, size_t key_len, const uint32_t* path, size_t path_len,
Why this scored 18/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.