Skip to content

Elections on Quidnug

**Government · Voting · Registration · Universal verifiability · Bring-your-own-identity**

Overview

Elections on Quidnug

Government · Voting · Registration · Universal verifiability · Bring-your-own-identity

A complete design for running a public election on Quidnug: voter registration, poll books, ballot anonymity, universal verifiability, and instant recount, with no central database anyone has to trust, no proprietary voting machine, and a paper-ballot fallback that cryptographically matches the digital record.

This README covers the protocol-level semantics. The companion docs cover the operational story that turns this design into a deployable system:

  • integration.md, how the design composes on top of the three architectural pillars (QDPs 0012 / 0013 / 0014). The governance, multi-jurisdictional federation, and operational topology layers this README only sketches.
  • operations.md, deployment at five scales (pilot to federal), capacity planning, election-day operations, incident response, cost analysis.
  • launch-checklist.md, sequential T-180 through T+30 go-live checklist.

If you’re new to Quidnug as a whole, read ../ARCHITECTURE.md first, it places the elections use case in the broader protocol context. Elections is the most complex worked example of a coordination-archetype use case; its depth of trust-edge semantics and multi-party governance pushes the protocol harder than any other use case in the library.


The problem

US elections today are run on a patchwork of systems:

  • Voter registration databases sit in each state’s SOS office. The voter has to trust that the DB hasn’t been edited, corrupted, or subjected to a data-entry error.
  • Poll books at each precinct are either printed that morning (static, can’t be rechecked during the day) or “electronic poll books” running vendor software the public cannot audit.
  • Voting machines run closed-source firmware. Public’s ability to verify their behavior is negligible.
  • Counting is done by proprietary tabulation software. Recounts require physical re-scanning of paper ballots on the same machines, i.e. re-running the same vendor software.
  • The public audit story is “trust the bipartisan election boards and approved vendors.” Which is reasonable but obviously not cryptographically verifiable.

Independent observers, candidates losing close races, and ordinary citizens have no technical path to:

  • Personally verify the voter roll is correct.
  • Know that poll books at a specific precinct match the authorized voter list.
  • Verify their own vote was recorded as cast and counted as recorded.
  • Recount an election themselves without waiting for officials.
  • Audit a single ballot from paper through to final tally.

Quidnug’s primitives, signed trust edges, per-signer monotonic nonces, guardian-recoverable keys, per-observer relational trust, and public append-only event streams, map directly onto what an election protocol needs. This document specifies a complete design.


Design principles

Before the mechanics, here’s what the design commits to:

PropertyCommitment
Secret ballotNo party can correlate a specific cast vote with the voter who cast it, not even the election authority.
Universal verifiabilityAny member of the public can cryptographically verify the tally from primary data. No special access.
Individual verifiabilityEvery voter can verify their own vote was counted as they intended, from their own records.
Eligibility verificationOnly registered voters can cast ballots; every ballot cast traces to an eligibility attestation.
One voter, one voteCryptographic double-vote prevention; no “poll worker discovers it later.”
Bring-your-own-identityVoter’s cryptographic identity is generated by the voter, not issued by the authority.
Paper-ballot parityEvery digital vote has a paper-ballot equivalent printed at cast time and deposited in a ballot box.
Recount on demandAnyone, candidate, journalist, citizen, can run a full recount in seconds from the public chain.
Coercion resistance(Trade-off, addressed in §11.) A voter should not be able to cryptographically prove to a third party how they voted.
Tamper-evident auditEvery protocol action is signed and append-only; after-the-fact edits leave evidence.

These aren’t aspirations. Each maps to specific mechanisms below.


The 5 quid types in an election

Quidnug elections use five distinct kinds of quids. Keeping them separate is what gives the design its properties.

1. Election Authority Quid (election-authority-*)

The government body running the election. Examples:

  • election-authority-williamson-county-tx-2026-nov
  • election-authority-state-of-ohio-2028-primary

Each election is its own quid, a new quid per election cycle rather than one persistent “elections office” quid. This scopes authority cleanly: a fresh election’s keys can’t be used to manipulate a prior election’s records.

Under QDP-0012 Domain Governance, the authority’s authority-to-act is expressed through two distinct mechanisms that the original version of this document conflated. Both are needed; they do different things.

Governor quorum for the election’s domains (QDP-0012): the humans / institutions authorized to vote on changes to the consortium roster + parameters of the election’s domain tree. Typically:

  • Chief election official (e.g., county clerk)
  • State Secretary of State office
  • Bipartisan oversight board (one D-aligned, one R-aligned, one independent)
  • Panel of independent election observers (League of Women Voters, etc.)

Recommended structure: 7 governors with weighted votes, 5-of-7 weighted quorum for routine governance actions, unanimous for UPDATE_GOVERNORS. Notice period 72 hours normally; with a pre-negotiated 1-hour emergency-notice clause active during the week of the election for REMOVE_VALIDATOR actions against compromised consortium members. See integration.md §2 for the concrete setup.

Guardian quorum per governor key (QDP-0002): each individual governor’s cryptographic identity has its own M-of-N guardian set for recovering a lost or compromised key. Guardians are independent parties trusted for that specific role (spouse, lawyer, federal monitor, state ethics officer, etc.), not the same people as the governor quorum. Losing a governor’s key doesn’t lose their position, guardians rotate the key to a fresh one, typically over a 24-hour time-lock.

The two mechanisms serve different purposes: governor quorum authorizes policy changes; guardian quorum recovers individual keys. Elections need both.

2. Voter Registration Quid (VRQ)

