swisspost-evoting-go-poc/pkg/mixnet/commitment.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

105 lines
2.7 KiB
Go

package mixnet
import (
"math/big"
"github.com/user/evote/pkg/hash"
emath "github.com/user/evote/pkg/math"
)
var oneBI = big.NewInt(1)
// CommitmentKey represents a Pedersen commitment key (h, g_1, ..., g_v).
type CommitmentKey struct {
H emath.GqElement // First element h
G *emath.GqVector // Elements g_1, ..., g_v
}
// Size returns v (the number of g elements, not counting h).
func (ck CommitmentKey) Size() int {
return ck.G.Size()
}
// Group returns the group of this commitment key.
func (ck CommitmentKey) Group() *emath.GqGroup {
return ck.H.Group()
}
// GenCommitmentKey generates a verifiable Pedersen commitment key.
// Uses hash-based generation: w = (hash(q, "commitmentKey", i, count) + 1)^2 mod p
func GenCommitmentKey(numElements int, group *emath.GqGroup) CommitmentKey {
q := group.Q()
p := group.P()
g := group.Generator()
var values []emath.GqElement
count := 0
i := 0
for count <= numElements {
// u = RecursiveHashToZq(q, "commitmentKey", q, i, count) + 1
u := hash.RecursiveHashToZq(q,
hash.HashableString{Value: "commitmentKey"},
hash.HashableBigInt{Value: q},
hash.HashableBigInt{Value: big.NewInt(int64(i))},
hash.HashableBigInt{Value: big.NewInt(int64(count))},
)
uPlusOne := new(big.Int).Add(u, oneBI)
// w = uPlusOne^2 mod p
w := new(big.Int).Exp(uPlusOne, big.NewInt(2), p)
// Check w != 1 AND w != g AND w not already in values
wIsOne := w.Cmp(oneBI) == 0
wIsG := w.Cmp(g.Value()) == 0
wInValues := false
for _, v := range values {
if v.Value().Cmp(w) == 0 {
wInValues = true
break
}
}
if !wIsOne && !wIsG && !wInValues {
elem, _ := emath.NewGqElement(w, group)
values = append(values, elem)
count++
}
i++
}
// h = values[0], g = values[1..numElements]
gElems := make([]emath.GqElement, numElements)
copy(gElems, values[1:numElements+1])
return CommitmentKey{
H: values[0],
G: emath.GqVectorOf(gElems...),
}
}
// Commit computes a Pedersen commitment: C = h^r * Π(g_i^a_i)
func (ck CommitmentKey) Commit(a *emath.ZqVector, r emath.ZqElement) emath.GqElement {
if a.Size() > ck.Size() {
panic("vector size must not exceed commitment key size")
}
// h^r
result := ck.H.Exponentiate(r)
// Π(g_i^a_i)
for i := 0; i < a.Size(); i++ {
result = result.Multiply(ck.G.Get(i).Exponentiate(a.Get(i)))
}
return result
}
// CommitMatrix computes commitments to each column of a matrix.
func (ck CommitmentKey) CommitMatrix(A *emath.ZqMatrix, r *emath.ZqVector) *emath.GqVector {
if A.NumCols() != r.Size() {
panic("number of columns must match randomness vector size")
}
commitments := make([]emath.GqElement, A.NumCols())
for j := 0; j < A.NumCols(); j++ {
commitments[j] = ck.Commit(A.GetColumn(j), r.Get(j))
}
return emath.GqVectorOf(commitments...)
}