Files
dex/pkg
zeekayandHanzo Dev 7ca0c2c521 fix(dex): F9 — gate TxDeposit to a trusted bridge authority; close the infinite-money self-signed deposit
A TxDeposit MINTS ledger balance, but its authorization was the DEPOSITOR'S OWN
signature (verifyTxSignature required signer == the body's beneficiary), and
handleDeposit exposed it on the same public ZAP route as dex_place/dex_submit. So
any authenticated principal could self-sign a deposit and mint unlimited unbacked
balance, then withdraw it (redteam_unbacked_deposit_test proved 500B minted).

OPTION B (gate to the trusted bridge authority) — chosen over Option A (route all
deposits through the atomic import) because: (1) the trustless atomic import
(TxImport, atomic.go) is only wired in production (runtime + shared memory) and
credits a DIFFERENT account axis (crossChainAccount(C-address)) than the DEX
tx-auth account (Account16) — routing all deposits through it would require wiring
shared memory + reconciling account models across 17 test files; (2) tx.go:47
already documents TxDeposit as the bridge/proxy-credit path — this implements that
intent correctly (the bug was the missing authority check); (3) the trustless
atomic import remains UNCHANGED and available for cross-chain swaps. Both backing
mechanisms now coexist: trustless import for swaps, authority-gated deposit for
bridge-custodied funds.

The fix:
- VM gains depositAuthority (a CONSENSUS config param, vmConfig.DepositAuthority
  hex, decoded at Initialize). ZERO => no authority => EVERY TxDeposit rejected
  fail-closed (value then enters only via the backed atomic import).
- authorizeTx (the authoritative consensus gate): a TxDeposit is accepted ONLY when
  the recovered signer == depositAuthority; else rejected (no mint, no nonce burn).
- verifyTxSignature: a deposit no longer requires signer==beneficiary (the signer is
  the authority; the beneficiary is bound via the signed digest). Withdraw is
  UNCHANGED — still signer==beneficiary (a user withdraws their own funds).
- submitTx: boundary pre-screen rejects a non-authority deposit before the mempool.

Backing invariant enforced: Σ(ledger credits via TxDeposit) ≤ Σ(bridge-authorized
deposits); combined with the import's Σ(credits) ≤ Σ(consumed C->D objects), NO path
credits the ledger without backing. The authority gate also HARDENS RED #4: a
stranded/self-signed deposit that lands in a later block is rejected, so it cannot
mint; withdraw is untouched (debits own available, clamped — no burn).

TDD: TestRedteam_UnbackedSelfSignedDepositMints now PASSES (self-signed deposit
rejected at boundary + consensus; minted 0, withdrew 0). New
TestBackedAuthoritySignedDepositCredits proves the legitimate authority-signed
deposit credits + withdraws correctly. Test funding migrated to an authority-signed
helper (fundAcct/fundTx); auth_test nonce expectations updated (a funded account's
first own tx is nonce 0 — funding no longer consumes the user's nonce). ALL suites
green (go test ./... -count=1), go vet clean; the F1-F5 cross-arch execRoots + ledgers
still match byte-for-byte across arm64/amd64.

KNOWN FOLLOW-UPS (tracked): (a) chains/dexvm + precompile/dex are separate modules
that also encode the ZAP frame — they need the same exact-integer wire bump to
interoperate; (b) F7 — retract the inflated 12.76B/9.13B/5.60B "ord/s" GPU benchmark
numbers in ~/work/lux/papers (they measure all-cross synthetic fills/s ~71-85x
inflated; the paper's own text already caps a single GPU below 10^9 fills/s).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 10:07:12 -07:00
..