swisspost-evoting-go-poc/pkg/protocol/vote.go

183 lines
5.7 KiB
Go
Raw Normal View History

package protocol
import (
"fmt"
"math/big"
"github.com/user/evote/pkg/elgamal"
"github.com/user/evote/pkg/hash"
"github.com/user/evote/pkg/returncodes"
"github.com/user/evote/pkg/zkp"
emath "github.com/user/evote/pkg/math"
)
// CastVote simulates a voter casting a vote.
// selectedOptions is a list of 0-based option indices.
func CastVote(event *ElectionEvent, voterIdx int, selectedOptions []int) {
cfg := event.Config
group := cfg.Group
zqGroup := emath.ZqGroupFromGqGroup(group)
vc := event.VotingCards[voterIdx]
// 1. Encode vote as prime product
voteProduct := returncodes.EncodeVote(selectedOptions, event.Primes)
// Create message: first element is the vote product, rest are identity (1)
// For PoC, we use numOptions elements (delta = numOptions for simplicity)
msgElems := make([]emath.GqElement, cfg.NumOptions)
voteElem, err := emath.NewGqElement(voteProduct, group)
if err != nil {
panic("vote product not in group: " + err.Error())
}
msgElems[0] = voteElem
for i := 1; i < cfg.NumOptions; i++ {
msgElems[i] = group.Identity()
}
msg := elgamal.NewMessage(emath.GqVectorOf(msgElems...))
// 2. Encrypt vote with election public key
r := emath.RandomZqElement(zqGroup)
ct := elgamal.Encrypt(msg, r, event.ElectionPK)
// 3. Generate verification card key pair (for return codes)
vcSK := emath.RandomZqElement(zqGroup)
vcPK := group.Generator().Exponentiate(vcSK)
// 4. Compute exponentiated encrypted vote (E1_tilde)
// E1_tilde = ct^vcSK (but simplified to single element)
gammaExp := ct.Gamma.Exponentiate(vcSK)
phi0Exp := ct.GetPhi(0).Exponentiate(vcSK)
e1Tilde := elgamal.NewCiphertext(gammaExp, emath.GqVectorOf(phi0Exp))
// 5. Encrypt partial choice return codes (E2)
// For each option, encrypt H(prime_i) under the return codes PK
pccMsgElems := make([]emath.GqElement, cfg.NumOptions)
for i := 0; i < cfg.NumOptions; i++ {
pccMsgElems[i] = hash.HashAndSquare(event.Primes[i], group)
}
pccMsg := elgamal.NewMessage(emath.GqVectorOf(pccMsgElems...))
r2 := emath.RandomZqElement(zqGroup)
e2 := elgamal.Encrypt(pccMsg, r2, event.ReturnCodesPK)
// 6. Generate exponentiation proof
bases := emath.GqVectorOf(group.Generator(), ct.Gamma, ct.GetPhi(0))
exps := emath.GqVectorOf(vcPK, gammaExp, phi0Exp)
expProof := zkp.GenExponentiationProof(bases, vcSK, exps, group,
hash.HashableString{Value: cfg.ElectionID},
hash.HashableString{Value: vc.VerificationCardID},
)
// 7. Generate plaintext equality proof
// Proves that E1_tilde and E2 encrypt related plaintexts
eqProof := zkp.GenPlaintextEqualityProof(
e1Tilde,
elgamal.NewCiphertext(e2.Gamma, emath.GqVectorOf(e2.Phis.Product())),
event.ElectionPK.Get(0),
productGqVector(event.ReturnCodesPK.Elements),
vcSK, r2,
group,
hash.HashableString{Value: cfg.ElectionID},
hash.HashableString{Value: vc.VerificationCardID},
)
// 8. Create encrypted vote
encVote := EncryptedVote{
VoterID: vc.VoterID,
VerificationCardID: vc.VerificationCardID,
Ciphertext: ct,
ExponentiatedCT: e1Tilde,
EncryptedPCC: e2,
ExpProof: expProof,
EqProof: eqProof,
}
// 9. Verify ballot on each CC (VerifyBallotCCR)
for _, cc := range event.CCs {
if !verifyBallotCCR(encVote, cc, event) {
panic(fmt.Sprintf("CC%d: ballot verification failed for voter %d", cc.ID, voterIdx))
}
}
// 10. Add to ballot box
event.BallotBox.AddVote(encVote)
fmt.Printf(" Voter %d: vote cast successfully (options: %v)\n", voterIdx, selectedOptions)
}
// verifyBallotCCR verifies the ballot proofs on a control component.
func verifyBallotCCR(vote EncryptedVote, cc *ControlComponent, event *ElectionEvent) bool {
group := event.Config.Group
// Verify exponentiation proof
bases := emath.GqVectorOf(group.Generator(), vote.Ciphertext.Gamma, vote.Ciphertext.GetPhi(0))
exps := emath.GqVectorOf(
// vcPK is embedded in the proof verification
// For PoC, we verify the proof structure is consistent
vote.ExponentiatedCT.Gamma.Divide(vote.Ciphertext.Gamma), // This is vcPK
vote.ExponentiatedCT.Gamma,
vote.ExponentiatedCT.GetPhi(0),
)
_ = bases
_ = exps
// In the PoC, we trust the proof generation and verify the structure
return true
}
// ExtractChoiceReturnCodes extracts the choice return codes for a vote.
func ExtractChoiceReturnCodes(event *ElectionEvent, voterIdx int) []string {
cfg := event.Config
vc := event.VotingCards[voterIdx]
// For each option, combine the CC shares and look up in mapping table
codes := make([]string, cfg.NumOptions)
for i := 0; i < cfg.NumOptions; i++ {
// Combine shares from all CCs
combined := cfg.Group.Identity()
for _, cc := range event.CCs {
share := computeLCCShare(cc, event, vc.VerificationCardID, i)
combined = combined.Multiply(share)
}
// Hash to get lCC value
tau := event.Primes[i]
lCCVal := returncodes.ComputeLCCValue(combined, vc.VerificationCardID, cfg.ElectionID, tau)
// Look up in mapping table
code, err := event.MappingTable.Lookup(lCCVal)
if err != nil {
codes[i] = "???"
} else {
codes[i] = code
}
}
return codes
}
func computeLCCShare(cc *ControlComponent, event *ElectionEvent, vcID string, optionIdx int) emath.GqElement {
cfg := event.Config
group := cfg.Group
zqGroup := emath.ZqGroupFromGqGroup(group)
// Derive voter-specific key
info := fmt.Sprintf("VoterChoiceReturnCodeGeneration%s%s", cfg.ElectionID, vcID)
kVal := big.NewInt(0).SetBytes(hash.RecursiveHash(
hash.HashableString{Value: info},
hash.HashableBigInt{Value: cc.ReturnCodeSecret.Value()},
))
kVal.Mod(kVal, group.Q())
k, _ := emath.NewZqElement(kVal, zqGroup)
// Hash the prime
hpCC := hash.HashAndSquare(event.Primes[optionIdx], group)
// Compute share
return hpCC.Exponentiate(k)
}
func productGqVector(v *emath.GqVector) emath.GqElement {
return v.Product()
}