Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
80648df
test(zeronym): model the divert protocol in Quint and check it in CI
Sep 23, 2026
4a456c9
test(zeronym): scope the divert invariants by hub honesty; verdict-aw…
Sep 23, 2026
2e90d6f
ci(zeronym): pin the spec job's checkout and give it a read-only token
Sep 23, 2026
262bd78
test(zeronym): assert the lying-hub noQueuedBytes counterexample
Sep 23, 2026
5f1b3d8
test(zeronym): record a non-conforming indexer's zero reply
hu55a1n1 Oct 6, 2026
82eb22b
test(zeronym): check queue-hit pending on an honest hub
hu55a1n1 Oct 6, 2026
d303058
test(zeronym): lock the lookup arms with runs
hu55a1n1 Oct 6, 2026
16491f2
test(zeronym): let a hostile hub hide a queued migration
hu55a1n1 Oct 6, 2026
8845332
test(zeronym): record honest status regression
hu55a1n1 Oct 6, 2026
62ec0d9
test(zeronym): miss a queued body that has no txid
hu55a1n1 Oct 6, 2026
c22aa12
Revert "test(zeronym): miss a queued body that has no txid"
hu55a1n1 Oct 6, 2026
46e7082
test(zeronym): count a hostile not-found by the reply's disposition
hu55a1n1 Oct 6, 2026
91e1382
Reapply "test(zeronym): miss a queued body that has no txid"
hu55a1n1 Oct 6, 2026
ee1fde5
test(zeronym): drop the second hub that never sees a submit
hu55a1n1 Oct 6, 2026
fb45f02
test(zeronym): remember a broadcast after the queue copy is gone
hu55a1n1 Oct 6, 2026
89f2058
test(zeronym): deliver the reply the hub actually queued
hu55a1n1 Oct 6, 2026
bdad505
test(zeronym): ask the indexer when a queued body has no txid
hu55a1n1 Oct 6, 2026
cb5644a
test(zeronym): refuse an unparseable body in the found arm
hu55a1n1 Oct 6, 2026
6c485d4
test(zeronym): add the spells and message soup for the protocol spec
hu55a1n1 Oct 6, 2026
26de07f
test(zeronym): add the protocol vocabulary and wire layer
hu55a1n1 Oct 6, 2026
f0406bf
test(zeronym): name the draining refusal apart from the draining phase
hu55a1n1 Oct 6, 2026
7eea1c6
test(zeronym): specify the hub and indexer as input-output functions
hu55a1n1 Oct 6, 2026
369b070
test(zeronym): specify the shim as an input-output function
hu55a1n1 Oct 6, 2026
84e06c3
test(zeronym): add the protocol state machine, its properties and con…
hu55a1n1 Oct 6, 2026
461c6dc
test(zeronym): script the witnesses, the known gaps and the trust matrix
hu55a1n1 Oct 7, 2026
1dcd8fa
test(zeronym): pin the early free-running flush that spends the next …
hu55a1n1 Oct 7, 2026
de778a7
test(zeronym): add the gate for the protocol spec
hu55a1n1 Oct 7, 2026
ae11e60
test(zeronym): document the protocol spec and what it found
hu55a1n1 Oct 7, 2026
1d5d6fa
test(zeronym): run the protocol spec gate in CI
hu55a1n1 Oct 7, 2026
ed95bd8
test(zeronym): judge offers at the verdict, make the hub poll each bl…
hu55a1n1 Oct 7, 2026
8834382
test(zeronym): gate the verdict-time claim, the slow flight and the r…
hu55a1n1 Oct 7, 2026
55d8808
test(zeronym): record the review's corrections and the re-derived ver…
hu55a1n1 Oct 7, 2026
83133e3
test(zeronym): bind drainIsFinal to honest hubs and pin a Byzantine h…
hu55a1n1 Oct 7, 2026
7c41c7c
test(zeronym): import the properties unqualified so the specification…
hu55a1n1 Oct 7, 2026
a9c35ca
test(zeronym): draw tips and clock estimates from constant ranges
hu55a1n1 Oct 7, 2026
c34647b
test(zeronym): add a wrapper that checks one invariant exhaustively w…
hu55a1n1 Oct 7, 2026
faf489c
test(zeronym): add the hub machine with named inits and record what T…
hu55a1n1 Oct 7, 2026
1fca688
test(zeronym): give the hub machine its Byzantine roles and port the …
hu55a1n1 Oct 7, 2026
9efa237
test(zeronym): gate the hub specification with TLC and record finding 8
hu55a1n1 Oct 7, 2026
87ab195
test(zeronym): forget timeliness when the hub goes down and pin the c…
hu55a1n1 Oct 7, 2026
e2649ad
test(zeronym): drop the schedule rows from the protocol gate now the …
hu55a1n1 Oct 7, 2026
6678b59
test(zeronym): remove the HTTP transport from the protocol spec
hu55a1n1 Oct 7, 2026
91f5ac3
test(zeronym): remove the Byzantine shim from the protocol spec
hu55a1n1 Oct 7, 2026
34d926a
test(zeronym): check one hub in the protocol spec
hu55a1n1 Oct 7, 2026
07a5335
test(zeronym): remove the capacity and size refusals
hu55a1n1 Oct 7, 2026
745401d
test(zeronym): drop the slower free-running clock variant
hu55a1n1 Oct 8, 2026
44d02f9
test(zeronym): check the two-state properties over every reachable hu…
hu55a1n1 Oct 8, 2026
bdb640b
test(zeronym): add the abstract hub and the abstraction lemma
hu55a1n1 Oct 8, 2026
f500dd9
test(zeronym): swap in the abstract hub in the protocol spec
hu55a1n1 Oct 8, 2026
e541e77
test(zeronym): remove heights and the block clock from the protocol spec
hu55a1n1 Oct 8, 2026
1d1d24b
test(zeronym): drop the unread step label from the protocol spec
hu55a1n1 Oct 8, 2026
4dfe8db
test(zeronym): give the protocol spec named inits and fold it into on…
hu55a1n1 Oct 8, 2026
1a14405
test(zeronym): record one TLC measurement of the protocol spec
hu55a1n1 Oct 8, 2026
c783fb9
test(zeronym): cut the simulation rows the scripted runs already carry
hu55a1n1 Oct 8, 2026
dfd4171
test(zeronym): cut the disclosure step; Byzantine components leak thr…
hu55a1n1 Oct 8, 2026
f162913
test(zeronym): cut the third party's own payloads
hu55a1n1 Oct 8, 2026
28d0b99
test(zeronym): cut the size class and the well-formedness invariant
hu55a1n1 Oct 8, 2026
c40ea72
test(zeronym): check universeCoversLies and read messages as records
hu55a1n1 Oct 8, 2026
da3f129
test(zeronym): redo the mutation table on the final protocol spec
hu55a1n1 Oct 8, 2026
0720a33
test(zeronym): final README pass and the measured CI budget
hu55a1n1 Oct 8, 2026
154bde4
test(zeronym): record review round 2 in the README and give TLC its o…
hu55a1n1 Oct 8, 2026
8950187
test(zeronym): forget offers with the run, link realisations to their…
hu55a1n1 Oct 8, 2026
21fe2ae
test(zeronym): a narrower step for G4's antecedent and a floor on eac…
hu55a1n1 Oct 8, 2026
d50ecd3
test(zeronym): drop the ack guarantee, the Byzantine hub's false ack …
hu55a1n1 Oct 8, 2026
53b9b00
test(zeronym): drop the all-payload offer guarantee and check the tim…
hu55a1n1 Oct 8, 2026
48cd163
test(zeronym): add a threat model to the spec README and record what …
hu55a1n1 Oct 8, 2026
0abd651
test(zeronym): say in the README that the Byzantine hub's ack is mode…
hu55a1n1 Oct 8, 2026
14aa25f
test(zeronym): remove the divert model now the protocol spec covers e…
hu55a1n1 Oct 8, 2026
01775f4
test(zeronym): assert each guarantee before the Byzantine step in pla…
hu55a1n1 Oct 8, 2026
e89743f
test(zeronym): move the Quint spec to zeronym/spec/quint
hu55a1n1 Oct 8, 2026
2b60d77
test(zeronym): restructure the spec README around the threat model, g…
hu55a1n1 Oct 8, 2026
f21e708
test(zeronym): trim the spec README's guarantees section and record t…
hu55a1n1 Oct 8, 2026
86f2522
test(zeronym): replace the spec README's scope section with what is o…
hu55a1n1 Oct 8, 2026
edd181c
test(zeronym): give the README's configurations headings and fold the…
hu55a1n1 Oct 8, 2026
08f8942
test(zeronym): drop ids the README no longer defines from the spec's …
hu55a1n1 Oct 8, 2026
0cd9bdc
test(zeronym): say in the README which known gaps are by design and w…
hu55a1n1 Oct 8, 2026
e3c361b
test(zeronym): fail a row killed before it finishes, fetch Apalache o…
hu55a1n1 Oct 8, 2026
fb67834
test(zeronym): fetch Quint and its evaluator once before rows run in …
hu55a1n1 Oct 8, 2026
a806432
test(zeronym): classify each finding in the README and correct what a…
hu55a1n1 Oct 8, 2026
6cdd82e
test(zeronym): give the README's code bugs a severity and record the …
hu55a1n1 Oct 8, 2026
b24ee91
test(zeronym): define the spec README's terms at first use, link its …
hu55a1n1 Oct 8, 2026
5a66d3f
test(zeronym): take twins and the third wire height out of the protoc…
hu55a1n1 Oct 8, 2026
af8a29a
test(zeronym): drop the Byzantine hub from the hub spec
hu55a1n1 Oct 8, 2026
4745d4e
test(zeronym): state the verdict-time guarantee as an unchecked corol…
hu55a1n1 Oct 8, 2026
1127e75
test(zeronym): restore the precise argument for G6b under a Byzantine…
hu55a1n1 Oct 8, 2026
20edd12
test(zeronym): check the quiet-run wallet observations in simulation
hu55a1n1 Oct 12, 2026
d18228b
test(zeronym): model the #85 hub admission and indexer-read fixes, wi…
hu55a1n1 Oct 15, 2026
941c879
Revert "test(zeronym): model the #85 hub admission and indexer-read f…
hu55a1n1 Oct 15, 2026
130f3c9
test(zeronym): model the #85 hub admission and indexer-read fixes
hu55a1n1 Oct 15, 2026
48363f9
test(zeronym): restore the Byzantine G4 and G5 runs and align the REA…
hu55a1n1 Oct 15, 2026
4e442ac
docs(zeronym): update the README for the code after #85
hu55a1n1 Oct 15, 2026
3d4558f
docs(zeronym): drop the state diagrams from the spec README
hu55a1n1 Oct 21, 2026
9207a97
docs(zeronym): define an honest shim and hub by build and attestation
hu55a1n1 Oct 21, 2026
cf943df
docs(zeronym): state the spec's scope before its threats and guarantees
hu55a1n1 Oct 21, 2026
5cc2ed3
docs(zeronym): scope covert channels out of the spec
hu55a1n1 Oct 21, 2026
94a07e3
docs(zeronym): state G1 by message type
hu55a1n1 Oct 21, 2026
735e018
docs(zeronym): replace trust wording with reliance in the spec
hu55a1n1 Oct 21, 2026
8ec5152
docs(zeronym): narrow G1 to forwarded transactions
hu55a1n1 Oct 21, 2026
911b178
docs(zeronym): mark K4 accepted in the spec README
hu55a1n1 Oct 23, 2026
d7dd4f8
Merge remote-tracking branch 'origin/main' into test/zeronym-divert-q…
hu55a1n1 Oct 23, 2026
e18b149
docs(zeronym): re-point the spec's code line references at main after…
hu55a1n1 Oct 23, 2026
74b39e7
docs(zeronym): pin the spec README to main at 2bae0a6
hu55a1n1 Oct 23, 2026
c52ce9d
test(zeronym): model the #92 hub tip rule in the spec
Oct 26, 2026
c89c548
Merge pull request #93 from ShieldedLabs/spec/zeronym-quint-tip-guard
hu55a1n1 Oct 27, 2026
ecb5e40
test(zeronym): model the #94 transparent-only divert in the spec
Oct 27, 2026
ae31f61
Merge pull request #95 from ShieldedLabs/spec/zeronym-quint-tex-leg
hu55a1n1 Oct 28, 2026
832dbbb
docs(zeronym): scope the spec's source-IP threat to the hub and the n…
Oct 28, 2026
e3c7e1c
docs(zeronym/spec): add the hub-hop and lookup threats from the third…
Oct 28, 2026
aeb70a9
spec(zeronym): record the free junk burst as finding 13
Oct 28, 2026
55d0db2
docs(zeronym): follow #96's review in the spec's T14 row
Oct 28, 2026
6ff560d
Merge pull request #97 from ShieldedLabs/spec/zeronym-quint-ip-join
sl-mrh Oct 28, 2026
d05175f
Update zeronym/spec/quint/README.md
sl-mrh Oct 30, 2026
182cc8b
Merge pull request #103 from ShieldedLabs/spec/zeronym-quint-junk-fill
sl-mrh Oct 30, 2026
c28a4bc
Merge remote-tracking branch 'origin/test/zeronym-divert-quint' into …
Oct 31, 2026
08a8abf
Merge pull request #101 from ShieldedLabs/claude/spec-hub-substitution
hu55a1n1 Nov 1, 2026
1a51836
spec(zeronym): rename the third party to observer and merge the role …
hu55a1n1 Nov 3, 2026
a84df5c
docs(zeronym): simplify the two-spec summary and drop the component t…
hu55a1n1 Nov 3, 2026
403348d
docs(zeronym): move README links to reference definitions
hu55a1n1 Nov 3, 2026
b7b2154
docs(zeronym): name the delivery lag constant MAX_DELIVERY_LAG
hu55a1n1 Nov 3, 2026
73baa16
docs(zeronym): fold the one-hub scope into Not modelled and trim assu…
hu55a1n1 Nov 3, 2026
6d44173
docs(zeronym): merge T12 into T5, trim Not modelled and add shim futu…
hu55a1n1 Nov 3, 2026
b041ff6
spec(zeronym): model the burn and the nullifier refusal, keyed by txid
hu55a1n1 Nov 4, 2026
0c114e3
spec(zeronym): fail a gate row on a nonzero exit and start an abstrac…
hu55a1n1 Nov 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/zeronym-guards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,51 @@ jobs:
- name: Repository claim guards
run: sh zeronym/guards.sh

