swisspost-evoting-go-poc/pkg/elgamal/ciphertext.go
saymrwulf e8b6f30871 Swiss Post E-Voting Go PoC
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.
2026-02-13 19:53:09 +01:00

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...)
}