Check a result yourself
Every published result comes with the ballots it was counted from, and a separate program that counts them again. You do not have to take our word for the total, and you do not need an account, a repository or anything installed to check it.
What you need
Three things, and you almost certainly have all of them.
- The address of the published result. Your club will have sent it round; it is the page that shows the totals.
- Python 3, which comes with macOS and every Linux, and is a free download on Windows. The tool uses nothing but the standard library — there is nothing to install.
- About five minutes.
How to do it
- Make an empty folder and download the six files of the sealed package into it, from the published result page.
- Download the recount tool into the same folder.
- Run it, pointed at that folder. It prints what it found and finishes with one line saying whether everything reconciles.
The commands
Replace the address on the third line with your own result page’s address — the part before /ballots.json. Everything else is copied as it stands.
mkdir check && cd check
# the six files that make up the sealed package
BASE=https://myelection.app/o/your-club/results/00000000-0000-0000-0000-000000000000
for f in ballots.json result.json summary.json receipts.txt ballots.csv MANIFEST.sha256; do
curl -fsSLO "$BASE/$f"
done
# the recount tool
curl -fsSL https://myelection.app/verify/recount.py -o recount.py
python3 recount.py .
On Windows without curl: open the result page, save each of the six files into one folder using "Save link as", put recount.py beside them, and run "python recount.py ." in that folder.
The tool
One file, 428 lines of Python, 16468 bytes. It is a second implementation of the counting rule, written in a different language from the software that produced the result and sharing no code with it — so a mistake in one would not be repeated by the other. It reads only the files you downloaded. It makes no network request of any kind.
Its SHA-256, so you can confirm you have the same file this page is describing, and so two people checking the same election can confirm they ran the same tool:
f7f7a9592c2f56ce4ea97298e483ceae2de34df27c929611573fe15ec317a1b3
What is in the sealed package
Six files, published with every result. None of them contains a name, an address, or anything else that identifies a voter — that is a property of how they are built, not something removed on the way out.
| File | What it is |
|---|---|
| ballots.json | Every anonymous ballot with its selections, plus the rules that were in force: the candidates or options, the seats, how many each voter could choose, the tie rule, and for a motion the threshold. This is the whole input to the count. |
| result.json | What the application concluded. The tool compares its own totals against this, line by line. |
| summary.json | The electorate, the accepted ballots, the redeemed codes, the turnout, and the configuration and register hashes taken when the election was frozen. |
| receipts.txt | The reference every voter was shown when they submitted. If you voted, yours is in here. |
| ballots.csv | The same ballots as a spreadsheet, if you would rather add them up in Excel than run anything. |
| MANIFEST.sha256 | A SHA-256 for each of the files above, and one hash covering the lot. This is what makes an alteration detectable. |
The audit log is the one thing not published here: it names your club’s officers and what each of them did, so it is available to them and to an appointed auditor rather than to the whole internet. Its head hash is published with the result, which is what makes a later rewrite of it detectable.
What it checks, and how to read the output
Five things, in this order. Any failure is printed with an exclamation mark and counted in the last line.
- The files are the ones the manifest describes. It recomputes the SHA-256 of every file named in MANIFEST.sha256. If a single byte of any of them has changed, this fails.
- It counts the ballots itself, from ballots.json alone — flagging any ballot counted twice, any that names an unknown candidate, and any that selects more than the rules allowed.
- It works out the outcome. For an election it marks which totals fill the seats and reports a tie at the seat boundary without resolving it. For a motion it applies the agreed threshold and says carried or not carried.
- It compares its totals against result.json, candidate by candidate. This is the line that matters: "AGREE: every candidate total matches."
- It reconciles. Accepted ballots must equal redeemed codes; its own ballot count must match the summary; the published references must be unique and as numerous as the ballots; and the configuration must not have changed since it was frozen.
What a run looks like
A real run against a three-seat committee election that had a tie for the last seat.
Sealed package: .
==================================================================
Manifest : 5 files, all hashes match
Package hash : a7f18f378be1afedad94d585bb0a29cf9e1fc9d32bafce2a364ac2e950139dbc
Independent recount
------------------------------------------------------------------
Ballots counted: 12 (blank: 1)
Anna Fields 10 <- fills a seat
Bob Marsh 8 <- fills a seat
Charles Okoro 6 <- tied for the last seat
Dana Reid 6 <- tied for the last seat
Eve Salter 2
Frank Tan 1
* the seat boundary fell inside a tie at 6 votes. The rule agreed beforehand
was: Lot drawn by the chair and secretary at the next committee meeting.
What the officers did about it is recorded on the published result page
and in the audit log.
Comparison with the application's result.json
------------------------------------------------------------------
AGREE: every candidate total matches.
Reconciliation
------------------------------------------------------------------
Accepted ballots : 12
Redeemed credentials : 12
==================================================================
RESULT: the count reconciles.
1 matter(s), marked * above, needed the officers' decision rather
than the count's.
Two things to read. "AGREE: every candidate total matches" is the line that says this program and the one that produced the page reached the same totals independently. And the last line separates two different things: a problem, marked with an exclamation mark, means the numbers do not add up; a matter marked with a star means the count was complete but a decision was left to the officers, which is what a tie is. A vote with no tie simply ends "RESULT: everything reconciles."
If it disagrees
Do not assume you have made a mistake. Send the output to your club’s officers and ask them to run it themselves; the whole point of a second implementation is that it is allowed to disagree. If they cannot explain it, the result should not stand.
What a clean run does and does not prove
It proves that the published ballots produce the published totals, and that none of the published files has been altered since they were sealed. That is a real thing to know, and it is the part most likely to go wrong through a bug rather than through bad faith.
It does not, on its own, prove that those ballots are the ones voters cast. Two other things carry that: the reconciliation above, which requires the number of accepted ballots to equal the number of codes actually used, and every voter who checks that their own reference appears in receipts.txt. A club whose members do that has closed the gap; a recount alone has not.
And it says nothing about how any individual voted, because nothing in these files could. There is no column anywhere in this service that joins a ballot to a person.
Why it is built this way
How names and votes are kept apart, what is encrypted, where the data sits, and what this does not protect you against.