consensus: BIP65 OP_CHECKLOCKTIMEVERIFY — activate at 1,055,555 #34

Closed
opened 2026-06-15 23:13:45 +00:00 by dobbscoin · 1 comment
dobbscoin commented 2026-06-15 23:13:45 +00:00

Revised 2026-06-16: activation height changed from 1,025,000 → 1,055,555 to comply with the feature-freeze policy in #20 (no consensus merges to main between h=998,000 and h=1,050,667). The mid-OFFSIG argument has been replaced with a post-freeze + bundle-with-#6 argument. See § Why activate at 1,055,555.

Goal

Add OP_CHECKLOCKTIMEVERIFY (BIP65) at consensus level, repurposing the OP_NOP2 slot. Enables script-level timelocks for vault patterns, refund clauses, and any cooperative-spend pattern that wants "this output can't be reclaimed until block/time X."

Flagged by @9019x on 2026-06-14 as part of the BIP66 family — paired because both are script-level soft forks that exchanges and modern wallets check together for "Bitcoin script compatibility."

Problem

OFF has OP_NOP2 = 0xb1 at src/script.h:199, currently a literal no-op (src/script.cpp:393, which catches OP_NOP1 through OP_NOP5 together). That's the script slot BIP65 redefines as OP_CHECKLOCKTIMEVERIFY. Without CLTV, the only timelock OFF supports is tx-level nLockTime (src/main.cpp:570-576) — a coarse-grained "this entire transaction is invalid before time X" that can't be expressed as a conditional inside a script.

Use cases CLTV unlocks for OFF:

  • HTLC-style escrows (cooperative refund clauses).
  • Vault patterns (long-cooldown spends with cancellation windows).
  • Inheritance/recovery scripts (P2SH that pays to the heir only after a delay).
  • Conclave Treasury time-bounded multisig overrides (one of the better fits — would let the Treasury authorize a "if Key A hasn't claimed within N blocks, Keys B and C alone can").

None urgent; each is currently impossible at script level.

What BIP65 actually requires

From https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki:

When SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY flag is set, OP_NOP2 (0xb1) is redefined as OP_CHECKLOCKTIMEVERIFY. The opcode body:

  1. Read the top stack item as a 5-byte little-endian unsigned integer (the "locktime").
  2. If the stack is empty → fail.
  3. If locktime is negative → fail.
  4. If locktime and tx.nLockTime represent different lock types (block-height vs unix-time, per LOCKTIME_THRESHOLD) → fail.
  5. If locktime > tx.nLockTime → fail.
  6. If the spending tx input has nSequence == 0xFFFFFFFF → fail (would otherwise disable nLockTime).
  7. Otherwise → leave the stack unchanged; control falls through.

When the flag is NOT set, OP_NOP2 continues to behave as a no-op (i.e., scripts compiled against post-fork rules deploy fine on pre-fork nodes — the soft-fork property of NOP-redefinition).

Constants

