Commit graph

5 commits

Author SHA1 Message Date
bc43c3d3aa Instrument real crypto ops to emit live LaTeX events
The running cryptography now narrates itself. Each headline operation emits a
trace.Event carrying its LaTeX notation plus the actual runtime values, the
instant it executes:

- Ed25519 signature on every inter-party message (envelope.Seal)
- X25519 ECDH key agreement for confidential card delivery (NewSecureChannel)
- ElGamal ballot encryption E1 = (g^r, pk^r·m) with the real r (castBallot)
- Fiat-Shamir challenge e = H(...) mod q (Schnorr proof)
- Bayer-Groth verifiable shuffle C' = {ReEnc_pk(C_π(i))} with N (mix-net)

Ceremony phases set trace phase/party context so events are attributed to the
acting stakeholder and phase. Instrumentation is behind the enabled-check, so
normal runs pay nothing.

Test: a full traced ceremony captures 184 live events across all five headline
kinds, each with non-empty LaTeX and live values, correctly phase/party-tagged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 14:06:08 +02:00
127ae61d11 Cast-as-intended return codes: CC extraction + voter check
The return code the voter checks is now genuinely computed by the CCs from the
submitted ciphertext, not looked up from the card:

- returncode_extract.go: after a ballot is accepted, the server asks each CC to
  exponentiate E2 by its return-code key (product over CCs = Enc(vote^Σk)), then
  each CC contributes a partial-decryption factor; the server recovers vote^Σk,
  which equals the card base prime_sel^Σk, and looks up the short code.
- The server returns that code to the voter, who checks it against the card for
  the chosen option; a mismatch aborts with a clear error.

Soundness test: a malicious client that encrypts option A for the tally (E1) but
option B in the return-code channel (E2) is REJECTED by the plaintext-equality
proof — so the code shown always reflects the tallied vote. This closes the
cast-as-intended gap (the old return codes were decorative, finding F16).

Card lCC now uses a fixed tau so extraction can recompute it without learning
the option up front.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:52:30 +02:00
d2cfbf291f Ballot carries E2 + plaintext-equality proof, CC-verified
The voter now also submits E2 = Enc(vote, returnCodesPK[0]) and a plaintext-
equality proof that E2 and the ballot's slot 0 encrypt the SAME vote. Every CC
verifies this proof during ballot verification. This is the soundness link that
makes the return code cast-as-intended: a client that encrypts one vote for the
tally and a different one for the return-code channel is rejected, so the code
the CCs compute from E2 necessarily reflects the tallied vote.

- transcript: publish the combined return-codes public key.
- voter stores returnCodePK from the (confidential) card delivery.
- wire: plaintext-equality proof DTO.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:47:53 +02:00
23723fddd6 Tally + verification: full multi-party ceremony runs end-to-end
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>
2026-07-06 15:23:56 +02:00
a443e44875 Voting phase: ballot submission with cross-party proof verification
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>
2026-07-06 15:19:07 +02:00