Additional integrity checks for Merkle proofs
What changed, and why it matters
This commit adds extra safety checks when the Ledger Bitcoin app verifies Merkle proofs, which are cryptographic evidence used to confirm that a piece of data belongs to a larger set. The changes ensure that the proof length matches the expected tree depth and that streamed data has the correct leaf prefix. Without these checks, a malicious or malformed proof might have been accepted incorrectly, potentially allowing an attacker to lie about which data is stored in the wallet's Merkle tree.
Review whether any other Merkle proof consumers in the codebase perform equivalent length and prefix validation. Consider adding regression tests for undersized/oversized proofs and preimages missing the 0x00 prefix. Users should update to a firmware/app version containing this commit once available.
Security signals we found
Merkle proof length validation against leaf depth
Enforcement of Merkle leaf prefix byte 0x00
Prevention of streaming invalid preimage data before callback invocation
Hardening of cryptographic verification paths in a hardware wallet app
Evidence from the diff
The patch hardens three Merkle-related handlers. In get_merkle_leaf_hash.c, it now validates that proof_size equals the depth of the leaf by using merkle_get_ith_direction: the direction at proof_size must be -1 and the direction at proof_size-1 must be non-negative. In get_merkle_preimage.c and stream_preimage.c, it now rejects preimages whose first byte is not 0x00, enforcing the documented Merkle leaf serialization (0x00 || element). The stream_preimage check is placed before any length callback to prevent streaming invalid data.
Changed components
src/common/merkle.hsrc/handler/lib/get_merkle_leaf_hash.csrc/handler/lib/get_merkle_preimage.csrc/handler/lib/stream_preimage.cInspect captured patch +32 / −4
diff --git a/src/common/merkle.h b/src/common/merkle.h
index c1762e4..47d58f3 100644
--- a/src/common/merkle.h
+++ b/src/common/merkle.h
@@ -69,7 +69,12 @@ static inline uint8_t ceil_lg(uint32_t n) {
}
// Returns the ith member of the directions array for the leaf with the given index in a Merkle tree
-// of the given size. Returns -1 on error.
+// of the given size, where 0 = left and 1 = right. Returns -1 on error.
+//
+// A non-negative direction is returned exactly for the indexes i that are within the path from the
+// root to the leaf, that is, for i < depth of the leaf; -1 is returned for any larger i (and if
+// size or index are out of range). Therefore, this can also be used to compute the depth of a leaf,
+// or to check that it equals a given value.
int merkle_get_ith_direction(size_t size, size_t index, size_t i);
/**
diff --git a/src/handler/lib/get_merkle_leaf_hash.c b/src/handler/lib/get_merkle_leaf_hash.c
index d791266..53f33f9 100644
--- a/src/handler/lib/get_merkle_leaf_hash.c
+++ b/src/handler/lib/get_merkle_leaf_hash.c
@@ -54,6 +54,16 @@ int call_get_merkle_leaf_hash(dispatcher_context_t *dc,
return -1;
}
+ // The proof length must be exactly the depth of the leaf in the tree
+ if (merkle_get_ith_direction(tree_size, leaf_index, proof_size) != -1 ||
+ (proof_size > 0 &&
+ merkle_get_ith_direction(tree_size, leaf_index, (size_t) proof_size - 1) < 0)) {
+ PRINTF("Merkle proof length does not match the depth of the leaf.\n");
+
+ // Wrong length of the Merkle proof.
+ return -1;
+ }
+
if (n_proof_elements > proof_size) {
PRINTF("Received more proof data than expected.\n");
diff --git a/src/handler/lib/get_merkle_preimage.c b/src/handler/lib/get_merkle_preimage.c
index ebce71e..d99b4f5 100644
--- a/src/handler/lib/get_merkle_preimage.c
+++ b/src/handler/lib/get_merkle_preimage.c
@@ -58,6 +58,12 @@ int call_get_merkle_preimage(dispatcher_context_t *dispatcher_context,
uint8_t *data_ptr =
dispatcher_context->read_buffer.ptr + dispatcher_context->read_buffer.offset;
+ // Merkle tree leaves are hashes of 0x00 || element
+ if (data_ptr[0] != 0x00) {
+ PRINTF("Not a Merkle tree leaf preimage\n");
+ return -12;
+ }
+
cx_sha256_t hash_context;
cx_sha256_init(&hash_context);
diff --git a/src/handler/lib/stream_preimage.c b/src/handler/lib/stream_preimage.c
index 9f9fda0..318d430 100644
--- a/src/handler/lib/stream_preimage.c
+++ b/src/handler/lib/stream_preimage.c
@@ -51,13 +51,20 @@ int call_stream_preimage(dispatcher_context_t *dispatcher_context,
return -4;
}
+ uint8_t *data_ptr =
+ dispatcher_context->read_buffer.ptr + dispatcher_context->read_buffer.offset;
+
+ // Merkle tree leaves are hashes of 0x00 || element
+ // Checked before any callback, so that nothing is streamed out for an invalid preimage.
+ if (data_ptr[0] != 0x00) {
+ PRINTF("Not a Merkle tree leaf preimage\n");
+ return -11;
+ }
+
if (len_callback != NULL) {
len_callback(preimage_len - 1, callback_state);
}
- uint8_t *data_ptr =
- dispatcher_context->read_buffer.ptr + dispatcher_context->read_buffer.offset;
-
cx_sha256_t hash_context;
cx_sha256_init(&hash_context);
// update hash
Why this scored 62/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.