swisspost-evoting-go-poc/pkg/returncodes/decode.go
saymrwulf 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

42 lines
1.2 KiB
Go

package returncodes
import (
"fmt"
"math/big"
)
// DecodeVote factorizes a vote product back into the indices of selected
// options. It panics if the product is not a smooth product of the encoding
// primes; use DecodeVoteChecked when the input may be untrusted.
func DecodeVote(product *big.Int, primes []*big.Int) []int {
selected, err := DecodeVoteChecked(product, primes)
if err != nil {
panic(err.Error())
}
return selected
}
// DecodeVoteChecked is like DecodeVote but returns an error instead of panicking
// when the product does not fully factor over the encoding primes (which can
// happen for a corrupted or malicious ciphertext once inputs are remote).
func DecodeVoteChecked(product *big.Int, primes []*big.Int) ([]int, error) {
remaining := new(big.Int).Set(product)
var selected []int
for idx, p := range primes {
for {
quo, rem := new(big.Int).DivMod(remaining, p, new(big.Int))
if rem.Sign() == 0 {
selected = append(selected, idx)
remaining = quo
} else {
break
}
}
}
if remaining.Cmp(big.NewInt(1)) != 0 {
return nil, fmt.Errorf("vote does not factor over encoding primes (remaining %s)", remaining.String())
}
return selected, nil
}