lightningd: scan outputs for BIP86 addresses
What changed, and why it matters
This commit fixes a wallet-recovery bug in Core Lightning. When a user restored a wallet that used BIP86 (a modern Bitcoin address type), the node failed to scan the blockchain for those addresses, so previously received funds would appear missing until the user manually generated an address. The patch makes the rescan process look for both BIP32 and BIP86 addresses.
Treat as a functional bug fix rather than an exploitable vulnerability. Users relying on BIP86 wallets should upgrade to ensure restored wallets discover all historical funds during rescan. No immediate incident-response action is indicated.
Security signals we found
Funds-availability/recovery bug
Missing address type in blockchain scan filter
BIP86 Taproot output discovery gap
Evidence from the diff
init_txfilter() previously only added BIP32-derived keys to the transaction filter. The patch adds BIP86-derived P2TR scriptPubKeys to the filter when w->ld->bip86_base is set, using bip86_pubkey() and scriptpubkey_p2tr(). It also pre-fills the wallet’s internal address table up to keyscan_gap for BIP86 so rescans can match outputs without prior address allocation. A comment about libbacktrace is relocated and test stubs are added.
Changed components
lightningd/lightningd.cwallet/wallet.clightningd/test/run-find_my_abspath.ccommon/daemon.cInspect captured patch +41 / −6
diff --git a/common/daemon.c b/common/daemon.c
index c3adac2..6515828 100644
--- a/common/daemon.c
+++ b/common/daemon.c
@@ -1,5 +1,8 @@
#include "config.h"
#include <assert.h>
+/*~ This is Ian Lance Taylor's libbacktrace. It turns out that it's
+ * horrifically difficult to obtain a decent backtrace in C; the standard
+ * backtrace function is useless in most programs. */
#include <backtrace-supported.h>
#include <backtrace.h>
#include <ccan/err/err.h>
diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c
index f512822..fda955d 100644
--- a/lightningd/lightningd.c
+++ b/lightningd/lightningd.c
@@ -21,9 +21,8 @@
* before anything else. */
#include "config.h"
-/*~ This is Ian Lance Taylor's libbacktrace. It turns out that it's
- * horrifically difficult to obtain a decent backtrace in C; the standard
- * backtrace function is useless in most programs. */
+/*~ Various bitcoin-related helpers live in the bitcoin/ directory */
+#include <bitcoin/script.h>
/*~ These headers are from CCAN: http://ccodearchive.net.
*
@@ -56,8 +55,8 @@
#include <common/timeout.h>
#include <common/trace.h>
#include <common/version.h>
-#include <db/exec.h>
+#include <db/exec.h>
#include <errno.h>
#include <fcntl.h>
#include <header_versions_gen.h>
@@ -676,7 +675,7 @@ static void init_txfilter(struct wallet *w,
struct ext_key ext;
/*~ Note the use of ccan/short_types u64 rather than uint64_t.
* Thank me later. */
- u64 bip32_max_index;
+ u64 bip32_max_index, bip86_max_index;
bip32_max_index = db_get_intvar(w->db, "bip32_max_index", 0);
/*~ One of the C99 things I unequivocally approve: for-loop scope. */
@@ -686,6 +685,17 @@ static void init_txfilter(struct wallet *w,
}
txfilter_add_derkey(filter, ext.pub_key);
}
+
+ /* If BIP86 is enabled, also add BIP86-derived keys to the filter */
+ if (w->ld->bip86_base) {
+ bip86_max_index = db_get_intvar(w->db, "bip86_max_index", 0);
+ for (u64 i = 0; i <= bip86_max_index + w->keyscan_gap; i++) {
+ struct pubkey pubkey;
+ bip86_pubkey(w->ld, &pubkey, i);
+ u8 *script = scriptpubkey_p2tr(tmpctx, &pubkey);
+ txfilter_add_scriptpubkey(filter, take(script));
+ }
+ }
}
/*~ The normal advice for daemons is to move into the root directory, so you
diff --git a/lightningd/test/run-find_my_abspath.c b/lightningd/test/run-find_my_abspath.c
index 7f15dc1..9953225 100644
--- a/lightningd/test/run-find_my_abspath.c
+++ b/lightningd/test/run-find_my_abspath.c
@@ -227,6 +227,12 @@ struct wallet *wallet_new(struct lightningd *ld UNNEEDED, struct timers *timers
/* Generated stub for wallet_sanity_check */
bool wallet_sanity_check(struct wallet *w UNNEEDED)
{ fprintf(stderr, "wallet_sanity_check called!\n"); abort(); }
+/* Generated stub for bip86_pubkey */
+void bip86_pubkey(struct lightningd *ld UNNEEDED, struct pubkey *pubkey UNNEEDED, u32 index UNNEEDED)
+{ fprintf(stderr, "bip86_pubkey called!\n"); abort(); }
+/* Generated stub for txfilter_add_scriptpubkey */
+void txfilter_add_scriptpubkey(struct txfilter *filter UNNEEDED, const u8 *script TAKES UNNEEDED)
+{ fprintf(stderr, "txfilter_add_scriptpubkey called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */
struct logger *crashlog;
diff --git a/wallet/wallet.c b/wallet/wallet.c
index e761929..0e0d5a0 100644
--- a/wallet/wallet.c
+++ b/wallet/wallet.c
@@ -148,6 +148,12 @@ static void our_addresses_add_for_index(struct wallet *w, u32 i)
/* FIXME: We could deprecate P2SH once we don't see
* any, since we stopped publishing them in 24.02 */
if (!wallet_get_addrtype(w, i, &addrtype)) {
+ if (w->ld->bip86_base) {
+ /* Derive and add BIP86 script for this index */
+ our_addresses_add_bip86_for_index(w, i);
+ return;
+ }
+
const u8 *addr;
scriptpubkey = scriptpubkey_p2wpkh_derkey(NULL, ext.pub_key);
addr = scriptpubkey_p2sh(NULL, scriptpubkey);
@@ -205,7 +211,17 @@ static void our_addresses_init(struct wallet *w)
w->our_addresses_maxindex = 0;
w->our_addresses = new_htable(w, wallet_address_htable);
- our_addresses_add_for_index(w, w->our_addresses_maxindex);
+ /* If BIP86 is enabled, prefill the address table up to keyscan_gap so
+ * rescans immediately include BIP86 scripts without needing prior
+ * address allocations. */
+ if (w->ld->bip86_base) {
+ for (u32 i = 0; i <= w->keyscan_gap; i++) {
+ our_addresses_add_for_index(w, i);
+ }
+ w->our_addresses_maxindex = w->keyscan_gap;
+ } else {
+ our_addresses_add_for_index(w, w->our_addresses_maxindex);
+ }
}
static void outpointfilters_init(struct wallet *w)
Why this scored 42/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.