swisspost-evoting-go-poc/pkg/party/setup_test.go
saymrwulf 6933835bac Distributed key generation over signed transport + validated wire layer
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>
2026-07-06 15:11:34 +02:00

50 lines
1.6 KiB
Go

package party
import (
"testing"
)
// TestRunSetupDistributedKeygen runs the distributed key generation over the
// transport and checks that: every CC's Schnorr proof verified (RunSetup would
// have errored otherwise), the combined election public key equals the product
// of the individual CC and EB keys, and the transcript captured the artifacts.
func TestRunSetupDistributedKeygen(t *testing.T) {
cfg := testConfig(t, 5, 3)
c, err := NewCeremony(cfg, nil)
if err != nil {
t.Fatalf("NewCeremony: %v", err)
}
if err := c.RunSetup(); err != nil {
t.Fatalf("RunSetup: %v", err)
}
tr := c.Transcript
if len(tr.CCElectionPKs) != cfg.NumCCs {
t.Fatalf("transcript has %d CC keys, want %d", len(tr.CCElectionPKs), cfg.NumCCs)
}
if len(tr.Primes) != cfg.NumOptions {
t.Fatalf("transcript has %d primes, want %d", len(tr.Primes), cfg.NumOptions)
}
// Recompute election PK = Π CC_j.PK * EB.PK per component and compare.
for i := 0; i < cfg.NumOptions; i++ {
want := cfg.Group.Identity()
for j := 0; j < cfg.NumCCs; j++ {
want = want.Multiply(tr.CCElectionPKs[j].Elements.Get(i))
}
want = want.Multiply(tr.EBPublicKey.Elements.Get(i))
got := tr.ElectionPK.Elements.Get(i)
if !got.Equals(want) {
t.Fatalf("election PK component %d mismatch", i)
}
}
// The setup component must actually hold private primes; a CC must hold a
// secret key that never appeared in the transcript.
if c.Setup.st.primes == nil {
t.Fatal("setup component did not retain encoding primes")
}
if c.CCs[0].st.keyPair.SK.Elements == nil {
t.Fatal("cc0 did not retain its private key")
}
}