Skip to content

K0 Fix B1: consensus min-ring-4 floor for NORMAL/BURN txs (fixes #81) - #82

Closed
liqdmetal wants to merge 4 commits into
DEROFDN:community-devfrom
liqdmetal:fix/k0-min-ring4
Closed

K0 Fix B1: consensus min-ring-4 floor for NORMAL/BURN txs (fixes #81)#82
liqdmetal wants to merge 4 commits into
DEROFDN:community-devfrom
liqdmetal:fix/k0-min-ring4

Conversation

@liqdmetal

@liqdmetal liqdmetal commented Aug 22, 2026

Copy link
Copy Markdown

Summary

Hard-fork proposal: from a fixed height, reject NORMAL/BURN transactions with ringsize < 4 at consensus. Ringsize-2 txs expose the signer on-chain by design — the ring is just [sender, receiver] and the proof's parity bit identifies the sender uniquely (Extract_signer). Mainnet baseline: ~57% of txs ship at ringsize 2.

The privacy floor of 4 (sender + receiver + 2 decoys) is the smallest meaningful anonymity set.

This PR (Fix B1)

  • config/config.goK0_MIN_RING4_HEIGHT (mainnet 7,600,000 post-HF3; testnet 0 = apply at genesis)
  • blockchain/transaction_verify.goK0RingSizeFloorReject(): NORMAL/BURN ringsize < 4 rejected post-gate, keyed off the current chain tip at verification time
  • blockchain/k0_ringsize_test.go — 3 rule tests (post-gate reject, SC_TX exemption, pre-fork unaffected)
  • blockchain/wargame_k0_bypass_test.go + wargame_k0_sctxtype_test.go — 4 wargame regression tests pinning the two hardening fixes below

Exemptions: SC_TX / coinbase / registration / premine. SC owner-gated calls still require ringsize 2 today because they use SIGNER() — forcing a higher ring would break them until the verify_sig intrinsic (K0 Fix C) lands. This is explicit in the code comments.

Hardening (wargame findings, now in the code)

Two bypasses were found and closed before this PR shipped:

  1. Fork-boundary bypass (closed). The original rule keyed off tx.Height, which is attacker-settable (walletapi/transaction_build.go:37). Since TX_VALIDITY_HEIGHT = 11 lets a tx reference a block up to 11 back, a ring-2 NORMAL tx pinned to a pre-fork block would dodge the gate for the first ~11 blocks after activation. The floor now keys off chain.Get_Height() at verification time — the window is closed.
  2. SC_TX type-lie bypass (closed). TransactionType is an attacker-set header field (transaction.go:316), and process_transaction_sc treats an SC_TX with no SCACTION in SCDATA as a no-op (transaction_execute.go:267,291). A ring-2 NORMAL transfer stamped SC_TX used to dodge the SC_TX exemption for free. Now any SC_TX that does not carry SCACTION is re-stamped NORMAL and meets the floor.

Both are pinned by wargame regression tests (TestWargame_K0FloorNoPreForkDodge, TestWargame_K0FloorSC_TXTypeLieBypass, plus the execute-path no-op case).

Design notes

  • Dust threshold: recommend an outright ban for all NORMAL/BURN (the fee floor already prices micro-txs); no value > dust carve-out.
  • HF3 whitelist interaction: the affected-tx whitelist (hardfork_fixes.go) covers pre-fork txs only, so no conflict — verified.
  • Fee impact: ringsize-16 txs are larger (fee = FEE_PER_KB × (len/16 + mult)); privacy has a price, documented.
  • No state-tree write: this is a pure rejection rule (no graviton/SC_META mutation), so there is no chain-split vector — all nodes above the fork height run the same rule, below it nothing changes.

Relationship to the other K0 PRs

  • Fix A (wallet warning, non-consensus, ships immediately) — separate PR, the wallet/API half.
  • Fix B2 (contract registry uses_signer) — follow-up: ringsize 2 only for genuinely owner-gated SC entrypoints.
  • Fix C (verify_sig intrinsic) — lets owner-gated contracts authorize at ringsize ≥ 4, removing the last ringsize-2 need.

This PR is deliberately scoped to the consensus floor only.


Branch: fix/k0-min-ring4 in the fork liqdmetal/derohe-improvements-by-liqdmetal. Carries the build fixes: re-vendored modules (vendor/modules.txt present) and the KickReader removal, so the tree builds from a fresh clone.

…hard fork)

From K0_MIN_RING4_HEIGHT onward, NORMAL/BURN transactions with ringsize
< 4 are rejected by consensus. Ringsize-2 txs expose the signer on-chain
by design (Extract_signer); the privacy floor of 4 = sender + receiver
+ 2 decoys is the smallest meaningful anonymity set.

- config/config.go: K0_MIN_RING4_HEIGHT (mainnet 7,600,000 post-HF3;
  testnet 0 = apply at genesis)
- blockchain/transaction_verify.go: k0RingSizeFloorReject() — NORMAL/BURN
  ringsize < 4 rejected post-gate; SC_TX/coinbase/registration/premine
  exempt (owner-gated SC calls still need ringsize 2 until the verify_sig
  migration, K0 Fix C)
- blockchain/k0_ringsize_test.go: 3 tests (post-gate reject, SC_TX
  exemption, pre-fork unaffected)

Mainnet baseline driving this: ~57% of txs ship at ringsize 2.
This is the hard-fork half of the K0 fix; the wallet warning (Fix A) is
the non-consensus half (separate PR).

Also carries the build-manifest fix (go.mod/go.sum): the current tree
does not build from a fresh clone (3-line go.mod, no vendor/modules.txt,
unresolvable jrpc2/readline pins).
@liqdmetal

Copy link
Copy Markdown
Author

Related PRs in this K0/DVM series

These are part of one coordinated package, deliberately split into independent, reviewable threads:

PR What Consensus Role
#80 Fix A — wallet ringsize-2 warning + PrivacyWarning RPC none (ships now) the non-consensus wallet/API half
#82 Fix B1 — consensus min-ring-4 floor for NORMAL/BURN hard fork the consensus enforcement
#84 DVM v9verify_sig, hash_to_point, pedersen_commit/verify_commit, asset_balance, ec_add hard fork (VM surface) the foundation for Fix C (anonymous owner auth at ringsize ≥ 4, replacing the last ringsize-2 need)

Ordering logic:

liqdmetal added a commit to liqdmetal/derohe-improvements-by-liqdmetal that referenced this pull request Aug 23, 2026
…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).
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).
1. Fork-boundary bypass: the floor keyed off tx.Height (attacker-settable,
   walletapi/transaction_build.go:37). TX_VALIDITY_HEIGHT=11 lets a tx
   reference a block up to 11 back, so right after the floor activates a
   ring-2 NORMAL tx pinned to a pre-fork block dodged the gate. Now keyed
   off the current chain tip (chain.Get_Height()).

2. SC_TX type-lie bypass: TransactionType is an attacker-set header field
   (transaction.go:316), and process_transaction_sc treats SC_TX with no
   SCACTION as a no-op (transaction_execute.go:267,291). A ring-2 NORMAL
   transfer stamped SC_TX dodged the SC_TX exemption for free. Now any
   SC_TX that does not carry SCACTION in SCDATA is re-stamped NORMAL and
   meets the floor.

Wargame regression tests pin both bypasses.
liqdmetal added a commit to liqdmetal/derohe-improvements-by-liqdmetal that referenced this pull request Aug 23, 2026
…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.
@liqdmetal

Copy link
Copy Markdown
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.

@liqdmetal liqdmetal closed this Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant