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.
252 lines
6.7 KiB
Go
252 lines
6.7 KiB
Go
package mixnet
|
||
|
||
import (
|
||
"math/big"
|
||
|
||
"github.com/user/evote/pkg/elgamal"
|
||
"github.com/user/evote/pkg/hash"
|
||
emath "github.com/user/evote/pkg/math"
|
||
)
|
||
|
||
// ZeroArgument is a proof that Σ_i a_i ★ b_{i-1} = 0 for committed matrices A, B.
|
||
type ZeroArgument struct {
|
||
CA0 emath.GqElement // Commitment to a_0 (prepended column)
|
||
CBm emath.GqElement // Commitment to b_m (appended column)
|
||
CD *emath.GqVector // Commitments to diagonal d vector (size 2m+1)
|
||
APrime *emath.ZqVector // Aggregated a' vector
|
||
BPrime *emath.ZqVector // Aggregated b' vector
|
||
RPrime emath.ZqElement // Aggregated randomness for A
|
||
SPrime emath.ZqElement // Aggregated randomness for B
|
||
TPrime emath.ZqElement // Aggregated randomness for D
|
||
}
|
||
|
||
// GenZeroArgument generates a ZeroArgument proof.
|
||
func GenZeroArgument(
|
||
cA *emath.GqVector, // Commitments to A columns (size m)
|
||
cB *emath.GqVector, // Commitments to B columns (size m)
|
||
A *emath.ZqMatrix, // n×m matrix
|
||
B *emath.ZqMatrix, // n×m matrix
|
||
r *emath.ZqVector, // Randomness for A (size m)
|
||
s *emath.ZqVector, // Randomness for B (size m)
|
||
y emath.ZqElement, // Star map parameter
|
||
pk elgamal.PublicKey, // Public key (needed for Fiat-Shamir hash)
|
||
ck CommitmentKey,
|
||
group *emath.GqGroup,
|
||
) ZeroArgument {
|
||
zqGroup := emath.ZqGroupFromGqGroup(group)
|
||
n := A.NumRows()
|
||
m := A.NumCols()
|
||
|
||
// 1. Prepend random a_0 to A, append random b_m to B
|
||
a0 := emath.RandomZqVector(n, zqGroup)
|
||
r0 := emath.RandomZqElement(zqGroup)
|
||
cA0 := ck.Commit(a0, r0)
|
||
|
||
bm := emath.RandomZqVector(n, zqGroup)
|
||
sm := emath.RandomZqElement(zqGroup)
|
||
cBm := ck.Commit(bm, sm)
|
||
|
||
// A' = [a_0 | A] (m+1 columns, n rows)
|
||
aPrimeCols := make([]*emath.ZqVector, m+1)
|
||
aPrimeCols[0] = a0
|
||
for j := 0; j < m; j++ {
|
||
aPrimeCols[j+1] = A.GetColumn(j)
|
||
}
|
||
|
||
// B' = [B | b_m] (m+1 columns, n rows)
|
||
bPrimeCols := make([]*emath.ZqVector, m+1)
|
||
for j := 0; j < m; j++ {
|
||
bPrimeCols[j] = B.GetColumn(j)
|
||
}
|
||
bPrimeCols[m] = bm
|
||
|
||
// r' = [r_0 | r] and s' = [s | s_m]
|
||
rPrepended := r.Prepend(r0)
|
||
sAppended := s.Append(sm)
|
||
|
||
// 2. Compute d vector (diagonal star map products)
|
||
// Java formula: d[k] = Σ StarMap(A'[i], B'[j]) where j = (m - k) + i
|
||
// Bounds: i = max(0, k-m) to m, break when j > m
|
||
dSize := 2*m + 1
|
||
dVec := make([]emath.ZqElement, dSize)
|
||
zero, _ := emath.NewZqElement(big.NewInt(0), zqGroup)
|
||
for k := 0; k < dSize; k++ {
|
||
dVec[k] = zero
|
||
for i := max(0, k-m); i <= m; i++ {
|
||
j := (m - k) + i
|
||
if j > m {
|
||
break
|
||
}
|
||
if j >= 0 {
|
||
sm := StarMap(aPrimeCols[i], bPrimeCols[j], y)
|
||
dVec[k] = dVec[k].Add(sm)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 3. Generate randomness for d (Java: t[m+1] = 0)
|
||
tVec := make([]emath.ZqElement, dSize)
|
||
for k := 0; k < dSize; k++ {
|
||
if k == m+1 {
|
||
tVec[k] = zero
|
||
} else {
|
||
tVec[k] = emath.RandomZqElement(zqGroup)
|
||
}
|
||
}
|
||
|
||
// 4. Compute commitments to d
|
||
cdElems := make([]emath.GqElement, dSize)
|
||
for k := 0; k < dSize; k++ {
|
||
cdElems[k] = ck.H.Exponentiate(tVec[k]).Multiply(ck.G.Get(0).Exponentiate(dVec[k]))
|
||
}
|
||
cD := emath.GqVectorOf(cdElems...)
|
||
|
||
// 5. Fiat-Shamir challenge x
|
||
// Java hash order: (p, q, pk, ck, c_A_0, c_B_m, c_d, c_B, c_A)
|
||
x := zeroArgumentChallenge(group, pk, &ck, cA0, cBm, cD, cB, cA)
|
||
|
||
// 6. Compute x^i powers
|
||
xPowers := computeXPowers(x, 2*m+1, zqGroup)
|
||
|
||
// 7. Compute proof elements
|
||
aPrimeVec := emath.ZqVectorOfZeros(n, zqGroup)
|
||
for i := 0; i <= m; i++ {
|
||
scaled := aPrimeCols[i].ScalarMultiply(xPowers[i])
|
||
aPrimeVec = aPrimeVec.Add(scaled)
|
||
}
|
||
|
||
bPrimeVec := emath.ZqVectorOfZeros(n, zqGroup)
|
||
for i := 0; i <= m; i++ {
|
||
scaled := bPrimeCols[i].ScalarMultiply(xPowers[m-i])
|
||
bPrimeVec = bPrimeVec.Add(scaled)
|
||
}
|
||
|
||
rPrimeVal := zero
|
||
for i := 0; i <= m; i++ {
|
||
rPrimeVal = rPrimeVal.Add(xPowers[i].Multiply(rPrepended.Get(i)))
|
||
}
|
||
|
||
sPrimeVal := zero
|
||
for i := 0; i <= m; i++ {
|
||
sPrimeVal = sPrimeVal.Add(xPowers[m-i].Multiply(sAppended.Get(i)))
|
||
}
|
||
|
||
tPrimeVal := zero
|
||
for k := 0; k < dSize; k++ {
|
||
tPrimeVal = tPrimeVal.Add(xPowers[k].Multiply(tVec[k]))
|
||
}
|
||
|
||
return ZeroArgument{
|
||
CA0: cA0,
|
||
CBm: cBm,
|
||
CD: cD,
|
||
APrime: aPrimeVec,
|
||
BPrime: bPrimeVec,
|
||
RPrime: rPrimeVal,
|
||
SPrime: sPrimeVal,
|
||
TPrime: tPrimeVal,
|
||
}
|
||
}
|
||
|
||
// VerifyZeroArgument verifies a ZeroArgument proof.
|
||
func VerifyZeroArgument(
|
||
arg ZeroArgument,
|
||
cA *emath.GqVector,
|
||
cB *emath.GqVector,
|
||
y emath.ZqElement,
|
||
pk elgamal.PublicKey,
|
||
ck CommitmentKey,
|
||
group *emath.GqGroup,
|
||
) bool {
|
||
zqGroup := emath.ZqGroupFromGqGroup(group)
|
||
m := cA.Size()
|
||
|
||
// 1. Reconstruct x
|
||
x := zeroArgumentChallenge(group, pk, &ck, arg.CA0, arg.CBm, arg.CD, cB, cA)
|
||
xPowers := computeXPowers(x, 2*m+1, zqGroup)
|
||
|
||
// 2. Check c_D[m+1] commits to 0 (Java: c_d.get(m+1) == 1)
|
||
if !arg.CD.Get(m + 1).IsIdentity() {
|
||
return false
|
||
}
|
||
|
||
// 3. Check Π(c_A[:,i]^{x^(i+1)}) * c_A_0^{x^0} = commit(a', r')
|
||
lhs1 := arg.CA0.Exponentiate(xPowers[0])
|
||
for i := 0; i < m; i++ {
|
||
lhs1 = lhs1.Multiply(cA.Get(i).Exponentiate(xPowers[i+1]))
|
||
}
|
||
rhs1 := ck.Commit(arg.APrime, arg.RPrime)
|
||
if !lhs1.Equals(rhs1) {
|
||
return false
|
||
}
|
||
|
||
// 4. Check Π(c_B[:,i]^{x^(m-i)}) * c_B_m^{x^0} = commit(b', s')
|
||
lhs2 := arg.CBm.Exponentiate(xPowers[0])
|
||
for i := 0; i < m; i++ {
|
||
lhs2 = lhs2.Multiply(cB.Get(i).Exponentiate(xPowers[m-i]))
|
||
}
|
||
rhs2 := ck.Commit(arg.BPrime, arg.SPrime)
|
||
if !lhs2.Equals(rhs2) {
|
||
return false
|
||
}
|
||
|
||
// 5. Check Π(c_D[k]^{x^k}) = commit(starMap(a', b', y), t')
|
||
lhs3 := group.Identity()
|
||
for k := 0; k < arg.CD.Size(); k++ {
|
||
lhs3 = lhs3.Multiply(arg.CD.Get(k).Exponentiate(xPowers[k]))
|
||
}
|
||
starMapVal := StarMap(arg.APrime, arg.BPrime, y)
|
||
rhs3 := ck.H.Exponentiate(arg.TPrime).Multiply(ck.G.Get(0).Exponentiate(starMapVal))
|
||
return lhs3.Equals(rhs3)
|
||
}
|
||
|
||
// zeroArgumentChallenge computes the Fiat-Shamir challenge for ZeroArgument.
|
||
// Java hash order: (p, q, pk, ck, c_A_0, c_B_m, c_d, c_B, c_A)
|
||
func zeroArgumentChallenge(group *emath.GqGroup, pk elgamal.PublicKey, ck *CommitmentKey, cA0, cBm emath.GqElement, cD, cB, cA *emath.GqVector) emath.ZqElement {
|
||
zqGroup := emath.ZqGroupFromGqGroup(group)
|
||
q := group.Q()
|
||
|
||
hashBytes := hash.RecursiveHash(
|
||
hash.HashableBigInt{Value: group.P()},
|
||
hash.HashableBigInt{Value: group.Q()},
|
||
pkToHashable(pk),
|
||
ckToHashable(ck),
|
||
hash.HashableBigInt{Value: cA0.Value()},
|
||
hash.HashableBigInt{Value: cBm.Value()},
|
||
gqVectorToHashable(cD),
|
||
gqVectorToHashable(cB),
|
||
gqVectorToHashable(cA),
|
||
)
|
||
|
||
eVal := new(big.Int).SetBytes(hashBytes)
|
||
eVal.Mod(eVal, q)
|
||
e, _ := emath.NewZqElement(eVal, zqGroup)
|
||
return e
|
||
}
|
||
|
||
func computeXPowers(x emath.ZqElement, count int, group *emath.ZqGroup) []emath.ZqElement {
|
||
powers := make([]emath.ZqElement, count)
|
||
one, _ := emath.NewZqElement(big.NewInt(1), group)
|
||
powers[0] = one
|
||
if count > 1 {
|
||
powers[1] = x
|
||
for i := 2; i < count; i++ {
|
||
powers[i] = powers[i-1].Multiply(x)
|
||
}
|
||
}
|
||
return powers
|
||
}
|
||
|
||
func max(a, b int) int {
|
||
if a > b {
|
||
return a
|
||
}
|
||
return b
|
||
}
|
||
|
||
func min(a, b int) int {
|
||
if a < b {
|
||
return a
|
||
}
|
||
return b
|
||
}
|