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.
131 lines
3.5 KiB
Go
131 lines
3.5 KiB
Go
package elgamal
|
|
|
|
import (
|
|
emath "github.com/user/evote/pkg/math"
|
|
)
|
|
|
|
// Ciphertext represents an ElGamal multi-recipient ciphertext.
|
|
// Gamma is the shared randomness component g^r.
|
|
// Phis are the per-recipient encrypted message components pk_i^r * m_i.
|
|
type Ciphertext struct {
|
|
Gamma emath.GqElement
|
|
Phis *emath.GqVector
|
|
}
|
|
|
|
// NewCiphertext creates a new ciphertext.
|
|
func NewCiphertext(gamma emath.GqElement, phis *emath.GqVector) Ciphertext {
|
|
return Ciphertext{Gamma: gamma, Phis: phis}
|
|
}
|
|
|
|
// Size returns the number of phi elements (recipients).
|
|
func (c Ciphertext) Size() int {
|
|
return c.Phis.Size()
|
|
}
|
|
|
|
// GetPhi returns the i-th phi element.
|
|
func (c Ciphertext) GetPhi(i int) emath.GqElement {
|
|
return c.Phis.Get(i)
|
|
}
|
|
|
|
// Group returns the GqGroup of this ciphertext.
|
|
func (c Ciphertext) Group() *emath.GqGroup {
|
|
return c.Gamma.Group()
|
|
}
|
|
|
|
// Multiply returns the component-wise product of two ciphertexts.
|
|
// (a.gamma * b.gamma, a.phi_i * b.phi_i)
|
|
func (c Ciphertext) Multiply(other Ciphertext) Ciphertext {
|
|
if c.Size() != other.Size() {
|
|
panic("ciphertexts must have the same size")
|
|
}
|
|
return Ciphertext{
|
|
Gamma: c.Gamma.Multiply(other.Gamma),
|
|
Phis: c.Phis.Multiply(other.Phis),
|
|
}
|
|
}
|
|
|
|
// Exponentiate returns this ciphertext raised to the power e.
|
|
// (gamma^e, phi_i^e)
|
|
func (c Ciphertext) Exponentiate(e emath.ZqElement) Ciphertext {
|
|
return Ciphertext{
|
|
Gamma: c.Gamma.Exponentiate(e),
|
|
Phis: c.Phis.ExpScalar(e),
|
|
}
|
|
}
|
|
|
|
// CiphertextVector is a vector of Ciphertexts.
|
|
type CiphertextVector struct {
|
|
elements []Ciphertext
|
|
group *emath.GqGroup
|
|
}
|
|
|
|
// NewCiphertextVector creates a vector of ciphertexts.
|
|
func NewCiphertextVector(elements []Ciphertext) *CiphertextVector {
|
|
if len(elements) == 0 {
|
|
return &CiphertextVector{elements: []Ciphertext{}}
|
|
}
|
|
copied := make([]Ciphertext, len(elements))
|
|
copy(copied, elements)
|
|
return &CiphertextVector{elements: copied, group: elements[0].Group()}
|
|
}
|
|
|
|
// CiphertextVectorOf creates a vector from variadic ciphertexts.
|
|
func CiphertextVectorOf(cts ...Ciphertext) *CiphertextVector {
|
|
return NewCiphertextVector(cts)
|
|
}
|
|
|
|
// Size returns the number of ciphertexts.
|
|
func (v *CiphertextVector) Size() int {
|
|
return len(v.elements)
|
|
}
|
|
|
|
// Get returns the i-th ciphertext.
|
|
func (v *CiphertextVector) Get(i int) Ciphertext {
|
|
return v.elements[i]
|
|
}
|
|
|
|
// Group returns the common group.
|
|
func (v *CiphertextVector) Group() *emath.GqGroup {
|
|
return v.group
|
|
}
|
|
|
|
// Elements returns a copy of the elements.
|
|
func (v *CiphertextVector) Elements() []Ciphertext {
|
|
copied := make([]Ciphertext, len(v.elements))
|
|
copy(copied, v.elements)
|
|
return copied
|
|
}
|
|
|
|
// Append creates a new vector with the ciphertext appended.
|
|
func (v *CiphertextVector) Append(ct Ciphertext) *CiphertextVector {
|
|
newElems := make([]Ciphertext, len(v.elements)+1)
|
|
copy(newElems, v.elements)
|
|
newElems[len(v.elements)] = ct
|
|
return NewCiphertextVector(newElems)
|
|
}
|
|
|
|
// PhiSize returns the size of the phi vectors (all must be the same).
|
|
func (v *CiphertextVector) PhiSize() int {
|
|
if len(v.elements) == 0 {
|
|
return 0
|
|
}
|
|
return v.elements[0].Size()
|
|
}
|
|
|
|
// Gammas returns a vector of all gamma values.
|
|
func (v *CiphertextVector) Gammas() *emath.GqVector {
|
|
elems := make([]emath.GqElement, len(v.elements))
|
|
for i, ct := range v.elements {
|
|
elems[i] = ct.Gamma
|
|
}
|
|
return emath.GqVectorOf(elems...)
|
|
}
|
|
|
|
// GetPhiColumn returns a vector of the j-th phi from each ciphertext.
|
|
func (v *CiphertextVector) GetPhiColumn(j int) *emath.GqVector {
|
|
elems := make([]emath.GqElement, len(v.elements))
|
|
for i, ct := range v.elements {
|
|
elems[i] = ct.GetPhi(j)
|
|
}
|
|
return emath.GqVectorOf(elems...)
|
|
}
|