One per registered voter. Generated by the voter, not issued by the authority (this is the Bring-Your-Own-Quid principle).

  • Voter generates a keypair on their device (phone, hardware token, or at a voter-registration kiosk under their control).
  • The quid ID is sha256(voter's public key)[:16].
  • Voter keeps the private key; the authority never sees it.

VRQ attributes (public):

  • precinctID
  • registeredParty (for primary elections)
  • registrationYear
  • Nothing else, no SSN, no address, no DOB on-chain.

Real-world identity (name, address, DOB) is held off-chain by the election authority in a separate secure database, mapped one-way to the VRQ. Queries like “is this quid registered?” are public; queries like “what is this quid’s name?” require the off-chain DB and legitimate access.

3. Ballot Quid (BQ)

One per voter per election, but generated fresh at voting time, not persistent.

  • Anonymous by construction.
  • No on-chain link to the VRQ it corresponds to.
  • Voter generates it locally; the authority signs a blind attestation that it is eligible to vote (see §6 below).

The BQ is how the voter actually votes. It casts trust edges to candidates/options.

4. Contest Quid

One per contest on the ballot. A “contest” is a single voting decision.

  • contest-williamson-2026-us-senate
  • contest-williamson-2026-proposition-5-sales-tax
  • contest-williamson-2026-sheriff

Contest attributes:

  • Title, description, precincts eligible to vote
  • Voting method: plurality, ranked-choice-irv, approval, rated-3-star, yes-no
  • Candidates (list of quids)
  • Start / end times
  • Certification deadline

5. Candidate Quid

One per candidate or ballot option. Candidates are real quids (generated by the candidate themselves when they filed candidacy); ballot options like “YES” and “NO” on propositions are well-known quids per jurisdiction (e.g., option-yes-williamson-2026 and option-no-williamson-2026).

A write-in candidate’s quid can be issued on-the-spot by a voter who specifies a name; write-ins are flagged differently in the tally.


Domain hierarchy

Everything election-related lives under a carefully-scoped namespace:

elections.<jurisdiction>.<year-cycle>.registration (voter roll)
elections.<jurisdiction>.<year-cycle>.poll-book.<precinct> (per-precinct roll)
elections.<jurisdiction>.<year-cycle>.ballot-issuance (eligibility attestations)
elections.<jurisdiction>.<year-cycle>.contests.<contest> (votes cast per contest)
elections.<jurisdiction>.<year-cycle>.tally (official tally events)
elections.<jurisdiction>.<year-cycle>.audit (audit observations)
For primaries:
elections.<jurisdiction>.<year-cycle>.primary.<party>.contests.<contest>

Concrete example: Williamson County, Texas, November 2026 general:

elections.williamson-county-tx.2026-nov.registration
elections.williamson-county-tx.2026-nov.poll-book.precinct-042
elections.williamson-county-tx.2026-nov.ballot-issuance
elections.williamson-county-tx.2026-nov.contests.us-senate
elections.williamson-county-tx.2026-nov.contests.governor
elections.williamson-county-tx.2026-nov.contests.proposition-5

Each domain has its own consortium (the election authority

  • observer organizations running physical nodes that produce blocks; see QDP-0012 for the cache-replica / consortium-member / governor role separation). Consortium membership is granted by on-chain ADD_VALIDATOR governance transactions signed by the governor quorum, not self-declared, new operators join the consortium by policy action, not by spinning up a node.

Vote edges in contests.us-senate only count if the edge signer (the BQ) has a valid ballot issuance in ballot-issuance. This eligibility check is cryptographic and runs at tally time; nothing in the consortium can bypass it.

Cache replicas (typically precinct polling-place devices plus any observer / journalist / candidate running a mirror) trust the consortium, mirror the agreed chain locally, serve reads, and relay transactions, but do not produce blocks. This keeps the number of block-producing nodes small and governance-controlled while letting the read-serving infrastructure scale out cheaply.


Replacing the voter registration system

Today’s voter registration is a centralized state database. In Quidnug it’s a public, append-only set of trust edges from the election authority to voter quids.

Registration flow (Bring-Your-Own-Quid)

Step 1. Voter generates a quid
────────────────────────────────
On their phone, hardware token, or at a voter-registration
kiosk they physically control:
$ quidnug-voter generate
Your quid ID: a1b2c3d4e5f67890
Save your private key: <securely stored locally>
The voter's private key never leaves their device.
Step 2. Voter visits a registrar
────────────────────────────────
Brings: driver's license / passport / other identity document.
Registrar verifies real-world identity.
Registrar enters into their internal system:
- Name, DOB, address (off-chain DB)
- Voter's quid ID (from QR code / NFC from voter's device)
- Precinct (derived from address)
- Party affiliation (if applicable)
Step 3. Registrar issues the registration
────────────────────────────────
The election authority's signing service emits a trust edge:
TRUST:
truster: "election-authority-williamson-2026-nov"
trustee: "voter-a1b2c3d4e5f67890" (voter's VRQ)
trustLevel: 1.0
domain: "elections.williamson-county-tx.2026-nov.registration"
attributes:
precinct: "042"
registeredParty: "democratic"
registrationTimestamp: 1713400000
validUntil: <next election cycle + 1 year>
nonce: <next registration nonce>
The edge is signed by the authority's current-epoch key.
Step 4. Voter can verify they're registered
────────────────────────────────
From any public Quidnug node:
$ quidnug-voter check-registration voter-a1b2c3d4e5f67890
✓ Registered in: elections.williamson-county-tx.2026-nov.registration
✓ Precinct: 042
✓ Party: democratic
✓ Valid until: 2027-12-31
The voter did not need to visit the registrar's website or
trust their proprietary portal. Any Quidnug node has the same
answer.

How bring-your-own-quid defends against malfeasance

Because the voter generates the quid, the authority cannot:

  1. Assign a voter a quid they control (and vote as that voter). If the authority tried, the voter would detect it, their registered quid wouldn’t match the public key they generated.

  2. Silently change a voter’s quid (de-registering them). Any modification to the registration trust edge requires the authority’s current-epoch signature. A change without a corresponding event on the voter’s stream is auditable.

  3. Deny registration arbitrarily. If the authority refuses to issue the trust edge, the voter has a cryptographic record of their attempt (they submitted their quid and ID) and the refusal is visible to observers.

The authority CAN legitimately deny registration for cause (ineligibility, duplicate), but that denial is documented as a signed event, not a silent database omission.

Anyone can count registered voters

Terminal window
# Anyone, from any Quidnug node with visibility into the domain:
curl "https://any-node/api/v1/trust/edges?domain=elections.williamson-county-tx.2026-nov.registration&truster=election-authority-williamson-2026-nov" | jq 'length'
# -> 142,847

This number matches the authority’s count, because it is the authority’s count. There is no separate “authoritative database” that could disagree.


Replacing poll books

A poll book is the per-precinct list of voters authorized to cast ballots at that precinct. In Quidnug, this is not a separate document, it’s a query against the registration trust edges filtered by precinct.

Terminal window
# Poll book for precinct 042, as used by a poll worker's tablet
curl "https://precinct-042-node/api/v1/trust/edges?\
domain=elections.williamson-county-tx.2026-nov.registration&\
precinct=042"

This returns every VRQ registered at precinct 042. The poll worker’s tablet caches this for the morning and refreshes throughout the day via push gossip (QDP-0005). Unlike printed poll books, the digital poll book reflects late registrations and corrections in real time.

Check-in at the polling place

When a voter arrives:

Step 1. Voter identifies themselves
Shows driver's license or other ID.
Poll worker looks up off-chain by name → retrieves associated
VRQ (e.g., "voter-a1b2c3d4e5f67890").
Alternative: voter scans a QR code from their wallet app that
contains their VRQ ID + a freshly-signed challenge proving they
hold the private key.
Step 2. Poll worker's tablet verifies eligibility
Queries the local Quidnug node:
"Does voter-a1b2c3d4e5f67890 have a registration trust edge in
precinct 042 for this election, and is it active?"
Additionally checks:
"Does this VRQ have a `voter.voted` event for this election?"
If yes → voter has already voted; reject (or route to provisional
if they claim otherwise).
Step 3. Poll worker emits a check-in event
(ONLY a "voted" flag, reveals nothing about what they voted for)
EVENT:
subjectId: "voter-a1b2c3d4e5f67890"
subjectType: "QUID"
eventType: "voter.checked-in"
payload:
electionId: "elections.williamson-county-tx.2026-nov"
precinct: "042"
checkedInAt: 1713400000
signer: poll-worker-quid
Step 4. Voter moves to ballot issuance
(See next section.)

The voter.checked-in event is public but only reveals the fact of voting, not the contents. This is equivalent to the “I voted” sticker and the public fact of showing up to the polls. Some jurisdictions prefer not to record even this; the protocol supports both modes via an election-authority config flag.


Ballot anonymity: the blind-signature issuance flow

This is the critical piece. The voter is already publicly checked-in. Now we need them to cast a vote that cannot be correlated with their VRQ, while still proving the vote is authorized.

The mechanism is a blind signature: the election authority signs an attestation that a specific Ballot Quid (BQ) is authorized to vote, but the authority never learns which BQ it signed.

Cryptographic primer (blind signature, in plain language)

Think of a blind signature as an opaque envelope with carbon paper inside.

  1. Voter writes their BQ’s ID on a piece of paper, seals it in the envelope.
  2. Authority signs the outside of the envelope. The signature bleeds through the carbon paper onto the BQ ID inside.
  3. Voter opens the envelope. Their BQ ID now has the authority’s signature on it, but the authority never saw the ID itself.

Cryptographically: voter generates BQ ID bq_id, picks a random blinding factor r, computes blind(bq_id, r) = commitment. Authority signs commitment. Voter computes unblind(signature, r) = valid-signature(bq_id). Authority’s signature is on bq_id but they never saw bq_id unblinded.

Quidnug uses ECDSA P-256; blind signatures on ECDSA curves are well-studied (RSA blind signatures are the classical form; Schnorr and EC variants exist). An election-authority node runs a Ballot Issuance Service that implements the blind-signature protocol.

Ballot issuance flow

Step 1. Voter's device generates a fresh BQ
Happens inside the voting booth or on the voter's phone in a
private polling place kiosk.
$ quidnug-voter-booth generate-ballot-quid
BQ ID: ballot-Xz7mN2pQ8rK4vL9t
BQ private key: <stored locally, in voter's wallet app>
Voter may generate multiple BQs, one per contest, or one per
election, depending on jurisdiction. Using separate BQs per
contest provides stronger anonymity (attacker can't cross-
correlate the voter's votes in different contests).
Step 2. Voter blinds the BQ ID
Device computes:
commitment = blind(BQ_ID, random_r)
The voter's device keeps `random_r` locally.
Step 3. Voter submits commitment to the Issuance Service
Authenticates with the VRQ's private key + the check-in event:
POST /api/v2/elections/issue-ballot
Headers: signed-with-vrq-key
Body:
electionId: "elections.williamson-county-tx.2026-nov"
commitment: <blinded BQ commitment>
contestId: "contests.us-senate" (one call per contest)
nullifier: <one-time secret derived from VRQ + contest>
Issuance service verifies:
- VRQ is registered in the election
- VRQ has checked in (at polling place) or is otherwise eligible
(mail-in, absentee)
- For THIS contest, this VRQ has not already been issued a ballot
(nullifier not yet seen in the issuance domain)
- Party affiliation matches, for primaries
If OK, service emits:
EVENT:
subjectId: "voter-a1b2c3d4e5f67890"
subjectType: "QUID"
eventType: "ballot.issued"
payload:
electionId: ...
contestId: "contests.us-senate"
nullifier: <the same nullifier the voter submitted>
signer: election-authority
And returns to the voter:
signature_over_commitment (the "blinded signature")
Now everyone can see "this VRQ received a ballot for this
contest" but no one can see the BQ ID.
Step 4. Voter unblinds
Device computes:
unblinded_sig = unblind(signature_over_commitment, random_r)
This is a valid authority signature on the BQ_ID that nobody
else has ever seen, including the authority.
Step 5. Voter's BQ now has its eligibility attestation
Locally, the voter's device builds:
BQ "identity transaction":
quidId: "ballot-Xz7mN2pQ8rK4vL9t"
publicKey: <BQ's public key>
creator: "election-authority-williamson-2026-nov"
attributes:
electionId: ...
contestId: "contests.us-senate"
issuanceEpoch: 0
signature_from_authority: <unblinded_sig>
This identity transaction can be submitted to a Quidnug node
to register the BQ. The authority's signature verifies as
normally, they signed it, even though they don't know they did.

Why this works

  • Anonymity: The authority signed the commitment but not the BQ ID. Anyone (including the authority, a subpoena’d authority, a hacked authority) cannot correlate BQs back to VRQs. The blinding factor is only on the voter’s device.

  • Uniqueness: The authority saw the VRQ + contest combination and the nullifier. If the voter tries to request another ballot for the same contest, the nullifier is already seen → rejected. One ballot per (VRQ, contest).

  • Authority compromise-resistant: Even if the authority’s signing key is stolen and the attacker tries to forge ballots, they can’t retroactively attach themselves to an already-cast BQ. Rotation + invalidation of the compromised epoch are standard Quidnug responses.

Mail-in / absentee variant

Same flow. Voter receives a mail-in ballot packet that contains:

  • A URL / QR to the issuance service.
  • Instructions to generate a BQ on their home computer.
  • Their identity challenge (based on registered info).

The blind-signature flow happens over HTTPS. Voter casts votes online, then prints a paper confirmation that gets mailed back (see §9 Paper-ballot parity).


Casting a vote: “trust” as the voting primitive

Once the voter has a BQ with an authority-signed eligibility attestation, casting a vote is a trust edge from the BQ to a candidate.

Plurality vote (most US elections):
TRUST:
truster: "ballot-Xz7mN2pQ8rK4vL9t" (voter's BQ)
trustee: "candidate-jane-smith-2026" (candidate's quid)
trustLevel: 1.0 (unit vote)
domain: "elections.williamson-county-tx.2026-nov.contests.us-senate"
nonce: 1
signature: <signed with BQ's private key>

One trust edge = one vote.

Ranked-choice voting

1st choice: trustLevel = 1.0
truster: "ballot-Xz7m..."
trustee: "candidate-jane-smith"
trustLevel: 1.0
domain: "...contests.us-senate"
2nd choice: trustLevel = 0.8
truster: "ballot-Xz7m..."
trustee: "candidate-bob-jones"
trustLevel: 0.8
domain: "...contests.us-senate"
3rd choice: trustLevel = 0.6
truster: "ballot-Xz7m..."
trustee: "candidate-carol-li"
trustLevel: 0.6
domain: "...contests.us-senate"

The tally algorithm interprets the levels as ranking positions.

Approval voting

Multiple trust edges at 1.0 each:

trust: BQ → candidate-A at 1.0
trust: BQ → candidate-B at 1.0
trust: BQ → candidate-C at 1.0
(all in the same contest domain)

Yes/no proposition

TRUST:
truster: "ballot-Xz7m..."
trustee: "option-yes-williamson-2026"
trustLevel: 1.0
domain: "elections.williamson-county-tx.2026-nov.contests.proposition-5"

Why vote validity is cryptographically enforced

When the tally engine counts trust edges in a contest’s domain, it applies these checks for each edge:

  1. Truster must be a valid BQ. The truster quid’s identity transaction must be signed by the election authority’s key for this election. (Rules out random people trying to vote as imaginary BQs.)
  2. BQ must have a ballot.issued event on a VRQ. Wait, no, the BQ has no link to a VRQ. What the tally verifies is that the BQ’s identity transaction carries a valid authority signature (the unblinded one from issuance). That’s the eligibility proof.
  3. One trust edge per BQ per contest (for plurality) / edges match the contest’s voting method (for ranked / approval).
  4. Domain matches the contest.
  5. Signed with the BQ’s private key and well-formed.

If any check fails → the edge doesn’t count.


Paper-ballot parity

Digital-only voting is a hard sell, and rightly so. The design gives every digital vote a paper-ballot counterpart.

At the in-person polling place

Voting booth has a printer. When the voter finishes casting their BQ’s trust edges (steps above), the booth:

  1. Prints a paper ballot with:
    • The voter’s BQ ID printed as QR + human-readable hex.
    • A table of contests and the votes cast.
    • The authority’s signed issuance attestation as QR.
    • A ballot sequence number.
  2. The voter reviews the paper ballot.
  3. Voter drops the ballot into a physical ballot box.
┌──────────────────────────────────────────────────────┐
│ Official Paper Ballot, Williamson County TX │
│ General Election, 2026-11-03 │
│ │
│ Ballot ID (QR): ballot-Xz7mN2pQ8rK4vL9t │
│ │
│ Contest: US Senate │
│ Your vote: Jane Smith │
│ │
│ Contest: Governor │
│ Your vote: (no vote) │
│ │
│ Contest: Proposition 5 (Sales Tax) │
│ Your vote: YES │
│ │
│ Authority issuance proof (QR): <compact blob> │
│ Sequence #: 00142-0042 │
└──────────────────────────────────────────────────────┘

Cross-verification

At the end of the day, the physical ballot box is opened and every paper ballot is hand-scanned (or can be hand-verified visually). Each paper ballot has a BQ ID. Cross-verification:

  • Every scanned paper BQ ID should have a matching set of trust edges in the digital chain.
  • Every digital BQ that voted should have a matching paper ballot in the box.
  • Any mismatch is a discovered discrepancy, investigated per jurisdiction procedure.

Because the BQ is pseudonymous (anonymous to the authority, but the voter’s own device can identify their own ballot), the voter can privately verify the paper ballot in the box matches their digital vote (by retrieving their own BQ ID).

What this gives us that pure-paper doesn’t

Traditional paper ballots: counted by hand or by the same proprietary tabulator each time. A recount re-counts the same pile of paper by the same process.

Quidnug paper ballots: every paper ballot has a public digital counterpart. A recount can be done by:

  • Re-scanning paper and comparing to digital.
  • Re-running the digital tally from the chain (takes seconds).
  • Comparing two digital tallies from two independent Quidnug nodes (they should agree exactly).
  • Hand-counting paper per standard procedures.

All four should agree. Disagreement points to a specific discrepancy that can be investigated, not “the machine said so.”

What this gives us that pure-digital doesn’t

A skilled attacker could, in principle, compromise enough validators or gossip nodes to attempt to manipulate the digital record. The paper ballots are the ground truth:

  • If every paper ballot cross-references a digital record, and the digital tally matches paper hand-count, the election is verified.
  • If they disagree, the paper wins. The digital chain is a convenience layer for instant tally, not the ultimate record.

Counting and recounts

This is where Quidnug shines. A recount is a trivial query.

Official initial tally

Pollclose time. The election authority runs the tally from its own view of the chain:

Terminal window
# Pseudocode, actual implementation in §Implementation
GET /api/v1/trust/edges?\
domain=elections.williamson-county-tx.2026-nov.contests.us-senate
Group by trustee:
candidate-jane-smith 47,282 edges
candidate-bob-jones 51,039 edges
candidate-carol-li 21,476 edges
Total: 119,797 votes
Emit:
EVENT:
subjectId: contests.us-senate (the contest quid)
subjectType: QUID
eventType: contest.tallied
payload:
tallyResult:
jane-smith: 47282
bob-jones: 51039
carol-li: 21476
totalVotes: 119797
method: plurality
firstPlace: candidate-bob-jones
signer: election-authority

Anyone’s recount

Any member of the public with access to a Quidnug node serving this election’s domain can run the exact same query. The node returns the same edges. The grouping produces the same counts.

Terminal window
# From a citizen's laptop running a read-only Quidnug node
$ quidnug tally \
--domain elections.williamson-county-tx.2026-nov.contests.us-senate \
--method plurality
candidate-jane-smith: 47,282
candidate-bob-jones: 51,039
candidate-carol-li: 21,476
$ # The result is deterministic given the chain state. Independent
$ # nodes will produce byte-identical tallies.

Candidate recounts: the losing candidate’s team runs their own Quidnug node, fetches the chain, and tallies themselves. No “wait for the Secretary of State to authorize a recount.” No “re-feed paper through the same tabulator.”

Multiple-node agreement: by running the tally on 10 independent Quidnug nodes, the candidate’s team verifies the chain itself hasn’t been tampered with. If 10 nodes agree on 51,039 for Jones, that’s cryptographic evidence of the network consensus, not “the SOS said so.”

Discovered discrepancies

If a candidate’s independent tally disagrees with the authority’s declared result, that’s a major red flag. Two causes:

  1. Chain tampering somewhere, investigated via cross-node comparison. Quidnug’s K-of-K bootstrap (QDP-0008) and cross- node gossip (QDP-0005) make undetected tampering hard.
  2. Tally-method interpretation differences, contest-specific rules (e.g., how ranked-choice instant-runoff handles exhausted ballots) should be explicit in the contest’s attributes. Any ambiguity is a contest-specification bug.

Individual voter verification

Every voter can verify, from their own records, that their vote was cast and counted as they intended.

After voting, the voter’s device retains:

  • Their BQ ID(s) and private key(s).
  • The authority’s issuance attestation.
  • A local record of the trust edges they cast (which candidates, which contests).

Any time after election day:

Terminal window
$ quidnug-voter verify-my-vote
My ballot BQ: ballot-Xz7mN2pQ8rK4vL9t
Election: elections.williamson-county-tx.2026-nov
Casting evidence:
Issuance attestation signed by election-authority-...
BQ published on chain at block 123,456
Votes I cast:
Contest: US Senate
Trust edge BQ candidate-jane-smith @ level 1.0, ON CHAIN
Contest: Proposition 5
Trust edge BQ option-yes-williamson @ level 1.0, ON CHAIN
Tally result for US Senate:
Jane Smith: 47,282 votes (my vote counted in this pool)

The voter:

  • Sees their BQ cast the trust edges they expected.
  • Confirms the authority’s tally included those edges.
  • Knows this from reading the public chain, no special portal required.

Coercion-resistance trade-off

The same verifiability that lets a voter check their own vote can also be misused by a coercer: “Show me your BQ private key so I can verify you voted the way I told you to.”

Mitigations (any jurisdiction can choose their level):

  1. Do-nothing mitigation: accept that coercion happens, rely on criminal penalties + witness protection + ballot-booth privacy. This is what most current systems do.
  2. Multiple-ballot receipts: voter’s device generates N plausible BQs, only one of which is cast. Voter can show the coercer any of the N, so the coercer can’t distinguish the real vote from the decoys. Requires careful UI design.
  3. Revocable ballots: voter can re-cast later, only the last ballot counts. Coercion pressure is limited to final deadline. Trade-off: operational complexity + late-vote issues.
  4. Homomorphic voting (future research): the tally is computed over encrypted ballots; individual ballots are never revealed. Voter’s receipt shows only “my ballot was counted” but not what it was. Requires protocol extension beyond Quidnug’s current primitives.

The v1 recommended approach: mechanism #2 (multiple decoy BQs) combined with mechanism #1 (legal protections). Decoy BQs don’t cast votes, they’re generated for receipt-freeness only. The voter’s actual cast BQ is chosen privately.


Party affiliation and primaries

US primary elections are party-scoped. Registered Democrats vote in the Democratic primary; registered Republicans in the Republican primary; unaffiliated voters (in some states) get to choose.

In Quidnug:

Party-scoped trust edges

The voter’s registration trust edge includes a registeredParty attribute:

TRUST:
truster: election-authority-williamson-2026-nov
trustee: voter-a1b2c3d4e5f67890
domain: elections.williamson-county-tx.2026-nov.registration
attributes:
registeredParty: "democratic"
precinct: "042"

Party-scoped domain for primary contests

Democratic primary contests live in a party-scoped sub-domain:

elections.williamson-county-tx.2026-primary.democratic.contests.us-senate-primary
elections.williamson-county-tx.2026-primary.republican.contests.us-senate-primary

Party-gated ballot issuance

When a voter requests a ballot for a primary contest, the issuance service checks:

voter.registeredParty == contest's party scope?
Democratic voter requests Democratic ballot → OK
Democratic voter requests Republican ballot → rejected
Unaffiliated voter in open-primary state → they pick at issuance; their choice is recorded in an event, the issuance proceeds on that party's side

This enforces party-appropriate voting at the cryptographic level not just “the UI doesn’t show you the other party’s contests.”

Party-change during the year

Voter can change party affiliation between elections. This updates their registration trust edge (authority issues a new edge with a higher nonce and the new party attribute). Old edges are historical record; current attribute governs primary eligibility.


Ballot measures, judges, local offices

All of these are just contests.

  • Proposition: contest with candidates = option-yes and option-no quids.
  • Retention-judge question: same (yes/no pair).
  • School board (single-winner plurality): contest with 4 candidate quids.
  • Ranked-choice mayoral: contest with votingMethod: ranked-choice-irv.
  • Multi-member city council (vote-for-three): contest with votingMethod: approval and maxApproved: 3.

The voting primitive (trust edges from BQ to options) is the same. The contest’s votingMethod attribute dictates how the tally engine interprets the edges.


Write-in candidates

A voter wants to write in “Mickey Mouse” for Sheriff. The voting booth:

  1. Displays a write-in field.
  2. Voter types “Mickey Mouse.”
  3. Booth generates a one-off write-in candidate quid: candidate-write-in-williamson-2026-sheriff-<hash of name>
  4. Identity transaction registers the write-in with attribute isWriteIn: true, writtenName: "Mickey Mouse".
  5. Trust edge from BQ → write-in quid casts the vote.

At tally, the election authority:

  • Groups trust edges by trustee as usual.
  • Flags write-in trustees separately in the results.
  • If a write-in candidate receives enough votes to potentially win or qualify for further consideration, the name is reviewed for consistency (normalizing “Mickey Mouse” vs. “Mike E. Mouse”).

Timeline and state machine

T-90 days: Election Authority publishes election parameters
- Authority operator quid + per-governor guardian quorums installed
- Governor quorum for the election's domain tree configured
(5-of-7 weighted; see integration.md §2)
- Domain tree registered via DOMAIN_REGISTRATION transactions,
consortium members added via ADD_VALIDATOR governance
- Well-known file (`/.well-known/quidnug-network.json`) published
- Contests registered as quids
- Candidates file their quids
- Pre-election audit delegation configured
T-30 days: Voter registration deadline
- Authority stops accepting new registrations
- Publishes registration snapshot (Merkle root) for public audit
T-1 day: Poll books frozen
- Snapshot of the voter roll captured
- Each precinct node bootstraps with this snapshot
T-0: Election Day
06:00: Polls open (contest.opened events)
20:00: Polls close (contest.closed events)
T+1 to T+30 days: Certification period
- Audit observations emitted
- Challenges filed as events
- Paper-ballot hand audit subset (risk-limiting audit)
- Recounts on demand
T+30 days: Certification
- Authority emits contest.certified events
- Result is final

Each stage emits signed events; the full timeline is replayable from the chain.


What replaces what

Current systemQuidnug replacement
State voter registration DBRegistration domain + trust edges from authority to VRQs
Printed poll bookPer-precinct domain query, auto-refreshed on precinct tablets
Electronic poll book vendorOpen-source poll-worker tablet app querying Quidnug
Voting machine firmwareOpen-source voting-booth app generating BQs + emitting trust edges
Ballot-box chain of custodyPaper ballots PLUS cryptographically paired digital record
Proprietary tabulatorAny Quidnug node querying the contest domain
Recount procedureRe-run the tally query; anyone can do it
Audit trailSigned events end-to-end
Secretary of State DBPublic, signed chain

Key Quidnug features used

  • Bring-your-own-quid registration: voter owns identity.
  • Trust edges (per-domain): registration, poll book, votes.
  • Blind-signature ballot issuance: anonymity + uniqueness.
  • Event streams: check-in, voted, tallied, certified, disputes.
  • Guardian recovery (QDP-0002): authority’s key lifecycle.
  • Push gossip (QDP-0005): real-time propagation of events across precincts, state, and observer nodes.
  • K-of-K bootstrap (QDP-0008): fresh observer nodes seed from a quorum of trusted precinct + state nodes.
  • Fork-block (QDP-0009): coordinate protocol changes between elections (e.g., “all 2028 elections must use version 2 of ballot format”).
  • Lazy epoch probe (QDP-0007): detect stale authority-key signatures at cross-jurisdiction boundaries.
  • Relational trust: observers can personalize their trust in precinct validators.

Value delivered

DimensionTodayQuidnug
Voter verifies they’re registeredState website + call-backPublic chain query in 1 second
Voter verifies their own voteNot possiblePrivate record + chain query
Public verifies voter rollFOIA requestPublic chain query
Public does a recountNot possibleRun a query
Candidate recountOfficial process, weeksIndependent tally from chain, seconds
Cross-precinct verificationState aggregation (trust SOS)Any node sees all precincts
Tamper detectionPaper audit after-the-factImmediate cross-node discrepancy detection
Voter identity forgery resistanceDatabase controlsCryptographic (authority-signed trust edge)
Double-voting resistancePoll-book check (same day)Nullifier-enforced cryptographically
Ballot issuance transparency”Trust the vendor”Signed issuance events, per-VRQ, auditable
Cost of auditPer-jurisdiction audit firmsZero, any citizen can run the same queries

What’s in this folder

The elections use case is the most detailed in the library. Six files cover the full lifecycle from design to go-live:

  • README.md, this document. Protocol-level semantics: quid types, trust edges as votes, blind signatures, paper parity, recount, etc.
  • architecture.md, data model, domain hierarchy, event schemas, sequence diagrams.
  • implementation.md, concrete API flows for each role (voter, registrar, poll worker, tally engine).
  • integration.md, how the election design composes on top of the three architectural pillars (QDPs 0012 Domain Governance, 0013 Network Federation, 0014 Node Discovery + Sharding). Fills in the governance, multi-jurisdictional federation, and operational topology layers that this README only sketches.
  • operations.md, deployment topology at five scales (pilot to federal), capacity planning, election- day operations playbook, incident response, cost analysis.
  • launch-checklist.md, sequential T-180 through T+30 go-live checklist. Use this as the actual project plan.
  • threat-model.md, attackers, threats, and mitigations.

Recommended reading order for someone implementing:

  1. This README (problem + design principles + mechanics).
  2. architecture.md (data model).
  3. integration.md (how it fits the larger architecture).
  4. operations.md (how you’d actually deploy + run it).
  5. threat-model.md (what you’re defending against).
  6. launch-checklist.md (when you’re ready to execute).
  7. implementation.md for code-level detail when building.

Protocol QDPs this design uses:

Related use cases:

Operator playbooks (for deployment):

Architecture orientation (start here if you’re new):

  • ../ARCHITECTURE.md, single-doc tour of the protocol, the three pillars, and how every use case fits the same pattern. Elections is the most complex worked example.
  • ../BUILDING-A-USE-CASE.md, the six-phase recipe for designing a use case. Elections was designed following this pattern.

Open research questions (future QDPs)

  1. Blind-signature protocol specification. Quidnug’s native ECDSA P-256 doesn’t directly support blind signatures. Options: add RSA-based issuance as an auxiliary protocol, or use EC-Schnorr-style blind signatures (requires protocol extension). Needs a QDP.
  2. Homomorphic tallying for full receipt-freeness, could be a future QDP that adds Paillier or ElGamal ciphertext vote encoding, with tallying in the ciphertext space.
  3. Risk-limiting audit integration, formal statistical audit procedures that consume Quidnug’s paper-vs-digital matching.
  4. Voter’s private-key recovery, if a voter loses their VRQ key, how do they re-register? Guardian-recovery-for-voters needs an opinionated design.

These don’t prevent a production deployment; the core flow works with v1 Quidnug plus the blind-signature auxiliary. They’d make successive versions stronger.

Architecture

Data model, components, sequence diagrams.

Architecture: Elections on Quidnug

Detailed data model, protocol flows, and component breakdown. Assumes familiarity with README.md.

Participating components

A production election deployment involves these Quidnug node roles:

Node roleOperatorResponsibilities
Election Authority NodeCounty / state election officeSigns registrations, issues ballots, tallies
Precinct NodeIndividual polling placePoll-book lookups, voting-booth backend
Observer Node (read-only)Political parties, press, NGOsIndependent copy of the chain for verification
Audit NodeState audit board, academic groupsStatistical audits, paper-vs-digital cross-verification
Voter DeviceEach voterGenerates VRQ + BQ, signs transactions
Secretary of State NodeState SOSAggregates across counties, certifies state-wide races

All nodes run the same Quidnug protocol. Differences are permissions (who can sign what) and read-only vs. read-write on specific domains.

Cryptographic primitives used

PrimitiveUsed for
ECDSA P-256 (standard)All signatures on trust edges, events, titles
SHA-256Quid IDs, block hashes, canonical-bytes hashing
Blind signature (aux)Ballot issuance (anonymous to authority)
Merkle treeRegistration snapshots, ballot-box audit
HMAC-SHA256Inter-node auth (existing Quidnug mechanism)
Nullifier (hash-based)One-vote-per-contest enforcement

The blind-signature piece is the only crypto primitive beyond Quidnug’s current library. See §Blind-signature integration for how this is wired in.

Domain hierarchy (complete)

elections.<jurisdiction>.<cycle>
├── .meta (election-level config)
├── .candidates (candidate quid identity transactions)
├── .registration (voter roll)
├── .poll-book.<precinct> (precinct-scoped registration subset)
├── .ballot-issuance (ballot.issued events)
├── .ballots (BQ identity transactions)
├── .contests.<contest> (trust edges = votes)
├── .tally (official tallies)
├── .audit (audit observations, RLA events)
└── .certification (final certification events)
For primaries (parallel hierarchy):
elections.<jurisdiction>.<cycle>.primary.<party>.<same-structure>

Each sub-domain has:

  • Validators (the election authority itself, typically)
  • Storage rules (trusted-tier only from authority, Tentative-tier from observers, etc.)
  • Visibility: most are public-read, public-append-signed

Quid schemas

Election Authority Quid

type ElectionAuthorityIdentity struct {
QuidID string // e.g. "election-authority-williamson-2026-nov"
Jurisdiction string // "williamson-county-tx"
ElectionCycle string // "2026-nov-general"
Attributes struct {
OfficialTitle string // "Williamson County Election Administrator"
MasterSchedule struct {
PollsOpen time.Time
PollsClose time.Time
CertificationDue time.Time
}
PrimaryParties []string // ["democratic", "republican"] if primary
ContestCount int
PrecinctCount int
}
}
// Governor quorum for the authority's domain tree (QDP-0012).
// This is the policy layer, who authorizes consortium
// changes, threshold tweaks, child-domain delegation.
type AuthorityGovernors struct {
Chief string // chief election official (e.g. county clerk)
StateSOS string // Secretary of State's quid
BipartisanBoard []string // D-aligned, R-aligned, independent
ObserverPanel []string // League of Women Voters, etc.
Quorum float64 // e.g., 0.7 (5-of-7 weighted)
NoticeBlocks int64 // e.g., 1440 (~72h at 3-minute blocks)
EmergencyClause bool // if true, REMOVE_VALIDATOR gets 1h notice
// during active voting week
}
// Each individual governor ALSO has a personal guardian set
// (QDP-0002) for recovering their own key if lost/compromised.
// These are separate humans from the governor quorum above;
// typically chosen per-governor for their life context
// (spouse, lawyer, federal monitor, etc.).
type GovernorGuardians struct {
GovernorQuid string
Guardians []string // quids of the guardian humans/orgs
Threshold uint16 // e.g., 3-of-5
RecoveryDelay time.Duration // e.g., 24h typical
}

Two-layer key-resilience model. The governor quorum is the policy layer, it changes the authority’s validator set, domain parameters, delegation scopes. The per-governor guardian quorums are the key-recovery layer, each governor individually can recover a lost key without waiting for quorum agreement. Both layers are needed; one without the other leaves obvious failure modes (losing a key = stuck, losing a policy vote = stuck).

Voter Registration Quid (VRQ)

type VoterRegistrationQuid struct {
QuidID string // sha256 of voter's public key, 16 hex chars
PublicKey string // voter-generated ECDSA P-256 public key
Creator string // voter's own quid (self-issued)
Attributes struct {
// NOTE: No real-world PII on-chain.
// Precinct is public; matched off-chain to name+address
// by the authority's secure registration DB.
}
}

The VRQ exists before the registration trust edge. The edge is what grants eligibility; the quid is the voter’s identity.

Voter Registration Trust Edge (the “registration record”)

// Issued by election authority after identity verification.
type RegistrationTrustEdge struct {
Truster string // "election-authority-..."
Trustee string // voter's VRQ
TrustLevel float64 // 1.0 (registered)
Domain string // "elections.<jurisdiction>.<cycle>.registration"
Nonce int64 // monotonic per voter per election
Attributes struct {
Precinct string
RegisteredParty string // "democratic" | "republican" | "unaffiliated" | ""
RegistrationMethod string // "in-person" | "online" | "dmv" | "mail"
RegistrationTimestamp int64
}
ValidUntil int64
Signature string // authority's signature
}

Ballot Quid (BQ)

type BallotQuid struct {
QuidID string // voter-generated, e.g., "ballot-Xz7mN2pQ8rK4vL9t"
PublicKey string // voter-generated ECDSA key (different from VRQ's)
Creator string // election-authority (via blind signature, see below)
Attributes struct {
ElectionID string
ContestID string // optional: BQ-per-contest for enhanced anonymity
IssuanceEpoch uint32
IssuanceMethod string // "in-person" | "mail-in" | "early-vote"
}
// The signature on this identity transaction is the
// UNBLINDED authority signature from the blind-signature flow.
AuthoritySignature string
}

Critical property: the authority’s signature here is cryptographically valid (verifies against authority’s public key) but the authority never saw the raw QuidID. This is the mechanism by which eligibility is proven while anonymity is preserved.

Contest Quid

type ContestQuid struct {
QuidID string
Attributes struct {
Title string
Description string
EligiblePrecincts []string
StartTime int64
EndTime int64
VotingMethod string // "plurality" | "ranked-choice-irv" | "approval" | "rated-3-star" | "yes-no"
MaxApproved int // for approval voting
Candidates []string // list of candidate quids
TallyDomain string // the domain where trust edges count as votes
RequiresMajority bool // for runoffs
}
Creator string // election-authority
}

Candidate Quid

type CandidateQuid struct {
QuidID string // candidate-generated, e.g., "candidate-jane-smith-2026-senate"
PublicKey string // candidate's signing key
Creator string // candidate themselves
Attributes struct {
LegalName string
Party string
Office string
Bio string // or CID to bio blob
Website string
FilingStatement string // or CID to candidacy paperwork
}
}
// Authority confirms ballot placement via a trust edge
// election-authority → candidate-quid in domain
// "elections.<j>.<c>.candidates" with attribute of "contest".

Events

All events use Quidnug’s standard EVENT transaction. Specific event types for elections:

subject=VRQ:
voter.registered
voter.checked-in # at polling place
voter.voted # final "all contests cast" event
ballot.issued # per-contest issuance record (attached to VRQ)
voter.registration-updated
voter.registration-revoked # for cause
subject=BQ:
(none directly on BQ, its existence + signed identity is self-contained.
Votes are trust edges from BQ, not events.)
subject=Contest:
contest.opened
contest.closed
contest.tallied
contest.challenged
contest.certified
subject=Paper-Ballot-Box:
ballot-box.sealed
ballot-box.opened
ballot-box.scanned
ballot-box.discrepancy-detected
subject=Election-Authority:
election.setup-complete
election.pre-audit-passed
election.final-certified
election.dispute-filed

Protocol flows

Pre-election setup (T-90 days)

1. State SOS establishes the operator quid (the persistent
identity for the county election authority):
IDENTITY: election-authority-williamson
(separate from the per-election quid below)
2. Each governor generates their personal quid + installs
their own guardian set (QDP-0002):
For each governor G:
GuardianSetUpdate for G.quid, signed by G
Guardians: whatever humans G has chosen
Recovery delay: 24h typical
3. Authority registers the per-election domain tree using
DOMAIN_REGISTRATION transactions (QDP-0012):
DOMAIN_REGISTRATION: elections.williamson-county-tx.2026-nov
validators: {node-authority-primary: 1.0, ...}
governors: {chief: 2, sos: 2, r-obs: 1, d-obs: 1, lwv: 1}
governanceQuorum: 0.7 # 5-of-7 weighted
noticePeriod: 1440 blocks
(repeat for registration, poll-book, ballot-issuance,
contests.*, tally, audit sub-domains)
4. Authority publishes the well-known file (QDP-0014):
/.well-known/quidnug-network.json
operator: <county-authority-quid>
governors: [chief, sos, r-obs, d-obs, lwv with weights]
seeds: [node-authority-primary, node-observer-r, ...]
signed by county-authority operator key
5. Each consortium node publishes a NODE_ADVERTISEMENT
(QDP-0014):
NODE_ADVERTISEMENT for each node
operator: county-authority-quid (or observer org's operator)
endpoints: [https://node-xxx.elections.wilco.gov]
capabilities: {validator: true, archive: true}
supportedDomains: ["elections.williamson-county-tx.2026-nov.*"]
expires: +7 days (renewed weekly)
6. Authority creates contest quids:
For each race: IDENTITY: contest-williamson-2026-us-senate
etc.
7. Candidates file their candidacy:
Each candidate creates their own candidate quid, signs an
identity transaction, and submits to the authority.
Authority reviews, emits a trust edge from election-authority
to candidate-quid in domain ".candidates" attributing
which contest they're registered for.
5. Precinct structure:
Authority publishes the precinct list (contest→precinct
mapping) as another signed event.
6. Observer nodes bootstrap:
Via K-of-K (QDP-0008) from the authority node + state SOS
node + 1-2 trusted observer nodes.

Voter registration flow (T-270 to T-30 days)

┌────────────────────────────────────────────────────────────┐
│ Voter: generates VRQ locally │
│ (uses quidnug-voter CLI or mobile app) │
└──────────────────────────┬──────────────────────────────────┘
┌──────────────────────────▼──────────────────────────────────┐
│ Voter: visits DMV / registers online / visits county office │
│ Brings: ID document + VRQ's public key (as QR) │
└──────────────────────────┬──────────────────────────────────┘
┌──────────────────────────▼──────────────────────────────────┐
│ Registrar: verifies identity (off-chain) │
│ Looks up voter in state DB of eligible voters, confirms │
│ not already registered. Links VRQ to off-chain record. │
└──────────────────────────┬──────────────────────────────────┘
┌──────────────────────────▼──────────────────────────────────┐
│ Authority's signing service: emits trust edge │
│ TRUST: authority → VRQ │
│ domain: elections.<j>.<c>.registration │
│ attributes: {precinct, party, method, timestamp} │
│ │
│ And event: │
│ voter.registered on the VRQ │
└──────────────────────────┬──────────────────────────────────┘
┌──────────────────────────▼──────────────────────────────────┐
│ Push gossip: edge propagates to all precinct nodes + SOS + │
│ observer nodes within seconds │
└─────────────────────────────────────────────────────────────┘

Poll-book query (election day)

┌─────────────────────┐
│ Voter at polling │
│ place. Shows ID. │
└──────────┬──────────┘
┌──────────▼──────────────────────────────────────┐
│ Poll worker tablet queries local precinct node: │
│ GET /api/v1/trust/edges? │
│ truster=election-authority-williamson-2026-nov│
│ trustee=<voter-VRQ> │
│ domain=elections.williamson-2026-nov.registration│
│ │
│ Expected: 1 edge with precinct=042 attribute │
│ + no voter.voted event on this VRQ yet │
└──────────┬──────────────────────────────────────┘
│ OK
┌──────────▼─────────────────────────────────────┐
│ Poll worker emits voter.checked-in event │
│ EVENT: voter.checked-in on VRQ │
│ Signed by poll-worker quid │
│ │
│ Voter proceeds to booth │
└──────────────────────────────────────────────────┘

Ballot issuance (blind-signature)

This is the critical flow. Inside the voting booth:

┌───────────────────────────────────────────────────────────┐
│ Voter's booth device generates fresh BQs │
│ For plurality election with 5 contests, generate 5 BQs │
│ (one per contest, for enhanced anonymity) │
└──────────────────────────┬────────────────────────────────┘
┌──────────────────────────▼────────────────────────────────┐
│ For each BQ, device builds a BLINDED COMMITMENT │
│ commitment_i = Blind(BQ_i.id, random_r_i) │
└──────────────────────────┬────────────────────────────────┘
┌──────────────────────────▼────────────────────────────────┐
│ Device presents to authority's Issuance Service: │
│ POST /api/v2/elections/issue-ballot │
│ Body (signed with VRQ's private key): │
│ electionId: "elections.williamson-2026-nov" │
│ contests: [ │
│ { contestId: "us-senate", commitment: ... }, │
│ { contestId: "governor", commitment: ... }, │
│ { contestId: "proposition-5", commitment: ... }, │
│ ... │
│ ] │
│ nullifiers: [ │
│ { contestId: "us-senate", nullifier: ... }, │
│ ... │
│ ] │
└──────────────────────────┬────────────────────────────────┘
┌──────────────────────────▼────────────────────────────────┐
│ Authority's Issuance Service: │
│ 1. Verify VRQ signature on request │
│ 2. Verify VRQ is registered (trust edge exists) │
│ 3. Verify VRQ has been checked in (voter.checked-in event) │
│ 4. For each contest: │
│ a. Nullifier not already seen in this contest's domain │
│ b. If primary: VRQ's party matches contest's party │
│ c. Contest is currently OPEN │
│ 5. Emit ballot.issued events on the VRQ: │
│ EVENT: ballot.issued │
│ payload: { contestId, nullifier } │
│ (public record, "VRQ received a ballot for this │
│ contest" without revealing which BQ) │
│ 6. Sign each commitment with authority's blind-sig key │
│ 7. Return blinded_signatures[] to voter │
└──────────────────────────┬────────────────────────────────┘
┌──────────────────────────▼────────────────────────────────┐
│ Voter's device unblinds each signature: │
│ For each (blinded_sig_i, r_i): │
│ unblinded_sig_i = Unblind(blinded_sig_i, r_i) │
│ │
│ Now each BQ has an authority-signed identity transaction. │
│ Device holds onto BQ private keys + unblinded sigs. │
└──────────────────────────┬────────────────────────────────┘
┌──────────────────────────▼────────────────────────────────┐
│ Ballots are now "issued", voter can cast. │
└───────────────────────────────────────────────────────────┘

Nullifier scheme, one subtle point. The nullifier prevents the voter from requesting multiple ballots for the same contest. It’s derived deterministically:

nullifier_for_contest = HMAC-SHA256(
key = voter-VRQ-private-key,
message = "ballot-issuance" + electionId + contestId
)

Two properties:

  1. Only the voter (holding VRQ private key) can compute it.
  2. The same voter for the same contest always gets the same nullifier.

The authority stores all seen nullifiers per contest. Second request with the same nullifier is rejected. The nullifier itself doesn’t reveal the voter’s identity (it’s a hash, appears random to observers).

Vote casting

Inside the booth, voter makes selections on touchscreen UI:
US Senate: ● Jane Smith ○ Bob Jones ○ Carol Li
Governor: ○ Alice Wang ● Dan Chen ○ (no vote)
Proposition 5: ● YES ○ NO
Voter confirms. Device emits:
For each vote:
TRUST transaction from BQ to candidate/option:
For US Senate vote:
truster: ballot-Xz7m... (BQ for us-senate contest)
trustee: candidate-jane-smith-2026-senate
trustLevel: 1.0
domain: elections.williamson-2026-nov.contests.us-senate
nonce: 1
signature: <signed with BQ's private key>
For Prop 5 vote:
truster: ballot-ABC9... (BQ for prop-5 contest)
trustee: option-yes-williamson-2026
trustLevel: 1.0
domain: elections.williamson-2026-nov.contests.proposition-5
nonce: 1
signature: <signed with BQ's private key>
(No vote in Governor: NO trust edge emitted.)
Each BQ's identity transaction is also submitted alongside its
first vote so the chain has a record of the BQ's authorized
existence.
The BQ's identity transaction's signature (from authority) is
verified at tally time. The trust edge's signature (from BQ) is
verified at submission time.
Voter's voter.voted event is emitted on their VRQ:
EVENT: voter.voted
payload: { electionId, contestCount: 5, completedAt: ... }
signer: voting-booth-quid + VRQ-cosigned

Tally

func (t *Tallier) TallyContest(ctx context.Context, contestID string) (*Tally, error) {
contest, err := t.GetContest(ctx, contestID)
if err != nil { return nil, err }
// 1. Get all trust edges in this contest's tally domain.
edges, err := t.GetTrustEdges(ctx, &GetEdgesFilter{
Domain: contest.Attributes["tallyDomain"].(string),
})
if err != nil { return nil, err }
// 2. For each edge, validate.
valid := []Edge{}
for _, e := range edges {
// a. Truster must be a valid BQ for this election.
bqID, err := t.GetIdentity(ctx, e.Truster)
if err != nil { continue }
if bqID.Creator != t.authorityQuid { continue }
if bqID.Attributes["electionId"] != contest.Attributes["electionId"] { continue }
// b. Authority's signature on BQ identity must verify.
if !t.verifyAuthoritySignature(bqID) { continue }
// c. Trust edge's signature must verify (BQ signed it).
if !t.verifyEdgeSignature(e, bqID.PublicKey) { continue }
// d. For primary contests, BQ's party must match contest.
valid = append(valid, e)
}
// 3. Apply voting method.
switch contest.Attributes["votingMethod"] {
case "plurality":
return t.TallyPlurality(contest, valid), nil
case "ranked-choice-irv":
return t.TallyIRV(contest, valid), nil
case "approval":
return t.TallyApproval(contest, valid), nil
case "yes-no":
return t.TallyYesNo(contest, valid), nil
}
return nil, fmt.Errorf("unknown voting method")
}

Paper-ballot cross-verification

End of election day:
1. Poll workers seal physical ballot box.
Event: ballot-box.sealed
payload: { precinct, physicalSeal: <tamper-seal-serial> }
signer: poll-workers-quorum
2. Ballot box transported to counting facility (chain of
custody documented in Quidnug events throughout).
3. Counting facility opens box.
Event: ballot-box.opened
payload: { sealVerified: true | false, observedBy: [...] }
4. Paper ballots are scanned.
Each paper ballot's QR code yields: BQ-ID + authority
issuance signature.
Compared against the digital chain:
- Does the digital chain have a trust edge from this BQ
in each contest marked on the paper?
- Do the digital trust-edge targets match the paper?
5. Discrepancies logged:
Event: ballot-box.discrepancy-detected
payload: { ballotID, expected: ..., actual: ..., reason: ... }
6. Statistical audit (risk-limiting):
Sample N random paper ballots.
For each: verify paper-to-digital match.
Compute statistical confidence level.
Event: audit.rla-completed
payload: { sampleSize, matches, confidenceLevel, pass: true }

Certification

After certification period elapses:
1. All recounts finalized.
2. All disputes resolved (or time-barred).
3. Risk-limiting audit passed.
Authority emits certification events per contest:
EVENT: contest.certified
subjectId: contest quid
payload:
tallyResult: { ... }
totalEligibleVoters: ...
totalCastBallots: ...
disputesResolved: [...]
auditsPassed: true
certifiedBy: election-authority-williamson-2026-nov
cosignedBy:
- chief-election-admin
- bipartisan-board-D
- bipartisan-board-R
- state-sos

Blind-signature integration

This is the one non-standard-Quidnug piece. The authority’s Ballot Issuance Service implements a blind-signature scheme against the authority’s issuance key.

Options for implementation

Option A (Recommended): RSA blind signatures as auxiliary

Authority maintains a separate RSA keypair for blind signatures. The public key is published in the authority’s attributes. Each issuance key is short-lived (per election); expired keys are retired (another election.issuance-key-retired event).

Voter code:

# Pseudocode
commitment = blind(BQ_id, authority_pubkey, random_r)
POST /api/v2/elections/issue-ballot
response = blinded_signature
unblinded_sig = unblind(blinded_signature, random_r)
# unblinded_sig verifies against authority_pubkey on BQ_id

Authority side:

signature = rsa_sign(commitment, authority_privkey)

Quidnug’s identity transaction storage accepts the unblinded signature as the authority’s signature field; verification is a standard RSA verify.

Option B: EC-Schnorr blind signatures (deferred pending QDP)

More aligned with Quidnug’s P-256 family, but Schnorr blind signatures have known concurrency attacks that need careful protocol design (blind Schnorr requires ROS-hard setup or alternatives like Okamoto-Schnorr).

The issuance service

type IssuanceService struct {
authorityQuid string
authorityPrivateKey ecdsa.PrivateKey // for Quidnug signatures
blindSigPrivateKey rsa.PrivateKey // for blind sigs
ledger *NonceLedger
nullifierStore map[string]map[string]bool // contest → nullifier → seen
}
func (s *IssuanceService) IssueBallot(req IssueRequest) (*IssueResponse, error) {
// Verify request signed with VRQ's private key.
if !s.verifyVRQSig(req) {
return nil, ErrBadVRQSignature
}
// Check VRQ is registered for this election.
if !s.isRegistered(req.VRQ, req.ElectionID) {
return nil, ErrNotRegistered
}
// Check VRQ has checked in (or is otherwise eligible, mail, early vote).
if !s.hasCheckedIn(req.VRQ, req.ElectionID) {
return nil, ErrNotCheckedIn
}
// For each contest in the request:
response := &IssueResponse{}
for _, contest := range req.Contests {
// 1. Nullifier not already seen.
if s.nullifierStore[contest.ContestID][contest.Nullifier] {
return nil, fmt.Errorf("already voted in contest %s", contest.ContestID)
}
// 2. For primary: VRQ's party matches contest's party.
if isPrimaryContest(contest.ContestID) && !s.partyMatches(req.VRQ, contest.ContestID) {
return nil, fmt.Errorf("party mismatch for %s", contest.ContestID)
}
// 3. Contest is open.
if !s.isContestOpen(contest.ContestID) {
return nil, ErrContestNotOpen
}
// 4. Mark nullifier consumed.
s.nullifierStore[contest.ContestID][contest.Nullifier] = true
// 5. Emit ballot.issued event.
event := EventTransaction{
SubjectID: req.VRQ,
SubjectType: "QUID",
EventType: "ballot.issued",
Payload: map[string]interface{}{
"contestId": contest.ContestID,
"nullifier": contest.Nullifier,
"timestamp": time.Now().Unix(),
},
}
s.submitEvent(event)
// 6. Blind-sign the commitment.
blindedSig := rsa.SignPKCS1v15(nil, &s.blindSigPrivateKey, crypto.SHA256, contest.Commitment)
response.BlindSignatures = append(response.BlindSignatures, ContestSig{
ContestID: contest.ContestID,
BlindSignature: blindedSig,
})
}
return response, nil
}

The nullifier store is in-memory per issuance epoch; persisted out to snapshots for audit purposes. It’s small (one entry per voter per contest → ~hundreds of thousands of entries per county-wide election).

Scale estimates

Mid-size US county election:

  • 200,000 registered voters
  • 100,000 actual turnout (50%)
  • 20 contests per ballot
  • 50 precincts

Workload:

  • Registration edges: 200K (one-time, during registration period)
  • Poll-book lookups: 100K (one per voter check-in)
  • Ballot.issued events: 100K × 20 = 2M (one per voter per contest)
  • Vote trust edges: 100K × ~10 average-contests-voted = 1M
  • Gossip propagation: spread across 50 precinct nodes + state + observers

Quidnug’s consortium-scale target handles this comfortably. Peak load during polls-close hour: ~20K BQs / 20K vote-sets submitted simultaneously. Multiple Ballot Issuance Service instances at the authority node handle parallelism.

Next

Implementation

Concrete API calls, pseudocode, signing shape.

Implementation: Elections on Quidnug

Concrete API flows for each participant role. Code examples in Go and shell; real implementations would layer in HSM integration and UI.

0. Configuration for election nodes

Terminal window
# Election Authority node
ENABLE_NONCE_LEDGER=true
ENABLE_PUSH_GOSSIP=true # real-time propagation
ENABLE_LAZY_EPOCH_PROBE=true # cross-jurisdiction safety
ENABLE_KOFK_BOOTSTRAP=true # observer onboarding
SUPPORTED_DOMAINS=elections.*
NODE_AUTH_SECRET=<32-byte hex>
REQUIRE_NODE_AUTH=true
# Precinct node, same flags; different SUPPORTED_DOMAINS if restricted
SUPPORTED_DOMAINS=elections.williamson-county-tx.2026-nov.poll-book.precinct-042
# Observer/read-only node, accepts gossip but doesn't produce
# transactions in the election domains

1. Authority: pre-election setup

Terminal window
# 1a. Create the authority quid
curl -X POST $AUTHORITY_NODE/api/identities -d '{
"quidId":"election-authority-williamson-2026-nov",
"name":"Williamson County 2026 November General Election",
"creator":"election-authority-williamson-2026-nov",
"updateNonce":1,
"homeDomain":"elections.williamson-county-tx.2026-nov",
"attributes":{
"jurisdiction":"williamson-county-tx",
"cycle":"2026-nov-general",
"pollsOpen":"2026-11-03T07:00:00-06:00",
"pollsClose":"2026-11-03T19:00:00-06:00",
"certificationDue":"2026-12-03T23:59:59-06:00",
"precinctCount":50
}
}'
# 1b. Install guardian set for the authority
curl -X POST $AUTHORITY_NODE/api/v2/guardian/set-update -d '{
"subjectQuid":"election-authority-williamson-2026-nov",
"newSet":{
"guardians":[
{"quid":"chief-election-admin-quid","weight":1,"epoch":0},
{"quid":"bipartisan-board-d-quid","weight":1,"epoch":0},
{"quid":"bipartisan-board-r-quid","weight":1,"epoch":0},
{"quid":"bipartisan-board-indep-quid","weight":1,"epoch":0},
{"quid":"state-sos-tx-quid","weight":2,"epoch":0},
{"quid":"observer-lwv-williamson-quid","weight":1,"epoch":0}
],
"threshold":4,
"recoveryDelay":259200000000000, /* 72 hours */
"requireGuardianRotation":true
},
"anchorNonce":1,
"validFrom":<now>,
"primarySignature":{...},
"newGuardianConsents":[ /* each guardian signs */ ]
}'
# 1c. Create contest quids
for contest in us-senate governor proposition-5 proposition-6 sheriff; do
curl -X POST $AUTHORITY_NODE/api/identities -d '{
"quidId":"contest-williamson-2026-'$contest'",
"name":"'$contest' contest",
"creator":"election-authority-williamson-2026-nov",
"updateNonce":1,
"homeDomain":"elections.williamson-county-tx.2026-nov.contests.'$contest'",
"attributes":{
"contest":"'$contest'",
"votingMethod":"plurality",
"startTime":"2026-11-03T07:00:00-06:00",
"endTime":"2026-11-03T19:00:00-06:00",
"eligiblePrecincts":["001","002","003","...","050"]
}
}'
done
# 1d. Candidates file their candidacy
# (Each candidate does this from their own quid)
# Example for Jane Smith for US Senate
curl -X POST $AUTHORITY_NODE/api/identities -d '{
"quidId":"candidate-jane-smith-2026-senate",
"name":"Jane Smith for Senate",
"creator":"candidate-jane-smith-2026-senate",
"updateNonce":1,
"attributes":{
"party":"democratic",
"office":"US Senate",
"bioURL":"https://jane-smith.example/bio",
"filingStatementHash":"<sha256>"
}
}'
# Authority endorses ballot placement
curl -X POST $AUTHORITY_NODE/api/trust -d '{
"truster":"election-authority-williamson-2026-nov",
"trustee":"candidate-jane-smith-2026-senate",
"trustLevel":1.0,
"domain":"elections.williamson-county-tx.2026-nov.candidates",
"nonce":1,
"description":"Qualified for ballot, US Senate, Williamson County 2026",
"attributes":{
"contestRef":"contest-williamson-2026-us-senate"
}
}'

2. Voter: register (bring-your-own-quid)

2a. Voter generates their VRQ locally

Terminal window
# On voter's device (phone app, hardware token, home computer)
# using a voter-facing CLI or app:
$ quidnug-voter init
Generated new keypair.
Your Voter Registration Quid (VRQ): voter-a1b2c3d4e5f67890
Public key: 0x04abcd...
Private key saved to: ~/.quidnug-voter/vrq.key (keep secure!)

2b. Voter registers in person / online

The registrar’s system (at DMV / county office / online portal):

package registrar
import (
"context"
"time"
)
func (r *RegistrarService) Register(ctx context.Context, req RegistrationRequest) error {
// 1. Verify identity off-chain
record, err := r.verifyIdentity(req.IDDocumentType, req.IDDocumentNumber)
if err != nil {
return err
}
// 2. Check not already registered
if r.isAlreadyRegistered(record.SSN_hash) {
return ErrAlreadyRegistered
}
// 3. Determine precinct from address
precinct := r.precinctForAddress(record.Address)
// 4. Verify voter's quid (signature challenge)
challenge := randomBytes(32)
if !req.VRQ.VerifySignature(challenge, req.ChallengeResponse) {
return ErrBadVRQSignature
}
// 5. Store the VRQ ↔ off-chain-identity link
r.db.Insert(record.SSN_hash, req.VRQ.ID, precinct)
// 6. Emit registration trust edge on the authority's chain
edge := TrustTransaction{
Type: "TRUST",
Truster: r.authorityQuid,
Trustee: req.VRQ.ID,
TrustLevel: 1.0,
Domain: fmt.Sprintf("elections.%s.%s.registration", r.jurisdiction, r.cycle),
Nonce: r.nextRegistrationNonce(),
ValidUntil: r.nextElectionCutoff(),
Attributes: map[string]interface{}{
"precinct": precinct,
"registeredParty": record.RegisteredParty,
"registrationMethod": req.Method,
"registrationTimestamp": time.Now().Unix(),
},
}
return r.submitTrust(ctx, edge)
}

2c. Voter verifies their registration

Terminal window
# From anywhere, any Quidnug node
$ curl "$ANY_NODE/api/v1/trust/edges?\
truster=election-authority-williamson-2026-nov&\
trustee=voter-a1b2c3d4e5f67890&\
domain=elections.williamson-county-tx.2026-nov.registration"
{
"edges": [{
"truster": "election-authority-williamson-2026-nov",
"trustee": "voter-a1b2c3d4e5f67890",
"trustLevel": 1.0,
"domain": "elections.williamson-county-tx.2026-nov.registration",
"attributes": {
"precinct": "042",
"registeredParty": "democratic",
"registrationMethod": "dmv",
"registrationTimestamp": 1713400000
},
"validUntil": 1767225599,
"nonce": 47283
}]
}
# Voter sees: registered ✓

3. Poll worker: check in a voter on election day

package pollworker
func (p *PollWorker) CheckInVoter(ctx context.Context, voterID string) (*CheckInResult, error) {
// 1. Query the precinct node for the registration edge
edges, err := p.client.GetTrustEdges(ctx, GetEdgesFilter{
Truster: p.authorityQuid,
Trustee: voterID,
Domain: fmt.Sprintf("elections.%s.%s.registration", p.jurisdiction, p.cycle),
})
if err != nil || len(edges) == 0 {
return nil, ErrNotRegistered
}
edge := edges[0]
if edge.TrustLevel < 1.0 {
return nil, ErrRegistrationInvalid
}
if edge.Attributes["precinct"] != p.precinct {
return nil, ErrWrongPrecinct
}
// 2. Check for prior voter.voted event
events, err := p.client.GetSubjectEvents(ctx, voterID, "QUID")
if err != nil { return nil, err }
for _, ev := range events {
if ev.EventType == "voter.voted" &&
ev.Payload["electionId"] == p.electionID {
return nil, ErrAlreadyVoted
}
}
// 3. Emit voter.checked-in event
event := EventTransaction{
SubjectID: voterID,
SubjectType: "QUID",
EventType: "voter.checked-in",
Payload: map[string]interface{}{
"electionId": p.electionID,
"precinct": p.precinct,
"checkedInAt": time.Now().Unix(),
},
Creator: p.pollWorkerQuid,
Signature: p.sign(/*...*/),
}
if err := p.client.SubmitEvent(ctx, event); err != nil {
return nil, err
}
return &CheckInResult{
Party: edge.Attributes["registeredParty"].(string),
Precinct: p.precinct,
}, nil
}

4. Voter’s device: ballot issuance (blind signature)

package voter
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"math/big"
)
// Minimal client-side blind-signature implementation (RSA-based)
type BlindSigner struct {
authorityRSAPublicKey *rsa.PublicKey
}
// 1. Generate fresh BQ
func (v *Voter) GenerateBallotQuid() (*BallotQuid, error) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil { return nil, err }
pubBytes := elliptic.Marshal(priv.PublicKey.Curve, priv.PublicKey.X, priv.PublicKey.Y)
quidID := fmt.Sprintf("ballot-%s", hex.EncodeToString(sha256sum(pubBytes)[:8]))
return &BallotQuid{
QuidID: quidID,
PublicKey: pubBytes,
PrivateKey: priv,
}, nil
}
// 2. Blind the BQ ID
func (v *Voter) BlindBQID(bqID string) (*BlindedCommitment, error) {
// Hash the BQ ID.
hashed := sha256.Sum256([]byte(bqID))
m := new(big.Int).SetBytes(hashed[:])
// Random blinding factor r, gcd(r, N) = 1.
N := v.authorityRSAPublicKey.N
var r *big.Int
for {
r, _ = rand.Int(rand.Reader, N)
if new(big.Int).GCD(nil, nil, r, N).Cmp(big.NewInt(1)) == 0 {
break
}
}
// Blinded commitment: m' = m * r^e mod N
e := big.NewInt(int64(v.authorityRSAPublicKey.E))
rToE := new(big.Int).Exp(r, e, N)
commitment := new(big.Int).Mul(m, rToE)
commitment.Mod(commitment, N)
return &BlindedCommitment{
BQID: bqID,
Commitment: commitment.Bytes(),
Blinding: r.Bytes(),
}, nil
}
// 3. Submit to authority's issuance service
func (v *Voter) SubmitBallotRequest(ctx context.Context, req IssuanceRequest) (*IssuanceResponse, error) {
// Sign with VRQ's private key
sig := v.signWithVRQ(req)
return v.authorityClient.IssueBallot(ctx, req, sig)
}
// 4. Unblind the authority's response
func (v *Voter) UnblindSignature(blinded *big.Int, blindingFactor *big.Int) *big.Int {
N := v.authorityRSAPublicKey.N
rInv := new(big.Int).ModInverse(blindingFactor, N)
unblinded := new(big.Int).Mul(blinded, rInv)
unblinded.Mod(unblinded, N)
return unblinded
}
// 5. Compose the BQ identity transaction with unblinded sig
func (v *Voter) BuildBallotIdentity(bq *BallotQuid, unblindedSig []byte, electionID, contestID string) *IdentityTransaction {
return &IdentityTransaction{
QuidID: bq.QuidID,
PublicKey: bq.PublicKey,
Creator: v.authorityQuid,
UpdateNonce: 1,
Attributes: map[string]interface{}{
"electionId": electionID,
"contestId": contestID,
"issuanceEpoch": 0,
"issuanceMethod": "in-person",
},
AuthoritySignature: unblindedSig, // RSA-verifiable against authority key
}
}

Full voting-booth flow:

func (v *VotingBooth) ConductVoting(ctx context.Context, voterVRQ string, selections VoterSelections) error {
// 1. Generate fresh BQs per contest
bqPerContest := map[string]*BallotQuid{}
for _, contest := range v.election.Contests {
bq, _ := v.voter.GenerateBallotQuid()
bqPerContest[contest.ID] = bq
}
// 2. Build nullifiers per contest
nullifiers := map[string][]byte{}
for _, contest := range v.election.Contests {
nullifiers[contest.ID] = v.computeNullifier(voterVRQ, contest.ID)
}
// 3. Blind BQ IDs
commitments := map[string]*BlindedCommitment{}
for contestID, bq := range bqPerContest {
c, _ := v.voter.BlindBQID(bq.QuidID)
commitments[contestID] = c
}
// 4. Request ballot issuance
req := IssuanceRequest{
VRQ: voterVRQ,
ElectionID: v.election.ID,
Contests: buildContestReqs(commitments, nullifiers),
}
resp, err := v.voter.SubmitBallotRequest(ctx, req)
if err != nil { return err }
// 5. Unblind signatures and build BQ identity transactions
for _, contestSig := range resp.BlindSignatures {
bq := bqPerContest[contestSig.ContestID]
unblinded := v.voter.UnblindSignature(
new(big.Int).SetBytes(contestSig.BlindSignature),
new(big.Int).SetBytes(commitments[contestSig.ContestID].Blinding),
)
identityTx := v.voter.BuildBallotIdentity(bq, unblinded.Bytes(), v.election.ID, contestSig.ContestID)
// Submit BQ identity
if err := v.submitIdentity(ctx, identityTx); err != nil {
return err
}
}
// 6. Submit vote trust edges
for contestID, candidate := range selections {
bq := bqPerContest[contestID]
vote := TrustTransaction{
Truster: bq.QuidID,
Trustee: candidate,
TrustLevel: 1.0,
Domain: fmt.Sprintf("elections.%s.%s.contests.%s",
v.election.Jurisdiction, v.election.Cycle, contestID),
Nonce: 1,
}
vote.Signature = signWithBQKey(vote, bq.PrivateKey)
if err := v.submitTrust(ctx, vote); err != nil { return err }
}
// 7. Print paper ballot
v.printPaperBallot(bqPerContest, selections)
// 8. Emit voter.voted event
event := EventTransaction{
SubjectID: voterVRQ,
SubjectType: "QUID",
EventType: "voter.voted",
Payload: map[string]interface{}{
"electionId": v.election.ID,
"contestCount": len(selections),
"completedAt": time.Now().Unix(),
},
}
return v.submitEvent(ctx, event)
}

5. Anyone: recount

Terminal window
# Run from any Quidnug node with election domain visibility
$ curl "$ANY_NODE/api/v1/trust/edges?\
domain=elections.williamson-county-tx.2026-nov.contests.us-senate"
# The node returns the full list of trust edges. Client-side tally:
$ curl "..." | jq '.edges | group_by(.trustee) |
map({candidate: .[0].trustee, votes: length}) |
sort_by(.votes) | reverse'
[
{"candidate": "candidate-bob-jones-2026-senate", "votes": 51039},
{"candidate": "candidate-jane-smith-2026-senate", "votes": 47282},
{"candidate": "candidate-carol-li-2026-senate", "votes": 21476}
]

For a more robust tally (verifying signatures, applying the contest’s voting method), use a Go-based tally tool:

Terminal window
$ quidnug-tally \
--node $ANY_NODE \
--contest contest-williamson-2026-us-senate \
--election-authority election-authority-williamson-2026-nov
Verifying BQ signatures... 119,797 valid
Verifying no-double-vote... (no duplicate nullifiers)
Applying plurality tally...
Results:
Bob Jones: 51,039 (42.6%) WINNER
Jane Smith: 47,282 (39.5%)
Carol Li: 21,476 (17.9%)
Total: 119,797
Tally hash: sha256:abc123... (for independent comparison)

Independent observers on different nodes should compute the same tally hash. If they disagree, discrepancy is loud.

6. Voter: verify their own vote

Terminal window
$ quidnug-voter verify --election williamson-2026-nov
My VRQ: voter-a1b2c3d4e5f67890
I have records of 5 BQs from this election:
┌────────────────────────────────────────────────────────┐
Contest: US Senate
My BQ: ballot-Xz7mN2pQ8rK4vL9t
I cast: candidate-jane-smith
Verifying on chain...
BQ identity tx: found, authority-signed
Trust edge: found, BQ candidate-jane-smith
Edge signature: valid against BQ pubkey
Edge counted in tally
└────────────────────────────────────────────────────────┘
[Similar for each of my other BQs]
All my votes were cast and counted.

7. Paper-ballot audit

func (a *Auditor) CrossVerify(ctx context.Context, paperBallots []PaperBallot) (*AuditResult, error) {
result := &AuditResult{
TotalPaper: len(paperBallots),
TotalDigital: 0,
Matches: 0,
Discrepancies: []Discrepancy{},
}
// 1. For each paper ballot, look up its BQs on the chain
for _, paper := range paperBallots {
for _, bqID := range paper.BQIDs {
identity, err := a.client.GetIdentity(ctx, bqID)
if err != nil {
result.Discrepancies = append(result.Discrepancies, Discrepancy{
BallotID: paper.ID,
Reason: "digital BQ not found for paper ballot",
})
continue
}
// Verify BQ is authority-signed for this election
if !a.verifyAuthoritySig(identity) {
result.Discrepancies = append(result.Discrepancies, ...)
continue
}
// Cross-check the vote
digitalEdges := a.getEdgesFor(bqID)
paperVotes := paper.VotesForBQ(bqID)
if !matchesVotes(digitalEdges, paperVotes) {
result.Discrepancies = append(result.Discrepancies, Discrepancy{
BQID: bqID,
PaperVote: paperVotes,
DigitalVote: digitalEdges,
Reason: "mismatch between paper and digital",
})
continue
}
result.Matches++
}
}
// 2. For each digital vote, ensure there's a paper ballot
digitalOnly := a.findDigitalBQsWithoutPaper(paperBallots)
for _, bq := range digitalOnly {
result.Discrepancies = append(result.Discrepancies, Discrepancy{
BQID: bq,
Reason: "digital vote with no paper ballot",
})
}
result.TotalDigital = a.countAllBQs()
// 3. Emit audit event
a.submitEvent(ctx, EventTransaction{
SubjectID: a.electionQuid,
SubjectType: "QUID",
EventType: "audit.cross-verify-completed",
Payload: map[string]interface{}{
"paperCount": result.TotalPaper,
"digitalCount": result.TotalDigital,
"matches": result.Matches,
"discrepancyCount": len(result.Discrepancies),
},
})
return result, nil
}

8. Testing

func TestElection_FullFlow(t *testing.T) {
// Setup: authority, contests, candidates
// Voter: generates VRQ, registers
// Poll worker: checks in voter
// Voting booth: issues blind-signed BQs, cast votes
// Tally: correct results
// Voter: verifies own vote
// Observer: independent tally matches
}
func TestElection_DoubleVoteBlocked(t *testing.T) {
// Voter requests ballot for same contest twice
// Second request rejected (nullifier duplicate)
}
func TestElection_WrongPartyPrimaryBlocked(t *testing.T) {
// Democratic voter requests Republican primary ballot
// Issuance service rejects
}
func TestElection_InvalidBQSignature(t *testing.T) {
// Vote trust edge signed with wrong key
// Tally excludes the edge
}
func TestElection_PaperDigitalCrossVerify(t *testing.T) {
// Set up mock paper ballots matching digital
// Auditor cross-verifies; all match
// Flip one digital record; auditor flags discrepancy
}
func TestElection_RecountMatches(t *testing.T) {
// Two independent observer nodes run tally
// Results byte-identical
}
func TestElection_AuthorityKeyCompromise(t *testing.T) {
// Authority key rotated via guardian recovery mid-election
// Pre-rotation ballots still valid
// Post-rotation ballots use new key for issuance
}

Where to go next

Threat model

Adversaries, assumed capabilities, mitigations.

Threat Model: Elections on Quidnug

Elections are an unusually adversarial domain: nation-state-level adversaries, insider threats at every layer, voter coercion, social engineering at massive scale, and legal/reputation consequences for any detected flaw. This document is correspondingly detailed.

Assets, ordered by criticality

  1. Election integrity, the certified result must reflect the votes actually cast.
  2. Ballot secrecy, no party should learn how any specific voter voted.
  3. Voter eligibility enforcement, only eligible voters cast valid votes; no double voting.
  4. Public verifiability, any citizen should be able to independently confirm the result.
  5. Voter registration integrity, the voter roll must reflect eligible citizens, not attacker-controlled quids.
  6. Voter’s private keys, VRQ + BQ private keys represent eligibility and cast ballots.
  7. Authority’s signing keys, the issuance key, tally key, and guardian keys of the election authority.
  8. Paper ballot chain of custody, the physical backup.

Attacker inventory

AttackerCapabilityPrimary goal
Nation-stateComprehensive: network, infrastructure, personnelChange election outcome
Insider: election adminAuthority signing key accessSilent manipulation
Insider: IT operatorNode configuration, patches, logsIntroduce backdoors
Insider: poll workerPolling-place tablet accessLimited-scope manipulation
Voter-buyerCash, influenceCoerce / buy votes
Impersonation fraudsterStolen ID or VRQ private keyCast unauthorized ballots
Voting machine vendorFirmware / software supply chainSystematic manipulation
Partisan activistLegitimate voter + social coordinationSybil registration, coordinated fraud
Media / investigative outletPublic-observer accessFind real flaws (good actor, mostly)
DDoS attackerBandwidth, botnetsDisrupt polling or tally
Physical attackerIn-person accessDamage equipment, ballots

Threats and mitigations

Category A: Election outcome manipulation

A1. Authority compromise, silent vote modification

Attack. Attacker has compromised the election authority’s signing key and uses it to retroactively forge trust edges (fake votes) or re-sign the tally with altered numbers.

Mitigations.

  • Append-only chain. Trust edges and events are written to the Quidnug blockchain. Attacker can’t “modify” existing edges, they can only add new ones. Observers see additions.
  • Guardian-based authority recovery (QDP-0002), authority’s own key is rotatable via multi-party quorum. A compromised key is invalidated via AnchorInvalidation; subsequent signatures at the compromised epoch are rejected.
  • Independent tally by observers. Observer nodes (media, parties) run their own tally queries. An attacker adding fake edges has to add them to every observer’s chain, requiring compromising the gossip network, which requires orders-of-magnitude more resources.
  • Paper-ballot cross-verification. Any discrepancy between paper and digital is flagged. Paper wins. An attacker adding fake digital votes has to also stuff matching paper ballots into physical boxes, physical access required.
  • Merkle root snapshots. At polls-close time, a snapshot of the voter roll and ballot issuance is published. Later manipulation is detectable by comparing.

Residual risk. A coordinated attacker with authority-key compromise + network-level control + physical ballot-stuffing could manipulate an election. This is a “sovereign-scale” attack requiring massive resources. Quidnug doesn’t make this easier; it provides more evidence paths than current systems.

A2. Insider adds forged registrations

Attack. Corrupt insider at the authority adds fake trust edges for non-existent voters, allowing fake ballots later.

Mitigations.

  • Public registration count. Anyone can count registered voters per precinct. Unusual growth (e.g., Precinct 42 gains 5000 voters in a week) is visible.
  • Registration domain rate-limits. Fork-block (QDP-0009) can establish per-week / per-month registration caps per jurisdiction. Attempts to exceed require explicit fork-block signoff.
  • Cross-reference with off-chain voter file. State SOS’s voter file comes from DMV + Social Security death index + etc. Discrepancies between on-chain count and SOS file flagged.
  • Periodic audit events. Scheduled audit.registration-check events from independent observers comparing on-chain vs. SOS.

Residual risk. Insider + compromised oversight could sneak in forgeries. Normal audit processes (already required by state law) apply.

A3. Vote-changing attack after the polls close

Attack. After polls close, attacker (with authority access) modifies trust edges to change votes.

Mitigations.

  • Trust edges are append-only, attacker can only add new edges, not modify. A new edge from the same BQ would show up as a duplicate in the tally → flagged.
  • Per-BQ anchor nonce ensures only one valid first-vote from a given BQ; subsequent trust edges with the same nonce are rejected by the ledger.
  • Paper ballot cross-check catches any anomaly.
  • Independent observer tallies lock in the result within minutes of polls closing.

A4. Tally algorithm manipulation

Attack. The election authority publishes a “tally” that doesn’t correctly reflect the chain state (e.g., counting edges wrong).

Mitigations.

  • Tally algorithm is publicly specified in contest attributes. Any observer can re-run it.
  • Open-source tally tools make the logic auditable.
  • Authority’s published tally is a signed event, if it differs from independent computation, the challenge is specific and on the record.

Category B: Ballot secrecy attacks

B1. Authority learns who voted for whom

Attack. Corrupt authority operator tries to correlate BQs back to VRQs to identify voters.

Mitigations.

  • Blind signature during ballot issuance, authority never sees the unblinded BQ ID. This is a cryptographic guarantee: the correlation is mathematically unrecoverable without the voter’s blinding factor (held only on voter’s device).
  • Even authority-level access to the Issuance Service’s logs doesn’t reveal BQ IDs.

Residual risk. The blind signature’s security relies on the cryptographic hardness assumption (RSA + secure blinding). A flaw in the blind-signature implementation would be catastrophic. Mitigated by using well-audited implementations and by multiple-BQ-per-voter design (breaking any one BQ’s anonymity only reveals one contest, not the whole ballot).

B2. Traffic analysis to correlate check-in with ballot cast

Attack. Observer of network traffic correlates “voter X checked in at 10:15” with “BQ ABC appeared at 10:17.”

Mitigations.

  • Mix-network batching, ballot issuance events are processed in batches, not in real-time order. Multiple voters’ issuance requests are shuffled before publication.
  • Delay randomization, BQ publication to the chain is randomly delayed within a short window.
  • Tor-style anonymity for BQ submissions (optional), voter submits votes through an anonymizing relay.
  • Polling-place processing, all voters at precinct 42 check in publicly but their BQs are processed together in a batch; observer sees many BQs appear “at once” without per-voter timing.

Residual risk. A very-well-resourced observer (e.g., ISP- level traffic inspection) with network position could in theory correlate. Mitigation: defense-in-depth with mix networks + Tor.

B3. Coercion via BQ private key

Attack. Coercer demands voter’s BQ private key to verify they voted “correctly.”

Mitigations (layered).

  • Legal, criminal penalties for vote buying.
  • Decoy BQs, voter’s device can generate N plausible BQs; voter shows any to a coercer. Only the voter knows which was actually cast.
  • Revocable ballots (where legal), voter may re-cast up to deadline; final vote counts. Coercer can verify an initial vote but voter can change later.
  • Physical private voting booth, coercer can’t accompany voter into booth.

Residual risk. Perfect coercion resistance is an open research problem. Current protocol gives better defense-in-depth than paper-based voting.

Category C: Voter impersonation

C1. Stolen VRQ private key

Attack. Attacker steals a voter’s VRQ private key and attempts to vote as them.

Mitigations.

  • Physical check-in at polling place, in-person voters must show physical ID matching the VRQ’s registered voter. An attacker with VRQ key but not the ID can’t check in.
  • Online voting (where enabled) requires additional authentication (biometric, SMS-based second factor), application-layer but standard.
  • Voter alert, if voter notices their checked-in event when they haven’t voted, they can file a dispute immediately.
  • Guardian recovery of VRQ, voter can rotate their VRQ via guardians, invalidating the stolen key.

Residual risk. Stolen key + stolen ID = impersonation possible. Same threat level as current ID-based voting.

C2. Registration of non-existent voters

Attack. Attacker creates fake quids and registers them.

Mitigations.

  • Registration requires in-person or online ID verification. Authority’s registrar is the check.
  • Duplicate detection via off-chain voter file.
  • Public registration count, unusual growth is flagged.
  • Monotonic nonces on registration edges prevent simple replay.

Category D: Double voting

D1. Voter attempts to vote at two polling places

Attack. Voter checks in at precinct 42, then drives to precinct 43.

Mitigation. Push gossip (QDP-0005) propagates the voter.checked-in event from 42 to every other precinct within seconds. At 43, the lookup finds the prior check-in → reject.

Residual risk. Narrow window during network partition. If precincts are offline-isolated for some time, a voter could theoretically double-check-in. Mitigation: precinct nodes refuse check-in if they haven’t seen a heartbeat from the authority in

1 minute.

D2. Voter requests multiple ballots for same contest

Attack. Voter tries to trick the Issuance Service into signing multiple BQs for the same contest.

Mitigation. Nullifier, derived from voter’s VRQ + contest ID. Same voter + same contest → same nullifier. Second request rejected.

D3. Authority issues duplicate ballots

Attack. Corrupt issuance operator manually creates extra BQ identity transactions for a compromised voter, inflating their vote count.

Mitigation. Each BQ identity transaction has an authority signature. The authority’s signing key is monitored; unusual signing patterns visible in push-gossip traffic. Observer nodes can count the per-VRQ ballot issuances (via public ballot.issued events). A VRQ with ballot.issued events exceeding the contest count is anomalous.

Category E: Infrastructure attacks

E1. DDoS on authority node on election day

Attack. Flood the authority’s issuance service to prevent voters from getting ballots.

Mitigations.

  • Standard DDoS mitigations (CloudFlare-style).
  • Issuance service horizontally scaled across precincts, attack on one precinct doesn’t affect others.
  • Paper ballot fallback, if issuance service is down, voters cast paper ballots directly; these are later reconciled.

E2. Network partition between precincts

Attack. Attacker cuts network links between precincts.

Mitigations.

  • Push gossip resumes when network heals.
  • Paper ballot fallback.
  • Precinct-local tally possible in isolation; state-level aggregate later.

E3. Voting booth software compromise

Attack. Supply-chain attack on the open-source voting booth application.

Mitigations.

  • Open-source + reproducible builds, anyone can verify the running binary.
  • Multiple independent implementations encouraged.
  • Paper ballot cross-check is the backstop. Compromised booth software produces votes; paper ballot’s human-readable version ground-truths.

F1. Authority compelled to hand over keys

Attack. State actor compels election authority to hand over signing keys.

Mitigations.

  • Multi-party guardian set, no single person has the keys.
  • HSM-protected, keys can’t be “handed over” in practice (HSMs only sign, don’t export).
  • Guardian-set rotation under legal duress, new guardians replace any compelled ones.

Residual risk. Sufficiently broad legal compulsion defeats distributed trust. No protocol helps here; this is a jurisdictional problem.

Attack scenarios, end-to-end

Scenario 1: “Attacker wants to add 50,000 fake votes for Candidate X”

Required actions:

  1. Register 50,000 fake VRQs (requires defeating identity verification 50,000 times, detectable).
  2. Check-in each (requires either physical presence at polling places, or mail-in, each has its own verification).
  3. Issue 50,000 ballots (nullifiers per contest).
  4. Cast 50,000 votes.
  5. Print 50,000 paper ballots and stuff them into ballot boxes (physical access to every precinct).
  6. Ensure observer nodes see the same chain (network-level compromise of gossip).
  7. Ensure state SOS doesn’t compare to their voter file.
  8. Ensure the risk-limiting audit picks a sample that doesn’t catch the forgeries.

Defeating all 8 is a massive operation. Any single defense working catches the attack.

Scenario 2: “Attacker wants to know how a specific voter voted”

Required actions:

  1. Compromise the voter’s VRQ private key.
  2. Compromise the blind-signature cryptographic secret (the voter’s blinding factor, on their device).
  3. OR: compromise the Issuance Service’s logs during the blind- signature phase before the commitment is unblinded.

Step 2 is cryptographically hard; step 3 is only possible during a short window and would be visible in the logs the authority is supposed to retain.

Scenario 3: “Attacker wants to suppress a specific voter”

Required actions:

  1. Prevent the voter from registering (difficult, they can register via alternate channels).
  2. OR: cause the registration trust edge to be invalid (requires authority key compromise).
  3. OR: physically prevent voting (in-person intimidation).

On-chain suppression is hard because any modification to a voter’s registration is an append-only event, revocation leaves a trail.

Monitoring

Operator dashboards and alerts:

MetricAlert condition
Registrations per hour> baseline × 3
Check-ins per precinct per minute> precinct capacity
Ballot.issued events per minute> baseline × 3
Nullifier-duplicate rejections> 0.1% of issuances
Vote trust-edge signature failures> 0 (should be 0)
Tally discrepancies between independent nodesany
Paper-digital cross-verify discrepancies> sampling tolerance
Authority signing key epochchange during election day
quidnug_gossip_rate_limited_total{producer=auth}> 0
quidnug_probe_failure_total> baseline
Guardian-resignation events on authority> 0 during election

Incident response

Playbooks (excerpts):

  1. Authority signing key compromise detected.

    • Invalidate affected epoch immediately (AnchorInvalidation).
    • Initiate emergency guardian recovery.
    • Pause new ballot issuance at all precinct nodes.
    • Communicate with bipartisan oversight board.
    • Continue accepting paper ballots; digital rejoins after rotation complete.
  2. Unusual registration spike.

    • Observer flags via monitoring.
    • Temporarily suspend auto-approval of online registrations pending investigation.
    • Cross-check with state voter file.
    • Public notice if fraud confirmed.
  3. Paper-digital mismatch beyond statistical tolerance.

    • Full hand-count of affected precinct.
    • Forensic review of voting-booth devices (open-source, so binary verification possible).
    • Recount via paper.
    • Certification delayed per statutory process.

Comparison with traditional election attack surfaces

Attack surfaceTraditionalQuidnug
Tally manipulationCentral tabulatorIndependently verifiable by observers
Voter roll manipulationCentral DBPublic chain, anyone counts
Ballot stuffingPaper ballot-box accessPaper + digital cross-verify
ImpersonationID document + pollsSame + cryptographic VRQ
CoercionBooth privacy + lawsSame + decoy BQs
Tech supply chainVendor firmwareOpen-source + paper backup
Insider adminDB accessMulti-party guardian quorum
Post-certification challengeLawsuitsChain replay + paper recount

Not defended against (explicit limits)

  1. Sovereign-scale adversary with nation-state resources targeting multiple layers simultaneously.
  2. Compromise of the blind-signature cryptographic primitive (an implementation flaw would be catastrophic; mitigated by using well-audited libraries).
  3. Out-of-protocol vote buying that doesn’t require cryptographic proof (e.g., handing voters cash based on trust).
  4. Registration-eligibility fraud at the DMV or other identity-issuance upstream sources.
  5. Coordinated mass voter suppression via real-world intimidation (a legal / policing matter).
  6. Perfect receipt-freeness, the protocol offers layered defenses but no cryptographic guarantee absent full homomorphic tallying (future work).

References

Integration

How existing systems plug in without a rewrite.

Elections integration with the three architectural pillars

How the elections use case composes on top of the three architectural QDPs (0012 Domain Governance, 0013 Network Federation, 0014 Node Discovery + Sharding). Written as a companion to README.md + architecture.md, which predate the pillars and describe the election semantics without addressing the larger protocol architecture.

Read the three pillar documents first: docs/design/0012-domain-governance.md, docs/design/0013-network-federation.md, docs/design/0014-node-discovery-and-sharding.md, or their operator-facing summaries under deploy/public-network/.

1. Why this integration matters

The original elections design (README.md, architecture.md) specifies the election semantics: quid types, trust edges as votes, blind-signature ballot issuance, paper-ballot parity, universal recount. It’s complete at the “what events get signed” layer.

What it doesn’t specify, because the relevant QDPs landed later, is how the system is deployed, governed, and discovered:

  • Who exactly are the governors for the election’s domains? The original doc has “Guardian set” for the authority quid but uses pre-QDP-0012 vocabulary that conflates guardians with governance quorum.
  • What runs the blocks, and at what scale? Precinct-level polling places, county-wide counting, state-wide certification: this is a sharding question the original doc hand-waves.
  • How do federal elections federate across state-run infrastructure? Pre-QDP-0013, the original doc implicitly assumed one giant network; realistically every jurisdiction runs its own.
  • How do voters’ clients find the right nodes for their precinct? The original doc assumes a central api.quidnug endpoint; per QDP-0014 this should be explicit.

This document fills those gaps.

2. QDP-0012 Domain Governance, the election authority as a governor

2.1 The mental model update

In the original doc, the “Election Authority Quid” is described as a quid with a guardian set. Under QDP-0012 we separate three roles more precisely:

RoleWho, for elections
GovernorThe humans / institutions authorized to vote on changes to the election’s domain consortium + parameters. Typically: chief election official, state SoS office, bipartisan oversight board, independent observers.
Consortium member (validator)The physical nodes that produce blocks for the election’s domains. Typically operated by the authority + observer organizations, in geographically diverse locations.
Cache replicaEvery precinct polling-place device, every observer’s laptop, every journalist’s monitor, any node that mirrors the agreed chain for reading but doesn’t produce blocks.
Guardian(Orthogonal to governance.) The specific quorum that can recover a compromised governor’s key via QDP-0002.

Any one human might hold two or three of these hats. The chief election official is typically a governor AND operates one of the consortium-member nodes. But the roles are independently defined.

2.2 Governance structure for a county election

For elections.williamson-county-tx.2026-nov:

Domain: elections.williamson-county-tx.2026-nov
governors:
county-clerk-quid weight=2
state-sos-tx-quid weight=2
r-party-observer-quid weight=1
d-party-observer-quid weight=1
lwv-observer-quid weight=1 # League of Women Voters
governanceQuorum: 0.7 # 5 of 7 weighted votes
notice period: 72 hours # longer than reviews; election-critical
consortium (validators):
node-authority-primary weight=1
node-authority-backup weight=1
node-observer-r weight=1
node-observer-d weight=1
node-observer-lwv weight=1
validatorTrustThreshold: 0.67 # 3-of-5 blocks accepted
cache replicas:
precinct-001-polling-place
precinct-002-polling-place
...
precinct-423-polling-place
(~423 precincts, each running a precinct-local cache-only node)
(plus any observer / journalist / candidate running a mirror)

Five block-producing consortium members, three of which must agree (2/3 quorum) for a block to reach Trusted on any observer. Seven governors with weighted votes; 5-of-7 needed to mutate anything (add a validator, remove one, etc.).

Every governor has a separate guardian quorum (QDP-0002). The chief election official’s guardian quorum might be: spouse, business partner, state ethics officer, federal monitor, lawyer. Losing the chief’s key doesn’t lose the election’s authority, guardians can rotate.

2.3 Governance actions during an election cycle

With QDP-0012’s transactions, these become first-class, on-chain events:

ActionGovernance transactionNotice
Install a new validator (e.g. add a third observer org mid-cycle)ADD_VALIDATOR72h
Remove a compromised validatorREMOVE_VALIDATOR72h (but see emergency path below)
Change the blocking thresholdSET_TRUST_THRESHOLD72h
Add a new precinct domainchild domain registration + DELEGATE_CHILD72h
Change the oversight composition (replace an observer)UPDATE_GOVERNORS7 days, unanimous
Emergency: suspected compromised nodeREMOVE_VALIDATOR with expedited notice1 hour by pre-negotiated emergency clause

Emergency clause. For the week of the election, a pre-negotiated emergency governance track allows 1-hour notice periods on REMOVE_VALIDATOR actions signed by a full quorum (5-of-7 weighted). This lets the oversight board remove a compromised node the same day a breach is detected, without waiting three days. The emergency clause itself is installed via a standard 72h SET_EMERGENCY_WINDOW governance action before election day.

2.4 Domain tree as governance hierarchy

Each sub-domain of the election can have its own governance scope or inherit from the parent:

elections.williamson-county-tx.2026-nov (governed by
the root
county-level
quorum)
├── registration (inherits)
├── poll-book.precinct-001 (inherits, or
│ delegated to
│ the precinct
│ judges for
│ operational
│ signing?)
├── ballot-issuance (inherits)
├── contests.us-senate (inherits)
├── contests.governor (inherits)
└── audit (inherits,
possibly
delegated to
post-election
audit firm)

For most sub-domains, inherit-from-parent is correct, the county authority governs everything uniformly. For audit, a post-election delegation to an independent auditor (via DELEGATE_CHILD) lets the auditor publish findings on-chain with their own quorum, which is stronger proof than having the county authority publish them.

3. QDP-0013 Network Federation, multi-jurisdiction elections

3.1 The problem without federation

In the US, elections are administered at the county level, but contests span multiple levels:

  • County races (e.g. county commissioner), county authority runs the show.
  • State races (governor, US senator), state-wide tally is the sum of county-level results.
  • Federal races (US president), multi-state aggregation with Electoral College complications.

If each county runs its own Quidnug network (which is the natural deployment), how does the state get a view across all counties? How does a federal election observer verify totals without trusting every county’s infrastructure individually?

3.2 The federation topology

Each jurisdiction runs its own network. Parent jurisdictions (state, federal) federate trust-lookups against the child jurisdictions’ networks:

┌──────────────────────────┐
│ FEDERAL NETWORK │
│ (elections.us.2028-pres)│
│ federates with: │
│ - all 50 state nets │
│ - reputation via │
│ `TRUST_IMPORT` │
└─────────────┬────────────┘
│ federates
┌───────────┼───────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ STATE NET (TX) │ │ STATE NET (CA) │
│ state SoS │ │ state SoS │
│ governs │ │ governs │
│ federates with: │ │ federates with: │
│ - 254 counties │ │ - 58 counties │
└────────┬────────┘ └────────┬────────┘
│ federates │
┌─────┼─────┐ ┌────┼────┐
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌─────────┐┌─────────┐
│ COUNTY │ │ COUNTY │ │ COUNTY ││ COUNTY │
│ Williams │ │ Travis │ │ LA ││ SF │
│ own net │ │ own net │ │ own net ││ own net │
│ own │ │ own │ │ own ││ own │
│ conso. │ │ conso. │ │ conso. ││ conso. │
└──────────┘ └──────────┘ └─────────┘└─────────┘

Each level operates its own Quidnug network, own consortium, own governors, own chain. Parent levels federate to their children for cross-jurisdictional reads.

3.3 What federation actually means for elections

Three distinct federation scenarios:

Scenario A: State publishes per-county totals at tally time. The state SoS network queries each county’s network at certification time, importing the signed tally events into the state-level chain via TRUST_IMPORT. Any observer can verify:

  1. The state-level tally event has M-of-N state governor signatures.
  2. Each imported county tally has its originating county’s consortium signatures.
  3. The import chain is intact (no county’s tally was dropped or altered at import time).

No observer has to trust the state’s claim about a county’s total; they can fetch the county’s own signed tally directly and compare.

Scenario B: Voter registered in one county, voting in another. A voter registers in County A, then moves to County B before the election. County B needs to verify their registration. Instead of a faxed form, County B’s network configures County A’s network as an external_trust_source in the elections.us.<year>.registration domain. When County B’s ballot-issuance flow needs to verify a voter’s registration, it can fetch the registration record directly from County A with cryptographic proof.

This replaces a currently-fragile state DMV “motor voter” interstate data-exchange system with a signed, auditable cross-network lookup.

Scenario C: Federal observer runs a national recount. An NGO or news organization wants to compile a national tally. They run a single node that federates with all 50 state networks (or, via state → county transitivity, all ~3000 county networks). Their node’s discovery API answers “what is the total vote count for president, from each state’s chain, aggregated?” All cryptographic, all signed by the originating jurisdictions, all runnable from anyone’s laptop.

3.4 Cross-jurisdictional reputation fungibility

Beyond tallies, federation enables reputation-carrying for election officials + observer organizations:

  • A certified election monitor (League of Women Voters, etc.) has trust edges from multiple state authorities in their respective observers.elections.* domains. Federal-election observation trusts the union of these.
  • A precinct election judge with years of clean operation (no disputed close-outs, no irregularity reports) accumulates reputation in their county’s domain. When they move to another county, their reputation travels via TRUST_IMPORT.
  • Post-election auditing firms accumulate multi-jurisdictional reputation the same way. Hiring an auditor becomes a reputation-weighted decision, not just a procurement checklist.

None of this requires a new primitive, it’s QDP-0013’s federation mechanisms applied to the election domain.

4. QDP-0014 Node Discovery + Sharding, operational topology

4.1 The operator-to-nodes hierarchy

Per QDP-0014, the election authority is an operator quid with multiple node quids under it. For a county election:

county-clerk-quid (operator)
──TRUST 1.0──► node-authority-primary
(validator, archive; runs in county data center)
──TRUST 1.0──► node-authority-backup
(validator, archive; runs in a different facility)
──TRUST 1.0──► node-certification
(cache, archive; post-election audit-focused)
in operators.elections.williamson-county-tx.2026-nov
r-party-observer-quid (operator)
──TRUST 1.0──► node-observer-r
in operators.elections.williamson-county-tx.2026-nov
... (similarly for D, LWV, SoS)
county-clerk-quid (via precinct judges' sub-delegation)
──TRUST 1.0──► precinct-001-polling-device
(cache only)
──TRUST 1.0──► precinct-002-polling-device
...
in operators.elections.williamson-county-tx.2026-nov.precincts

At election-day scale for a medium county: ~5 validator nodes (the consortium), ~10 dedicated cache nodes (for fast check-in queries), ~500 precinct-device cache replicas, plus an unknown number of observer / journalist nodes.

4.2 Node advertisements

Each node publishes a signed NODE_ADVERTISEMENT declaring:

  • Endpoints, where it’s reachable.
  • Supported domains, which parts of the tree this node serves (registration, poll-book.precinct-042, etc.).
  • Capabilities, validator, cache, archive, bootstrap, gossip-sink.
  • Expiration, short TTL (6 hours at election launch; 1 hour during the active voting day so dead nodes age out fast).

Precinct devices publish “cache-only” advertisements scoped to their own precinct’s poll-book + ballot-issuance domains. The authority’s primary validators publish “validator + archive” advertisements scoped to the full tree.

4.3 Discovery for voters + poll workers + observers

The voter’s phone app or the poll worker’s device uses the QDP-0014 discovery API:

# Poll worker checks in a voter at precinct 042:
GET /api/v2/discovery/domain/elections.williamson-county-tx.2026-nov.poll-book.precinct-042
Returns:
- block tip for this precinct's domain
- consortium members authorized to serve this domain
- cache replicas currently reachable
- endpoints, sorted by region (local precinct first)
Poll worker's device picks the local precinct cache (lowest
latency) for the actual check-in query.

Observers run their own nodes and hit api.<authority>.gov (the authority’s api gateway). The gateway routes to appropriate backend nodes via the same discovery logic, they don’t need to know physical topology.

4.4 Sharding patterns for elections

Combinations:

  • Geographic, nodes deployed in every precinct + county seat + state-level backup.
  • Domain-tree, heavy-traffic domains (poll-book during voting hours, ballot-issuance, contest streams) get more cache nodes; lightly-touched domains (certification, audit) get fewer.
  • Capability, validators are few (5-9 per jurisdiction); cache nodes scale horizontally; archive nodes are 2-3 per jurisdiction for post-election audit.
  • Network federation, some state nodes also act as federation bridges to county networks; some federal nodes federate to all 50 states.

Example sharding for election day at a major county:

node-authority-primary validator+archive, region=data-center-1
node-authority-backup validator+archive, region=data-center-2
node-observer-r validator, region=party-r-office
node-observer-d validator, region=party-d-office
node-observer-lwv validator, region=neutral-facility
[above = the five-member consortium]
node-cache-east-1..3 cache, region=east-county
node-cache-west-1..3 cache, region=west-county
node-cache-central-1..3 cache, region=central-county
[above = dedicated cache nodes for heavy read traffic]
node-archive-1 archive, region=state-archive
node-archive-2 archive, region=federal-archive
[above = post-election audit sources]
precinct-001..423-device cache, region=precinct-local
supportedDomains=[poll-book.<their-precinct>]
[above = hundreds of precinct-level cache replicas, each only
serving its own precinct's poll-book lookups]

The QDP-0014 discovery API routes each client query to the closest + most-capable-matching node automatically.

4.5 No-node participation for voters

A voter doesn’t run a node. They hold:

  • Their VRQ private key (on their phone, in a hardware token, or on paper).
  • Their BQ private key (ephemeral; generated per ballot).

Their client app:

  1. Fetches the well-known election file from the authority’s HTTPS URL.
  2. Uses the discovery API to find a reachable cache node.
  3. Signs transactions (registration, ballot request, vote) locally.
  4. Submits via the api gateway.
  5. Reads back own records for verification (individual verifiability).

This is exactly QDP-0014’s “lightweight participation” pattern (§14 of QDP-0014). Voters get full cryptographic participation with zero infrastructure. An election with 10 million voters doesn’t require 10 million nodes, it requires 1 authority + ~500 precinct caches + 5-9 validator consortium members.

4.6 Per-domain quid index for precinct lookups

The QDP-0014 per-domain quid index is load-bearing for one specific election flow: “is this voter registered in this precinct?”

GET /api/v2/discovery/quids
?domain=elections.williamson-county-tx.2026-nov.registration
&since=<last-registration-deadline>
&sort=first-seen
&limit=500
&offset=0

Returns: every quid with a VOTER_REGISTERED event in the domain within the window. The poll-book device paginates through to build its local precinct roll at polling-place opening.

Alternatively, a precinct’s poll book is scoped to a child domain (poll-book.precinct-042), and the registration authority issues REGISTERED_IN_PRECINCT events to that domain, making per-precinct lookups trivial.

5. Well-known discovery for elections

Every election authority publishes a .well-known/quidnug-network.json file (per QDP-0014 §7) that serves as the cold-start entry point for all participants.

Example URL for a county election:

https://elections.williamson-county-tx.gov/.well-known/quidnug-network.json

Contents (abbreviated):

{
"version": 1,
"operator": {
"quid": "<county-clerk-quid>",
"name": "Williamson County Clerk",
"publicKey": "04..."
},
"apiGateway": "https://api.elections.williamson-county-tx.gov",
"seeds": [
{ "nodeQuid": "...", "url": "https://node1.elections.wilco.gov", "capabilities": ["validator", "archive"] },
{ "nodeQuid": "...", "url": "https://observer-r.elections.wilco.gov", "capabilities": ["validator"] },
{ "nodeQuid": "...", "url": "https://observer-d.elections.wilco.gov", "capabilities": ["validator"] },
{ "nodeQuid": "...", "url": "https://observer-lwv.elections.wilco.gov", "capabilities": ["validator"] }
],
"domains": [
{ "name": "elections.williamson-county-tx.2026-nov", "description": "November 2026 general election", "tree": "elections.williamson-county-tx.2026-nov.*" }
],
"governance": {
"documentedAt": "https://elections.williamson-county-tx.gov/governance",
"governors": [
{ "quid": "...", "name": "County Clerk", "publicKey": "04...", "weight": 2 },
{ "quid": "...", "name": "State SoS TX", "publicKey": "04...", "weight": 2 },
{ "quid": "...", "name": "R Party Observer", "publicKey": "04...", "weight": 1 },
{ "quid": "...", "name": "D Party Observer", "publicKey": "04...", "weight": 1 },
{ "quid": "...", "name": "LWV Observer", "publicKey": "04...", "weight": 1 }
],
"quorum": 0.7
},
"federationAvailable": true,
"lastUpdated": 1748000000,
"signature": "..."
}

Voter apps, observer software, and federated state networks all fetch this file first, pin the operator pubkey, and use it to verify everything they see afterwards.

The file is also mirrored publicly on the elections.williamson-county-tx.gov website’s landing page so anyone can verify the declared governors + consortium before trusting the election output.

6. Federated-network scenario: US presidential election

Putting all three pillars together for the hardest case.

6.1 Network topology

  • Federal network (elections.us.2028-pres), governed by a narrow consortium (FEC? bipartisan federal electors board?, policy question, not technical). Light; mostly contains an imports sub-tree that pulls per-state certifications.
  • 50 state networks, each governed by its SoS + legislative oversight + federal monitor. Each state-level network contains aggregated per-county totals.
  • ~3000 county networks, each governed by the county election authority as described above.

6.2 Data flow, election night

23:00 EST counties begin closing polls
23:30 EST county authorities publish signed TALLY events
on their own chains
00:00 EST state-level networks run scheduled federated
aggregation jobs:
- fetch each county's latest TALLY event via
the federation API
- verify signatures against the county's
governor pubkeys (pinned from each county's
well-known file)
- aggregate and sign a STATE_AGGREGATION event
on the state's own chain
01:00 EST federal-level network aggregates per-state:
- federated fetch of each state's
STATE_AGGREGATION event
- verify + sum + emit FEDERAL_TALLY event
(this is not "official certification", that
follows Electoral-College + canvassing rules.
it's the auditable cryptographic aggregation.)

At every level, the aggregated output carries cryptographic proof of every input it incorporates. Anyone with a laptop can redo the federation walk and confirm the aggregation is correct. The chain IS the audit trail, the post-election audit becomes a replay of the chain, not a parallel manual verification exercise.

6.3 Recount at federal level

“Candidate X contests the presidential result.”

  1. Candidate X’s lawyer runs the aggregation from a cold laptop:
    quidnug-cli elections aggregate-federated \
    --root https://elections.us.gov/.well-known/quidnug-network.json \
    --year 2028 \
    --contest us-president \
    --verify-signatures
  2. The CLI federates across all 50 state networks, fetches all county tallies, verifies every signature, and prints:
    • Total per candidate.
    • State-level breakdowns.
    • Any county whose tally signature fails verification (red-flagged for manual review).
  3. If the candidate disputes a specific county’s tally:
    quidnug-cli elections county-detail \
    --county williamson-county-tx \
    --year 2028-nov \
    --contest us-president
    Returns the full per-ballot vote edge list for the contest, cryptographically tied to ballot issuances, which are tied to voter registrations. Every vote is independently verifiable from primary data.
  4. No waiting for the county to re-scan ballots. No vendor software rerunning on the same counts. The recount is an O(n) read of the public chain.

Compare to 2020 Arizona, where the Cyber Ninjas ballot audit took months and cost millions. Under this design, the same audit is a few hours of laptop time and costs the electricity to run it.

7. Governance changes mid-cycle

Elections don’t happen on schedule; unexpected events force governance changes. Three scenarios the original design didn’t formalize:

7.1 A governor is unavailable on election day

A governor has a heart attack, loses their phone, gets arrested, doesn’t matter. Their key is unavailable.

Quorum math matters: the election’s 5-of-7 quorum still functions with 6 available governors. One missing governor can’t block actions. If TWO become unavailable simultaneously, the remaining 5 can still act.

Guardian recovery (QDP-0002) for a lost key takes ~24h at minimum. For election-day issues, this isn’t fast enough, but because the quorum is resilient, it doesn’t need to be.

7.2 A consortium node is compromised election night

An observer’s node is detected publishing inconsistent blocks.

  1. Election-night IR team confirms compromise.
  2. Governor quorum signs REMOVE_VALIDATOR with the pre-negotiated 1h emergency-notice clause.
  3. In 1 hour, the validator is removed; the remaining 4 nodes continue at 3-of-4 threshold.
  4. Post-incident: the REMOVE_VALIDATOR tx + incident-report event on the audit domain give a complete public record.

The 1-hour window isn’t zero, but a compromised single node out of 5 can’t unilaterally produce accepted blocks anyway, the 3-of-5 threshold means it needs collusion. The emergency removal is about cleaning up, not stopping in-progress harm.

7.3 The whole consortium is compromised

Extremely unlikely (would require ~all 5 node operators independently compromised) but design for it.

The answer is federation (QDP-0013). If the county’s consortium is untrustworthy, the state SoS network refuses to federate with that county’s results. The county’s chain is still there, still verifiable, but loses its legitimacy at the aggregation layer. The state can run an emergency paper-ballot audit at the county level using the county’s own paper ballots (QDP-0013 doesn’t require the county’s electronic consortium to cooperate, the paper ballots are in a physical lockbox the state has access to).

This is why paper-ballot parity is non-negotiable: it’s the fallback when the digital consortium is worst-case compromised. The cryptographic design makes that fallback almost never needed, but it’s there.

8. Operational readings

For actually running an election on this architecture:

For understanding the protocol layer referenced above:

9. What this integration changes in the existing design

A handful of specific corrections to the pre-QDP-0012 text in README.md and architecture.md:

  1. “Guardian set” vs “governor quorum.” The README §1 “Election Authority Quid” section describes a requireGuardianRotation: true model. Under QDP-0012, guardians (QDP-0002 recovery) and governors (QDP-0012 voting) are separate. Each governor has their own guardian quorum; the election authority as a whole has a governor quorum.

  2. Domain validators. README §domain-hierarchy says “Each domain has its own validators (the election authority + observers).” Under QDP-0012, this means the consortium members for each domain, who must be added via ADD_VALIDATOR governance transactions, not just declared ad-hoc.

  3. No mention of cache replicas. The existing design implicitly assumes every precinct device is a full-fledged node. With QDP-0012 + QDP-0014, precinct devices are cache replicas (read-only, much cheaper to deploy). This is a material cost reduction for large-county deployments.

  4. No federation story. The existing design is single-jurisdiction. Real elections span jurisdictions. §3 above fills this gap.

  5. Discovery is implicit. The existing design says “hit the api gateway.” QDP-0014 makes this discoverable + signed

    • cache-friendly + shardable.

These corrections don’t invalidate the existing design, they layer on top, making the deployment story complete. Treat this integration document as the authoritative ops-layer companion to the semantics layer in README.md + architecture.md.

Operations

Runbooks, observability, incident response.

Elections operations

Deployment topology, capacity planning, election-day operations, and incident response for running a Quidnug- based election at any scale from precinct-only pilot to federated presidential general.

Read README.md for the election semantics, architecture.md for the data model, integration.md for the architectural- pillar integration. This document assumes you’ve internalized those.

Operationally this parallels deploy/public-network/home-operator-plan.md but for elections instead of reviews, same protocol, different deployment profile.

1. The deployment-scale matrix

Pick the row that matches your election:

ScaleExampleVotersNodesMonthly costComplexity
PilotStudent body election, 1 precinct< 1k2 (validator + backup)< $50Single-day launch
Small municipalTown council, 5 precincts1-10k4 (consortium + observer + 2 caches)$100-3001-2 week launch
CountyWilliamson County TX example100k-1M10-20 (5 consortium + cache + precinct devices)$500-2k3-6 month launch
StateTexas SoS statewide10M+30-100 (state consortium + federated to counties)$5k-20k1-2 year launch
FederalUS presidential150M+200+ (federal consortium + federated to 50 states + 3k counties)$50k-250k2-4 year launch

The protocol is the same at every scale; only topology and operational rigor change. This document gives you a capacity-planning and deployment recipe for each.

2. Deployment architecture per scale

2.1 Pilot (student body, HOA, office election)

Simplest possible deployment:

┌─────────────────────┐
│ Your laptop (WSL) │
│ ┌───────────────┐ │
│ │ quidnug node │ │ port :8087
│ │ (validator + │ │
│ │ archive + │ │
│ │ cache) │ │
│ └───────────────┘ │
└──────────┬──────────┘
│ cloudflared tunnel
https://vote.your-org.example
│ voters hit this URL
│ from their phones
[voter phones / browsers]

One node. One operator quid (you). Maybe one observer key on a friend’s laptop for two-person control. No physical infrastructure beyond a laptop and a Cloudflare tunnel.

Governance: 1-of-1 governor (you). Consortium: your node. Cache: your node. Everyone connects direct to your URL.

Launch time: an afternoon. Follows the home-operator-plan exactly, with “elections” swapped for “reviews.”

2.2 Small municipal (town council, school board)

Three nodes, two physical sites:

┌──────────────────────┐ ┌──────────────────────┐
│ Town clerk machine │ │ Observer laptop │
│ (data center or │ │ (town hall WAN │
│ home broadband │ │ or LTE) │
│ via CF tunnel) │ │ │
│ validator+archive │ │ validator │
└──────────┬───────────┘ └──────────┬───────────┘
│ │
│ gossip │
└──────────────────────────┘
┌───────────▼───────────┐
│ Cache-only VPS │
│ ($5/mo on Oracle │
│ Free Tier) │
│ cache+bootstrap │
└───────────────────────┘
│ discovery api
voters + poll workers

Three-node consortium, 2-of-3 quorum (so either the clerk or an observer + the cache VPS can produce a block; the observer alone can’t, preserving check-and-balance).

Per-precinct check-in: each precinct’s polling place has a laptop or iPad running a cache-only profile that syncs from the VPS. Operates in polling-place LAN mode during the day, syncs to the VPS at regular intervals. At polls close, precinct device publishes POLLS_CLOSED events.

Launch time: 1-2 weeks to register domains, publish governance, brief poll workers, test.

2.3 County (100k-1M voters)

This is where it starts to look like infrastructure:

┌──────────────────────────────────────────────┐
│ Authority + Observer validator consortium │
│ (5 nodes, 3-of-5 quorum) │
│ - authority-primary (gov data center 1) │
│ - authority-backup (gov data center 2) │
│ - observer-r (R party HQ) │
│ - observer-d (D party HQ) │
│ - observer-lwv (LWV office) │
└──────────────────────┬───────────────────────┘
│ gossip
┌──────────────────────┴───────────────────────┐
│ Dedicated cache tier (10 nodes in 3 regions) │
│ - cache-east-1..3 (east county data) │
│ - cache-west-1..3 (west county data) │
│ - cache-central-1..4 │
└──────────────────────┬───────────────────────┘
│ api gateway
api.elections.<county>.gov
┌──────────────────────┼───────────────────────┐
▼ ▼ ▼
precinct-001 device precinct-002 device ... precinct-423
(cache, scoped (cache, scoped (cache, scoped
to precinct-001) to precinct-002) to precinct-423)

~15 dedicated nodes + ~423 precinct devices. The precinct devices are cheap (iPad or small laptop); the dedicated tier uses proper server hardware.

Dedicated cache tier is a new pattern for this scale. They serve nearly all read traffic (poll-book lookups, ballot-issuance verification, observer queries). The consortium validators only handle writes (registrations, ballots cast, tally events). This separation prevents election-day read traffic from overwhelming the block-producing nodes.

Launch time: 3-6 months of planning + deploy + test + training + dry-run + legal sign-off + poll-worker cert.

2.4 State (10M+ voters)

┌─────────────────────────────────────────────┐
│ State consortium (9 nodes, 5-of-9 quorum) │
│ - state SoS primary + backup + 2 DRs │
│ - state legislature observer (2 seats) │
│ - federal monitor observer (1 seat) │
│ - civil-society observer (2 seats: LWV + similar)│
└──────────────────────┬──────────────────────┘
│ federates to
┌─────────────────────────────────────────────┐
│ 254 county networks (one per TX county) │
│ each with its own consortium │
└─────────────────────────────────────────────┘
│ state cache-gateway
┌─────────────────────────────────────────────┐
│ State-level discovery gateway + cache tier │
│ (20-40 cache nodes in 4-5 regions of TX) │
└─────────────────────────────────────────────┘
│ api.elections.tx.gov
observers / journalists / candidates statewide

State consortium produces aggregation events. County networks produce per-county tallies. State federates down to counties via QDP-0013. State-level cache tier serves state- wide queries (state-level candidate totals, statewide prop counts).

Launch time: 1-2 years. State-wide rollout is a multi-year project involving every county’s adoption + new legal framework + vendor transition.

2.5 Federal (US presidential general)

Three-tier federated network of networks (see integration.md §3.2 for the diagram). Federal consortium is deliberately minimal, its only job is aggregation, so 3-5 nodes with 2-of-3 quorum is enough. Most of the federal network’s operational weight is in the federation configuration: pinning 50 state networks’ operator pubkeys, maintaining well-known file mirrors, running aggregation jobs.

3. Hardware specification

By role.

3.1 Validator node (consortium member)

Produces blocks. Modest load but critical uptime.

RequirementMinimumRecommended
CPU2 cores4 cores
RAM4 GB8 GB
Disk50 GB SSD200 GB NVMe
Disk IOPS2k10k
Network100 Mbps1 Gbps
Uptime SLO99.5%99.9%

At county scale: a single Hetzner CX41 ($10/mo) comfortably handles the validator role. For state scale: a dedicated VM with burstable CPU. For federal: a small physical server in a certified data center.

3.2 Cache node

Scales with read volume. Easy to horizontally add more.

RequirementSmall countyLarge county
CPU2 cores8 cores
RAM4 GB16 GB
Disk100 GB SSD500 GB SSD
Network100 Mbps1 Gbps
Uptime SLO99% (redundant; one down is fine)99.5%

Target: 5k-20k read QPS per cache node. If election-day peak exceeds that, add more cache nodes (QDP-0014 discovery auto-routes).

3.3 Archive node

Stores full history. Rarely queried in real-time; used for post-election audit + forensics.

RequirementPer year’s elections
CPU2 cores
RAM8 GB
Disk1-4 TB (post-election data can be moved to cold storage)
Network10 Mbps (low real-time; high for audit days)
Uptime SLO99%

At least 2 archive nodes per jurisdiction, geographically diverse, ideally hosted by different governance entities (one by the state, one by an observer org).

3.4 Precinct device (cache-only)

The thing a poll worker hands a voter.

RequirementValue
CPUAny reasonable phone / tablet / laptop CPU
RAM2 GB
Disk8 GB (of which ~1 GB is the precinct’s local cache)
Battery8+ hours (polling-place day)
NetworkPrimary: LTE or precinct wifi; fallback: none (offline operation for 4h)
Supported hardwareiPad Pro (recommended), Surface Go, Chromebook, Linux laptop
SecuritySecure enclave for storing the precinct device’s node key

Critical: must degrade gracefully when network is out. Precinct device runs as a cache node with a large local cache so offline queries (check-in verification) still work. Votes cast offline queue locally + sync on reconnection with full cryptographic integrity.

4. Capacity planning

4.1 Transaction volume estimates

For a county-scale election with 500k registered voters:

PhaseEventsDurationRate
Pre-election (registration period, 90 days)500k90 days~6k/day = ~0.07/s
Early voting (14 days)100k-150k14 days~10k/day = ~0.12/s
Election day350k12 hours~30k/hour = ~8/s
Peak hour (noon lunch rush)75k in 1 hour1 hour21/s
Tally + certification~1001-7 daysnegligible

8/s sustained, 21/s peak. The node’s default rate limit at 100k/min (which is what we configured for the reviews demo) is way more than enough. The real constraint is storage (500k registrations + 350k ballots + vote events = ~2M events, ~500MB at ~250 bytes/event) and validator throughput for block production.

At a 60-second block interval, a county election never generates more than 50 transactions per block during peak hours, comfortable margin.

4.2 Read load estimates

Much higher than write load, because every interaction generates reads.

QueryPer voterPer day (election day)
Voter checks their own registration2-5500k × 3 = 1.5M
Poll worker checks in voter1350k
Voter checks their own vote (individual verifiability)1-3350k × 2 = 700k
Observer queries10k-100k per observervaries
Journalist / candidate queries100k-1M totalvaries
Total3M-5M

3-5 million reads per day on election day. Distributed across 10 cache nodes: 300-500k per node per day, or 3-6 QPS per node. Trivial. CDN edge caching of idempotent GETs further reduces origin load.

4.3 Spike handling

Traffic isn’t uniform. Expect:

  • Opening of polls (6-7am): ~5x normal read load for 30 minutes as poll workers come online.
  • Lunch rush (11am-1pm): ~3x normal voter traffic.
  • Pre-close (6-7pm): ~4x normal traffic as commuters arrive after work.
  • Post-close (7-10pm): ~10x normal observer / journalist traffic as tallies publish.

Cloudflare edge cache handles most of this. Origin load stays reasonable. Rate-limiting on the node side is set to allow 10x normal volume so bursts don’t cause legitimate requests to fail.

4.4 Storage growth

Per voter per election:

  • 1 registration event: ~500 bytes
  • 1 ballot issuance: ~500 bytes (includes blind signature)
  • 5-20 vote edges (depending on contests): ~500 bytes each = 2.5k-10k
  • 1-3 verification events: ~300 bytes each = ~1k

Per voter per election: ~4-12 KB on-chain.

County of 500k voters, full election: ~2-6 GB of on-chain data. Over 10 years of elections: ~20-60 GB per county. Archive nodes handle this easily. Cache nodes keep only hot state (current + recent elections) which is much smaller.

5. Election-day operations playbook

5.1 T-30 minutes: final check

Terminal window
# Health check every consortium member
for node in authority-primary authority-backup observer-r observer-d observer-lwv; do
curl -fsS "https://$node.elections.<county>.gov/api/health" \
| jq '{uptime, status, blockTip}'
done
# Confirm consortium in-sync
curl -fsS "https://api.elections.<county>.gov/api/v2/discovery/domain/elections.<county>.2026-nov" \
| jq '.blockTip'
# Verify precinct devices registered as cache replicas
curl -fsS "https://api.elections.<county>.gov/api/v2/discovery/operator/<county-clerk-quid>" \
| jq '[.[] | select(.capabilities.cache == true)] | length'
# Expected: ~423 (number of precincts)
# Check audit pipeline
curl -fsS "https://api.elections.<county>.gov/api/streams/<county-quid>/events?eventType=AUDIT_READY" \
| jq '.data | length'

If any of these fail: delay poll opening if the failure is consortium-level; handle locally if it’s a single precinct.

5.2 Polls open (typically 7am local)

  1. Each precinct’s chief judge signs + publishes a POLLS_OPENED event to their precinct’s poll-book domain.
  2. Precinct devices start accepting check-ins.
  3. Consortium starts producing blocks containing check-ins + ballot issuances + cast votes.
  4. Observers begin scheduled monitoring queries.
  5. Monitoring dashboard shows traffic per precinct, with alerts for any precinct that’s processing 0 check-ins more than 30 minutes after open.

5.3 During voting day

Continuous monitoring for:

  • Check-in rate per precinct. Anomalies (e.g., 10x surge) may indicate attempted ballot-stuffing.
  • Ballot-issuance rate, should equal check-in rate.
  • Vote-cast rate, should equal ballot-issuance rate within an hour (voters finish marking).
  • Block-tier distribution, every validator’s block count should be roughly equal. Asymmetry may indicate one validator falling behind or failing.
  • Consortium agreement rate, percentage of blocks accepted as Trusted by all consortium members. Should be 100%; below 99% is a warning.
  • Failed tx rate, should be near-zero. Spike indicates either attack or bug.

Dashboard at status.elections.<county>.gov publishes:

  • Total registered voters who’ve voted (turnout live).
  • Per-precinct turnout rates.
  • Any precinct with alerts.

5.4 Polls close (typically 7pm local)

  1. Each precinct’s chief judge signs a POLLS_CLOSED event with final local totals + cryptographic hash of paper ballots (confirming paper = digital).
  2. Consortium waits 30 minutes for straggler tx from precincts with poor connectivity to gossip through.
  3. Tally computation runs:
    • Sum all vote edges per contest.
    • Emit CONTEST_TALLY_PRELIMINARY event per contest, signed by the consortium.
  4. Publish preliminary results to the status dashboard + the official website.

5.5 Post-close (7-10pm)

  1. Observer teams run their own independent recounts (anyone can; see §5.6).
  2. Any discrepancies trigger paper-ballot comparison (procedure in implementation.md §7).
  3. Candidates who dispute results receive fine-grained per-ballot drill-down access (still cryptographically tied to paper).

5.6 T+24 hours: certification

After a 24-hour observer-review window:

  1. County canvassing board meets + certifies.
  2. Authority publishes CERTIFIED event to the tally domain with full quorum signatures.
  3. Results federate to the state network via TRUST_IMPORT.
  4. Certified totals become official.

6. Incident response

Scenarios + response protocols.

6.1 A consortium node goes offline during voting

Detection: health check fails, or validator’s block participation rate drops.

Response:

  1. Remaining consortium continues (3-of-5 quorum preserved as long as 4 are up).
  2. IR team investigates the failing node; bring back up if possible.
  3. If recovery > 2 hours, consider emergency REMOVE_VALIDATOR (via the 1h-emergency-notice clause from integration.md §7.2).
  4. Publish NODE_DEGRADED event to the audit domain explaining what happened; public transparency reduces legitimacy risk.

6.2 Precinct device compromised

Detection: precinct device publishing invalid events, duplicate check-ins, or just stopped responding.

Response:

  1. Chief judge at the precinct switches to paper-only check-in (manual poll book).
  2. Compromised device is removed; paper check-ins get cryptographically bridged at a later point.
  3. IR team investigates the compromised device:
    • What was its node key’s scope? (Just the precinct’s poll-book domain, per QDP-0014.)
    • Any tx published under the key are flagged for extra verification.
  4. Revoke the device’s node-quid via an operator-attestation TRUST edge update.

Because the precinct device only holds a cache-only key scoped to one precinct, the blast radius is bounded.

6.3 Suspected registration fraud

Detection: an observer notices a voter registration that seems suspicious (address doesn’t exist, voter is deceased, etc.).

Response:

  1. Observer publishes a REGISTRATION_CHALLENGE event.
  2. Authority investigates within 24h.
  3. If fraudulent: publish REGISTRATION_REVOKED event (stops future ballot issuance).
  4. If voter already cast a ballot: flag for paper-ballot verification at tally time.
  5. Legal referral to prosecutor for follow-up.

The challenge/revocation mechanism is cryptographic + auditable: no silent changes.

6.4 Network partition / DDoS / outage

Detection: ISP connectivity fails, DDoS floods the gateway, Cloudflare incident.

Response:

  1. Gateway failover to backup DNS entries.
  2. Precinct devices operate in local-cache mode; sync deferred.
  3. If outage exceeds 30 minutes, chief election officer extends voting hours per statutory authority; publishes VOTING_HOURS_EXTENDED event.
  4. Post-incident, publish full timeline on audit domain.

7. Security discipline

7.1 Key custody per role

RoleKey custody
Governor (human)HSM or YubiKey. Paper backup in a physical vault. QDP-0002 guardian quorum for recovery.
Consortium member nodeHSM-backed signing on dedicated hardware. Daily health check.
Precinct deviceSecure-enclave-generated key, locked to the precinct domain. Device returns to sealed storage between elections.
Voter VRQVoter’s own custody (phone secure enclave, YubiKey, paper).
Voter BQEphemeral; generated per ballot, discarded after cast.

7.2 Pre-election hardening

  • All software audited by an independent firm (publish the audit report).
  • All nodes on isolated networks during voting hours; no outbound traffic except to the consortium + CDN.
  • Admin access logs on the audit domain (every console login is an on-chain event).
  • Disaster-recovery drill ~30 days before election.

7.3 Election-day rules

  • No governance changes during active voting (except emergency REMOVE_VALIDATOR).
  • No node key rotation during voting.
  • No software updates between polls-open and polls-close.
  • All governors + consortium operators on-call until certification.

8. Cost

By scale.

8.1 Small municipal (under $500 total)

  • 1 VPS @ $6/mo × 12 = $72
  • Backup storage @ $1/mo × 12 = $12
  • Domain rental = $10/yr
  • Total: ~$100/yr

Plus one-time: legal sign-off (~$3k if you’re buying it commercial; $0 if municipal legal already does it).

8.2 County (mid-size)

  • 5 validators @ $20/mo each = $100/mo = $1200/yr
  • 10 cache @ $10/mo = $100/mo = $1200/yr
  • 2 archive @ $50/mo = $100/mo = $1200/yr
  • Domain + TLS + CDN free tier = $0
  • Monitoring (Grafana Cloud free tier) = $0
  • Total: ~$3600/yr

Plus one-time: precinct devices (~$500 each × 423 = $211k) but these already exist for traditional electronic poll books, so usually not incremental.

8.3 State

  • 9 state validators @ $100/mo = $900/mo = $11k/yr
  • 40 state caches @ $50/mo = $2000/mo = $24k/yr
  • Monitoring paid tier = $5k/yr
  • Legal + audit = $100k/yr
  • Operations staff (2 FTEs) = $250k/yr
  • Total: ~$400k/yr

8.4 Federal

  • Federal consortium (minimal) = $50k/yr
  • Federation gateway + observation = $100k/yr
  • Audit + compliance = $500k/yr
  • Staff (10 FTEs across oversight) = $2M/yr
  • Total: ~$2.5M/yr

Compare to current US election spending: ~$5B / year across all jurisdictions. The Quidnug deployment replaces proprietary voting-machine contracts ($500M-$1B / year) and registration-database licenses ($100M+/year). Net savings are substantial at any scale.

9. Running drills before the real thing

Pre-real-election checklist:

  • T-90 days: full dry-run with 1000 fake voters across 10 test precincts. Include a suspected-fraud scenario and a consortium-node-failure scenario.
  • T-60 days: security audit final report + fixes deployed.
  • T-30 days: second dry-run with 10,000 fake voters; observer teams run recounts.
  • T-14 days: final integration test + all poll workers certified on the precinct device.
  • T-7 days: freeze code. No deploys until post-election.
  • T-1 day: polling-place hardware verified.
  • Day 0: operate the election.

10. What this doc doesn’t cover

  • Specific local legal requirements. These vary by state and by country. Work with election counsel.
  • Voter accessibility (ADA, multilingual). The protocol handles signing; the UX/client apps handle accessibility. That’s a substantial separate design track.
  • Long-term archival policy. Events live forever on chain; but storing + serving 30 years of election data is an evolving operations question.
  • Integration with existing SoS workflows. Every state has its own admin software. Bridges are use-case-specific.

For all of these, adapt the pattern. The protocol layer is the same; the policy and UX layers wrap it.

11. References

Launch checklist

Everything to verify before going live.

Elections launch checklist

Sequential T-90 through T+30 go-live steps for running a Quidnug-based election. Work top to bottom; each item gates the next. Designed for a county-scale election; scale items up/down for different deployments.

Parallels deploy/public-network/reviews-launch-checklist.md but for elections. Read README.md, integration.md, and operations.md first, this checklist assumes you’ve done the design work.


T-180 to T-90 days: Foundation

Before you’re actually running against a real election, you need the infrastructure standing.

  • Legal counsel reviews + approves the overall design. Specifically: does running elections on Quidnug satisfy state election law? (Varies by state; some require specific vendor certifications, some allow custom systems with public audit provisions.)
  • If required: engage an independent election-software auditor for pre-certification.
  • Bipartisan oversight board formed + briefed on the protocol architecture.
  • Governance structure defined + documented (see integration.md §2.2): - Governor quorum (who, weights, threshold) - Consortium membership (who runs validators) - Emergency-notice clause - Guardian quorums per governor
  • Legal agreements signed with observer organizations (LWV, R party, D party, SoS office) re: key custody, emergency procedures, liability.
  • Public governance documentation published at elections.<county>.gov/governance.

Hardware + infrastructure

  • Consortium node hardware procured: - Authority-primary (in gov data center 1) - Authority-backup (in gov data center 2) - 3-4 observer nodes (one per observer org’s site)
  • Cache tier hardware procured (10+ nodes in geographically-diverse regions).
  • Archive node hardware procured (2+ nodes).
  • Precinct device fleet ordered (iPad Pro or equivalent, ~1 per precinct + 10% spares).
  • Cloudflare account set up with: - Domain registered (elections.<county>.gov) - Tunnel account for home/remote access - Worker deployed for api.elections.<county>.gov - Monitoring configured

Software + keys

  • Quidnug node binary built from a specific tagged release (e.g. v2.4.0). SHA-256 hashes published.
  • All consortium node keys generated OFFLINE on an air-gapped machine. Paper backups printed.
  • Guardian quorums installed for each governor key (following QDP-0002).
  • Observer keys generated + distributed to observer organizations.
  • Precinct device keys generated + preloaded into secure enclaves.
  • CLI tooling (quidnug-cli elections) tested on staging.

Staging environment

  • Staging cluster set up mirroring production (different domain: elections-staging.<county>.gov).
  • 100 fake voters registered in staging.
  • Fake election run end-to-end in staging (registration → ballot-issuance → voting → tally → recount).
  • Staging election results match expected (seeded) data.

T-90 days: Domain + consortium setup

Publish the network descriptor

  • Publish https://elections.<county>.gov/.well-known/quidnug-network.json per QDP-0014 §7. Signed by the operator key. Includes: - Operator quid + pubkey - API gateway URL - Seed nodes + capabilities - Governors + quorum - Domain tree
  • Publish backup mirror of the well-known file at https://quidnug.com/.well-known/elections/<county>.json (federated publication for resilience).

Register the election domain tree

  • Register the root election domain: bash quidnug-cli domain register \ --name "elections.<county>.2026-nov" \ --validators "<consortium-quids>" \ --governors "<governor-quids>" \ --governance-quorum 0.7 \ --threshold 0.67 \ --key <authority-primary-key>
  • Register sub-domains (registration, poll-book, ballot-issuance, contests, tally, audit): bash for child in registration poll-book ballot-issuance \ ballot-issuance.precinct-001 \ [... per precinct ...] \ contests.us-senate contests.governor \ [... per contest ...] \ tally audit; do quidnug-cli domain register \ --name "elections.<county>.2026-nov.$child" \ --parent-delegation-mode inherit \ --key <authority-primary-key> done
  • Verify all domains show up in api.elections.<county>.gov/api/domains.

Stand up the consortium

  • All 5 consortium members deploy Quidnug node.
  • Each publishes their own NODE_ADVERTISEMENT per QDP-0014: bash quidnug-cli node advertise \ --operator-quid <county-authority-quid> \ --endpoints "https://node<i>.elections.<county>.gov:443,http/2,data-center-1,1,100" \ --capabilities "validator,archive" \ --supported-domains "elections.<county>.2026-nov.*" \ --expires-in "7d" \ --sign-with <node-i-key>
  • Each consortium member’s operator quid publishes the TRUST attestation edge: bash quidnug-cli trust grant \ --truster <county-authority-quid> \ --trustee <node-i-quid> \ --domain "operators.elections.<county>.2026-nov" \ --level 1.0 \ --sign-with <county-authority-key>
  • Run pairwise peering between consortium members (everyone trusts everyone at 0.95 in the peering.* domain).
  • Verify consortium is producing blocks (each member should see blocks from all others tier to Trusted).

T-60 days: Test + validate

Software audit

  • External security audit firm completes review of node binary, precinct device software, client app.
  • Audit report published publicly.
  • All audit findings addressed or explicitly deferred with rationale.

Dry run #1 (1,000 fake voters)

  • Reset staging to clean state.
  • Register 1,000 fake voters across 10 test precincts.
  • Simulate ballot issuance + voting for all 1,000.
  • Run tally. Results should match expected (seed data).
  • Run observer recount from a cold laptop; should match.
  • Run a “suspected-fraud” scenario: introduce 10 deliberately-invalid registrations; observer flags them; authority revokes.
  • Run a “node-failure” scenario: kill one consortium member mid-voting; system continues with 4-of-4.
  • Document all issues found in a dry-run-1-postmortem.md.
  • State SoS office confirms compliance.
  • Court injunction checks (if applicable).
  • Ballot design approved.
  • Candidate list finalized.

T-30 days: Polish + train

Dry run #2 (10,000 fake voters)

  • Larger-scale test with 10,000 fake voters across all real precincts.
  • Observer orgs run independent recounts.
  • Press invited to observe (not the full election, but the dry-run to build public confidence).
  • Issues documented + fixed.

Poll-worker training

  • Poll workers complete certified training on: - Precinct device operation - Paper-ballot parity procedures - Suspected-fraud reporting flow - Emergency procedures (network outage, device failure)
  • Test voters cast ballots with each poll worker observing.
  • Poll workers sign certification events (published on-chain for audit).

Voter education

  • Public education campaign explaining: - How to generate a VRQ (on phone / kiosk / paper) - How to verify your own registration - How the ballot is anonymous but your vote is verifiable - How to recount on your own laptop
  • Accessibility: multilingual + screen-reader content.
  • FAQ addressing common concerns (privacy, verifiability).

Infrastructure freeze

  • Code freeze: no deploys to production after T-14 days except for critical security fixes.
  • Final hardware count verified; precinct devices distributed to precincts.
  • All consortium members confirm they are on-call for election day.

T-14 days: Early voting opens

  • Publish EARLY_VOTING_STARTED event.
  • Dedicated early-voting locations run the same flow as election-day precincts; all their events flow through the same domain tree.
  • Daily status dashboards showing early-voting turnout.
  • Observer queries running continuously.
  • Incident-report channel open (email, phone, SMS).

Daily rhythm during early voting

Each day from T-14 to T-1:

  • Morning: confirm all consortium nodes healthy.
  • Midday: check turnout rates vs expected.
  • End-of-day: sum of early-voting events committed, ready for tally later.
  • Any incidents: publish INCIDENT_<type> event + fix.

T-3 days: Final verification

  • All poll workers confirmed available + trained.
  • All precinct devices verified at each precinct site (power on, connectivity, software version).
  • Paper-ballot supplies distributed.
  • Ballot boxes sealed + tamper-evident seals verified.
  • Election-day incident-response team on-call: - Chief election official - Oversight board members (rotating) - IT team lead + two engineers - Legal counsel - Communications lead

T-1 day: Pre-flight

  • All consortium members connect + verify blockchain head matches across all nodes.
  • Final status check (operations.md §5.1).
  • Publish ELECTION_DAY_READY event to audit domain.
  • Team rest; don’t schedule last-minute changes.

T-0 (Election Day)

Polls open (typically 7am local)

  • Each precinct chief judge signs + publishes POLLS_OPENED event.
  • Precinct devices start accepting check-ins.
  • Incident-response team on-call.
  • Status dashboard publicly visible.

Throughout the day

  • Monitor turnout per precinct (dashboard).
  • Respond to any reported issues within 15 minutes.
  • Publish occasional STATUS_UPDATE events with turnout summaries.
  • If a precinct has issues: deploy backup staff, switch to paper-only check-in if needed.

Polls close (typically 7pm local)

  • Each precinct chief judge signs POLLS_CLOSED event.
  • Consortium waits 30 minutes for straggler events.
  • Tally computation runs automatically.
  • Preliminary results published within 60 minutes of close.
  • Observer orgs run independent recounts; confirm agreement.

T+1 day: Observer review + audit

  • 24-hour observer-review window. Observers publish findings on the audit domain.
  • Any discrepancies flagged for paper-ballot comparison.
  • Paper-ballot audit (statistical sample, per state law) compared against digital tally.
  • Audit firm cross-checks: every winning contest’s signature chain is valid, no suppressed or inflated votes.

T+2 days: Canvassing + certification

  • County canvassing board meets.
  • Any challenges from candidates / observers heard.
  • Final adjudication on flagged ballots (typically paper-ballot cases).
  • Certification event published with full governor quorum signatures: bash quidnug-cli elections certify \ --year 2026-nov \ --quorum-signatures "<signatures>" \ --sign-with <authority-key>
  • Certified results federate to state network via TRUST_IMPORT.

T+7 days: Post-election report

  • Operational postmortem written and published: - Turnout statistics - Any incidents + resolutions - Performance metrics (response times, uptime) - Comparisons with prior elections
  • All code audit findings that were deferred get reviewed for future fixes.

T+30 days: Archival + handoff

  • Full chain data archived to long-term storage (archive nodes + offline backup).
  • Election authority quid retired; governor keys transitioned or stored.
  • Precinct devices collected + wiped (except key material is zeroized).
  • Post-election audit report published.
  • Legal + media archives finalized.
  • Begin planning for next election cycle.

Metrics to track

For every election, track these against targets from operations.md §4:

MetricTargetHow to measure
Total registered voters(set per election)Count of VOTER_REGISTERED events
Total voters who voted(set per election)Count of unique BQs with cast-vote events
Average voter wait time< 15 minutesLogged per precinct
Precinct device uptime> 99%Device-reported health checks
Consortium uptime> 99.9%Node-reported health + block production rate
Response time (poll worker query)< 500ms p99API logs
Response time (observer query)< 2s p99API logs
Discrepancies flagged< 0.01% of ballotsAudit reports
Post-election audit results match100%Audit summary
Observer satisfaction> 90%Survey post-election

What to do if…

…you find evidence of fraud during voting

  1. Don’t stop voting. Publish a FRAUD_SUSPECTED event.
  2. Route suspicious activity to IR team immediately.
  3. If the fraud appears widespread + actionable, consult legal about whether to extend hours or contest results.
  4. Post-election audit becomes the forum for adjudication.

…the consortium becomes partitioned

  1. Don’t panic. Partitions happen.
  2. Consortium members on each side produce blocks independently (tiered Tentative across the split).
  3. When partition heals, the existing tiered-block- acceptance machinery converges.
  4. No lost votes (they’re in local cache on each side and sync up on reconnection).

…a precinct device is stolen / compromised

  1. Precinct switches to paper-only check-in immediately.
  2. IR team revokes the stolen device’s node key.
  3. Any votes cast through that device flagged for paper verification.
  4. File a police report; file an INCIDENT_REPORT event.

…the election is contested

  1. Contesting candidate submits a CONTEST_CHALLENGE event with their basis.
  2. Canvassing board reviews per state law.
  3. Cryptographic recount is always available (anyone’s laptop can do it).
  4. If paper-ballot audit disagrees with digital tally, paper wins; investigation into discrepancy.

References