Script engine predates BIP65 — no CHECKLOCKTIMEVERIFY, so no atomic swaps #34

Closed
opened 2026-09-14 01:20:58 +00:00 by btcbob · 3 comments
Owner

The consensus script engine predates BIP65/112/141. CHECKLOCKTIMEVERIFY,
CHECKSEQUENCEVERIFY and segwit are absent from the tree entirely — not merely
unactivated. OP_NOP2 (0xb1) and OP_NOP3 (0xb2) are still plain NOPs.

Verification

grep -rl CHECKLOCKTIMEVERIFY    src/   -> 0 files
grep -rl CHECKSEQUENCEVERIFY    src/   -> 0 files
grep -rl SCRIPT_VERIFY_WITNESS  src/   -> 0 files

src/script/script.h:151    OP_NOP2 = 0xb1,
src/script/script.h:152    OP_NOP3 = 0xb2,

The newest consensus rule referenced anywhere in src/ is BIP66.
versionbits.cpp, blockencodings.cpp and policy/ are absent, so the tree is
pre-0.12 despite CLIENT_VERSION_MINOR 13.

Why it matters

Atomic swaps need a timelocked refund path. Without CHECKLOCKTIMEVERIFY the only
option is a pre-signed refund transaction referencing the funding txid, which is
fragile under transaction malleability — and malleability is also unfixed here.

BIP65 resolves both at once: it exists precisely so the funder can reclaim with a
freshly-constructed transaction after the timeout, instead of relying on a
pre-signed one. CLTV alone is sufficient for HTLC-based atomic swaps. Segwit is
not required for this.

Scope

Minimal path is BIP65 only:

  • redefine OP_NOP2 as OP_CHECKLOCKTIMEVERIFY in the interpreter
  • add the corresponding script-verify flag
  • gate activation at a fixed height

versionbits is absent, so BIP9 is not available — but height-gated activation is
a pattern already running in production in the sibling Offerings codebase
(IsAfterRestorationFork(nHeight)). A testnet exists (CTestNetParams) and should
carry the first activation.

BIP112 (CSV) and BIP141 (segwit) are separate, larger, and not required for swaps.

Also worth fixing regardless

CLIENT_VERSION_MINOR 13 implies a 0.13 lineage the code does not have. It will
mislead the next reader — it briefly misled this investigation.

The consensus script engine predates BIP65/112/141. `CHECKLOCKTIMEVERIFY`, `CHECKSEQUENCEVERIFY` and segwit are absent from the tree entirely — not merely unactivated. `OP_NOP2` (0xb1) and `OP_NOP3` (0xb2) are still plain NOPs. ### Verification ``` grep -rl CHECKLOCKTIMEVERIFY src/ -> 0 files grep -rl CHECKSEQUENCEVERIFY src/ -> 0 files grep -rl SCRIPT_VERIFY_WITNESS src/ -> 0 files src/script/script.h:151 OP_NOP2 = 0xb1, src/script/script.h:152 OP_NOP3 = 0xb2, ``` The newest consensus rule referenced anywhere in `src/` is **BIP66**. `versionbits.cpp`, `blockencodings.cpp` and `policy/` are absent, so the tree is pre-0.12 despite `CLIENT_VERSION_MINOR 13`. ### Why it matters Atomic swaps need a timelocked refund path. Without `CHECKLOCKTIMEVERIFY` the only option is a pre-signed refund transaction referencing the funding txid, which is fragile under transaction malleability — and malleability is also unfixed here. BIP65 resolves both at once: it exists precisely so the funder can reclaim with a freshly-constructed transaction after the timeout, instead of relying on a pre-signed one. **CLTV alone is sufficient for HTLC-based atomic swaps. Segwit is not required for this.** ### Scope Minimal path is BIP65 only: - redefine `OP_NOP2` as `OP_CHECKLOCKTIMEVERIFY` in the interpreter - add the corresponding script-verify flag - gate activation at a fixed height `versionbits` is absent, so BIP9 is not available — but height-gated activation is a pattern already running in production in the sibling Offerings codebase (`IsAfterRestorationFork(nHeight)`). A testnet exists (`CTestNetParams`) and should carry the first activation. BIP112 (CSV) and BIP141 (segwit) are separate, larger, and not required for swaps. ### Also worth fixing regardless `CLIENT_VERSION_MINOR 13` implies a 0.13 lineage the code does not have. It will mislead the next reader — it briefly misled this investigation.
Author
Owner

