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>