What changed, and why it matters
This commit increases several minimum CLTV (CheckLockTimeVerify) delta values in the LND Lightning node from 18 to 24 blocks. In the Lightning Network, these deltas are safety margins that give nodes enough time to react if a payment or channel state needs to be enforced on-chain. A larger delta makes certain timing-based attacks harder by extending the reaction window, but the commit itself does not fix a specific bug or add new checks. It is a policy hardening change rather than a patch for an active vulnerability.
Treat as a routine protocol-hardening update. Operators should ensure their node is running a version that includes this parameter change and review any custom timelockdelta or holdexpirydelta settings to confirm they meet or exceed the new defaults. No emergency response is indicated by the diff alone.
Security signals we found
Timing/parameter hardening for Lightning HTLC safety margins
Increase of final CLTV delta from 18 to 24 blocks
Adjustment of hold expiry delta from 12 to 18 in sample config
No new exploit path patched; change is policy/consistency
Evidence from the diff
The change raises the default/minimum assumed final CLTV delta from 18 to 24 blocks across tests, sample config, and the zpay32 hop-hint code. It also adjusts related constants (thaw height delta, max blocks mined per test, hold expiry delta) to keep them consistent with the new 24-block minimum. The routing.MinCLTVDelta constant is referenced as the source of truth; this commit updates callers and comments to match it. No new validation logic or security boundary is introduced.
Changed components
zpay32/hophint.gosample-lnd.conflntest/harness.goitest/lnd_channel_policy_test.goitest/lnd_htlc_timeout_resolver_test.goitest/lnd_route_blinding_test.goitest/lnd_sweep_test.goInspect captured patch +13 / −13
diff --git a/itest/lnd_channel_policy_test.go b/itest/lnd_channel_policy_test.go
index 7a333f0..7def317 100644
--- a/itest/lnd_channel_policy_test.go
+++ b/itest/lnd_channel_policy_test.go
@@ -295,7 +295,7 @@ func testUpdateChannelPolicy(ht *lntest.HarnessTest) {
// propagated.
baseFee = int64(800)
feeRate = int64(123)
- timeLockDelta = uint32(22)
+ timeLockDelta = uint32(24)
maxHtlc *= 2
inboundBaseFee := int32(-400)
inboundFeeRatePpm := int32(-60)
diff --git a/itest/lnd_htlc_timeout_resolver_test.go b/itest/lnd_htlc_timeout_resolver_test.go
index 25aa0af..2710086 100644
--- a/itest/lnd_htlc_timeout_resolver_test.go
+++ b/itest/lnd_htlc_timeout_resolver_test.go
@@ -14,8 +14,8 @@ import (
)
const (
- finalCltvDelta = routing.MinCLTVDelta // 18.
- thawHeightDelta = finalCltvDelta * 2 // 36.
+ finalCltvDelta = routing.MinCLTVDelta // 24.
+ thawHeightDelta = finalCltvDelta * 2 // 48.
)
// makeRouteHints creates a route hints that will allow Carol to be reached
diff --git a/itest/lnd_route_blinding_test.go b/itest/lnd_route_blinding_test.go
index b6c7f10..3ad1f45 100644
--- a/itest/lnd_route_blinding_test.go
+++ b/itest/lnd_route_blinding_test.go
@@ -352,7 +352,7 @@ func (b *blindedForwardTest) setupNetwork(ctx context.Context,
withInterceptor bool) {
carolArgs := []string{
- "--bitcoin.timelockdelta=18",
+ "--bitcoin.timelockdelta=24",
fmt.Sprintf("--bitcoin.defaultremotedelay=%v", toLocalCSV),
}
if withInterceptor {
@@ -360,7 +360,7 @@ func (b *blindedForwardTest) setupNetwork(ctx context.Context,
}
daveArgs := []string{
- "--bitcoin.timelockdelta=18",
+ "--bitcoin.timelockdelta=24",
fmt.Sprintf("--bitcoin.defaultremotedelay=%v", toLocalCSV),
}
cfgs := [][]string{nil, nil, carolArgs, daveArgs}
diff --git a/itest/lnd_sweep_test.go b/itest/lnd_sweep_test.go
index c5bcd3b..3874786 100644
--- a/itest/lnd_sweep_test.go
+++ b/itest/lnd_sweep_test.go
@@ -879,7 +879,7 @@ func testSweepHTLCs(ht *lntest.HarnessTest) {
// Before we mine empty blocks to check the RBF behavior, we need to be
// aware that Bob's incoming HTLC will expire before his outgoing HTLC
// deadline is reached. This happens because the incoming HTLC is sent
- // onchain at CLTVDelta-BroadcastDelta=18-10=8, which means after 8
+ // onchain at CLTVDelta-BroadcastDelta=24-16=8, which means after 8
// blocks are mined, we expect Bob force closes the channel Alice->Bob.
blocksTillIncomingSweep := cltvDelta -
lncfg.DefaultIncomingBroadcastDelta
diff --git a/lntest/harness.go b/lntest/harness.go
index 4b50257..f3ffc92 100644
--- a/lntest/harness.go
+++ b/lntest/harness.go
@@ -54,15 +54,15 @@ const (
// mining blocks.
maxBlocksAllowed = 100
- finalCltvDelta = routing.MinCLTVDelta // 18.
- thawHeightDelta = finalCltvDelta * 2 // 36.
+ finalCltvDelta = routing.MinCLTVDelta // 24.
+ thawHeightDelta = finalCltvDelta * 2 // 48.
)
var (
// MaxBlocksMinedPerTest is the maximum number of blocks that we allow
// a test to mine. This is an exported global variable so it can be
// overwritten by other projects that don't have the same constraints.
- MaxBlocksMinedPerTest = 50
+ MaxBlocksMinedPerTest = 70
)
// TestCase defines a test case that's been used in the integration test.
@@ -409,13 +409,13 @@ func (h *HarnessTest) checkAndLimitBlocksMined(startHeight int32) {
desc += "1. break test into smaller individual tests, especially if " +
"this is a table-drive test.\n" +
"2. use smaller CSV via `--bitcoin.defaultremotedelay=1.`\n" +
- "3. use smaller CLTV via `--bitcoin.timelockdelta=18.`\n" +
+ "3. use smaller CLTV via `--bitcoin.timelockdelta=24.`\n" +
"4. remove unnecessary CloseChannel when test ends.\n" +
"5. use `CreateSimpleNetwork` for efficient channel creation.\n"
h.Log(desc)
// We enforce that the test should not mine more than
- // MaxBlocksMinedPerTest (50 by default) blocks, which is more than
+ // MaxBlocksMinedPerTest (70 by default) blocks, which is more than
// enough to test a multi hop force close scenario.
require.LessOrEqualf(
h, int(blocksMined), MaxBlocksMinedPerTest,
diff --git a/sample-lnd.conf b/sample-lnd.conf
index 28f18b3..a487565 100644
--- a/sample-lnd.conf
+++ b/sample-lnd.conf
@@ -1878,7 +1878,7 @@
; DefaultIncomingBroadcastDelta set by lnd, otherwise the channel will be force
; closed anyway. A warning will be logged on startup if this value is not large
; enough to prevent force closes.
-; invoices.holdexpirydelta=12
+; invoices.holdexpirydelta=18
[routing]
diff --git a/zpay32/hophint.go b/zpay32/hophint.go
index 07872b0..dd1a2ee 100644
--- a/zpay32/hophint.go
+++ b/zpay32/hophint.go
@@ -12,7 +12,7 @@ const (
// We adhere to the recommendation in BOLT 02 for terminal payments.
// See also:
// https://github.com/lightning/bolts/blob/master/02-peer-protocol.md
- DefaultAssumedFinalCLTVDelta = 18
+ DefaultAssumedFinalCLTVDelta = 24
// feeRateParts is the total number of parts used to express fee rates.
feeRateParts = 1e6
Why this scored 51/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.