Constant Value Rationale
SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY (1U << 5) New flag. Distinct from SCRIPT_VERIFY_DERSIG (#33) so each can be enforced independently if needed.
OP_CHECKLOCKTIMEVERIFY OP_NOP2 (0xb1) Repurpose existing no-op slot — exactly what BIP65 specifies.
HARDFORK_CLTV_MAIN_OFF 1,055,555 Same height as COINBASE_MATURITY (#32) and BIP66 DERSIG (#33); bundled with #6 rolling-checkpoints. 4,889 blocks past freeze-end (~3.4 days).
HARDFORK_CLTV_TESTNET_OFF 100 Trivial.

Why activate at 1,055,555 (post-freeze, bundled with #6)

Original draft placed activation at h=1,025,000 inside the OFFSIG window, on the "Conclave-only mining → zero split risk" rationale. Revised 2026-06-16 to honor the feature-freeze policy in #20, which prohibits merging consensus changes to main between h=998,000 and h=1,050,667. Activation moves to the post-freeze slot.

Why 1,055,555 specifically:

  • Bundles with #6 (rolling checkpoints) at the same height — one upgrade cycle, one BCT post, one Conclave deploy.
  • 4,889 blocks (~3.4 days at 60s) past freeze-end at h=1,050,667. Announce at freeze-end with a 3-day countdown; permissionless miners returning at 1,050,667 have lead time to pull the new binary before the rule trips.
  • Repeats the 5s numerology already used by #6.

Trade vs the original mid-OFFSIG activation: outsider miners can in principle produce blocks between h=1,050,667 and h=1,055,555 under the old rule. The freeze-end upgrade-coordination announcement closes that gap. A 3.4-day lead time is short but standard for post-freeze coordination on a small chain.

Files touched

  • src/script.h — add SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY flag; alias OP_CHECKLOCKTIMEVERIFY to OP_NOP2.
  • src/script.cpp — add OP_CHECKLOCKTIMEVERIFY handler in EvalScript() around line 393 where OP_NOP2 is currently caught. ~30 LoC for the opcode body. Split the existing case OP_NOP1: case OP_NOP2: ... group so CLTV gets its own arm when the flag is set.
  • src/main.cpp — height-gated flag bit added to ConnectBlock flags (line 2120-2121) and AcceptToMemoryPool flags (line 968), same gate pattern as BIP66.
  • src/pow.h — HARDFORK_CLTV_MAIN_OFF / _TESTNET_OFF constants.

Approx 50 LoC total.

Semantics

The new rule applies to any script verification at nSpendHeight ≥ HARDFORK_CLTV_MAIN_OFF. Pre-fork: OP_NOP2 is a no-op, scripts using it pass without doing anything. Post-fork: scripts that use the opcode get the actual CLTV semantics.

This is forward-compatible: a script written for the post-fork CLTV semantics will also deploy on pre-fork nodes, because they treat OP_NOP2 as a no-op and don't enforce the timelock. That's the entire soft-fork property of NOP-redefinition. The reverse (a script that deliberately exploits the no-op nature of OP_NOP2) would break — but no such scripts are known on the OFF chain.

Test plan

  1. Regtest: extend qa/rpc-tests/ with:
    • Pre-fork: deploy and spend a P2SH locked with <locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP <pubkey> OP_CHECKSIG. Pre-fork: spend succeeds at any height (CLTV is no-op). Post-fork: spend fails before <locktime>, succeeds at-or-after.
    • All BIP65 reject conditions: empty stack, negative locktime, mismatched lock-type, locktime > tx.nLockTime, nSequence=0xFFFFFFFF.
  2. BIP65 test vectors: port the Bitcoin Core 0.11 script_tests.json BIP65 cases.
  3. Testnet activation: bundled with BIP66 (#33) and COINBASE_MATURITY (#32) in v2.0.x-rc-bipsoft at testnet h=100.
  4. Conclave Treasury preview: draft a P2SH redeem script that uses CLTV to express "if Treasury Key A hasn't claimed within 1000 blocks, Keys B and C alone can." Don't deploy — just demonstrate the script compiles and is valid post-fork.

Risks / mitigations

Risk Mitigation
Existing scripts deliberately exploit OP_NOP2 no-op behavior Chainstate audit: scan all UTXOs for OP_NOP2 (0xb1) in scriptPubKey. Expected count: zero. Document the result in the testnet announcement.
Bundle with BIP66 (#33) / COINBASE_MATURITY (#32) at h=1,055,555 Same activation height; one upgrade cycle. Distinct flag bit (SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY). Each revertable independently if testnet exposes a problem.
User confusion ("CLTV means I can do CSV too") BIP112 (CSV) and BIP68 (relative lock-time) are NOT part of this issue — they require additional consensus changes around nSequence semantics. Scope explicitly excludes them.
Soft-fork compatibility broken on pre-fork nodes Standard NOP-redefinition pattern. Pre-fork nodes treat OP_CLTV as no-op and pass the script through. Post-fork rules are strictly tighter, never looser.

Process

Bundle into v2.0.x-rc-bipsoft with COINBASE_MATURITY (#32) and BIP66 DERSIG (#33). All three rules share HARDFORK_*_MAIN_OFF = 1,055,555. Branch development on feat/v2.0.x-rc-bipsoft; do not merge to main until freeze-end (post h=1,050,667).

Community chat for live discussion: https://23skidoo.info/discord

Activation milestone (to add to WHERE_WE_LEFT_OFF.md)

BIP65 CLTV: h=1,055,555 (bundled in v2.0.x-rc-bipsoft)

References

  • BIP65: https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki
  • Bitcoin Core PR adding CLTV: bitcoin/bitcoin#5496 (Peter Todd, "BIP65 OP_CHECKLOCKTIMEVERIFY")
  • src/script.h:199 — OP_NOP2 = 0xb1 slot
  • src/script.cpp:393 — current OP_NOP2 no-op handling
  • src/main.cpp:570-576 — existing tx-level nLockTime enforcement
  • Issue #6 — rolling checkpoints at 1,055,555 (bundled at same height)
  • Issue #20 — feature-freeze policy h=998,000 → h=1,050,667 (the constraint that forced this revision)
  • Issue #32 — COINBASE_MATURITY (bundled at same height)
  • Issue #33 — BIP66 DERSIG (bundled at same height)
  • Issue #38 — public OFF testnet (live-test prerequisite)
> **Revised 2026-06-16:** activation height changed from 1,025,000 → 1,055,555 to comply with the feature-freeze policy in #20 (no consensus merges to `main` between h=998,000 and h=1,050,667). The mid-OFFSIG argument has been replaced with a post-freeze + bundle-with-#6 argument. See § Why activate at 1,055,555. ## Goal Add OP_CHECKLOCKTIMEVERIFY (BIP65) at consensus level, repurposing the `OP_NOP2` slot. Enables script-level timelocks for vault patterns, refund clauses, and any cooperative-spend pattern that wants "this output can't be reclaimed until block/time X." Flagged by @9019x on 2026-06-14 as part of the BIP66 family — paired because both are script-level soft forks that exchanges and modern wallets check together for "Bitcoin script compatibility." ## Problem OFF has `OP_NOP2 = 0xb1` at `src/script.h:199`, currently a literal no-op (`src/script.cpp:393`, which catches `OP_NOP1` through `OP_NOP5` together). That's the script slot BIP65 redefines as `OP_CHECKLOCKTIMEVERIFY`. Without CLTV, the only timelock OFF supports is tx-level `nLockTime` (`src/main.cpp:570-576`) — a coarse-grained "this entire transaction is invalid before time X" that can't be expressed as a conditional inside a script. Use cases CLTV unlocks for OFF: - HTLC-style escrows (cooperative refund clauses). - Vault patterns (long-cooldown spends with cancellation windows). - Inheritance/recovery scripts (P2SH that pays to the heir only after a delay). - Conclave Treasury time-bounded multisig overrides (one of the better fits — would let the Treasury authorize a "if Key A hasn't claimed within N blocks, Keys B and C alone can"). None urgent; each is currently impossible at script level. ## What BIP65 actually requires From https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki: When `SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY` flag is set, `OP_NOP2` (0xb1) is redefined as `OP_CHECKLOCKTIMEVERIFY`. The opcode body: 1. Read the top stack item as a 5-byte little-endian unsigned integer (the "locktime"). 2. If the stack is empty → fail. 3. If locktime is negative → fail. 4. If `locktime` and `tx.nLockTime` represent different lock types (block-height vs unix-time, per `LOCKTIME_THRESHOLD`) → fail. 5. If `locktime > tx.nLockTime` → fail. 6. If the spending tx input has `nSequence == 0xFFFFFFFF` → fail (would otherwise disable `nLockTime`). 7. Otherwise → leave the stack unchanged; control falls through. When the flag is NOT set, `OP_NOP2` continues to behave as a no-op (i.e., scripts compiled against post-fork rules deploy fine on pre-fork nodes — the soft-fork property of NOP-redefinition). ## Constants | Constant | Value | Rationale | |---|---|---| | `SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY` | (1U << 5) | New flag. Distinct from `SCRIPT_VERIFY_DERSIG` (#33) so each can be enforced independently if needed. | | `OP_CHECKLOCKTIMEVERIFY` | OP_NOP2 (0xb1) | Repurpose existing no-op slot — exactly what BIP65 specifies. | | `HARDFORK_CLTV_MAIN_OFF` | **1,055,555** | Same height as COINBASE_MATURITY (#32) and BIP66 DERSIG (#33); bundled with #6 rolling-checkpoints. 4,889 blocks past freeze-end (~3.4 days). | | `HARDFORK_CLTV_TESTNET_OFF` | 100 | Trivial. | ## Why activate at 1,055,555 (post-freeze, bundled with #6) **Original draft** placed activation at h=1,025,000 inside the OFFSIG window, on the "Conclave-only mining → zero split risk" rationale. **Revised 2026-06-16** to honor the feature-freeze policy in #20, which prohibits merging consensus changes to `main` between h=998,000 and h=1,050,667. Activation moves to the post-freeze slot. Why 1,055,555 specifically: - Bundles with #6 (rolling checkpoints) at the same height — one upgrade cycle, one BCT post, one Conclave deploy. - 4,889 blocks (~3.4 days at 60s) past freeze-end at h=1,050,667. Announce at freeze-end with a 3-day countdown; permissionless miners returning at 1,050,667 have lead time to pull the new binary before the rule trips. - Repeats the 5s numerology already used by #6. Trade vs the original mid-OFFSIG activation: outsider miners can in principle produce blocks between h=1,050,667 and h=1,055,555 under the old rule. The freeze-end upgrade-coordination announcement closes that gap. A 3.4-day lead time is short but standard for post-freeze coordination on a small chain. ## Files touched - `src/script.h` — add `SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY` flag; alias `OP_CHECKLOCKTIMEVERIFY` to `OP_NOP2`. - `src/script.cpp` — add `OP_CHECKLOCKTIMEVERIFY` handler in `EvalScript()` around line 393 where `OP_NOP2` is currently caught. ~30 LoC for the opcode body. Split the existing `case OP_NOP1: case OP_NOP2: ...` group so CLTV gets its own arm when the flag is set. - `src/main.cpp` — height-gated flag bit added to `ConnectBlock` flags (line 2120-2121) and `AcceptToMemoryPool` flags (line 968), same gate pattern as BIP66. - `src/pow.h` — `HARDFORK_CLTV_MAIN_OFF` / `_TESTNET_OFF` constants. Approx 50 LoC total. ## Semantics The new rule applies to any **script verification at** `nSpendHeight ≥ HARDFORK_CLTV_MAIN_OFF`. Pre-fork: `OP_NOP2` is a no-op, scripts using it pass without doing anything. Post-fork: scripts that use the opcode get the actual CLTV semantics. This is **forward-compatible**: a script written for the post-fork CLTV semantics will also deploy on pre-fork nodes, because they treat `OP_NOP2` as a no-op and don't enforce the timelock. That's the entire soft-fork property of NOP-redefinition. The reverse (a script that deliberately exploits the no-op nature of `OP_NOP2`) would break — but no such scripts are known on the OFF chain. ## Test plan 1. **Regtest**: extend `qa/rpc-tests/` with: - Pre-fork: deploy and spend a P2SH locked with `<locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP <pubkey> OP_CHECKSIG`. Pre-fork: spend succeeds at any height (CLTV is no-op). Post-fork: spend fails before `<locktime>`, succeeds at-or-after. - All BIP65 reject conditions: empty stack, negative locktime, mismatched lock-type, `locktime > tx.nLockTime`, `nSequence=0xFFFFFFFF`. 2. **BIP65 test vectors**: port the Bitcoin Core 0.11 `script_tests.json` BIP65 cases. 3. **Testnet activation**: bundled with BIP66 (#33) and COINBASE_MATURITY (#32) in `v2.0.x-rc-bipsoft` at testnet h=100. 4. **Conclave Treasury preview**: draft a P2SH redeem script that uses CLTV to express "if Treasury Key A hasn't claimed within 1000 blocks, Keys B and C alone can." Don't deploy — just demonstrate the script compiles and is valid post-fork. ## Risks / mitigations | Risk | Mitigation | |---|---| | Existing scripts deliberately exploit `OP_NOP2` no-op behavior | Chainstate audit: scan all UTXOs for `OP_NOP2` (0xb1) in scriptPubKey. Expected count: zero. Document the result in the testnet announcement. | | Bundle with BIP66 (#33) / COINBASE_MATURITY (#32) at h=1,055,555 | Same activation height; one upgrade cycle. Distinct flag bit (`SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY`). Each revertable independently if testnet exposes a problem. | | User confusion ("CLTV means I can do CSV too") | BIP112 (CSV) and BIP68 (relative lock-time) are NOT part of this issue — they require additional consensus changes around `nSequence` semantics. Scope explicitly excludes them. | | Soft-fork compatibility broken on pre-fork nodes | Standard NOP-redefinition pattern. Pre-fork nodes treat `OP_CLTV` as no-op and pass the script through. Post-fork rules are strictly tighter, never looser. | ## Process Bundle into `v2.0.x-rc-bipsoft` with COINBASE_MATURITY (#32) and BIP66 DERSIG (#33). All three rules share `HARDFORK_*_MAIN_OFF = 1,055,555`. Branch development on `feat/v2.0.x-rc-bipsoft`; do not merge to `main` until freeze-end (post h=1,050,667). Community chat for live discussion: https://23skidoo.info/discord ## Activation milestone (to add to WHERE_WE_LEFT_OFF.md) `BIP65 CLTV: h=1,055,555 (bundled in v2.0.x-rc-bipsoft)` ## References - BIP65: https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki - Bitcoin Core PR adding CLTV: bitcoin/bitcoin#5496 (Peter Todd, "BIP65 OP_CHECKLOCKTIMEVERIFY") - `src/script.h:199` — `OP_NOP2 = 0xb1` slot - `src/script.cpp:393` — current `OP_NOP2` no-op handling - `src/main.cpp:570-576` — existing tx-level `nLockTime` enforcement - Issue #6 — rolling checkpoints at 1,055,555 (bundled at same height) - Issue #20 — feature-freeze policy h=998,000 → h=1,050,667 (the constraint that forced this revision) - Issue #32 — COINBASE_MATURITY (bundled at same height) - Issue #33 — BIP66 DERSIG (bundled at same height) - Issue #38 — public OFF testnet (live-test prerequisite)
dobbscoin commented 2026-07-27 21:50:53 +00:00

Live on mainnet. Activated on schedule at h=1,055,555 (2026-07-23), block 00000000438cf73291060c7c2caeadd568b7e432c192f4f44b3d84eafb157c7c.

OP_CHECKLOCKTIMEVERIFY is now consensus-enforced — OFF scripts can bind coins until an absolute height or time, enabling trustless timelock constructions (vaults, escrows, HTLC building blocks). ~5,800 blocks validated under the rule with zero incident; the mixed-version soak (v2.0.8.7 / v2.0.9 / v2.1.0-rc) remains fork-free at delta 0.

Shipped in v2.0.9-Eldersign. Closing.

Live on mainnet. Activated on schedule at h=1,055,555 (2026-07-23), block `00000000438cf73291060c7c2caeadd568b7e432c192f4f44b3d84eafb157c7c`. `OP_CHECKLOCKTIMEVERIFY` is now consensus-enforced — OFF scripts can bind coins until an absolute height or time, enabling trustless timelock constructions (vaults, escrows, HTLC building blocks). ~5,800 blocks validated under the rule with zero incident; the mixed-version soak (v2.0.8.7 / v2.0.9 / v2.1.0-rc) remains fork-free at delta 0. Shipped in v2.0.9-Eldersign. Closing.
dobbscoin closed this issue 2026-07-27 21:50:54 +00:00
Sign in to join this conversation.
No labels
enhancement
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
SubGeniusFinance/Offerings-to-Cthulhu#34
No description provided.