K0 Fix B2: uses_signer contract registry (fixes #90) - #91
Closed
liqdmetal wants to merge 6 commits into
Closed
Conversation
…T auto-detect) The consensus half of closing the ringsize-2 loophole for SC calls: a ringsize-2 SC_TX exposes the signer by design (the ring IS sender+receiver, parity selects the sender). This makes ringsize 2 structurally impossible for contracts that don't need it. Design (k0-fix-design.md Fix B2): - SC_META_DATA gains a NoSigner bit (high bit of the Type byte). The 33-byte wire format is UNCHANGED, so existing metadata stays valid and existing contracts default to uses_signer=true (preserving behavior). - At install (transaction_execute.go), the parsed contract AST is scanned for SIGNER() calls (dvm.ContractUsesSigner). No SIGNER() -> NoSigner bit set. Contract authors need no changes. - Consensus enforcement (transaction_verify.go): SC_TX + ringsize 2 + NoSigner-marked contract -> rejected with a clear error. Contracts that genuinely call SIGNER() keep ringsize 2 (owner-gated entrypoints) until the verify_sig migration (K0 Fix C, DVM v9 PR DEROFDN#84) removes that need. Tests (dvm/k0_b2_test.go): - TestContractUsesSigner: detects SIGNER(), no false positive - TestSCMetaNoSignerBit: bit set/clear, 33-byte round-trip, private+NoSigner coexistence Composes with the K0 package: Fix A (DEROFDN#80) warns, Fix B1 (DEROFDN#82) bans ringsize-2 NORMAL/BURN, Fix B2 bans ringsize-2 SC_TX for NoSigner contracts, Fix C (DEROFDN#84 verify_sig) removes the last legitimate ringsize-2 need. Also carries the build-manifest fix (go.mod/go.sum).
liqdmetal
force-pushed
the
feature/k0-fix-b2-uses-signer
branch
from
August 23, 2026 03:26
1fe892b to
a909b6b
Compare
Regenerates vendor/ via go mod tidy + go mod vendor (adds missing vendor/modules.txt) so the tree builds from a fresh clone without -mod=mod. Deps unchanged.
community-dev base calls l.Operation.KickReader(), which no published chzyer/readline implements (v1.5.1) -> wallet-cli fails to compile from a fresh clone. Replace with the same UI shim used on the main fork branch: _ = l.Operation (read-unblock helper, wallet-only, not consensus).
…jection Two gaps closed on top of the B2 NoSigner bit: 1. IsPrivate(): the private-SC check was meta.Type == 1, which breaks for a private contract that is also NoSigner (Type == 0x81 != 1) — the InitializePrivate entrypoint would never run. IsPrivate() masks the B2 bit so private + NoSigner coexist in one Type byte. 2. SC_INSTALL at ringsize 2 was unchecked — only SC_CALL read the stored NoSigner bit. An install of a no-SIGNER contract at ringsize 2 now parses the code in SCDATA and rejects (k0SCInstallRing2Reject), closing the deployer-exposure hole. Adds 4 blockchain consensus-rule tests + 7 dvm tests (detection, bit round-trip, 33-byte legacy compatibility, private+NoSigner coexistence).
This was referenced Aug 23, 2026
…hardening) Wargame finding: the B2 NoSigner bit was set unconditionally at install and the SC_INSTALL ring-2 rejection fired unconditionally in verify. SC_META bytes feed the chain state root, so a B2 node setting/rejecting differently than a legacy node pre-fork makes the two node types disagree on block validity -> INSTANT CHAIN SPLIT. Fix: both the install-path SetNoSigner and the verify-path SC_INSTALL rejection are now gated on MAJOR_HF3_HEIGHT (the post-HF3 hard-fork window). Pre-fork: byte-identical behavior, no divergence. Post-fork: all nodes run the same rule. SC_CALL is naturally safe because the bit can only appear post-fork (install gated).
…EIGHT) The B2 SC rules were gated on MAJOR_HF3_HEIGHT (7,504,640) while the B1 floor (PR DEROFDN#82) activates at K0_MIN_RING4_HEIGHT (7,600,000) — a ~95k-block window where B2 would reject ring-2 SC installs/calls while ring-2 NORMAL txs were still legal. Both now share K0_MIN_RING4_HEIGHT (mainnet 7,600,000 post-HF3, testnet 0), so the floor and the SC-signer rules turn on together at one activation height.
This was referenced Aug 24, 2026
Author
|
Superseded by PR #121 (the consolidated K0 privacy package). Closing in its favour — same code, but as one reviewable 17-file PR with no vendored-dependency noise. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hard-fork proposal: the consensus half of closing the ringsize-2 loophole for SC calls. A ringsize-2 SC_TX exposes the signer by design (the ring IS sender+receiver; the proof's parity bit identifies the sender). This makes ringsize 2 structurally impossible for contracts that don't need it — without requiring any contract-author changes.
The mechanism
SC_META_DATAgains aNoSignerbit — the high bit of the existingTypebyte. The 33-byte wire format is unchanged, so existing metadata stays valid and existing contracts default touses_signer=true(preserving behavior, exactly as the K0 design specifies).SIGNER()calls (ContractUsesSigner, case-insensitive to match the DVM's dispatch). If none are found, theNoSignerbit is set. No contract author changes needed — a contract that never callsSIGNER()is automatically eligible for ringsize ≥ 4.NoSigner-marked contract → rejected with a clear error. Contracts that genuinely callSIGNER()keep ringsize 2 (owner-gated entrypoints) until theverify_sigmigration (K0 Fix C) removes that need.IsPrivate()masks the bit — the private-SC check (meta.Type == 1) would have broken for a private contract that is also NoSigner (0x81 ≠ 1).IsPrivate()masks the B2 bit so private + NoSigner coexist in one Type byte, andInitializePrivatestill dispatches correctly.Why this matters
The ringsize-2 SC call is the last legitimate reason the protocol needs ringsize 2. Once
verify_sig(DVM v9, PR #84) lands, owner-gated contracts can authorize at ringsize ≥ 4 via signature-in-payload, and ringsize 2 becomes fully extinct.Changes
dvm/sc.goSC_META_DATANoSigner bit (high bit of Type byte) +ContractUsesSignerAST scan +IsPrivate()maskblockchain/transaction_execute.goblockchain/transaction_verify.godvm/k0_b2_test.goblockchain/k0_b2_consensus_test.goHardening (wargame findings, now in the code)
k0SCInstallRing2Rejectnow parses the SCDATA code, scans forSIGNER(), and rejects a ring-2 install of a no-SIGNER contract.blockchain.gosc_change_cache→ tree hash). If a B2 node set the bit (or rejected a ring-2 install) before the fork while a legacy node did not, the two node types would produce different state roots / disagree on block validity → instant chain split. Both the install-pathSetNoSignerand the verify-path SC_INSTALL rejection are therefore gated on the hard-fork height (K0_MIN_RING4_HEIGHTon this branch — same activation height as the B1 floor). Pre-fork: byte-identical behavior, zero divergence. Post-fork: all nodes run the same rule. SC_CALL is naturally safe because the bit can only appear post-fork (install is gated), so pre-fork contracts always readNoSigner=false.Tests
TestContractUsesSigner— detectsSIGNER()(incl. lowercase), no false positive on SIGNER-free contractsTestSCMetaNoSignerBit— bit set/clear, 33-byte serialization round-tripTestSCMetaPrivateAndNoSignerCoexist— private + NoSigner in one byteTestSCMeta33ByteCompatibility— legacy meta still decodesTestK0SCInstallRing2Reject_*— no-SIGNER install rejected at ring 2, SIGNER contract allowed, no-SCDATA allowed, case-insensitiveRelationship to the K0 package
verify_sigremoves the last ringsize-2 needOpen questions (for review)
OPTION NOSIGNERpragma could be added later. The scan matches the DVM's own dispatch (case-insensitive, whole-lineREM/;comment handling — a Rust reimplementation of the parser was differentially tested against this rule and had a mid-line-comment bug fixed as a result).K0_MIN_RING4_HEIGHT(mainnet 7,600,000, testnet 0). This branch carries its ownK0_MIN_RING4_HEIGHTconfig field (mainnet 7,600,000 / testnet 0) and both the install-pathSetNoSignerand the verify-path SC_INSTALL rejection key off it — so B2's rejection and B1's floor turn on together at one height. (Earlier drafts gated onMAJOR_HF3_HEIGHT7,504,640, which would have activated B2 ~95k blocks before B1 — that window is closed.)dvm_uses_signer, 12 vectors); the floor rule is pinned too (k0_floor, 159 vectors). The NoSigner meta-bit round-trip would benefit from the same treatment when the fork is green-lit.Branch:
feature/k0-fix-b2-uses-signerin the forkliqdmetal/derohe-improvements-by-liqdmetal. Carries the build fixes: re-vendored modules (vendor/modules.txtpresent) and theKickReaderremoval.