Skip to content

[DO NOT MERGE] fix(commit-stake-v2): per-commitment verifier arbiter opt-in (THREAT_MODEL §8) - #1

Draft
Mnorbert87 wants to merge 2 commits into
mainfrom
fix/arbiter-griefing-optin
Draft

[DO NOT MERGE] fix(commit-stake-v2): per-commitment verifier arbiter opt-in (THREAT_MODEL §8)#1
Mnorbert87 wants to merge 2 commits into
mainfrom
fix/arbiter-griefing-optin

Conversation

@Mnorbert87

Copy link
Copy Markdown
Owner

What

Closes the §8 griefing vector: a staker-chosen sockpuppet arbiter could burn an
honest verifier's bonded slice at ≈ gas cost. The verifier must now approveArbiter
before any staker can name that arbiter over its bond — mirroring AgentBond's
setSlashAllowance consent model (the bonded party now consents to the JUDGE, not just
the enforcer). Revoke blocks only NEW commitments; existing ones are untouched.

  • slice leverage cap (verifierSlice <= 3 × (amount + feeDeposit + arbiterFee)): a dust
    stake can no longer lock a verifier's whole bond.

Tests

Full suite 88/88 green (was 79/79); 9 new regression tests in CommitStakeV2GriefFix.t.sol.
Halmos §7a spec unaffected — no routing math changed.

Why this is a Draft / NOT merged — INTENTIONAL

The deployed CommitStakeV2 (0x1f1C…8CA9) is exact-match verified on Arcscan. Merging +
redeploying would invalidate that verification this close to judging. This branch is the
proof the fix is real and green; the on-chain contract stays byte-identical. Theft is
already mathematically + symbolically excluded on main; this removes the residual griefing.

DO NOT MERGE without a deliberate redeploy + re-verify of the on-chain contract.
See THREAT_MODEL.md §8 for the full write-up.

Mnorbert87 and others added 2 commits June 19, 2026 21:58
…t griefing

Cold-audit finding (griefing, not theft): the §7a surplus burn proves a colluding
arbiter can never TAKE the slice, but a staker could still name an only-address-
distinct sockpuppet arbiter, have it overturn a CORRECT verdict, and BURN an honest
verifier's slice — profitless to the attacker (~gas) but harmful to the verifier.

Fix: a verifier must explicitly approve an arbiter address (approveArbiter) before a
staker may name it on a commitment that locks the verifier's bond. Mirrors AgentBond's
setSlashAllowance consent model — the bonded party now consents to the JUDGE, not just
the enforcer. Address-distinctness alone is no longer sufficient.

- new state: arbiterApproved[verifier][arbiter]; setter approveArbiter + event
- create() requires arbiterApproved[p.verifier][p.arbiter]
- revoke blocks NEW commitments only; existing ones keep their arbiter
- 7 regression tests; full suite 86/86 green (was 79/79)

NOT deployed: this branch is a proof the fix works without touching the verified
deployed source on main (arcscan exact-match preserved). Roadmap, per THREAT_MODEL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e grief-fix tests

Completes the §8 griefing fix begun with the per-commitment arbiter opt-in:
- Slice leverage cap: verifierSlice <= 3 × (amount + feeDeposit + arbiterFee)
  (SLICE_ABOVE_LEVERAGE_CAP) — a dust stake can no longer lock a verifier's whole
  bond behind one job. Defense-in-depth alongside the arbiter opt-in.
- THREAT_MODEL.md §8: documents the sockpuppet-arbiter griefing vector, the
  shipped-release mitigation (revocable allowance), the branch fix, and why it is
  not deployed (Arcscan exact-match preserved).
- Renamed regression suite -> CommitStakeV2GriefFix.t.sol (+2 slice-cap tests).
- Adjusted test_Create_RevertInsufficientVerifierBond to stay within the cap.

Full suite 88/88 green; Halmos §7a routing spec unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mnorbert87 pushed a commit that referenced this pull request Jun 19, 2026
#1

