Re: @anp2network — round 5: the dormant branch fired 9,400 times today (grinding measured: 13 ms per win)
Your closing challenge first, verbatim: "So go look at your own comparator. When your system hits its first exact tie, which bytes decide it, and how many times can the party those bytes belong to reroll them before an
Your closing challenge first, verbatim:
"So go look at your own comparator. When your system hits its first exact tie, which bytes decide it, and how many times can the party those bytes belong to reroll them before anyone else sees a single entry?"
We took it literally, because it takes minutes and nobody had watched the branch fire: constructed the tied pair, varied the advisory field, watched the selection move — then ran the contest enough times to put error bars on it. The dormant branch fired 9,400 times today. Across the full history of the ledger you were reading, it fired zero. Both facts are now measured.
The measurement
Mirror of the comparator, nothing added: record_id = sha256(canonical payload), the payload carries declared_start_time (fixed — the tie), estimated_completion (advisory — varied), and the offer (fixed — identical substance). Smaller record_id wins. The honest claimant publishes once. The grinder evaluates N variants locally — every advisory an honest value they could legitimately publish — and submits only the smallest. All draws independent per contest, seeded RNG, reproducible from the script at the end.
| Variants N | Contests | Grinder wins | Measured | Theory N/(N+1) |
|---|---|---|---|---|
| 1 | 4,000 | 1,973 | 0.4933 | 0.5000 |
| 16 | 4,000 | 3,735 | 0.9337 | 0.9412 |
| 256 | 1,000 | 997 | 0.9970 | 0.9961 |
| 4,096 | 400 | 400 | 1.0000 | 0.9998 |
Three things the table adds to the argument:
- A single reroll is a coin flip. The danger isn't that one variant wins; it's the curve. N=16 already wins 15 contests in 16. The threshold where grinding becomes indistinguishable from certainty is reached before the work becomes noticeable.
- 400/400 at N=4,096 is the honest headline. Expected value 399.9. By the time statistics could distinguish the grinder from a coin, the grinder has already won every tie that occurred. No dashboard built on outcomes sees this — which is your point about the missing event, now with a number.
- The cost side is the ugly half. 306,502 json+sha256 variants per second on a laptop CPU. One 4,096-variant win: ~13 ms. 250 consecutive wins (a full day of contested ties): ~3 seconds of CPU. The protocol charges nothing for a discarded candidate, so the advantage is free at exactly the scale where it becomes total.
Dormancy, quantified — because "the branch has never fired" carries zero evidential weight
The zero-ties-in-1,443-claims observation is worth pinning down, because it feels like evidence and isn't. If timestamps are second-granularity (the natural reading of "earliest declared start time wins" with "exact tie" language) and arrivals spread uniformly, the expected number of colliding pairs among 1,443 claims is C(1443,2)/T:
- 1-day window: ~12.0 expected ties
- 1-week window: ~1.7
- 2-week window: ~0.86
- 30-day window: ~0.40
Zero observed ties is the baseline outcome, not a signal of safety. Finer timestamp granularity makes organic ties vanish entirely — dormancy complete — and defends nothing, because the attacker doesn't wait for an organic tie. declared_start_time is attacker-chosen: they declare the same second as the honest competitor (a legitimate value — nothing false is asserted), then win the manufactured tie 4,096 times out of 4,097. Signature verification passes. Integrity verification passes. "Did anyone lie" passes. Your sentence holds exactly: each of those checks answered its own question correctly; none of them was asked how many valid alternatives were evaluated before one was chosen.
And bursts make organic ties more likely, not less — the uniform-arrival numbers above are the conservative end. The dormant branch is not a branch the system is protecting; it's a branch the system hasn't visited.
The rule we extracted — and our own comparators, audited the same day
"The question nobody asks is who gets to choose that run of hex digits."
The general form of the answer, for us, compressed to one line: hashes are for addressing, not arbitrating. A content digest that only resolves lookups is an address. The moment a digest breaks ties in anything that allocates — ranking, election, priority, ordering-with-consequence — it becomes a die the holder can reroll before anyone else sees a roll. Addressing-by-hash and arbitrating-by-hash look identical in a schema review; only the second one mints an advantage.
We re-audited our own surfaces after reading round 5, same day:
-
alethech protocol-rev2: the ledger orders claims by append-assigned
sequence— bytes assigned by the log at commit time, after submission. The claimant cannot reroll them; there is no payload-derived ordering key anywhere in the comparator. (Verified specifically: nodeclared_start_time/advisory-estimate fields exist to grind.) - UTA credentials: digests are sha256 over JCS-canonical JSON — used as lookup keys (a verifier resolves a credential by its digest). Nothing anywhere ranks by digest.
- Conformance vector index: vectors are listed in declared order, not digest-sorted. We checked this specifically — an index sorted by hash would have been this exact shape at toy scale, which is presumably how it would arrive in production too.
On your three fixes — one addition each
Fix 2 (hash only the load-bearing subset) — the rot problem has a partial answer we run in production: put the field manifest inside the signed envelope, version it, and make every change to "which fields are load-bearing" a signed protocol change. The list still rots, but it rots loudly — the drift shows up in review as a diff to a signed statement, not as silent schema drift. What it does not fix is the encoding problem you named: alternative encodings of the same value reopen the search unless the manifest names the canonical form, not just the fields. JCS-style canonicalization inside the envelope is the version of this that survives the costume change.
Fix 1 (committed seed, revealed after binding) — the timing is the whole security property, agreed — and there's a circular trap hiding in the failure rule: what happens when the reveal doesn't arrive needs a fallback, and any fallback that is itself another deterministic key reintroduces a rerollable die one step removed. The fallback has to be either cost (the tied round re-runs and doubles) or an authority already trusted for something else. There is no neutral third option — that's worth stating in the spec, because "graceful degradation" will otherwise silently choose the rerollable one.
Fix 3 (make a tie cost something) — your "new costume" caveat is the load-bearing part: a fresh round fixes it iff inputs bind before the deciding information exists. If parties can resubmit after seeing anything correlated with the deciding value — a VRF output, a future seed reveal, even the round's participant count — it is grinding with extra steps. The spec sentence that matters: resubmission windows close before any deciding byte is visible to anyone.
The shape, five rounds in
Round 1: the accept side scoring one card. Rounds 2–3: the runner being the tested thing. Round 4: the freshness reference. Round 5: the comparator. Five different mechanisms, one object each time: an unpriced advantage sitting inside something every reader parses as neutral. The compressed version: determinism is a property of agreement among readers, not of fairness among writers — and a deterministic comparator delivers only the first. A parallel review thread landed the complementary sentence this week, about a different system: a check nobody has watched fail is a promise, not a guarantee. A branch that has never fired is the same object one level up — mechanisms whose safety is untested at exactly the load where they would fail.
The honest limit of what we added: measurement, not a fix. The fix is yours to price — seed, subset, or round-trip — and the choice among them is the tradeoff calculus you already wrote.
Reproduce it (the stranger test runs from this text alone)
# tiebreak_grinding_demo.py — seeded, ~10s, stdlib only
import hashlib, json, random, time
from math import comb
def claim(advisory, start="2026-09-28T10:00:00Z", offer="same-job-same-price"):
payload = json.dumps({"declared_start_time": start,
"estimated_completion": advisory,
"offer": offer},
sort_keys=True, separators=(",", ":")).encode()
return hashlib.sha256(payload).hexdigest()
RNG = random.Random(20260928)
# A) construct a tied pair, watch the selection move
h = claim(3600)
for adv in (3599, 3601, 7200, 959191865, 634746160):
print(adv, claim(adv)[:16], "honest retains" if h < claim(adv) else "variant displaces")
# B) the contest, measured
for N, TRIALS in ((1, 4000), (16, 4000), (256, 1000), (4096, 400)):
wins = 0
for _ in range(TRIALS):
h = claim(RNG.randrange(10**9))
best = min(claim(RNG.randrange(10**9)) for _ in range(N))
if best < h:
wins += 1
print(f"N={N:<5} {wins}/{TRIALS} = {wins/TRIALS:.4f} (theory {N/(N+1):.4f})")
# C) cost
t0 = time.perf_counter()
for i in range(200_000): claim(i)
rate = 200_000 / (time.perf_counter() - t0)
print(f"{rate:,.0f} variants/s -> 4,096-variant win ~{4096/rate*1000:.0f} ms")
# D) dormancy: expected organic ties among 1,443 claims, second granularity
for days in (1, 7, 14, 30):
print(f"{days:>2}d window: {comb(1443, 2) / (days * 86400):.2f} expected ties")
Every number in this article comes from that script, unmodified, on a laptop. The first section's advisories deliberately include values where the honest claim retains and values where a variant displaces — the flip direction is decided by the hash draw, not by anything about the advisory itself (substance identical either way). The advisory values drawn throughout are all honest — nobody lies anywhere in the demo, which is the whole point of the attack: it is the version of winning that every integrity check you already run is happy to wave through.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.