Changes the choice-code base from HashAndSquare(prime_i) to the encoding prime
itself (a G_q element). The card code becomes prime_i^{Σ_j k_j}, which is
algebraic and therefore recomputable by the CCs directly from the submitted
ciphertext at vote time — the prerequisite for a genuine cast-as-intended
return-code path. Setup and (upcoming) vote-time extraction use the same base,
keeping them consistent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mix-net now runs across separate parties over the signed transport: the
server pads the ballot box and hands it to CC0; each CC shuffles + partially
decrypts and passes the (validated) ciphertexts to the next; the electoral
board performs the final shuffle + decryption. Ciphertext handoffs cross the
authenticated transport; each party posts its shuffle and decryption proofs to
the public transcript (the bulletin board).
- tally.go: RunTally orchestration + per-party handlers (server pad, CC shuffle,
EB final decrypt). Persists the padded mix input and per-stage partial
decrypts to the transcript (fixes F7/F8 in the multi-party setting).
- verify.go: RunVerify has the verifier independently re-check every CC Schnorr
proof and the whole shuffle chain from the transcript alone (no secrets).
- returncodes: DecodeVoteChecked returns an error instead of panicking on a
non-smooth plaintext (fixes F12), used on the tally path so a corrupt ballot
is counted as spoiled rather than crashing the tally.
Tests: the full ceremony (setup -> cards -> voting -> tally -> verify) produces
the correct tally over 124 verified transport messages; the verifier rejects a
transcript with swapped Schnorr proofs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Voters encrypt their selection under the election key, build the (sound)
exponentiation proof binding the ballot to their verification-card key, and
submit to the voting server. The server validates every group element on
receipt, routes the ballot to all four CCs for proof verification, and stores
it only on unanimous acceptance — persisting vcPK (finding F6) so the proof
statement is reconstructible by any party.
- voting.go: castBallot (voter), handleCastBallot (server), handleVerifyBallot
(CC). The CC re-derives the proof statement and verifies it; a malformed proof
or bad group element yields a clean reject, never a panic (the trust-boundary
hardening deferred from the due-diligence pass).
- wire.go: exponentiation-proof DTO.
Tests: 4 ballots flow end-to-end and are stored with vcPK; a ballot with a
zeroed proof is rejected by the CCs and never stored.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Setup now generates each voter's return-code card by collecting shares from all
four CCs over the bus (the GenEncLongCodeShares exchange) and distributes cards
and the mapping table over CONFIDENTIAL channels.
- codes.go: deriveReturnCodeKey is the SINGLE key-derivation function used by a
CC both when contributing to card assembly and (later) at vote-time extraction
— structurally preventing the setup/extraction derivation mismatch (F1). Each
CC computes its choice/confirm shares from its private return-code secret;
only the shares (validated as G_q members on decode) cross the bus.
- RunCards assembles cards, registers mapping-table entries, and delivers cards
to voters + the mapping table to the server via sendConfidential (X25519 ECDH
session key + AES-256-GCM, then Ed25519-signed) — exercising the secure
channel in the ceremony, not just in tests.
- returncodes: MappingTable Export/ImportMappingTable for transport.
Test confirms every voter receives its card confidentially with the right code
count and the server receives the full mapping table + public election keys.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
wire.go: the validated serialization boundary between parties. Crypto objects
(group elements, public keys, Schnorr proofs, ciphertexts) travel as decimal
DTOs; every decode routes through NewGqElement/NewZqElement so a peer cannot
inject a value outside G_q or Z_q — closing the small-subgroup / non-residue
hole (finding M4) at the trust boundary.
setup.go: RunSetup drives distributed key generation over the bus. Each CC
generates its ElGamal keypair + return-code secret PRIVATELY and returns only
its public key and Schnorr proofs; the setup component verifies every proof on
receipt before combining keys. The electoral board derives its own key and
returns only the public key. Combined election PK and setup artifacts are
published to the public transcript.
Test confirms the combined election key equals the product of the individually
generated CC and EB keys, and that private key material stays with each party
(never appears in the transcript).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pkg/party models each endpoint of the system as a separate object holding only
its own private state, wired together through the transport bus.
- ceremony.go: NewCeremony bootstraps the Ed25519 root CA, enrolls all parties
(setup, 4 CCs, electoral board, voting server, verifier, N voters) with
CA-signed identity certs, registers each in the directory, and wires its
handler into the bus.
- parties.go: the six party types and a shared hello/ack handshake; Handshake()
proves the full sign -> route -> verify -> reply -> verify path for every
party before any election logic runs.
- state.go: per-party private state structs (nothing shared across parties).
- transcript.go: PublicTranscript, the append-only bulletin board a remote
verifier will consume (no secrets).
- phases.go: phase handlers reject unknown message types cleanly (the transport
boundary never panics on unexpected input) — filled in over the next commits.
Transport CA API simplified to own its serial counter (NewCA/Issue).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>