contractcourt: implement production taproot witness selection
What changed, and why it matters
This commit updates part of LND's on-chain dispute-resolution code so that a new 'final' taproot channel type uses its own dedicated witness types when spending commitment outputs. It is a feature-completion change that adds the correct script/witness selection for production taproot channels while keeping older channel types on their existing paths. There is no direct evidence in the commit that this fixes an active security vulnerability; it appears to be correctness and compatibility work for a new channel type.
Treat as a normal feature/correctness commit. Review the related HTLC resolver and input constructor changes mentioned in the commit message to confirm the full witness-selection matrix is consistent. Run or verify taproot channel sweep tests, especially for final vs. staging channel types, before deploying in production.
Security signals we found
Witness-type selection logic for commitment output sweeps
New channel feature flag (IsTaprootFinal) introduced in selection path
Maintains backward-compatible fall-through for staging taproot and legacy channels
Commit message frames change as production taproot correctness, not a vulnerability fix
Evidence from the diff
The change is confined to contractcourt/commit_sweep_resolver.go. It extends decideWitnessType() to branch on c.chanType.IsTaprootFinal() before the existing IsTaproot() check. For local commitment sweeps it now selects TaprootLocalCommitSpendFinal for final taproot channels and keeps TaprootLocalCommitSpend for staging taproot channels. For remote commitment sweeps it selects TaprootRemoteCommitSpendFinal for final taproot channels and keeps TaprootRemoteCommitSpend for staging. Legacy channels are unaffected. The commit message also mentions corresponding updates to HTLC timeout/success resolvers and a new production input constructor, but those changes are not present in the supplied diff.
Changed components
contractcourt/commit_sweep_resolver.godecideWitnessType() methodTaproot channel commitment sweep pathInspect captured patch +10 / −2
diff --git a/contractcourt/commit_sweep_resolver.go b/contractcourt/commit_sweep_resolver.go
index d8c8c39..dd02e84 100644
--- a/contractcourt/commit_sweep_resolver.go
+++ b/contractcourt/commit_sweep_resolver.go
@@ -470,11 +470,19 @@ func (c *commitSweepResolver) decideWitnessType() (input.WitnessType, error) {
// commitment tweak to discern which type of commitment this is.
var witnessType input.WitnessType
switch {
- // The local delayed output for a taproot channel.
+ // The local delayed output for a final taproot channel.
+ case isLocalCommitTx && c.chanType.IsTaprootFinal():
+ witnessType = input.TaprootLocalCommitSpendFinal
+
+ // The local delayed output for a staging taproot channel.
case isLocalCommitTx && c.chanType.IsTaproot():
witnessType = input.TaprootLocalCommitSpend
- // The CSV 1 delayed output for a taproot channel.
+ // The CSV 1 delayed output for a final taproot channel.
+ case !isLocalCommitTx && c.chanType.IsTaprootFinal():
+ witnessType = input.TaprootRemoteCommitSpendFinal
+
+ // The CSV 1 delayed output for a staging taproot channel.
case !isLocalCommitTx && c.chanType.IsTaproot():
witnessType = input.TaprootRemoteCommitSpend
Why this scored 32/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.