Skip to content
Condorcet.vote Alpha
← Technical documentation

Tally & recount

The result is never a block of the register: it is a deterministic recomputation from the published configuration and the signed ballots, which anyone can replay.

What is authoritative is the fixed list of signed ballots; the recomputation is a cross-check of it. A divergence between two implementations or two library versions would be a bug in a calculation program, not a verifiability failure: the published ballots are unaffected. Every method offered is deterministic, so the result is stable for a given list of ballots.

Protocol v1

Inputs

What a recount reads.

Public data only: the frozen configuration (block 0), the ballots (votes blocks), the closure electoral roll for the weights, and the method published with the result. The CEF document is not a required input.

candidates[].label strings · configuration
The declared candidates, including those nobody ranked. The label is the only candidate identifier.
election_type preferential | fptp · configuration
Ranking (Condorcet methods) or single choice (majority vote). Also sets the implicit-ranking rule.
ballot_rules object · configuration
The four ballot parameters, from which the tally constraints follow by a fixed rule (below).
seats integer ≥ 1 · configuration
The number of elected candidates: the top seats positions of the ranking.
weighted_voting boolean · configuration
When true, each ballot weighs the weight signed in its payload; otherwise every ballot weighs 1.
ballots[].payload JSON string · votes
The signed payload: its ballot field (CEF ranking or /EMPTY_RANKING/) and its weight field.
electoral_roll[].weight integer ≥ 1 · closure
Each voter's authorised weight, to check the signed weights of a weighted election.
method canonical name · result
Chosen by the organizer, frozen with the configuration at launch, published with the result rather than in the chain. Named by its exact library name, for example Schulze Winning.
Ballots

Every published ballot, one mechanical rule.

Ballot content is not validated on submission: the tally decides, by a public, deterministic rule. A rejected ballot stays published and verifiable; it is only left out of the ranking.

  1. 1 Verify. Every published ballot carries a valid ring signature and a key image unique within the election: the platform checks this on receipt, the verifier checks it again. A failure would be a fault of the register, not a tally rule.
  2. 2 Gate the format. The ballot field goes through cef-writer's strict check: an empty string, the || tag separator, an empty rank (A>>B), invalid UTF-8, a line break, the reserved characters ; , # / * ^ or a repeated candidate reject it. The library's parser then reads it, and may refuse it in turn. A rejected ballot is null by format: it never becomes a vote.
  3. 3 Recognize the blank. The /EMPTY_RANKING/ sentinel is a blank ballot: an abstention, counted.
  4. 4 Apply the constraints. The constraints derived from ballot_rules classify each vote as valid or null by constraint. A ranking naming an undeclared candidate counts as expressed; the unknown name is ignored in the pairwise duels.
  5. 5 Weigh. In a weighted election, the weight applied is the weight of the signed payload, never a multiplier read from the ranking (the format gate forbids it). The signed weights must form exactly the multiset of the weights of the voters marked has_voted, summing to totals.voted_weight. Without weighting, every vote weighs 1.

From parameters to constraints

NoTie if ties_allowed = false
Rejects a vote expressing a tie, including the implicit tie of the unranked candidates of a partial ranking.
NoBlankVote if blank_allowed = false
Rejects the blank ballot: it is then null by constraint instead of blank.
CompleteRanking if partial_ranking_allowed = false
Rejects a ranking that does not explicitly name every candidate.
Implicit Ranking by election_type
On for preferential: an unranked candidate is tied last. Off for fptp: a single choice leaves the other candidates unranked.

The constraints are not published as such: they follow from the published parameters by this rule. A blank is an abstention, neither a tie nor a partial ranking: NoTie and CompleteRanking exempt it, only NoBlankVote rejects it. The platform applies this exemption through its own variants of CondorcetPHP's native constraints; an independent recount must reproduce it.

For an fptp election the published parameters are fixed (ties forbidden, partial ranking allowed, complete ranking never required): NoTie always applies, NoBlankVote when abstention is forbidden. A ranking with several preferences is read at its first preference.

Counts

Six counters, two invariants.

Computed once per tally, identical for every method. A recount under the same constraints must reproduce them exactly.

total integer
The votes registered by the library.
valid integer
The votes that pass every constraint.
expressed integer
The valid, non-blank votes: those that make the ranking.
blank integer
The valid votes with an empty ranking (/EMPTY_RANKING/).
invalidByConstraint integer
The votes registered but rejected by a constraint: null by constraint.
invalidByFormat integer
The ballots that never became a vote: null by format, outside total.

total = valid + invalidByConstraint; valid = expressed + blank. The published null count is invalidByFormat + invalidByConstraint. The same breakdown, summed by weight instead of by ballot, gives the voices of a weighted election (with no counterpart for invalidByFormat: those ballots carry no weight); without weighting it copies the counts.

Methods

