common: BOLT update which adds taproot fallback.
What changed, and why it matters
This commit updates the version of the Lightning BOLT specification used by Core Lightning and adds a test case for a new BOLT #11 example that includes a Taproot (P2TR) fallback address. It does not change production code behavior; it only adds test coverage for parsing a Taproot fallback in a BOLT11 invoice. There is no direct security vulnerability visible in the diff.
No immediate security action required. Review the corresponding BOLT specification changes at the new pinned commit to confirm no unexpected protocol-level security implications, and ensure the new test passes.
Security signals we found
Specification version bump only
Test-only addition for new BOLT11 fallback address type (P2TR/Taproot)
No production code changes in diff
No memory safety, validation, or cryptographic changes visible
Evidence from the diff
The commit bumps DEFAULT_BOLTVERSION in the Makefile to a newer BOLT specification commit and extends common/test/run-bolt11.c with a new test vector. The test vector exercises a BOLT11 invoice containing a fallback address with witness version 1 (Taproot/P2TR). The test file gains a helper fallbacks() that reuses json_to_address_scriptpubkey to convert a bech32m P2TR address into a scriptPubKey, plus autogenerated mock stubs needed to link the test. No production parsing or consensus code is modified.
Changed components
Makefile (BOLT specification version pin)common/test/run-bolt11.c (BOLT11 invoice unit tests)Inspect captured patch +203 / −1
diff --git a/Makefile b/Makefile
index dd31c360..737bd605 100644
--- a/Makefile
+++ b/Makefile
@@ -33,7 +33,7 @@ CCANDIR := ccan
# Where we keep the BOLT RFCs
BOLTDIR := ../bolts/
-DEFAULT_BOLTVERSION := 89dfc59529a9ce845b06e7d6a62851a4ea096158
+DEFAULT_BOLTVERSION := 40e21b134e76efaa93eb2027ed0bcc3e2afb1d90
# Can be overridden on cmdline.
BOLTVERSION := $(DEFAULT_BOLTVERSION)
diff --git a/common/test/run-bolt11.c b/common/test/run-bolt11.c
index b36ae796..33b228bc 100644
--- a/common/test/run-bolt11.c
+++ b/common/test/run-bolt11.c
@@ -3,6 +3,7 @@
#include "../bech32.c"
#include "../bech32_util.c"
#include "../bolt11.c"
+#include "../json_param.c"
#include "../features.c"
#include "../node_id.c"
#include "../hash_u5.c"
@@ -13,6 +14,137 @@
#include <stdio.h>
/* AUTOGENERATED MOCKS START */
+/* Generated stub for command_check_done */
+struct command_result *command_check_done(struct command *cmd)
+
+{ fprintf(stderr, "command_check_done called!\n"); abort(); }
+/* Generated stub for command_check_only */
+bool command_check_only(const struct command *cmd UNNEEDED)
+{ fprintf(stderr, "command_check_only called!\n"); abort(); }
+/* Generated stub for command_deprecated_in_ok */
+bool command_deprecated_in_ok(struct command *cmd UNNEEDED,
+ const char *param UNNEEDED,
+ const char *depr_start UNNEEDED,
+ const char *depr_end UNNEEDED)
+{ fprintf(stderr, "command_deprecated_in_ok called!\n"); abort(); }
+/* Generated stub for command_dev_apis */
+bool command_dev_apis(const struct command *cmd UNNEEDED)
+{ fprintf(stderr, "command_dev_apis called!\n"); abort(); }
+/* Generated stub for command_fail */
+struct command_result *command_fail(struct command *cmd UNNEEDED, enum jsonrpc_errcode code UNNEEDED,
+ const char *fmt UNNEEDED, ...)
+
+{ fprintf(stderr, "command_fail called!\n"); abort(); }
+/* Generated stub for command_fail_badparam */
+struct command_result *command_fail_badparam(struct command *cmd UNNEEDED,
+ const char *paramname UNNEEDED,
+ const char *buffer UNNEEDED,
+ const jsmntok_t *tok UNNEEDED,
+ const char *msg UNNEEDED)
+{ fprintf(stderr, "command_fail_badparam called!\n"); abort(); }
+/* Generated stub for command_set_usage */
+void command_set_usage(struct command *cmd UNNEEDED, const char *usage UNNEEDED)
+{ fprintf(stderr, "command_set_usage called!\n"); abort(); }
+/* Generated stub for command_usage_only */
+bool command_usage_only(const struct command *cmd UNNEEDED)
+{ fprintf(stderr, "command_usage_only called!\n"); abort(); }
+/* Generated stub for json_get_membern */
+const jsmntok_t *json_get_membern(const char *buffer UNNEEDED,
+ const jsmntok_t tok[] UNNEEDED,
+ const char *label UNNEEDED, size_t len UNNEEDED)
+{ fprintf(stderr, "json_get_membern called!\n"); abort(); }
+/* Generated stub for json_next */
+const jsmntok_t *json_next(const jsmntok_t *tok UNNEEDED)
+{ fprintf(stderr, "json_next called!\n"); abort(); }
+/* Generated stub for json_scan */
+const char *json_scan(const tal_t *ctx UNNEEDED,
+ const char *buffer UNNEEDED,
+ const jsmntok_t *tok UNNEEDED,
+ const char *guide UNNEEDED,
+ ...)
+{ fprintf(stderr, "json_scan called!\n"); abort(); }
+/* Generated stub for json_strdup */
+char *json_strdup(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED)
+{ fprintf(stderr, "json_strdup called!\n"); abort(); }
+/* Generated stub for json_to_bool */
+bool json_to_bool(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, bool *b UNNEEDED)
+{ fprintf(stderr, "json_to_bool called!\n"); abort(); }
+/* Generated stub for json_to_channel_id */
+bool json_to_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
+ struct channel_id *cid UNNEEDED)
+{ fprintf(stderr, "json_to_channel_id called!\n"); abort(); }
+/* Generated stub for json_to_millionths */
+bool json_to_millionths(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
+ u64 *millionths UNNEEDED)
+{ fprintf(stderr, "json_to_millionths called!\n"); abort(); }
+/* Generated stub for json_to_msat */
+bool json_to_msat(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
+ struct amount_msat *msat UNNEEDED)
+{ fprintf(stderr, "json_to_msat called!\n"); abort(); }
+/* Generated stub for json_to_node_id */
+bool json_to_node_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
+ struct node_id *id UNNEEDED)
+{ fprintf(stderr, "json_to_node_id called!\n"); abort(); }
+/* Generated stub for json_to_number */
+bool json_to_number(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
+ unsigned int *num UNNEEDED)
+{ fprintf(stderr, "json_to_number called!\n"); abort(); }
+/* Generated stub for json_to_outpoint */
+bool json_to_outpoint(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
+ struct bitcoin_outpoint *op UNNEEDED)
+{ fprintf(stderr, "json_to_outpoint called!\n"); abort(); }
+/* Generated stub for json_to_pubkey */
+bool json_to_pubkey(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
+ struct pubkey *pubkey UNNEEDED)
+{ fprintf(stderr, "json_to_pubkey called!\n"); abort(); }
+/* Generated stub for json_to_s64 */
+bool json_to_s64(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, s64 *num UNNEEDED)
+{ fprintf(stderr, "json_to_s64 called!\n"); abort(); }
+/* Generated stub for json_to_short_channel_id */
+bool json_to_short_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
+ struct short_channel_id *scid UNNEEDED)
+{ fprintf(stderr, "json_to_short_channel_id called!\n"); abort(); }
+/* Generated stub for json_to_short_channel_id_dir */
+bool json_to_short_channel_id_dir(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
+ struct short_channel_id_dir *scidd UNNEEDED)
+{ fprintf(stderr, "json_to_short_channel_id_dir called!\n"); abort(); }
+/* Generated stub for json_to_txid */
+bool json_to_txid(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
+ struct bitcoin_txid *txid UNNEEDED)
+{ fprintf(stderr, "json_to_txid called!\n"); abort(); }
+/* Generated stub for json_to_u16 */
+bool json_to_u16(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
+ uint16_t *num UNNEEDED)
+{ fprintf(stderr, "json_to_u16 called!\n"); abort(); }
+/* Generated stub for json_to_u32 */
+bool json_to_u32(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, u32 *num UNNEEDED)
+{ fprintf(stderr, "json_to_u32 called!\n"); abort(); }
+/* Generated stub for json_to_u64 */
+bool json_to_u64(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, u64 *num UNNEEDED)
+{ fprintf(stderr, "json_to_u64 called!\n"); abort(); }
+/* Generated stub for json_tok_bin_from_hex */
+u8 *json_tok_bin_from_hex(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED)
+{ fprintf(stderr, "json_tok_bin_from_hex called!\n"); abort(); }
+/* Generated stub for json_tok_full */
+const char *json_tok_full(const char *buffer UNNEEDED, const jsmntok_t *t UNNEEDED)
+{ fprintf(stderr, "json_tok_full called!\n"); abort(); }
+/* Generated stub for json_tok_full_len */
+int json_tok_full_len(const jsmntok_t *t UNNEEDED)
+{ fprintf(stderr, "json_tok_full_len called!\n"); abort(); }
+/* Generated stub for json_tok_is_null */
+bool json_tok_is_null(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED)
+{ fprintf(stderr, "json_tok_is_null called!\n"); abort(); }
+/* Generated stub for json_tok_is_num */
+bool json_tok_is_num(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED)
+{ fprintf(stderr, "json_tok_is_num called!\n"); abort(); }
+/* Generated stub for json_tok_strneq */
+bool json_tok_strneq(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
+ const char *str UNNEEDED, size_t len UNNEEDED)
+{ fprintf(stderr, "json_tok_strneq called!\n"); abort(); }
+/* Generated stub for lease_rates_fromhex */
+struct lease_rates *lease_rates_fromhex(const tal_t *ctx UNNEEDED,
+ const char *hexdata UNNEEDED, size_t len UNNEEDED)
+{ fprintf(stderr, "lease_rates_fromhex called!\n"); abort(); }
/* Generated stub for siphash_seed */
const struct siphash_seed *siphash_seed(void)
{ fprintf(stderr, "siphash_seed called!\n"); abort(); }
@@ -127,6 +259,27 @@ static void test_b11(const char *b11str,
assert(strlen(reproduce) == strlen(b11str));
}
+static const u8 **fallbacks(const tal_t *ctx,
+ const struct chainparams *chainparams,
+ const char *addr)
+{
+ jsmntok_t tok;
+ const u8 **addrs = tal_arr(ctx, const u8 *, 1);
+
+ tok.type = JSMN_STRING;
+ tok.start = 0;
+ tok.end = strlen(addr);
+ tok.size = tok.end;
+
+ /* Reuse what we use in json_invoice for fallbacks! */
+ assert(json_to_address_scriptpubkey(ctx,
+ chainparams,
+ addr, &tok,
+ &addrs[0])
+ == ADDRESS_PARSE_SUCCESS);
+ return addrs;
+}
+
int main(int argc, char *argv[])
{
struct bolt11 *b11;
@@ -287,6 +440,55 @@ int main(int argc, char *argv[])
test_b11("lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs9qrsgq7ea976txfraylvgzuxs8kgcw23ezlrszfnh8r6qtfpr6cxga50aj6txm9rxrydzd06dfeawfk6swupvz4erwnyutnjq7x39ymw6j38gp7ynn44", b11, "One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon");
dev_bolt11_omit_c_value = false;
+ /* BOLT #11:
+ * > ### On mainnet, with fallback (P2TR) address bc1pptdvg0d2nj99568qn6ssdy4cygnwuxgw2ukmnwgwz7jpqjz2kszse2s3lm
+ * >
+ * lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfp4pptdvg0d2nj99568qn6ssdy4cygnwuxgw2ukmnwgwz7jpqjz2kszs9qrsgqy606dznq28exnydt2r4c29y56xjtn3sk4mhgjtl4pg2y4ar3249rq4ajlmj9jy8zvlzw7cr8mggqzm842xfr0v72rswzq9xvr4hknfsqwmn6xd
+ *
+ * * `lnbc`: prefix, Lightning on Bitcoin mainnet
+ * * `20m`: amount (20 milli-bitcoin)
+ * * `1`: Bech32 separator
+ * * `pvjluez`: timestamp (1496314658)
+ * * `s`: payment secret...
+ * * `p`: payment hash...
+ * * `h`: tagged field: hash of description...
+ * * `f`: tagged field: fallback address
+ * * `p4`: `data_length` (`p` = 1, `4` = 21; 1 * 32 + 21 == 53)
+ * * `p`: 1, so witness version 1
+ * * `ptdvg0d2nj99568qn6ssdy4cygnwuxgw2ukmnwgwz7jpqjz2kszs`: 260 bits = P2TR.
+ * * `9`: features...
+ * * `y606dznq28exnydt2r4c29y56xjtn3sk4mhgjtl4pg2y4ar3249rq4ajlmj9jy8zvlzw7cr8mggqzm842xfr0v72rswzq9xvr4hknfsq`: signature
+ * * `wmn6xd`: Bech32 checksum
+ * * Signature breakdown:
+ * * `269fa68a6051f26991ab50eb851494d1a4b9c616aeee892ff50a144af471554a3057b2fee45910e267c4ef6067da10016cf5519237b3ca1c1c2014cc1d6f69a6` hex of signature data (32-byte r, 32-byte s)
+ * * `0` (int) recovery flag contained in `signature`
+ * * `6c6e626332306d0b25fe64500d04444444444444444444444444444444444444444444444444444444444444444021a000081018202830384048000810182028303840480008101820283038404808105c343925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c10486a10adac43daa9c8a5a68e09ea10692b82226ee190e572db9b90e17a410484ab4050280704000` hex of data for signing (prefix + data after separator up to the start of the signature)
+ * * `116fdb0f18352c886deb263f6466eb40e5e6518b80231a1f9df86088bfa48043` hex of SHA256 of the preimage
+ * */
+ msatoshi = AMOUNT_MSAT(20 * (1000ULL * 100000000) / 1000);
+ b11 = new_bolt11(tmpctx, &msatoshi);
+ b11->chain = chainparams_for_network("bitcoin");
+ b11->timestamp = 1496314658;
+ b11->payment_secret = tal(b11, struct secret);
+ memset(b11->payment_secret, 0x11, sizeof(*b11->payment_secret));
+ if (!hex_decode("0001020304050607080900010203040506070809000102030405060708090102",
+ strlen("0001020304050607080900010203040506070809000102030405060708090102"),
+ &b11->payment_hash, sizeof(b11->payment_hash)))
+ abort();
+ b11->receiver_id = node;
+ b11->description_hash = tal(b11, struct sha256);
+ if (!hex_decode("3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1",
+ strlen("3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1"),
+ b11->description_hash, sizeof(*b11->description_hash)))
+ abort();
+ set_feature_bit(&b11->features, 8);
+ set_feature_bit(&b11->features, 14);
+ b11->fallbacks = fallbacks(b11, b11->chain, "bc1pptdvg0d2nj99568qn6ssdy4cygnwuxgw2ukmnwgwz7jpqjz2kszse2s3lm");
+
+ dev_bolt11_omit_c_value = true;
+ test_b11("lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfp4pptdvg0d2nj99568qn6ssdy4cygnwuxgw2ukmnwgwz7jpqjz2kszs9qrsgqy606dznq28exnydt2r4c29y56xjtn3sk4mhgjtl4pg2y4ar3249rq4ajlmj9jy8zvlzw7cr8mggqzm842xfr0v72rswzq9xvr4hknfsqwmn6xd", b11, "One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon");
+ dev_bolt11_omit_c_value = false;
+
/* Malformed bolt11 strings (no '1'). */
badstr = "lnbc20mpvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqscc6gd6ql3jrc5yzme8v4ntcewwz5cnw92tz0pc8qcuufvq7khhr8wpald05e92xw006sq94mg8v2ndf4sefvf9sygkshp5zfem29trqq2yxxz7";
Why this scored 19/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.