No description
Find a file
saymrwulf a5b9ce8ea3 Add pkg/trace: surface-agnostic live crypto-event stream
The foundation for the "watch the mathematics execute" cockpit. Each meaningful
crypto operation will emit one Event carrying its LaTeX notation plus the real
runtime values, tagged with party + phase. One stream feeds both a terminal view
(ASCII/Unicode) and a browser view (typeset KaTeX) — no double-maintenance.

- Off by default with near-zero cost: Emit/EmitFunc return immediately when no
  sink is attached (atomic fast path), so instrumentation is free in normal runs.
- Sinks: ChanSink (buffered, drops rather than stalling the ceremony), SliceSink
  (tests). Short() elides ~77-digit values for compact display.

Tests: disabled-is-cheap, monotonic sequence + context stamping, elision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:59:52 +02:00
cmd/evote Docs: fix remaining stale LOC claims in index.html manual + swe title 2026-07-06 19:17:58 +02:00
pkg Add pkg/trace: surface-agnostic live crypto-event stream 2026-07-07 13:59:52 +02:00
rust/transportsec Due-diligence hardening + Rust transport-security layer 2026-07-06 14:42:34 +02:00
.gitignore Due-diligence hardening + Rust transport-security layer 2026-07-06 14:42:34 +02:00
ARCHITECTURE.md Docs: cast-as-intended return codes are now real 2026-07-06 15:53:40 +02:00
go.mod Swiss Post E-Voting Go PoC 2026-02-13 19:53:09 +01:00
go.sum Swiss Post E-Voting Go PoC 2026-02-13 19:53:09 +01:00
Makefile Due-diligence hardening + Rust transport-security layer 2026-07-06 14:42:34 +02:00
manual.html Sync root standalone decks to updated web versions; refresh manual.html 2026-07-06 19:15:31 +02:00
presentation-crypto.html Sync root standalone decks to updated web versions; refresh manual.html 2026-07-06 19:15:31 +02:00
presentation-swe.html Docs: fix remaining stale LOC claims in index.html manual + swe title 2026-07-06 19:17:58 +02:00
presentation.html Sync root standalone decks to updated web versions; refresh manual.html 2026-07-06 19:15:31 +02:00
README.md README: correct stale numbers and claims 2026-07-06 19:03:35 +02:00

Swiss Post E-Voting — Go PoC

A ground-up reimplementation of the Swiss Post e-voting cryptographic protocol as a single Go binary, with a multi-party mode where the parties talk over a Rust-signed transport.

The Swiss Post system is Switzerland's official internet voting platform, used in binding federal elections. The production system spans 14 Java repositories, 500K+ lines of code, and requires 50GB of RAM. This PoC distills the core cryptographic protocol into ~94 Go files (~10.5K lines) plus a small Rust crate for transport security. Two runtime modes: demo (single process) and netdemo (each party a separate endpoint, communicating only over Ed25519-signed / X25519-encrypted messages implemented in Rust — see ARCHITECTURE.md).

What This Implements

The full election lifecycle with end-to-end verifiability:

Setup        4 Control Components + Electoral Board generate keys
             Voting cards with secret codes are produced
                              |
Vote         Voter encrypts ballot client-side (ElGamal)
             Server validates zero-knowledge proofs
             Return codes confirm vote was recorded correctly
                              |
Tally        5 sequential verifiable shuffles (Bayer-Groth)
             Each shuffle: permute -> re-encrypt -> partial decrypt
             Final decryption by air-gapped Electoral Board
                              |
Verify       Public audit: all proofs are independently checkable
             No secrets required — anyone can verify the election

Cryptographic Components

