consensus: BIP66 strict-DER signatures (full backport) — activate at 1,055,555 #33
Labels
No labels
enhancement
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
SubGeniusFinance/Offerings-to-Cthulhu#33
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Bring OFF's signature-encoding rules to consensus-level strict-DER (BIP66). Replace the existing pre-0.10-era lenient canonical-encoding helper with the self-contained strict-DER parser used by Bitcoin Core 0.11+ that doesn't depend on OpenSSL version quirks. Enforce at both mempool and block validation from fork height onward.
Flagged by @9019x on 2026-06-14 as the gating issue for OFF being taken seriously by exchanges and wallets — exchange-integration checklists literally grep source for
BIP66/DERSIG. He has stated he's stepping back from contribution until this lands.Problem
OFF carries the pre-BIP66 lineage from Bitcoin Core 0.10:
src/script.h:42—SCRIPT_VERIFY_STRICTENCflag exists.src/script.cpp:252-305—IsCanonicalSignature()is the manual canonical check that pre-dates BIP66.src/main.cpp:968— mempool (AcceptToMemoryPool) already passesSTRICTENC.src/main.cpp:2120-2121— block validation (ConnectBlock) does NOT passSTRICTENC.Two structural issues:
Enforcement gap. A non-strict-DER signature smuggled into a block (e.g., by a miner who patched their daemon to skip mempool relay) is currently accepted into the chain. The mempool rejects what blocks don't.
OpenSSL-version drift risk. The existing
IsCanonicalSignature()performs manual structural checks but the actual ECDSA verify path still passes bytes to OpenSSL. OpenSSL's lenient handling of edge-case DER encodings has shifted across versions in the past and could shift again — if it does, OFF's consensus rules drift with it. BIP66's distinguishing contribution was the self-contained strict-DER parser that removed OpenSSL from consensus.For OFF to credibly claim BIP66 compliance against an exchange-integration audit, both gaps need to close.
What BIP66 actually requires
From https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki:
A signature is valid only if:
30 <total-len> 02 <r-len> <r> 02 <s-len> <s> <sighash-byte>.<total-len>equals signature length minus 3.<r-len>is 1 to 33; if<r>is 33 bytes the leading byte must be0x00and the next byte must have its high bit set.<s-len>follows the same rules as<r-len>.<r>and<s>are non-negative when interpreted as 2's-complement signed (i.e., their high byte's high bit is unset) and not excessively padded.<sighash-byte>is one of0x01,0x02,0x03,0x81,0x82,0x83.The empty signature (used as
OP_CHECKMULTISIGpadding to signal "no signature") is still allowed.Constants
SCRIPT_VERIFY_DERSIGSCRIPT_VERIFY_STRICTENC. Allows mixed enforcement during transition.HARDFORK_DERSIG_MAIN_OFFHARDFORK_DERSIG_TESTNET_OFFWhy 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
mainbetween h=998,000 and h=1,050,667. Activation moves to the post-freeze slot.Why 1,055,555 specifically:
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— addSCRIPT_VERIFY_DERSIGflag.src/script.cpp— portCheckSignatureEncoding()strict-DER parser from Bitcoin Core 0.11+ (filescript/interpreter.cppin upstream). ~80 LoC, self-contained, no OpenSSL dependency. Plug it intoOP_CHECKSIGandOP_CHECKMULTISIGpaths next to the existingIsCanonicalSignature()call (the legacy check stays in place so we can run both during transition).src/main.cpp— height-gated flag computation inConnectBlockline 2120-2121:flags |= (pindex->nHeight >= HARDFORK_DERSIG_MAIN_OFF ? SCRIPT_VERIFY_DERSIG : 0). Same gate added toAcceptToMemoryPoolflags line 968.src/pow.h—HARDFORK_DERSIG_MAIN_OFF/_TESTNET_OFFconstants next to the LWMA-3 fork heights.Approx 150-200 LoC total, dominated by the strict-DER parser.
Semantics
The new rule applies to any script verification at
nSpendHeight ≥ HARDFORK_DERSIG_MAIN_OFF. Pre-fork txs and the historical chain replay/reindex through the legacy (lenient) path — no risk of invalidating buried blocks.Practically, libsecp256k1 and OpenSSL-since-2015 already produce strict-DER signatures, so the user-visible breakage surface is near zero. The change targets the smuggle-non-canonical-into-block attack vector, not legitimate signing.
Test plan
qa/rpc-tests/with three scenarios:bad-txns-nonstandard-inputsor equivalent reject.script_tests.jsontest vectors covering BIP66 edge cases (excessive padding, missing sighash byte, oversized<r>, etc.).v2.0.x-rc-bipsoftwith testnet fork at h=100 first. Mine a hand-crafted non-strict-DER tx pre-fork and post-fork; confirm behavior.Risks / mitigations
nSpendHeight ≥ fork. Pre-fork blocks revalidate with legacy lenient path.ECDSA_verifyfor the actual EC math, but encoding validation is now OFF's own code.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFYflag, distinct fromSCRIPT_VERIFY_DERSIG.Process
Bundle into
v2.0.x-rc-bipsoftwith COINBASE_MATURITY (#32) and BIP65 CLTV (#34). All three rules shareHARDFORK_*_MAIN_OFF = 1,055,555. Branch development onfeat/v2.0.x-rc-bipsoft; do not merge tomainuntil freeze-end (post h=1,050,667). Cut tag with testnet activation at h=100; live-test on testnet for ~3 days; promote to mainnet activation tag once green.Community chat for live discussion: https://23skidoo.info/discord
Activation milestone (to add to WHERE_WE_LEFT_OFF.md)
BIP66 strict-DER: h=1,055,555 (bundled in v2.0.x-rc-bipsoft)References
src/script.cpp:252-305— existing legacyIsCanonicalSignature()src/main.cpp:968— mempool currently passesSTRICTENCsrc/main.cpp:2120-2121— block validation currently does NOT passSTRICTENCsrc/main.cpp:1934-1941— the "for now" hedge comment from 2014Live on mainnet. Activated on schedule at h=1,055,555 (2026-07-23), block
00000000438cf73291060c7c2caeadd568b7e432c192f4f44b3d84eafb157c7c.Strict-DER signature encoding is now enforced on every transaction — the malleability class BIP66 targets is closed on OFF. ~5,800 blocks validated under the rule with no forks and no rejected transactions observed in the wild; 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. This also unblocks the
[post-#33]gate on #47 (OpenSSL evacuation), tracked separately. Closing.