Scheduling note, after reading forum topic 31 (merge mining) and topic 24 (roadmap).

This should ride the block 2,000,000 fork rather than wait for a governance track
of its own.

HARDFORK_AUXPOW_MAIN = 2000000 is a hard fork every node must take — currently
~95,500 blocks out, ~132 days. The expensive part of any consensus change is the
upgrade campaign, and merge mining is already buying one. After 2M the upgrade
audience includes foreign pool operators who merge-mine us incidentally; a fork
then is a request they can decline.

Two clarifications on scope:

  • CLTV alone is enough for atomic swaps. BIP65 exists so a funder can reclaim
    with a freshly-built transaction after a timeout, instead of a pre-signed refund.
    That removes the malleability dependency, so segwit is not required for swaps.
  • CLTV does not need transaction v2. Roadmap 3 correctly notes IsStandardTx
    rejects nVersion > CURRENT_VERSION (src/main.cpp:643), which blocks CSV.
    CLTV is unaffected — it is a soft fork on a redefined NOP and works on v1.

So the Roadmap 3 sequence (CSV -> segwit -> bech32 -> wallet) is not a prerequisite
here. This is one opcode, separable from that decision entirely.

Scheduling note, after reading forum topic 31 (merge mining) and topic 24 (roadmap). **This should ride the block 2,000,000 fork rather than wait for a governance track of its own.** `HARDFORK_AUXPOW_MAIN = 2000000` is a hard fork every node must take — currently ~95,500 blocks out, ~132 days. The expensive part of any consensus change is the upgrade campaign, and merge mining is already buying one. After 2M the upgrade audience includes foreign pool operators who merge-mine us incidentally; a fork then is a request they can decline. Two clarifications on scope: - **CLTV alone is enough for atomic swaps.** BIP65 exists so a funder can reclaim with a freshly-built transaction after a timeout, instead of a pre-signed refund. That removes the malleability dependency, so **segwit is not required for swaps.** - **CLTV does not need transaction v2.** Roadmap 3 correctly notes `IsStandardTx` rejects `nVersion > CURRENT_VERSION` (`src/main.cpp:643`), which blocks CSV. CLTV is unaffected — it is a soft fork on a redefined NOP and works on v1. So the Roadmap 3 sequence (CSV -> segwit -> bech32 -> wallet) is not a prerequisite here. This is one opcode, separable from that decision entirely.
Author
Owner

Implemented on branch bip65-cltv-height (ba8e8e00, 14bdb014). Not merged.

CLTV activates by height, not by a version supermajority — see #40 for why a version-gated deployment cannot work on this chain after block 2,000,000. HARDFORK_CLTV_MAIN is 2,000,000, the same height as the AuxPoW fork, so the network upgrades once rather than twice.

Before activation the opcode behaves exactly as OP_NOP2 did, which is what keeps this a soft fork.

One thing that would have made the feature useless if missed: DISCOURAGE_UPGRADABLE_NOPS is part of STANDARD_SCRIPT_VERIFY_FLAGS, so a CLTV transaction was non-standard and would never have relayed — consensus-valid and undeliverable. The CLTV flag is now in the standard set too, which also stops a node relaying a transaction its own ConnectBlock would later reject.

Coverage: 12 unit tests in src/test/cltv_tests.cpp, plus on-chain tests that build real blocks on regtest to check the height gate itself (unit tests cannot see a wiring error between the interpreter and ConnectBlock).