Package What It Does
pkg/math Quadratic residue groups (G_q), safe prime generation, group vectors/matrices
pkg/elgamal ElGamal encryption, partial decryption, homomorphic ciphertext operations
pkg/zkp Schnorr proofs, exponentiation proofs, decryption proofs, plaintext equality proofs
pkg/mixnet Bayer-Groth verifiable shuffle with 6 sub-arguments (product, Hadamard, zero, SVP, multi-exponentiation, shuffle)
pkg/hash SHA-256 hash-and-square for Fiat-Shamir transforms
pkg/kdf HKDF key derivation for return code generation
pkg/symmetric AES-GCM authenticated encryption
pkg/returncodes Vote encoding as small primes, return code mapping tables
pkg/protocol Single-process election orchestration (setup, vote, confirm, tally)
pkg/verify Independent verification of all proofs
pkg/transportsec Transport-layer security (Ed25519 signatures, X25519 ECDH) — implemented in Rust, linked via cgo (see below)
pkg/transport Authenticated message bus: Ed25519 X.509 PKI, signed envelopes, X25519 secure channels
pkg/party Multi-party ceremony: each endpoint (setup, 4 CCs, electoral board, voting server, voters, verifier) as a separate party communicating only over the signed transport

Transport Security in Rust

Channel security between parties uses Ed25519 signatures and X25519 (ECDH) key agreement — deliberately elliptic-curve, with no RSA anywhere. These primitives are implemented in Rust (rust/transportsec, using ed25519-dalek and x25519-dalek), compiled to a C-ABI static library, and called from Go through cgo (pkg/transportsec). The Go side never implements or duplicates this cryptography. A cross-language conformance test proves the Rust signatures are standard RFC 8032 Ed25519 (Go's crypto/ed25519 verifies them and vice-versa).

Quick Start

Building requires a Rust toolchain (for the transport-security static library) in addition to Go. The Makefile builds the Rust library first, then the Go binary:

make build          # cargo build --release, then go build -o evote ./cmd/evote
make test           # runs cargo test + go test ./...

# Run a complete election ceremony, single process (10 voters, 3 candidates)
./evote demo --voters 10 --options 3

# Run the SAME election as separate parties over Rust-signed transport
./evote netdemo --voters 10 --options 3
./evote netdemo --voters 3 --options 2 --verbose   # log every signed message

# Serve presentations on local network (for iPad viewing)
./evote serve --port 8080

# Theatrical step-by-step terminal walkthrough
./evote present

The demo command runs the whole protocol in one process. netdemo runs the multi-party architecture: every party is a separate endpoint holding only its own private state, and every message between them is Ed25519-signed (and, for card/mapping-table delivery, X25519-encrypted) — all transport cryptography in Rust. The netdemo return codes are cast-as-intended: each voter's code is recomputed by the control components from the submitted ciphertext (bound to the ballot by a plaintext-equality proof), so a substituted vote is detected. See ARCHITECTURE.md.

If you build Go directly (go build ./...), run make rust first so the static library at rust/transportsec/target/release/libtransportsec.a exists for cgo to link.

Demo Output

=== SWISS POST E-VOTING PROTOCOL PoC ===

Phase 1: SETUP
  Generated safe prime group (q: 256 bits, p: 257 bits)
  CC[0]: generated ElGamal keypair, Schnorr proof OK
  CC[1]: generated ElGamal keypair, Schnorr proof OK
  CC[2]: generated ElGamal keypair, Schnorr proof OK
  CC[3]: generated ElGamal keypair, Schnorr proof OK
  EB: generated ElGamal keypair, Schnorr proof OK
  Combined election public key (product of all 5)
  Generated 10 voting cards with return codes

Phase 2: VOTING
  Voter 1: encrypted vote for option 2, proof verified
  Voter 2: encrypted vote for option 0, proof verified
  ...

Phase 3: TALLY
  Shuffle 1/5 (CC[0]): permute + re-encrypt + partial decrypt
  Shuffle 2/5 (CC[1]): permute + re-encrypt + partial decrypt
  ...
  Final decryption by Electoral Board
  Results: Option 0: 4 votes, Option 1: 3 votes, Option 2: 3 votes

Phase 4: VERIFICATION
  All 5 Schnorr key proofs: VALID
  All 5 shuffle proofs: VALID
  Vote count matches ballot box: VALID
  Election integrity: VERIFIED

Production vs. PoC

Aspect Production (Swiss Post) This PoC
Group size 3072-bit safe prime 256-bit safe prime
Codebase 14 repos, 500K+ lines (Java) ~94 Go files (~10.5K lines) + Rust crate
Infrastructure Kubernetes, HSMs, air-gapped machines Single binary, your laptop
Signatures / key exchange RSASSA-PSS, RSA channels Ed25519 / X25519 (Rust), no RSA
Dependencies Spring Boot, Bouncy Castle, Angular, ... Cobra + golang.org/x/crypto; ed25519-dalek + x25519-dalek (Rust)
Party isolation Separate machines / operators Separate in-process endpoints over a signed bus (netdemo)
Memory 50GB+ RAM ~50MB
Binary N/A (Java services) ~9.5MB (Go + linked Rust static lib)
Startup Minutes (JVM + Spring) Instant

Presentations

HTML slide decks are embedded in the binary and served via ./evote serve (iPad-friendly); the same files live under cmd/evote/web/:

  • index.html — Landing page and operations overview
  • demo.html — Protocol walkthrough: how a cryptographic election works
  • crypto.html — The mathematics (ElGamal, ZKPs, Bayer-Groth, Ed25519/X25519 transport, cast-as-intended)
  • swe.html — Software engineering: building it in Go, plus the multi-party re-architecture and the Rust transport-security layer

Standalone copies (presentation*.html, manual.html) are kept at the repo root for direct browsing.

Project Structure

cmd/evote/
    main.go              Cobra CLI root
    demo.go              Full election ceremony
    serve.go             HTTP server for presentations
    present.go           Theatrical terminal demo (772 lines)
    web/                 Embedded HTML presentations
pkg/
    math/                Group theory (GQ, ZQ, vectors, matrices)
    elgamal/             Encryption, decryption, key management
    zkp/                 Zero-knowledge proofs (4 types)
    mixnet/              Verifiable shuffle (Bayer-Groth, 12 files)
    hash/                Hash-and-square, Fiat-Shamir
    kdf/                 HKDF key derivation
    symmetric/           AES-GCM
    returncodes/         Vote encoding, return code mapping
    protocol/            Single-process election orchestration
    verify/              Independent proof verification
    transportsec/        Rust FFI: Ed25519 sign/verify, X25519 ECDH
    transport/           PKI, signed envelopes, secure channels, message bus
    party/               Multi-party ceremony (one object per endpoint)
rust/transportsec/       Rust crate: ed25519-dalek + x25519-dalek, C ABI

Security Hardening (Due-Diligence Pass)

A full correctness/security review of the codebase produced the following fixes, each covered by a regression test:

Area Issue Fix
pkg/mixnet The multi-exponentiation verifier's c_{B_m} = commit(0;0) check was stubbed out (empty if), letting a malicious mixer prove a non-permutation shuffle Check enforced; honest shuffles still verify
pkg/zkp All four FiatShamir challenges were hash mod q — biased, and capped at 256 bits for production groups Switched to spec-correct RecursiveHashToZq (oversample-then-reduce)
pkg/protocol VerifyTally returned true unconditionally; Schnorr proofs were never actually verified Real zkp.VerifySchnorrProof calls; returns the true aggregate result
pkg/protocol Mix padding for N<2 was regenerated with fresh randomness at verify time → shuffle 0 always failed Padded input persisted as event.MixInput and reused by the verifier
pkg/kdf BuildKDFInfo concatenated parts without separators (("e1","23x") == ("e12","3x")) Length-prefixed, injective encoding
pkg/math GqElementFromSquareRoot rejected the valid root q (off-by-one), a latent panic; RandomGqElement skipped one element Range corrected to [1, q]
cmd/evote --options=0 / --voters=-1 panicked Validated flags return clean errors
everywhere math/rand used in the demo driver Replaced with crypto/rand

Trust-boundary hardening (validated deserialization, verifiers returning false rather than panicking on malformed peer input) is handled in the multi-party re-architecture, where inputs actually arrive over the wire.

References

  • Swiss Post E-Voting System Specification (PDF)
  • Bayer, S. & Groth, J. (2012). Efficient Zero-Knowledge Argument for Correctness of a Shuffle
  • Haines, T. & Groth, J. (2020). Verifiable Shuffle of Large Ciphertexts
  • ElGamal, T. (1985). A Public Key Cryptosystem and a Signature Scheme Based on Discrete Logarithms

License

MIT