lightningd: use BIP86 derivation for P2TR in HTLC rebroadcast change outputs
What changed, and why it matters
This change fixes how Core Lightning derives the public key used for 'change' outputs when rebroadcasting on-chain HTLC (Hashed Time-Locked Contract) transactions that pay to a Taproot (P2TR) address. Previously, the code always used the older BIP32 derivation even when the wallet was configured for the newer BIP86 Taproot-specific derivation. Using the wrong derivation path could produce a change output that the wallet does not recognize or cannot spend later, potentially locking up funds. The patch checks whether BIP86 is available and uses it for P2TR change outputs, falling back to BIP32 otherwise.
Apply the patch. After applying, verify that wallets using BIP86/Taproot can correctly identify and spend change outputs from HTLC rebroadcast transactions. Consider adding regression tests that exercise both BIP32-only and BIP86-enabled wallets for P2TR change outputs in the HTLC rebroadcast path.
Security signals we found
Key derivation mismatch between BIP32 and BIP86 for Taproot outputs
Potential fund lockup or unspendable change outputs
HTLC rebroadcast change output handling
Taproot (P2TR) output construction
Evidence from the diff
In lightningd/onchain_control.c, the consider_onchain_htlc_tx_rebroadcast() function appends a P2TR change output when an HTLC rebroadcast has leftover change. Before the patch, it unconditionally called bip32_pubkey() with channel->final_key_idx to derive the public key used in scriptpubkey_p2tr(). The patch introduces a conditional: if ld->bip86_base is set, it calls bip86_pubkey() instead; otherwise it falls back to bip32_pubkey(). This aligns the key derivation scheme with the output type (Taproot) and the wallet’s configured derivation base. The diff is small (+6/-1) and localized, but it addresses a real key-derivation mismatch that could affect fund recoverability.
Changed components
lightningd/onchain_control.cHTLC rebroadcast logicP2TR change output derivationInspect captured patch +6 / −1
diff --git a/lightningd/onchain_control.c b/lightningd/onchain_control.c
index 1e04ceb..8d7f99f 100644
--- a/lightningd/onchain_control.c
+++ b/lightningd/onchain_control.c
@@ -1154,7 +1154,12 @@ static bool consider_onchain_htlc_tx_rebroadcast(struct channel *channel,
if (!amount_sat_eq(change, AMOUNT_SAT(0))) {
/* Append change output. */
struct pubkey final_key;
- bip32_pubkey(ld, &final_key, channel->final_key_idx);
+ /* Use BIP86 derivation for P2TR if available, otherwise BIP32 */
+ if (ld->bip86_base) {
+ bip86_pubkey(ld, &final_key, channel->final_key_idx);
+ } else {
+ bip32_pubkey(ld, &final_key, channel->final_key_idx);
+ }
psbt_append_output(psbt,
scriptpubkey_p2tr(tmpctx, &final_key),
change);
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.