Full suite is unchanged at its pre-existing 62 failures (48 script_tests, 14 transaction_tests), verified identical on main — see #43.

Implemented on branch `bip65-cltv-height` (`ba8e8e00`, `14bdb014`). Not merged. CLTV activates by **height**, not by a version supermajority — see #40 for why a version-gated deployment cannot work on this chain after block 2,000,000. `HARDFORK_CLTV_MAIN` is 2,000,000, the same height as the AuxPoW fork, so the network upgrades once rather than twice. Before activation the opcode behaves exactly as `OP_NOP2` did, which is what keeps this a soft fork. One thing that would have made the feature useless if missed: `DISCOURAGE_UPGRADABLE_NOPS` is part of `STANDARD_SCRIPT_VERIFY_FLAGS`, so a CLTV transaction was non-standard and would never have relayed — consensus-valid and undeliverable. The CLTV flag is now in the standard set too, which also stops a node relaying a transaction its own `ConnectBlock` would later reject. Coverage: 12 unit tests in `src/test/cltv_tests.cpp`, plus on-chain tests that build real blocks on regtest to check the height gate itself (unit tests cannot see a wiring error between the interpreter and `ConnectBlock`). Full suite is unchanged at its pre-existing 62 failures (48 `script_tests`, 14 `transaction_tests`), verified identical on `main` — see #43.
Author
Owner

Merged to main

Merge commit 2513af45, 2026-09-15. What actually landed:

  • 9b82f8f0 consensus: OP_NOP2 becomes OP_CHECKLOCKTIMEVERIFY, activated by height
  • cdfc7d47 policy: relay CLTV transactions and enforce the lock in the mempool
  • 1748c666 rpc: restore the regtest exemption on getblocktemplate
  • 082aa2eb qa: activation tested on a real chain; testnet height moved above coinbase maturity
  • 37a1687e build: ignore stray .bak binaries in src/

The comment above named ba8e8e00 and 14bdb014. Those were rebased away before the merge and sit on no branch; the list above is what is on main.

Activation

HARDFORK_CLTV_MAIN = 2000000, deliberately the same height as the AuxPoW fork, so the network takes one upgrade instead of two. HARDFORK_CLTV_TESTNET = 20, past AuxPoW's 10, so regtest crosses each boundary on its own. Below the activation height the opcode behaves exactly as OP_NOP2 always did, which is what makes this a soft fork rather than a second hard fork.

What is not done

Merging is not shipping. No release carries this yet, and CLTV does nothing on mainnet until block 2,000,000 regardless. Closing the code half of this issue; deployment rides the next release, and enforcement rides the fork height.

## Merged to `main` Merge commit `2513af45`, 2026-09-15. What actually landed: - `9b82f8f0` consensus: `OP_NOP2` becomes `OP_CHECKLOCKTIMEVERIFY`, activated by height - `cdfc7d47` policy: relay CLTV transactions and enforce the lock in the mempool - `1748c666` rpc: restore the regtest exemption on `getblocktemplate` - `082aa2eb` qa: activation tested on a real chain; testnet height moved above coinbase maturity - `37a1687e` build: ignore stray `.bak` binaries in `src/` The comment above named `ba8e8e00` and `14bdb014`. Those were rebased away before the merge and sit on no branch; the list above is what is on `main`. ### Activation `HARDFORK_CLTV_MAIN = 2000000`, deliberately the same height as the AuxPoW fork, so the network takes one upgrade instead of two. `HARDFORK_CLTV_TESTNET = 20`, past AuxPoW's 10, so regtest crosses each boundary on its own. Below the activation height the opcode behaves exactly as `OP_NOP2` always did, which is what makes this a soft fork rather than a second hard fork. ### What is not done **Merging is not shipping.** No release carries this yet, and CLTV does nothing on mainnet until block 2,000,000 regardless. Closing the code half of this issue; deployment rides the next release, and enforcement rides the fork height.
Sign in to join this conversation.
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/dobbscoin-source#34
No description provided.