
Verified online voting has to satisfy two demands that sound contradictory. You must prove that every ballot came from someone entitled to vote, and you must be unable to prove how any particular person voted. Get the first wrong and the result is challengeable. Get the second wrong and members stop voting honestly, or stop voting at all.
The resolution is not clever cryptography. It is careful separation: verify identity in one place, store the ballot in another, and never keep a link between them.
Voter eligibility verification comes first
Eligibility is decided before the vote opens, not while it runs. You upload the roll, the members, shareholders, delegates, or residents entitled to vote, and it is frozen at that moment. Every entry gets its own registry record with a verification state.
Freezing matters more than people expect. If the roll can change mid-ballot, then a disputed result can always be attacked on the grounds that the electorate was different at the end than at the start. A frozen roll with a timestamp turns "who was allowed to vote" from an argument into a document.
Verification itself is a claim against that frozen roll: the member proves control of the address or identifier on the record, and the registry marks them verified. Someone who is not on the roll cannot become verified by any route inside the ballot, which is the point.
One member one vote, enforced by the database
A rule that lives in application code is a rule that survives until someone finds a second path to the same endpoint. So the one member one vote constraint belongs in the data layer: a uniqueness guarantee on the registry entry that makes a second ballot from the same member impossible rather than merely unlikely.
Two attempts arrive at once from two tabs? The database accepts one and rejects the other. The registry row flips from verified to voted in the same operation that records the ballot, so there is never a window where a vote is stored but the member still appears eligible.
How the anonymous ballot stays anonymous
Here is the part that does the real work. The verified registry and the ballot store are two separate tables, and no foreign key or shared identifier joins them.
When a verified member votes, the system writes two things. In the registry it records only that this member has voted, with a timestamp, never a choice. In the ballot store it records only the choice, under a ballot key that is derived so it cannot be traced back to the member. Anyone with full read access to both tables can count the ballots and can confirm that the number of ballots matches the number of members marked as voted. What nobody can do, including an administrator, including us, is reconstruct which row belongs to which person.
That is the honest definition of an anonymous ballot: not "we promise not to look", but "the join does not exist".
The voting audit trail
Every significant event during a ballot is appended to an audit log: the roll frozen, the vote opened, each verification, each ballot accepted, each rejection and why, the vote closed, the tally computed.
Each entry carries a cryptographic digest of its own contents plus the digest of the entry before it. That chaining is what makes the log tamper-evident. Altering an old entry changes its digest, which breaks every subsequent link, so a single edited row invalidates the visible chain rather than quietly disappearing. Deleting an entry does the same. You cannot prevent someone with database access from writing to a table; you can make it impossible for them to do so without leaving a mark.
The log records events and choices, never a member's identity next to a choice. A tamper-evident record of who voted for what would defeat the anonymity you just built.
What to export when the result is challenged
Assume every close result will be questioned, and prepare the pack in advance: the frozen eligibility roll with its timestamp, the verification report, the ballot count, the tally per option, and the full audit log with its digest chain intact. That set answers essentially every question a scrutineer, auditor, or unhappy member will ask, and it is far easier to export at the time than to reconstruct six weeks later.
Practical takeaways
- Freeze the eligibility roll before the vote opens and keep its timestamp.
- Verify members against that frozen roll only, no in-ballot route to becoming eligible.
- Enforce one member one vote with a database constraint, not application logic.
- Store the registry and the ballots in separate tables with no key joining them.
- Record only "has voted" against a member, and only the choice against an untraceable ballot key.
- Hash-chain the audit log so any alteration or deletion is visibly detectable.
- Export the roll, verification report, tally, and audit log together as soon as the vote closes.
Podieazy runs verified online voting on exactly this structure, so the result is defensible and the ballot stays secret at the same time.
Podieazy Team
The Podieazy production team writes about recording, editing and distribution workflows.
Related topics
Get the Podieazy newsletter
Recording, editing and governance playbooks. One email a fortnight, no spam.
Double opt-in. Unsubscribe in one click from any email.