# The Quint specification of the protocol: typecheck, tests
# and bounded random simulation. About 2 minutes on a 16-core machine.
spec-protocol:
runs-on: ubuntu-latest
timeout-minutes: 15
# It runs code fetched at run time (npx, Quint's evaluator), so it gets a
# read-only token and a pinned checkout.
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Protocol specification, tiers 1 to 3b
run: sh zeronym/spec/quint/check.sh
env:
CHECK_TIERS: simulation

# The hub specification checked exhaustively with TLC, which needs Java. The
# Apalache distribution that carries TLC is fetched by Quint on first use.
# About 8.5 minutes on a 16-core machine with four rows at a time. Rows that
# expect a violation run TLC on one worker, so the trace lengths are
# shortest; two rows at a time with 6 GB each fit a 4-core, 16 GB runner.
spec-protocol-tlc:
runs-on: ubuntu-latest
timeout-minutes: 45
# Same reasoning as `spec-protocol`: it runs code fetched at run time.
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: temurin
java-version: '21'
- name: Hub specification, tier 4
run: sh zeronym/spec/quint/check.sh
env:
CHECK_TIERS: tlc
QUINT_JOBS: '2'
TLC_HEAP: 6g
TLC_TIMEOUT: '900'

# Formatting drift, on the cheap runner and without waiting for a compile.
# `hub` and `shim` are separate workspaces, so each gets its own step.
fmt:
Expand Down
1 change: 1 addition & 0 deletions zeronym/spec/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_apalache-out/
2 changes: 2 additions & 0 deletions zeronym/spec/quint/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.itf.json
_apalache-out/
361 changes: 361 additions & 0 deletions zeronym/spec/quint/README.md

