fixup! Add API methods to spend funds sent to taproot channel addresses (#3220) (#3228)
What changed, and why it matters
This is a tiny one-line bugfix in Eclair's code for spending funds sent to Lightning channel addresses. The change corrects which Bitcoin script is checked to decide whether a channel uses the newer taproot format. Previously it checked the script of the destination address the user provided; now it checks the script of the actual output being spent from the funding transaction. Using the wrong script could cause the code to choose the wrong spending path or signature scheme, potentially making it impossible to recover funds sent to a taproot channel address or, in the worst case, constructing an invalid or insecure transaction. There is no claim in the commit that this is a security issue, and the fix is a follow-up to a recently added feature, so it likely fixes a functional bug rather than an active vulnerability.
Treat as a routine bugfix follow-up. Review the full PR #3220/#3228 for related correctness issues in taproot channel address spending, and add regression tests that verify taproot detection uses the actual UTXO script rather than the user-provided address. No emergency response is indicated absent further evidence.
Security signals we found
Incorrect input used for taproot script-type detection
MuSig2 nonce generation depends on the corrected taproot flag
Potential for invalid or unspendable taproot channel recovery transactions
No explicit security framing by the vendor
Evidence from the diff
In SpendFromChannelAddress.scala, the taproot detection was using Script.isPay2tr(Script.parse(pubKeyScript)), where pubKeyScript is derived from the user-supplied on-chain address. The fix changes the check to use the publicKeyScript of the actual transaction output being spent: inputTx.txOut(outPoint.index.toInt).publicKeyScript. This matters because the address-derived script and the UTXO’s script may differ (e.g., address parsing vs. actual output), and taproot-specific MuSig2 nonce generation and witness construction must match the real UTXO type. A mismatch could lead to incorrect witness data, failed spends, or possibly unsafe nonce handling. The commit is a fixup to PR #3220, part of PR #3228, with no security disclosure or CVE referenced.
Changed components
eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scalaTaproot channel address fund-recovery API methods introduced in PR #3220Inspect captured patch +1 / −1
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala b/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala
index 8d7cff2..5458d7e 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala
@@ -31,7 +31,7 @@ trait SpendFromChannelAddress {
Right(pubKeyScript) = addressToPublicKeyScript(appKit.nodeParams.chainHash, address).map(Script.write)
channelKeys = appKit.nodeParams.channelKeyManager.channelKeys(ChannelConfig.standard, fundingKeyPath)
localFundingPubkey = channelKeys.fundingKey(fundingTxIndex).publicKey
- isTaproot = Script.isPay2tr(Script.parse(pubKeyScript))
+ isTaproot = Script.isPay2tr(Script.parse(inputTx.txOut(outPoint.index.toInt).publicKeyScript))
(localNonce_opt, dummyWitness) = if (isTaproot) {
val serverNonce = Musig2.generateNonce(randomBytes32(), Right(localFundingPubkey), Seq(localFundingPubkey), None, None)
nonces.put(serverNonce.publicNonce, serverNonce)
Why this scored 41/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.