common/bolt11: Fix BOLT11 hash calculation for unknown fallback address versions
What changed, and why it matters
This change fixes how Core Lightning calculates the cryptographic hash of a BOLT11 invoice when the invoice contains a 'fallback address' whose version number is not recognized. Previously, the code would first read the version into the hash, then decide it was unknown and skip the field. That caused the hash to include data that the BOLT11 specification says should be left out, creating a mismatch with other implementations and potentially making the invoice unpayable or causing validation failures. The fix reads the version first without hashing it, skips unknown versions entirely, and only hashes the field for known versions.
Treat as a correctness fix with possible security implications for invoice integrity and interoperability. Review whether any other unknown-field handling paths similarly pre-hash data before skipping. Backport to maintained release branches if BOLT11 invoice parsing is exposed to untrusted input.
Security signals we found
BOLT11 invoice hash mismatch due to over-hashing of unknown fallback address version field
Spec non-compliance: unknown 'f' fields were partially included in the signed hash
Potential invoice validation/payment failure across implementations
No explicit security framing in commit message; described as 'hash calculation inconsistency'
Evidence from the diff
In common/bolt11.c’s decode_f(), the previous flow called pull_uint(hu5, data, field_len, &version, 5, false), which adds the 5-bit version to the hash (hu5) before checking whether the version is known. If the version was unknown, it then restored the data pointer and field length and called unknown_field(), but the version had already been hashed. The new code reads the version with pull_uint(NULL, …) so it is not committed to the hash, checks if it is a known version (version == 17, version == 18, or version < 17), and for unknown versions immediately calls unknown_field() without ever hashing the field. For known versions it re-reads/hashes the version and continues. This aligns with BOLT11’s requirement to skip unknown f fields entirely, including from the signature hash.
Changed components
common/bolt11.cBOLT11 invoice decoding (decode_f)fallback address handlingInspect captured patch +15 / −7
diff --git a/common/bolt11.c b/common/bolt11.c
index bb3db5b0..bd51fb3d 100644
--- a/common/bolt11.c
+++ b/common/bolt11.c
@@ -388,6 +388,18 @@ static const char *decode_f(struct bolt11 *b11,
size_t orig_len = *field_len;
const char *err;
+ /* Read version but don't commit to hash yet */
+ err = pull_uint(NULL, &orig_data, &orig_len, &version, 5, false);
+ if (err)
+ return tal_fmt(b11, "f: %s", err);
+
+ bool is_known_version = version == 17 || version == 18 || version < 17;
+
+ if (!is_known_version) {
+ return unknown_field(b11, hu5, data, field_len, 'f');
+ }
+
+ /* For known versions, process with hash */
err = pull_uint(hu5, data, field_len, &version, 5, false);
if (err)
return tal_fmt(b11, "f: %s", err);
@@ -442,13 +454,9 @@ static const char *decode_f(struct bolt11 *b11,
fallback = scriptpubkey_witness_raw(b11, version,
f, tal_count(f));
} else {
- /* BOLT #11:
- * - MUST skip over `f` fields that use an unknown `version`.
- */
- /* Restore version for unknown field! */
- *data = orig_data;
- *field_len = orig_len;
- return unknown_field(b11, hu5, data, field_len, 'f');
+ // This should be unreachable because all valid versions (17, 18, or <17)
+ // and invalid versions are caught above.
+ return tal_fmt(b11, "f: unknown version %"PRIu64, version);
}
if (b11->fallbacks == NULL)
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.