Large diffs are not rendered by default.

81 changes: 81 additions & 0 deletions zeronym/spec/quint/abstractHub.qnt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// -*- mode: Bluespec; -*-

/// The hub as the protocol sees it: which payloads it has queued, which are
/// out with a flush, and what it puts on the wire. No phase, no tip, no
/// schedule.
///
/// `hubTest` checks that every step of the real hub from a reachable state is
/// a step of this one (the abstraction lemma), so a protocol invariant that
/// holds over this hub, and reads only queue membership and wire replies,
/// holds over the real one. The converse does not hold: this hub answers
/// where the real one would refuse or error, so a reachable state here need
/// not be reachable there.
module abstractHub {
import basicSpells.* from "./spells/basicSpells"
import types.* from "./types"
import wire.* from "./wire"

type AHub = { queue: Set[Payload], held: Set[Payload] }

/// A client's request, with the indexer's answer for a lookup.
type ARequest =
| ASubmit(Payload)
| ALookup({ txid: TxId, answer: IndexerAnswer })

type AReply = AAck(WireAck) | AWire(WireReply)

/// A request's effect: the hub after it, and its reply.
type AAnswer = { hub: AHub, reply: AReply }

pure val emptyAHub: AHub = { queue: Set(), held: Set() }

pure val WIRE_ACKS: Set[WireAck] =
Set(WAccepted, WRefused(WExpiryTooTight), WRefused(WQueueFull), WRefused(WTipStale))

/// An honest hub accepts a submission into its queue or refuses it under any
/// code, and answers a lookup from its queue first and its indexer second.
pure def honestAnswers(h: AHub, request: ARequest): Set[AAnswer] =
match request {
| ASubmit(payload) =>
WIRE_ACKS.map(ack =>
{ hub: if (ack == WAccepted) { ...h, queue: h.queue.union(Set(payload)) } else h, reply: AAck(ack) })
| ALookup(lookup) =>
val hit = h.queue.exists(payload => payload.txid == Some(lookup.txid))
val outcome = if (hit) QueueHit else FromIndexer(lookup.answer)
Set({ hub: h, reply: AWire(render(outcome)) })
}

/// A Byzantine hub answers a lookup with anything: a body from `universe` or
/// none, at any height, or not found, or an error. A submission it accepts
/// or refuses as it likes, which the honest relation already allows; its ack
/// says which.
pure def byzantineAnswers(h: AHub, request: ARequest, universe: Set[Payload]): Set[AAnswer] =
match request {
| ASubmit(_) => honestAnswers(h, request)
| ALookup(_) =>
tuples(Set(None).union(universe.map(payload => Some(payload))), WIRE_HEIGHTS)
.map(((body, claimed)) => WFound({ body: body, height: claimed }))
.union(Set(WNotFound, WError))
.map(reply => { hub: h, reply: AWire(reply) })
}

/// The moves no client asks for: the queue goes out with a flush; an entry
/// out with it is settled, with every queued entry of its txid; what is
/// kept comes back; everything is lost.
pure def take(h: AHub): AHub = { queue: Set(), held: h.held.union(h.queue) }
pure def settle(h: AHub, payload: Payload): AHub = { ...h, held: h.held.exclude(Set(payload)) }
pure def settleWithCopy(h: AHub, payload: Payload): AHub = {
queue: h.queue.filter(other => not(sharesTxid(payload, other))),
held: h.held.exclude(Set(payload)),
}
pure def giveBack(h: AHub, kept: Set[Payload]): AHub = { queue: h.queue.union(kept), held: Set() }

pure def isInternal(h: AHub, after: AHub): bool =
or {
after == h,
after == h.take(),
h.held.exists(payload => after == h.settle(payload) or after == h.settleWithCopy(payload)),
after.held == Set() and after.queue.subseteq(h.queue.union(h.held)) and h.queue.subseteq(after.queue),
after == emptyAHub,
}
}
Loading
Loading