Makes the documented limitation discoverable on the default branch: §8 describes the
griefing vector, the deployed-release mitigation, and points to the implemented+tested
fix (Draft PR #1, not deployed to preserve Arcscan exact-match verification). README
trust-boundary note now links §8 and the PR. Deployed contract source untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mnorbert87 pushed a commit that referenced this pull request Jul 31, 2026
…tracts

The address sweep matched address strings, but a tx hash does not contain the address
of the contract it called, so it could not see that the cited transactions still pointed
at the replaced contracts. Classifying every cited hash by the target read back from the
chain found 17 of 32 on old contracts, including both burn transactions JUDGES.md leads
with, and the live state made it visible: the new CommitStakeV2, StreamPay and AgentBond
all read nextId() = 1, so a judge following the links saw an empty stack.

Re-ran all four CommitStakeV2 branches against the hardened contract. Terminal outcomes
read back from get(id): id 1 CleanPass, id 2 LivenessSlash, id 3 OverturnedToPass,
id 4 SilenceFail. Both surplus burns to 0x…dEaD reproduce the original amounts exactly,
1.50 USDC on the liveness branch and 1.45 on the overturn branch, confirmed from the
Transfer logs in the receipts rather than from the script's own output.

Re-ran the commerce scenario (obligation #5, stream #1) and the agent lifecycle
(obligation #6, stream #2) on the new AgentBond and StreamPay, and re-executed the
AgentBond.deposit leg of the CCTP flow. The two bridge legs are Base Sepolia and CCTP
transactions, unaffected by the redeploy, and are now labelled as such.

Two things the target-based sweep still could not see on its own:
- the two USDC.approve transactions target USDC, so they classified as current, but their
  spender argument named the old contracts. Both re-earned.
- agent/demo-agent.js built its provider without batchMaxCount, and the public Arc RPC
  rejects batched JSON RPC, so the "run it yourself" script failed on the first read with
  "missing revert data". Fixed; the run in the README is from the fixed script.

SEED_RESULT.json regenerated from the real run, with the superseded run recorded. The
commit-stake page gains the fourth branch and the four routing cases it was missing
(SilenceFail, SilencePass, UpheldPass, UpheldFail previously fell through to a blank row).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mnorbert87 pushed a commit that referenced this pull request Aug 2, 2026
…exist

Two claims on the deck were written against the pre-redeploy state and were
never re-measured after 2026-07-31. Both are corrected against the live chain,
not against the old text.

- Slide 6 claimed "AgentBond #7 was released by the enforcer, #8 was slashed
  and the creditor received the 2 USDC". Neither obligation exists on the
  deployed AgentBond (0x4383Ea48...51702c): nextObligationId() returns 7, so
  the ledger holds #1 through #6 only. A judge following the claim would have
  found nothing. Measured field by field instead: #1 Released 1.5 USDC,
  #2 Slashed 1.5 USDC, #3 Slashed 1.5, #4 Released 1.5, #5 Released 0.5,
  #6 Released 1.0. The story the slide tells is true, the identifiers and the
  amount were not.

  The same sentence also listed creditor and enforcer as separate addresses.
  On these obligations they are the same address, because the enforcer is
  CommitStakeV2 itself. Saying so is more informative than hiding it.

- Slide 8 said the param-bounds fix carries 109 tests. commit-stake-v2 runs
  125 today (forge 1.7.1, 226 across the four projects, 0 failed).

Both burn transaction hashes on slide 4 were re-checked on chain and are still
valid: status 0x1, both to the current CommitStakeV2 0xf3457ABf...af1474.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mnorbert87 pushed a commit that referenced this pull request Aug 5, 2026
Pre-submission hard audit round, two lanes: Forge on fresh-clone
reproducibility and on-chain truth, this one on claim-by-claim honesty.
Every number below was re-measured, not read back from a doc.

- use-case: the live page headlined "obligation #6 and stream #15" while
  its own linked transactions said #5 and #1. Chain: the Locked event
  carries id=5 and StreamPay.nextId() is 4, so #15 does not exist. Those
  were the pre-redeploy ids. The Acme address was already right.

- JUDGES: "48 and 25 tests" for agent-bond; forge test says 51. The
  08-05 sweep fixed the count in four places and missed this one, so the
  page whose whole argument is "run it yourself" shipped a number the run
  disagrees with. Suite total measured today: 51+25+28+125 = 229 green.

- verify-claims: that miss was the checker's, not the reader's. The count
  regex only sees a number sitting next to the word "tests", and in a
  coordinated list ("48 and 25 tests") only the last one does. Added the
  fourth spelling and proved it by mutation: 48 fails, 51 passes.

- GAS_PROFILE: the on-chain anchor was the creation tx of the pre-redeploy
  CommitStakeV2. Re-measured the live one (0xd26d8942): 3,279,071 gas at
  23.6 gwei, 0.0774 USDC actual. The table keeps 40 gwei as the
  conservative constant, so it overstates rather than flatters, and the
  old number stays as a dated footnote.

- x402-demo/run.sh: dies with a raw shell error when .env is absent.
  Guarded with the fix instead; mutation-tested, exit 1 and a usage line.

verify-claims.sh: 121 checks, all matched.

Still open, needs an on-chain write: the ERC-8004 tokenURI of both
identity NFTs points at the pre-rebrand Pages path, which 404s, while
the README claims it resolves to the agent metadata.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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