One method, fixed before the vote.

The platform implements no voting algorithm: every method is that of the julien-boudry/condorcet library (CondorcetPHP), named by its exact name. The election type is published in block 0; the method, chosen by the organizer, is frozen with the configuration at launch and published with the result.

FPTP fptp
First-past-the-post: the candidate with the most votes wins, the most weight in a weighted election. The result is still a complete ranking.
Schulze Winning preferential · default
Schulze, winning-votes variant.
Schulze Margin preferential
Schulze, margins variant.
Ranked Pairs Winning preferential
Ranked Pairs (Tideman), winning-votes variant.
Ranked Pairs Margin preferential
Ranked Pairs (Tideman), margins variant.
Copeland preferential
The Copeland method.
Dodgson Quick preferential
The polynomial approximation of Dodgson's method.
  • One variant, one name. Each variant is a distinct choice, mapped one to one to the library's canonical name: the recorded method replays verbatim.
  • Deterministic only. No non-deterministic method is offered: the same ballots always give the same result.
  • Weighting is not a method. With weighted_voting, the same method applies, each vote carrying its weight, which the library counts natively.
  • Seats are a cutoff. The elected candidates are the top seats positions of the returned ranking; nothing else is computed. The library's proportional methods are not offered.
  • Secondary methods, for information. For a Condorcet election, the results page may show the ranking the other methods of the list would have produced from the same ballots. Only the principal method makes the official result.
The Condorcet methods, explained
Ties

No invented tiebreaker.

The platform reports what the library returns and never alters the winner.

  • Shared ranks. The ranking is numbered from 1; tied candidates share the same rank.
  • Cycles. When there is no Condorcet winner, the cycle is resolved by the configured method, and by it alone.
  • Tie at the seat cutoff. If a tie straddles the boundary between the Nth and the (N+1)th position, no one is declared elected past the boundary. The organizer settles it outside the platform, typically with a runoff held as a new election.
  • No ballots. With no ballots there is nothing to tally: the official result is empty (zero counts, empty ranking, nobody elected), without calling the library.
Tally contract

The computation version, recorded with the result.

Each result records what is needed to replay exactly the same semantics: the tally contract version, the library version and the method. The public results page shows the method and, for a Condorcet election, the library version.

engine_contract_version integer · currently 1
The tally contract version (may be shown as v1): the shape of the result document and the counting rules described on this page.
condorcet_library_version string
The version of julien-boudry/condorcet that computed the result (Condorcet::VERSION).
method string
The method's canonical name, as the library reports it (Result->fromMethod).
counts, weights objects
The six counters above and their weighted counterpart.

The result document is kept as the library produces it: for each method, the ranking, the winner and the loser, the Condorcet winner and loser, the warnings; optionally, the pairwise matrix. The only operation the platform adds is the cutoff of the top seats positions.

CEF export

A convenience, not a part of the protocol.

Once the tally is done and the result published, the results page offers the ballots in the Condorcet Election Format (CEF), an open text format with a public specification, readable by the Condorcet library as by any compliant implementation.

  • Never required. The document can be rebuilt from the register alone: a verifier can regenerate it to cross-check the served file, and a recount does not need it.
  • No constraints, no method. They are not in it: they are read from the published ballot_rules and result.
  • Regenerated on every tally. Produced after every successful tally, including for an election with no ballots; a new tally produces a new document. It is never purged.
/elections/{election}/results/ballots.cef
Redirects to the stored document, once the election is published and the tally done.
CEF format specification

Document content

#/Candidates: parameter
The official labels, declared upfront: a candidate nobody ranked still exists.
#/Implicit Ranking: parameter
true for a preferential election, false for an fptp election.
#/Weight Allowed: parameter, weighting only
Present only in a weighted election; each vote line then carries its signed weight.
one line per ballot CEF ranking
The ballot field of each published payload, written by cef-writer, /EMPTY_RANKING/ blanks included. Ballots null by format are not in it.
Independent recount

Replay the tally yourself.

With the reference library at the recorded version, a recount reproduces the ranking and the counts identically. Any other CEF-compliant implementation should, in theory, reach the same result.

  1. 1 Download the election register and verify every ballot: signature, uniqueness of key images, consistency with the electoral roll and, in a weighted election, with the weights.
  2. 2 Read from block 0 the candidates, the type, ballot_rules, seats and weighted_voting; derive the constraints and the implicit-ranking rule.
  3. 3 Write each published payload as CEF with cef-writer, or apply the same format gate, with its signed weight if the election is weighted; the rejects are the ballots null by format.
  4. 4 Compute with julien-boudry/condorcet, at the recorded version, the method published with the result, under the derived constraints, the blank exempted from NoTie and CompleteRanking.
  5. 5 Compare the ranking and the six counters with the published result, then read the elected candidates: the top seats positions.