mirror of
https://github.com/saymrwulf/swisspost-evoting-go-poc.git
synced 2026-05-14 20:58:03 +00:00
Proof-of-concept reimplementation of the Swiss Post e-voting cryptographic protocol in Go. Single binary, 52 source files, 2 dependencies. Covers ElGamal encryption, Bayer-Groth verifiable shuffles, zero-knowledge proofs, return codes, and a full election ceremony demo.
103 lines
2.5 KiB
Go
103 lines
2.5 KiB
Go
package math
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
)
|
|
|
|
// GqGroup represents the quadratic residue group of integers modulo p,
|
|
// where p is a safe prime (p = 2q + 1) and q is the group order.
|
|
type GqGroup struct {
|
|
p *big.Int
|
|
q *big.Int
|
|
generator GqElement
|
|
identity GqElement
|
|
}
|
|
|
|
// NewGqGroup creates a new GqGroup with the given parameters.
|
|
// Validates that p and q are prime, p = 2q + 1, and g is a group member.
|
|
func NewGqGroup(p, q, g *big.Int) (*GqGroup, error) {
|
|
if p == nil || q == nil || g == nil {
|
|
return nil, fmt.Errorf("parameters must not be nil")
|
|
}
|
|
|
|
// Validate p is prime
|
|
if !p.ProbablyPrime(64) {
|
|
return nil, fmt.Errorf("p is not prime")
|
|
}
|
|
|
|
// Validate q is prime
|
|
if !q.ProbablyPrime(64) {
|
|
return nil, fmt.Errorf("q is not prime")
|
|
}
|
|
|
|
// Validate p = 2q + 1
|
|
twoQPlusOne := new(big.Int).Mul(big.NewInt(2), q)
|
|
twoQPlusOne.Add(twoQPlusOne, big.NewInt(1))
|
|
if p.Cmp(twoQPlusOne) != 0 {
|
|
return nil, fmt.Errorf("p != 2q + 1")
|
|
}
|
|
|
|
// Validate g is in range [2, p)
|
|
if g.Cmp(big.NewInt(2)) < 0 || g.Cmp(p) >= 0 {
|
|
return nil, fmt.Errorf("g must be in range [2, p)")
|
|
}
|
|
|
|
// Validate g is a quadratic residue (Jacobi symbol == 1)
|
|
if big.Jacobi(g, p) != 1 {
|
|
return nil, fmt.Errorf("g is not a quadratic residue mod p")
|
|
}
|
|
|
|
group := &GqGroup{
|
|
p: new(big.Int).Set(p),
|
|
q: new(big.Int).Set(q),
|
|
}
|
|
group.generator = GqElement{value: new(big.Int).Set(g), group: group}
|
|
group.identity = GqElement{value: big.NewInt(1), group: group}
|
|
|
|
return group, nil
|
|
}
|
|
|
|
// P returns the modulus p.
|
|
func (g *GqGroup) P() *big.Int {
|
|
return new(big.Int).Set(g.p)
|
|
}
|
|
|
|
// Q returns the group order q.
|
|
func (g *GqGroup) Q() *big.Int {
|
|
return new(big.Int).Set(g.q)
|
|
}
|
|
|
|
// Generator returns the generator of this group.
|
|
func (g *GqGroup) Generator() GqElement {
|
|
return g.generator
|
|
}
|
|
|
|
// Identity returns the identity element (1).
|
|
func (g *GqGroup) Identity() GqElement {
|
|
return g.identity
|
|
}
|
|
|
|
// IsGroupMember checks if value is in G_q: value > 0 AND value < p AND Jacobi(value, p) == 1.
|
|
func (g *GqGroup) IsGroupMember(value *big.Int) bool {
|
|
if value == nil {
|
|
return false
|
|
}
|
|
if value.Sign() <= 0 || value.Cmp(g.p) >= 0 {
|
|
return false
|
|
}
|
|
return big.Jacobi(value, g.p) == 1
|
|
}
|
|
|
|
// BitLength returns the bit length of q.
|
|
func (g *GqGroup) BitLength() int {
|
|
return g.q.BitLen()
|
|
}
|
|
|
|
// Equals checks if two groups are the same.
|
|
func (g *GqGroup) Equals(other *GqGroup) bool {
|
|
if other == nil {
|
|
return false
|
|
}
|
|
return g.p.Cmp(other.p) == 0 && g.q.Cmp(other.q) == 0 && g.generator.value.Cmp(other.generator.value) == 0
|
|
}
|