Files
zeekayandGitHub 6e46d242f6 Thinking Chains: chains (#6)
* dexvm: bind swap-settle authority+payout to authenticated escrow owner (CRITICAL escrow-theft fix)

The swap rail delegated per-taker refund-liveness + relay authority to the
unauthenticated RelayOrderTx sender with NO C-side owner binding, letting any
fee-paying tx name a victim's collateral ref and settle the locked collateral to
the attacker out of the shared seam reserve (cross-depositor theft).

Fix (load-bearing, derives authority from the already-authenticated cross-chain
object owner — the platformvm import/export model):
- state/state.go: escrow value is now owner(20)|asset(32)|amount(8); the owner
  leads. PutEscrow/GetEscrow carry it.
- atomic.go executeImport: persist the AUTHENTICATED importedOwner (read back from
  the consumed C->D shared-memory object) into the escrow.
- vm.go settleFromFills: a settle is authorized ONLY by the escrow's recorded
  owner (errSettleUnauthorized otherwise), and BOTH proceeds+refund legs export to
  that owner, never to the relay tx sender.
- txs/tx.go: RelayOrderTx.Verify now does present-gated secp256k1 authentication of
  From (Sign/SigningBytes/verifyFrom), wiring the dangling ErrInvalidSignature.
  From bears NO authority (authority is the escrow owner), so an unsigned relay is
  admissible and cannot escalate; a present-but-invalid signature is rejected.

Regression: redteam_escrow_owner_test.go proves the attacker is credited 0 even
with a valid own-key signature (defense in depth: escrow-owner bind holds
independent of the signature). Fixed a pre-existing off-by-one in
TestRED_FractionalFill (export asset offset). All dexvm tests pass (default +
-tags redteam, CGO_ENABLED=0 pure-Go).

Gated on red re-verify; NOT promoted to testnet/mainnet.

* aivm: A-Chain quorum-settlement engine (provider stake/slash, beacon selection, commit-reveal, settlement, receipt export, import committed C intent). LP-5300. NOTE: branched on swap-rail base; rebase to main on merge.

---------

Co-authored-by: zeekay <[email protected]>
2026-06-21 20:53:39 -07:00

46 lines
1.7 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package aivm
// quorum.go is the tally: group the revealed output_hashes and find the
// plurality. The quorum RULE (is the plurality >= threshold) and the payout /
// slash consequences live in settlement.go; this file is only the counting,
// kept separate so the agreement function is a pure, total, deterministic
// primitive.
import "github.com/luxfi/geth/common"
// plurality returns the most-common hash and its count. Deterministic tie-break:
// among hashes tied for the max count the big-endian-smallest hash wins, so
// every validator computes the identical result. (A tie cannot reach a strict-
// majority threshold anyway, but the tie-break keeps the function total.)
func plurality(hashes []common.Hash) (common.Hash, uint32) {
counts := make(map[common.Hash]uint32, len(hashes))
for _, h := range hashes {
counts[h]++
}
var best common.Hash
var bestN uint32
for h, c := range counts {
if c > bestN || (c == bestN && bytesLess(h, best)) {
best, bestN = h, c
}
}
return best, bestN
}
// tally reads the per-task revealer array and returns the revealers (in reveal
// order) with their revealed output_hashes. Bounded by N.
func tally(st QuorumState, taskID common.Hash) (revealers []common.Address, hashes []common.Hash) {
rc := revealCount(st, taskID)
revealers = make([]common.Address, rc)
hashes = make([]common.Hash, rc)
for i := uint32(0); i < rc; i++ {
op := common.BytesToAddress(st.GetState(slotHashIdx(nsRevealList, taskID, i)).Bytes())
revealers[i] = op
hashes[i] = st.GetState(slotHashAddr(nsReveal, taskID, op))
}
return revealers, hashes
}