Warn for far-future block-height locktime
What changed, and why it matters
This change adds a warning when a Bitcoin transaction's locktime is set to a block height more than 10 years in the future. It does not block the transaction but alerts the user, helping catch potential tricks where a transaction is made unspendable for a very long time without the user noticing.
No immediate action required; this is a defensive UX improvement. Users should still review locktime warnings carefully before signing.
Security signals we found
Adds user-visible warning for unusually distant block-height locktime
Does not enforce a hard limit; warning-only behavior
Targets potential UI deception / unspendable-funds scenario
Includes unit test covering boundary at 10 years plus one block
Evidence from the diff
The commit introduces MAX_FUTURE_BLOCKS (10 years of blocks at 144 blocks/day) and a check in shared/psbt.py’s validate() routine. When a transaction uses nLockTime as a block height and that height exceeds the chain’s known minimum block plus ~10 years, a ‘Distant Locktime’ warning is appended. A test verifies the warning appears exactly when expected.
Changed components
shared/psbt.pytesting/test_sign.pyreleases/Next-ChangeLog.mdInspect captured patch +32 / −0
### releases/Next-ChangeLog.md
@@ -4,6 +4,8 @@ This lists the new changes that have not yet been published in a normal release.
# Shared Improvements - Both Mk and Q
+- Enhancement: Warn when a transaction's block-height `nLockTime` is more than
+ ten years beyond the Bitcoin block height known to the firmware.
- Bugfix: Reject foreign inputs from BIP-322 Proof of Reserves, including inputs
disguised with forged key-path metadata or partial signatures.
- Bugfix: Detect and abort transaction signing if a Virtual Disk firmware import
### shared/psbt.py
@@ -38,6 +38,7 @@
# transaction version error
TX_VER_ERR = "bad txn version"
NO_KEY_ERR = "None of the keys involved in this transaction belong to this Coldcard"
+MAX_FUTURE_BLOCKS = const(10 * 365 * 144)
# single sha256 of b'BIP0322-signed-message'
BIP322_TAG_HASH = b'te\x84\xa1\x87/\xa1\x00AUN\xff\xa08\xd6\x12IB\xddy\xb4\xe5\x8aL\xda\x18N\x13\xdb\xe6,I'
@@ -1602,6 +1603,13 @@ async def validate(self):
msg = "This tx can only be spent after "
if self.lock_time < NLOCK_IS_TIME:
msg += "block height of %d" % self.lock_time
+
+ min_block = chains.current_chain().ccc_min_block
+ if min_block and self.lock_time > (min_block + MAX_FUTURE_BLOCKS):
+ self.warnings.append((
+ "Distant Locktime",
+ "Unusually distant block-height locktime (10+ years)."
+ ))
else:
try:
dt = datetime_from_timestamp(self.lock_time)
### testing/test_sign.py
@@ -2723,6 +2723,28 @@ def test_locktime_ux(use_regtest, bitcoind_d_sim_watch, start_sign, end_sign,
assert txid == story_txid
+@pytest.mark.parametrize("extra_blocks,warn", [(0, False), (1, True)])
+def test_far_future_locktime_warning(
+ extra_blocks, warn, use_mainnet, sim_exec, fake_txn, start_sign,
+ end_sign, cap_story):
+ use_mainnet()
+ min_block = int(sim_exec(
+ "import chains; RV.write(str(chains.current_chain().ccc_min_block))"))
+ locktime = min_block + (10 * 365 * 144) + extra_blocks
+
+ start_sign(fake_txn(1, 1, lock_time=locktime))
+ time.sleep(0.1)
+ title, story = cap_story()
+
+ assert title == "OK TO SEND?"
+ assert "Abs Locktime" in story
+ assert "block height of %d" % locktime in story
+ assert ("Distant Locktime" in story) is warn
+ assert ("Unusually distant block-height locktime (10+ years)." in story) is warn
+
+ end_sign(accept=False)
+
+
@pytest.mark.bitcoind
@pytest.mark.parametrize("num_ins", [1, 4, 11])
@pytest.mark.parametrize("differ", [True, False])Why this scored 29/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.