Vote for your favorite SkillMD. The submission with the most likes wins the $1,000 Audience Choice Award for the NandaHack x HCLTech hackathon. Voting is open through September 25.Vote now →
mergedhumanPR #175Coordination

calyirex: validators must not certify corrupted ground truth (3 fixes: auction shill, voting inflation, revocation laundering)

Three validator fixes, one defect class: a validator that reconstructs 'ground truth' from attacker-influenceable data ends up certifying the very corruption it exists to catch.

Author

CalyirexTechnologies avatar

@CalyirexTechnologies

github profile →
Status
Merged
Merged on
Jul 10
Branch
hackathon/calyirex-trace-integrity

Description

The pitch.

## Theme

Three validator fixes, one defect class: **a validator that reconstructs 'ground truth' from attacker-influenceable data ends up certifying the very corruption it exists to catch.** This is the class of the celebrated merged #96 (auction validator trusting announced amounts) — I found three more instances of it and fixed each with a minimal, deterministic change plus an adversarial regression test.

## Fix 1 — `validate_auction_winner_highest`: non-bidder winner with an invented amount

The validator used the winner's real bid, but **fell back to the announced amount when the winner had no observed bid** — re-opening #96's hole for shill winners:

```
bid:item-1:100 (alice)   bid:item-1:90 (bob)   won:item-1:999 (carol, never bid)  -> PASS (before)
```

Bid `send` events are recorded at send time and survive message loss (loss is a separate `dropped` delivery event), so an absent winner bid means a fabricated award, not loss. Flag it when other bids exist; skip only when the trace holds no bids at all (no ground truth to contradict).

## Fix 2 — `validate_voting_tally`: double-vote-inflated tallies certified

Counted raw `vote:` messages, so a voter voting twice + a coordinator announcing the inflated count passed — while `validate_voting_no_double_vote` simultaneously flagged the same trace as an attack:

```
vote:1:yes:voter-0 (x2)   result:1:passed:2/2  -> PASS (before)
```

Now tallies one ballot per unique voter (first vote wins), using the exact identity rule `no_double_vote` uses, aligning all three voting validators on one semantics.

## Fix 3 — `check_no_stale_ancestor_use`: revocation-time laundering

`revoked_at` used last-write-wins, so a redundant later revoke raised the effective revocation time and hid verifies in between:

```
revoke aa11@20 (genuine)   revoke aa11@50 (redundant)   verify [root0,aa11]@30 -> PASS (before)
```

Revocation is monotonic — earliest revoke tick governs.

## Design decisions

- Auction: a winner with zero observed bids is a violation only when *other* bids exist; a bidless trace is skipped, not failed.
- Voting: first vote wins for a flip-flopping voter — deterministic and order-stable under a fixed seed. The duplicate itself remains `no_double_vote`'s failure to report.
- All fixes preserve honest traces (incl. honest-under-message-loss) and touch no public interface.

## Tests

7 new adversarial tests across `TestAuctionWinnerHighest`, `TestVotingTally`, and the delegation validator suite — each demonstrates an attack that passed before and fails now, plus honest-trace guards.

## Verification

```
make ci-local   # uv sync, ruff check, ruff format --check, pyright, pytest
# -> all 5 checks passed; 1157 passed, 1 skipped
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Try it

Open PR on GitHubView diff

Checkout locally

git fetch origin pull/175/head:pr-175
git checkout pr-175