Merge pull request #11212 from ziggie1984/disable-legacy-channels
What changed, and why it matters
This change stops LND from opening new Lightning channels using the old 'legacy' commitment format. The legacy format makes it harder to recover funds if something goes wrong, because the money owed to you is tied to a secret key that changes with each channel state. From now on, LND will either use a safer default or refuse the channel open. Existing legacy channels keep working normally; only brand-new legacy channels are blocked.
Deploy this patch to prevent new legacy channels from being opened. No urgent incident response is required because existing legacy channels continue to operate, but operators should consider migrating liquidity to modern commitment types over time. Review any custom tooling that explicitly requests CommitmentType_LEGACY, as it will now fail.
Security signals we found
Prevents opening new channels with the legacy commitment type, whose tweaked to_remote output complicates data-loss recovery
Closes a negotiation path where an empty channel_type TLV bypassed feature checks and forced a legacy channel
Adds explicit RPC and wire rejection with a dedicated error code
Deprecates the dev-only protocol.legacy.committweak option that disabled static remote key signalling
Evidence from the diff
The commit removes support for negotiating lnwallet.CommitmentTypeLegacy for new channels. In funding/commitment_type_negotiation.go, explicitNegotiateCommitmentType now returns lnwire.ErrChanTypeDeprecated when the channel type is empty (legacy), and selectDefaultChannelType returns ErrDeprecatedChanType instead of falling back to legacy. rpcserver.go rejects CommitmentType_LEGACY at the RPC boundary. A new wire error code ErrChanTypeDeprecated (4) is added. The dev option protocol.legacy.committweak is deprecated and made a no-op. Tests and integration tests are updated to expect rejection or to use static remote key instead.
Changed components
funding/commitment_type_negotiation.gorpcserver.golnwire/error.golncfg/protocol_legacy_on.golnrpc/lightning.proto / .pb.go / .swagger.jsonfunding manager tests and integration testsInspect captured patch +265 / −47
### docs/release-notes/release-notes-0.20.5.md
@@ -80,10 +80,37 @@
## Breaking Changes
+* lnd [no longer opens or
+ accepts](https://github.com/lightningnetwork/lnd/pull/11212) new channels
+ using the legacy commitment type, which was
+ [removed](https://github.com/lightning/bolts/commit/91f4bd2383cc2fc7a0a43b697e209f9eb9f5183c)
+ from the spec in 2024. Its tweaked `to_remote` output is why funds in such a
+ channel cannot be recovered unilaterally after data loss: recovery needs the
+ peer to supply the relevant commitment point.
+
+ Note that an empty `channel_type` in `open_channel` asks for exactly this
+ type, and used to be accepted without any feature check at all, so a peer
+ could obtain a legacy channel from us no matter what either side signalled.
+ New channels now fall back to the static remote key commitment type instead.
+
+ Channels that already use the legacy type are **not** affected. They keep
+ working and can be operated, force closed and cooperatively closed as before.
+ Only opening new ones is refused.
+
+ `OpenChannel` now rejects `commitment_type` `LEGACY`. The enum value itself
+ remains, since it is also how existing channels are reported by
+ `ListChannels`, `ClosedChannels`, `PendingChannels` and the channel acceptor.
+
## Performance Improvements
## Deprecations
+* The dev build only `protocol.legacy.committweak` option is
+ [deprecated](https://github.com/lightningnetwork/lnd/pull/11212) and no longer
+ has any effect. It stopped the node from signalling
+ `option_static_remotekey`, which now leaves no commitment type left to
+ negotiate at all.
+
# Technical and Architectural Updates
## BOLT Spec Updates
### docs/release-notes/release-notes-0.21.4.md
@@ -88,10 +88,37 @@
## Breaking Changes
+* lnd [no longer opens or
+ accepts](https://github.com/lightningnetwork/lnd/pull/11212) new channels
+ using the legacy commitment type, which was
+ [removed](https://github.com/lightning/bolts/commit/91f4bd2383cc2fc7a0a43b697e209f9eb9f5183c)
+ from the spec in 2024. Its tweaked `to_remote` output is why funds in such a
+ channel cannot be recovered unilaterally after data loss: recovery needs the
+ peer to supply the relevant commitment point.
+
+ Note that an empty `channel_type` in `open_channel` asks for exactly this
+ type, and used to be accepted without any feature check at all, so a peer
+ could obtain a legacy channel from us no matter what either side signalled.
+ New channels now fall back to the static remote key commitment type instead.
+
+ Channels that already use the legacy type are **not** affected. They keep
+ working and can be operated, force closed and cooperatively closed as before.
+ Only opening new ones is refused.
+
+ `OpenChannel` now rejects `commitment_type` `LEGACY`. The enum value itself
+ remains, since it is also how existing channels are reported by
+ `ListChannels`, `ClosedChannels`, `PendingChannels` and the channel acceptor.
+
## Performance Improvements
## Deprecations
+* The dev build only `protocol.legacy.committweak` option is
+ [deprecated](https://github.com/lightningnetwork/lnd/pull/11212) and no longer
+ has any effect. It stopped the node from signalling
+ `option_static_remotekey`, which now leaves no commitment type left to
+ negotiate at all.
+
### ⚠️ **Warning:** Deprecated fields in `lnrpc.Hop` will be removed in release version **0.22**
### ⚠️ **Warning:** The deprecated fee rate option `--sat_per_byte` will be removed in release version **0.22**
### funding/commitment_type_negotiation.go
@@ -13,15 +13,33 @@ var (
// peer of the channel does not support it.
errUnsupportedChannelType = errors.New("requested channel type " +
"not supported")
+
+ // ErrDeprecatedChanType is returned when settling on the legacy
+ // commitment type is the only option left, either because the caller of
+ // our own RPC asked for it or because automatic selection would have
+ // fallen back to it. We keep operating the legacy channels we already
+ // have, but no longer open new ones.
+ //
+ // Unlike lnwire.ErrChanTypeDeprecated, which we send to a peer whose
+ // proposal we reject, this never goes on the wire. The audience is our
+ // own operator, who can act on the answer, so it spells out what to use
+ // instead.
+ ErrDeprecatedChanType = errors.New("the legacy commitment type is " +
+ "deprecated, new channels must use the static remote key " +
+ "commitment type or later")
)
// negotiateCommitmentType determines the commitment type of a newly opened
// channel. If desiredChanType is provided, it is validated against the
// commitment features supported by both peers. Otherwise, a default type is
// selected from those features.
//
-// The returned ChannelType is always non-nil and is always signaled on the
-// wire. An error is only returned if desiredChanType is not supported.
+// The legacy commitment type is never selected, whether it was requested
+// explicitly or would only have been reached by falling back.
+//
+// On success, the returned ChannelType is non-nil and is signaled on the wire.
+// An error is returned if the requested type is unsupported or deprecated, or
+// if no supported default type can be selected.
func negotiateCommitmentType(desiredChanType *lnwire.ChannelType, local,
remote *lnwire.FeatureVector) (*lnwire.ChannelType,
lnwallet.CommitmentType, error) {
@@ -38,7 +56,12 @@ func negotiateCommitmentType(desiredChanType *lnwire.ChannelType, local,
// No specific channel type was requested. Select a default type based
// on locally-known feature compatibility. This default is then sent
// explicitly over the wire.
- defaultChanType, commitType := selectDefaultChannelType(local, remote)
+ defaultChanType, commitType, err := selectDefaultChannelType(
+ local, remote,
+ )
+ if err != nil {
+ return nil, 0, err
+ }
return defaultChanType, commitType, nil
}
@@ -413,9 +436,14 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local,
return lnwallet.CommitmentTypeSimpleTaprootOverlay, nil
- // No features, use legacy commitment type.
+ // An empty channel type asks for the legacy commitment type, which was
+ // removed from the spec in 2024 and which we refuse outright, not by
+ // configuration. Note that this branch performs no feature check of its
+ // own, since the legacy type predates feature bits entirely: any peer
+ // sending an empty channel_type TLV used to get a legacy channel out of
+ // us no matter what either side signalled.
case channelFeatures.IsEmpty():
- return lnwallet.CommitmentTypeLegacy, nil
+ return 0, lnwire.ErrChanTypeDeprecated
default:
return 0, errUnsupportedChannelType
@@ -427,11 +455,14 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local,
// Taproot channels must be requested explicitly, so that defaults stay on
// channel types usable for both public and private channels.
//
+// An error is returned if there is no mutually supported type above the legacy
+// one, which we no longer open.
+//
// TODO(yy): Revisit taproot channel selection once public taproot channel
// announcements are supported.
func selectDefaultChannelType(local,
remote *lnwire.FeatureVector) (*lnwire.ChannelType,
- lnwallet.CommitmentType) {
+ lnwallet.CommitmentType, error) {
// If both peers are signalling support for anchor commitments with
// zero-fee HTLC transactions, we'll use this type.
@@ -441,7 +472,8 @@ func selectDefaultChannelType(local,
lnwire.StaticRemoteKeyRequired,
))
- return &chanType, lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx
+ return &chanType, lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx,
+ nil
}
// Since we don't want to support the "legacy" anchor type, we will fall
@@ -455,12 +487,13 @@ func selectDefaultChannelType(local,
lnwire.StaticRemoteKeyRequired,
))
- return &chanType, lnwallet.CommitmentTypeTweakless
+ return &chanType, lnwallet.CommitmentTypeTweakless, nil
}
- // Otherwise we'll fall back to the legacy type.
- chanType := lnwire.ChannelType(*lnwire.NewRawFeatureVector())
- return &chanType, lnwallet.CommitmentTypeLegacy
+ // Without a mutually supported type above it, the only one left to fall
+ // back on is the legacy type, which we never open. Either side failing
+ // to signal static remote key is enough to end up here.
+ return nil, 0, ErrDeprecatedChanType
}
// hasFeatures determines whether a set of features is supported by both the set
### funding/commitment_type_negotiation_test.go
@@ -227,7 +227,10 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
expectsErr: nil,
},
{
- name: "explicit legacy",
+ // An empty channel type asks for the legacy commitment
+ // type, which we no longer open. Note that this used to
+ // be accepted with no feature check at all.
+ name: "explicit legacy rejected",
channelFeatures: lnwire.NewRawFeatureVector(),
localFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyRequired,
@@ -237,11 +240,7 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
),
- expectsCommitType: lnwallet.CommitmentTypeLegacy,
- expectsChanType: (*lnwire.ChannelType)(
- lnwire.NewRawFeatureVector(),
- ),
- expectsErr: nil,
+ expectsErr: lnwire.ErrChanTypeDeprecated,
},
// No desired channel type is set, so we expect the default
// selection to return the corresponding chan type feature bits,
@@ -285,18 +284,18 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
expectsErr: nil,
},
{
- name: "default legacy",
+ // Without mutual support for static remote key there is
+ // nothing left to fall back on but the legacy type.
+ // Note that the test runs both peer orderings, so it
+ // covers either side being the one that lacks it.
+ name: "default legacy rejected",
channelFeatures: nil,
localFeatures: lnwire.NewRawFeatureVector(),
remoteFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
),
- expectsCommitType: lnwallet.CommitmentTypeLegacy,
- expectsChanType: (*lnwire.ChannelType)(
- lnwire.NewRawFeatureVector(),
- ),
- expectsErr: nil,
+ expectsErr: ErrDeprecatedChanType,
},
// Test cases for final taproot channels with explicit
@@ -443,15 +442,19 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
name: "default ignores staging taproot without anchors",
channelFeatures: nil,
localFeatures: lnwire.NewRawFeatureVector(
+ lnwire.StaticRemoteKeyOptional,
lnwire.SimpleTaprootChannelsOptionalFinal,
lnwire.SimpleTaprootChannelsOptionalStaging,
),
remoteFeatures: lnwire.NewRawFeatureVector(
+ lnwire.StaticRemoteKeyOptional,
lnwire.SimpleTaprootChannelsOptionalStaging,
),
- expectsCommitType: lnwallet.CommitmentTypeLegacy,
+ expectsCommitType: lnwallet.CommitmentTypeTweakless,
expectsChanType: (*lnwire.ChannelType)(
- lnwire.NewRawFeatureVector(),
+ lnwire.NewRawFeatureVector(
+ lnwire.StaticRemoteKeyRequired,
+ ),
),
expectsErr: nil,
},
@@ -460,14 +463,18 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
name: "default ignores final taproot without anchors",
channelFeatures: nil,
localFeatures: lnwire.NewRawFeatureVector(
+ lnwire.StaticRemoteKeyOptional,
lnwire.SimpleTaprootChannelsOptionalFinal,
),
remoteFeatures: lnwire.NewRawFeatureVector(
+ lnwire.StaticRemoteKeyOptional,
lnwire.SimpleTaprootChannelsOptionalFinal,
),
- expectsCommitType: lnwallet.CommitmentTypeLegacy,
+ expectsCommitType: lnwallet.CommitmentTypeTweakless,
expectsChanType: (*lnwire.ChannelType)(
- lnwire.NewRawFeatureVector(),
+ lnwire.NewRawFeatureVector(
+ lnwire.StaticRemoteKeyRequired,
+ ),
),
expectsErr: nil,
},
### funding/manager_test.go
@@ -674,6 +674,17 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
shutdownChannel: shutdownChan,
reportScidChan: reportScidChan,
addr: addr,
+
+ // Default both sides to static remote key. Without any
+ // features at all these nodes used to negotiate the legacy
+ // commitment type, which we no longer open. Tests that care
+ // about a specific type overwrite these.
+ localFeatures: []lnwire.FeatureBit{
+ lnwire.StaticRemoteKeyOptional,
+ },
+ remoteFeatures: []lnwire.FeatureBit{
+ lnwire.StaticRemoteKeyOptional,
+ },
}
f.cfg.NotifyWhenOnline = func(peer [33]byte,
@@ -5324,6 +5335,65 @@ func TestFundingManagerRejectMissingChanType(t *testing.T) {
assertNumPendingReservations(t, bob, alicePubKey, 0)
}
+// TestFundingManagerRejectLegacyChanType verifies that the fundee rejects an
+// OpenChannel message carrying an empty ChannelType, which asks for the legacy
+// commitment type.
+//
+// This branch used to perform no feature check whatsoever, since the legacy
+// type predates feature bits entirely, so any peer could obtain a legacy
+// channel from us no matter what either side signalled.
+func TestFundingManagerRejectLegacyChanType(t *testing.T) {
+ t.Parallel()
+
+ alice, bob := setupFundingManagers(t)
+ t.Cleanup(func() {
+ tearDownFundingManagers(t, alice, bob)
+ })
+
+ // Both peers support better than the legacy type, which is exactly the
+ // case that used to slip through.
+ featureBits := []lnwire.FeatureBit{
+ lnwire.StaticRemoteKeyOptional,
+ lnwire.AnchorsZeroFeeHtlcTxOptional,
+ }
+ alice.localFeatures = featureBits
+ alice.remoteFeatures = featureBits
+ bob.localFeatures = featureBits
+ bob.remoteFeatures = featureBits
+
+ emptyChanType := (*lnwire.ChannelType)(lnwire.NewRawFeatureVector())
+
+ openChannelReq := &lnwire.OpenChannel{
+ ChainHash: *fundingNetParams.GenesisHash,
+ PendingChannelID: [32]byte{0x01},
+ FundingAmount: btcutil.Amount(10000000),
+ PushAmount: 0,
+ DustLimit: btcutil.Amount(546),
+ MaxValueInFlight: lnwire.MilliSatoshi(100000000),
+ ChannelReserve: btcutil.Amount(10000),
+ HtlcMinimum: lnwire.MilliSatoshi(1000),
+ FeePerKiloWeight: 15000,
+ CsvDelay: 144,
+ MaxAcceptedHTLCs: 483,
+ FundingKey: alice.privKey.PubKey(),
+ RevocationPoint: alice.privKey.PubKey(),
+ PaymentPoint: alice.privKey.PubKey(),
+ DelayedPaymentPoint: alice.privKey.PubKey(),
+ HtlcPoint: alice.privKey.PubKey(),
+ FirstCommitmentPoint: alice.privKey.PubKey(),
+ ChannelType: emptyChanType,
+ }
+ bob.fundingMgr.ProcessFundingMsg(openChannelReq, alice)
+
+ // Bob should reject the OpenChannel message instead of echoing the
+ // empty channel type back in an AcceptChannel.
+ errMsg := assertFundingMsgSent(t, bob.msgChan, "Error")
+ err, ok := errMsg.(*lnwire.Error)
+ require.True(t, ok)
+ require.Equal(t, lnwire.ErrChanTypeDeprecated.Error(), string(err.Data))
+ assertNumPendingReservations(t, bob, alicePubKey, 0)
+}
+
// TestFundingManagerAcceptChanType verifies that the fundee accepts an
// OpenChannel message that includes the ChannelType field and echoes it back
// in AcceptChannel, even when neither peer advertises the explicit channel
### itest/lnd_funding_test.go
@@ -37,6 +37,10 @@ var basicFundingTestCases = []*lntest.TestCase{
Name: "basic flow simple taproot final",
TestFunc: testBasicChannelFundingSimpleTaprootFinal,
},
+ {
+ Name: "legacy chan type rejected",
+ TestFunc: testLegacyChanTypeRejected,
+ },
}
// allFundingTypes defines the channel types to test for the basic funding
@@ -48,6 +52,24 @@ var allFundingTypes = []lnrpc.CommitmentType{
lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL,
}
+// testLegacyChanTypeRejected asserts that lnd refuses to open a channel using
+// the legacy commitment type, which was removed from the spec in 2024.
+func testLegacyChanTypeRejected(ht *lntest.HarnessTest) {
+ carol := ht.NewNodeWithCoins("Carol", nil)
+ dave := ht.NewNodeWithCoins("Dave", nil)
+ ht.EnsureConnected(carol, dave)
+
+ // The RPC server turns the request down before the funding flow even
+ // starts, so no channel type ever reaches Dave.
+ ht.OpenChannelAssertErr(
+ carol, dave, lntest.OpenChannelParams{
+ Amt: funding.MaxBtcFundingAmount,
+ CommitmentType: lnrpc.CommitmentType_LEGACY,
+ },
+ funding.ErrDeprecatedChanType,
+ )
+}
+
// testBasicChannelFundingStaticRemote performs a test exercising expected
// behavior from a basic funding workflow. The test creates a new channel
// between Carol and Dave, with Carol using the static remote key commitment
### itest/lnd_remote_signer_test.go
@@ -614,7 +614,7 @@ func psbtTestCase(ht *lntest.HarnessTest,
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
runPsbtChanFundingWithNodes(
tt, carol, wo, false,
- lnrpc.CommitmentType_LEGACY,
+ lnrpc.CommitmentType_STATIC_REMOTE_KEY,
)
runSignPsbtSegWitV0P2WKH(tt, wo)
runSignPsbtSegWitV1KeySpendBip86(tt, wo)
### itest/lnd_watchtower_test.go
@@ -336,7 +336,7 @@ func testTowerClientSessionDeletion(ht *lntest.HarnessTest) {
// Carol's behalf sweeping her funds without a reward.
func testRevokedCloseRetributionAltruistWatchtower(ht *lntest.HarnessTest) {
for _, commitType := range []lnrpc.CommitmentType{
- lnrpc.CommitmentType_LEGACY,
+ lnrpc.CommitmentType_STATIC_REMOTE_KEY,
lnrpc.CommitmentType_ANCHORS,
lnrpc.CommitmentType_SIMPLE_TAPROOT,
lnrpc.CommitmentType_SIMPLE_TAPROOT_FINAL,
### lncfg/protocol_legacy_on.go
@@ -14,11 +14,11 @@ type LegacyProtocol struct {
// route won't use the new modern onion framing.
LegacyOnionFormat bool `long:"onion" description:"force node to not advertise the new modern TLV onion format"`
- // CommitmentTweak guards if we should use the old legacy commitment
- // protocol, or the newer variant that doesn't have a tweak for the
- // remote party's output in the commitment. If set to true, then we
- // won't signal StaticRemoteKeyOptional.
- CommitmentTweak bool `long:"committweak" description:"force node to not advertise the new commitment format"`
+ // CommitmentTweak is deprecated and no longer has any effect. The
+ // legacy commitment type is refused for new channels, so a node that
+ // stopped signalling StaticRemoteKeyOptional could no longer negotiate
+ // any commitment type at all.
+ CommitmentTweak bool `long:"committweak" hidden:"true" description:"deprecated: the legacy commitment format can no longer be used for new channels"`
}
// LegacyOnion returns true if the old legacy onion format should be used when
@@ -29,7 +29,10 @@ func (l *LegacyProtocol) LegacyOnion() bool {
}
// NoStaticRemoteKey returns true if the old commitment format with a tweaked
-// remote key should be used for new funded channels.
+// remote key should be used for new funded channels. The legacy commitment
+// type can no longer be used for new channels, so this always returns false.
+// The CommitmentTweak field is only kept around so that configs which still
+// set it continue to parse.
func (l *LegacyProtocol) NoStaticRemoteKey() bool {
- return l.CommitmentTweak
+ return false
}
### lnrpc/lightning.pb.go
@@ -216,6 +216,10 @@ const (
//
//A channel using the legacy commitment format having tweaked to_remote
//keys.
+ //
+ //This value is only reported for channels that already exist. It is
+ //rejected as an input when opening a channel, since lnd no longer opens
+ //or accepts channels of this type.
CommitmentType_LEGACY CommitmentType = 1
//
//A channel that uses the modern commitment format where the key in the
@@ -7433,6 +7437,10 @@ type BatchOpenChannel struct {
//The commitment type to request. If UNKNOWN, lnd selects a default from
//both peers' supported features; the selected type is always sent
//explicitly.
+ //
+ //LEGACY is rejected: lnd no longer opens or accepts channels of that
+ //type. Note that an empty channel type on the wire requests it too, so
+ //there is no way to ask for it at all.
CommitmentType CommitmentType `protobuf:"varint,9,opt,name=commitment_type,json=commitmentType,proto3,enum=lnrpc.CommitmentType" json:"commitment_type,omitempty"`
//
//The maximum amount of coins in millisatoshi that can be pending within
@@ -7777,6 +7785,10 @@ type OpenChannelRequest struct {
//The commitment type to request. If UNKNOWN, lnd selects a default from
//both peers' supported features; the selected type is always sent
//explicitly.
+ //
+ //LEGACY is rejected: lnd no longer opens or accepts channels of that
+ //type. Note that an empty channel type on the wire requests it too, so
+ //there is no way to ask for it at all.
CommitmentType CommitmentType `protobuf:"varint,18,opt,name=commitment_type,json=commitmentType,proto3,enum=lnrpc.CommitmentType" json:"commitment_type,omitempty"`
//
//If this is true, then a zero-conf channel open will be attempted.
### lnrpc/lightning.proto
@@ -1325,6 +1325,10 @@ enum CommitmentType {
/*
A channel using the legacy commitment format having tweaked to_remote
keys.
+
+ This value is only reported for channels that already exist. It is
+ rejected as an input when opening a channel, since lnd no longer opens
+ or accepts channels of this type.
*/
LEGACY = 1;
@@ -2240,6 +2244,10 @@ message BatchOpenChannel {
The commitment type to request. If UNKNOWN, lnd selects a default from
both peers' supported features; the selected type is always sent
explicitly.
+
+ LEGACY is rejected: lnd no longer opens or accepts channels of that
+ type. Note that an empty channel type on the wire requests it too, so
+ there is no way to ask for it at all.
*/
CommitmentType commitment_type = 9;
@@ -2414,6 +2422,10 @@ message OpenChannelRequest {
The commitment type to request. If UNKNOWN, lnd selects a default from
both peers' supported features; the selected type is always sent
explicitly.
+
+ LEGACY is rejected: lnd no longer opens or accepts channels of that
+ type. Note that an empty channel type on the wire requests it too, so
+ there is no way to ask for it at all.
*/
CommitmentType commitment_type = 18;
### lnrpc/lightning.swagger.json
@@ -3571,7 +3571,7 @@
},
"commitment_type": {
"$ref": "#/definitions/lnrpcCommitmentType",
- "description": "The commitment type to request. If UNKNOWN, lnd selects a default from\nboth peers' supported features; the selected type is always sent\nexplicitly."
+ "description": "The commitment type to request. If UNKNOWN, lnd selects a default from\nboth peers' supported features; the selected type is always sent\nexplicitly.\n\nLEGACY is rejected: lnd no longer opens or accepts channels of that\ntype. Note that an empty channel type on the wire requests it too, so\nthere is no way to ask for it at all."
},
"remote_max_value_in_flight_msat": {
"type": "string",
@@ -4812,7 +4812,7 @@
"SIMPLE_TAPROOT_OVERLAY"
],
"default": "UNKNOWN_COMMITMENT_TYPE",
- "description": " - UNKNOWN_COMMITMENT_TYPE: Returned when the commitment type isn't known or unavailable.\n - LEGACY: A channel using the legacy commitment format having tweaked to_remote\nkeys.\n - STATIC_REMOTE_KEY: A channel that uses the modern commitment format where the key in the\noutput of the remote party does not change each state. This makes back\nup and recovery easier as when the channel is closed, the funds go\ndirectly to that key.\n - ANCHORS: A channel that uses a commitment format that has anchor outputs on the\ncommitments, allowing fee bumping after a force close transaction has\nbeen broadcast.\n - SCRIPT_ENFORCED_LEASE: A channel that uses a commitment type that builds upon the anchors\ncommitment format, but in addition requires a CLTV clause to spend outputs\npaying to the channel initiator. This is intended for use on leased channels\nto guarantee that the channel initiator has no incentives to close a leased\nchannel before its maturity date.\n - TAPROOT: The production taproot channel type that uses musig2 for the funding\noutput and the new tapscript features, with final scripts and feature\nbits 80/81. This is the recommended taproot variant; new integrations\nshould select this enum value.\n - SIMPLE_TAPROOT_FINAL: Deprecated alias for TAPROOT, preserved so existing clients that select\nthe production taproot channel type by its historic name continue to\ncompile and serialize against the same wire value.\n - SIMPLE_TAPROOT: A legacy taproot channel type that uses musig2 for the funding output and\nthe new tapscript features, but with development scripts and the staging\nfeature bits. Retained for compatibility with peers that have not upgraded\nto TAPROOT; new integrations should prefer TAPROOT.\n - SIMPLE_TAPROOT_OVERLAY: Identical to the SIMPLE_TAPROOT channel type, but with extra functionality.\nThis channel type also commits to additional meta data in the tapscript\nleaves for the scripts in a channel."
+ "description": " - UNKNOWN_COMMITMENT_TYPE: Returned when the commitment type isn't known or unavailable.\n - LEGACY: A channel using the legacy commitment format having tweaked to_remote\nkeys.\n\nThis value is only reported for channels that already exist. It is\nrejected as an input when opening a channel, since lnd no longer opens\nor accepts channels of this type.\n - STATIC_REMOTE_KEY: A channel that uses the modern commitment format where the key in the\noutput of the remote party does not change each state. This makes back\nup and recovery easier as when the channel is closed, the funds go\ndirectly to that key.\n - ANCHORS: A channel that uses a commitment format that has anchor outputs on the\ncommitments, allowing fee bumping after a force close transaction has\nbeen broadcast.\n - SCRIPT_ENFORCED_LEASE: A channel that uses a commitment type that builds upon the anchors\ncommitment format, but in addition requires a CLTV clause to spend outputs\npaying to the channel initiator. This is intended for use on leased channels\nto guarantee that the channel initiator has no incentives to close a leased\nchannel before its maturity date.\n - TAPROOT: The production taproot channel type that uses musig2 for the funding\noutput and the new tapscript features, with final scripts and feature\nbits 80/81. This is the recommended taproot variant; new integrations\nshould select this enum value.\n - SIMPLE_TAPROOT_FINAL: Deprecated alias for TAPROOT, preserved so existing clients that select\nthe production taproot channel type by its historic name continue to\ncompile and serialize against the same wire value.\n - SIMPLE_TAPROOT: A legacy taproot channel type that uses musig2 for the funding output and\nthe new tapscript features, but with development scripts and the staging\nfeature bits. Retained for compatibility with peers that have not upgraded\nto TAPROOT; new integrations should prefer TAPROOT.\n - SIMPLE_TAPROOT_OVERLAY: Identical to the SIMPLE_TAPROOT channel type, but with extra functionality.\nThis channel type also commits to additional meta data in the tapscript\nleaves for the scripts in a channel."
},
"lnrpcConnectPeerRequest": {
"type": "object",
@@ -6608,7 +6608,7 @@
},
"commitment_type": {
"$ref": "#/definitions/lnrpcCommitmentType",
- "description": "The commitment type to request. If UNKNOWN, lnd selects a default from\nboth peers' supported features; the selected type is always sent\nexplicitly."
+ "description": "The commitment type to request. If UNKNOWN, lnd selects a default from\nboth peers' supported features; the selected type is always sent\nexplicitly.\n\nLEGACY is rejected: lnd no longer opens or accepts channels of that\ntype. Note that an empty channel type on the wire requests it too, so\nthere is no way to ask for it at all."
},
"zero_conf": {
"type": "boolean",
### lntest/node/config.go
@@ -47,10 +47,6 @@ var (
"btcdexec", "", "full path to btcd binary",
)
- // CfgLegacy specifies the config used to create a node that uses the
- // legacy channel format.
- CfgLegacy = []string{"--protocol.legacy.committweak"}
-
// CfgStaticRemoteKey specifies the config used to create a node that
// uses the static remote key feature.
CfgStaticRemoteKey = []string{}
### lntest/utils.go
@@ -158,8 +158,6 @@ func CommitTypeHasAnchors(commitType lnrpc.CommitmentType) bool {
// commitment type.
func NodeArgsForCommitType(commitType lnrpc.CommitmentType) []string {
switch commitType {
- case lnrpc.CommitmentType_LEGACY:
- return []string{"--protocol.legacy.committweak"}
case lnrpc.CommitmentType_STATIC_REMOTE_KEY:
return []string{}
case lnrpc.CommitmentType_ANCHORS:
### lnwire/error.go
@@ -31,6 +31,13 @@ const (
// FundingOpen request which doesn't specify an explicit channel type,
// as mandated by BOLT-02.
ErrChanTypeRequired FundingError = 3
+
+ // ErrChanTypeDeprecated is returned by a remote peer that receives a
+ // FundingOpen request for the legacy commitment type, which it no
+ // longer opens. It is kept terse on purpose: which type the peer
+ // should use instead is our local policy, not something the remote can
+ // act on.
+ ErrChanTypeDeprecated FundingError = 4
)
// String returns a human readable version of the target FundingError.
@@ -42,6 +49,8 @@ func (e FundingError) String() string {
return "channel too large"
case ErrChanTypeRequired:
return "channel type required"
+ case ErrChanTypeDeprecated:
+ return "channel type deprecated"
default:
return "unknown error"
}
### rpcserver.go
@@ -2318,9 +2318,11 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
return nil, fmt.Errorf("use anchors for zero-conf")
}
+ // The legacy commitment type is no longer opened at all. Reject it here
+ // so the caller gets a clear error before we touch the wallet or the
+ // peer, rather than one from deep inside the funding flow.
case lnrpc.CommitmentType_LEGACY:
- channelType = new(lnwire.ChannelType)
- *channelType = lnwire.ChannelType(*lnwire.NewRawFeatureVector())
+ return nil, funding.ErrDeprecatedChanType
case lnrpc.CommitmentType_STATIC_REMOTE_KEY:
channelType = new(lnwire.ChannelType)Why this scored 62/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.