Skip to content

Commit

Permalink
Refactoring and preparing the use of the 'Additional' fields
Browse files Browse the repository at this point in the history
Put more methods in evoting/lib/container.go
Prepared usage of multiple points per vote
  • Loading branch information
ineiti committed Jun 28, 2023
1 parent cc3fadf commit c71d3f8
Show file tree
Hide file tree
Showing 11 changed files with 436 additions and 84 deletions.
163 changes: 138 additions & 25 deletions evoting/lib/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import (
"go.dedis.ch/kyber/v3/proof"
"go.dedis.ch/kyber/v3/share/dkg/rabin"
"go.dedis.ch/kyber/v3/shuffle"
"go.dedis.ch/kyber/v3/sign/schnorr"
"go.dedis.ch/kyber/v3/util/random"
"go.dedis.ch/onet/v3/log"
"strconv"
)

// CandidatesPerPoint indicates how many candidates fit into an ed25519 Point:
Expand All @@ -21,23 +24,6 @@ type Box struct {
Ballots []*Ballot
}

// genMix generates n mixes with corresponding proofs out of the ballots.
func (b *Box) genMix(key kyber.Point, n int) []*Mix {
mixes := make([]*Mix, n)

x, y := Split(b.Ballots)
for i := range mixes {
v, w, prover := shuffle.Shuffle(cothority.Suite, nil, key, x, y, random.New())
proof, _ := proof.HashProve(cothority.Suite, "", prover)
mixes[i] = &Mix{
Ballots: Combine(v, w),
Proof: proof,
}
x, y = v, w
}
return mixes
}

// genPartials generates partial decryptions for a given list of shared secrets.
func (m *Mix) genPartials(dkgs []*dkg.DistKeyGenerator) []*Partial {
partials := make([]*Partial, len(dkgs))
Expand All @@ -56,21 +42,148 @@ func (m *Mix) genPartials(dkgs []*dkg.DistKeyGenerator) []*Partial {
}

// Split separates the ElGamal pairs of a list of ballots into separate lists.
func Split(ballots []*Ballot) (alpha, beta []kyber.Point) {
n := len(ballots)
alpha, beta = make([]kyber.Point, n), make([]kyber.Point, n)
func Split(ballots []*Ballot) (alpha, beta [][]kyber.Point) {
nbrBallots := len(ballots)
// Each encrypted point holds protocol.CandidatePerPoint choices of a voter.
// For backward-compatibility, the first point is Ballot.Alpha / Ballot.Beta.
nbrPoints := 1
if nbrBallots > 0 {
nbrPoints += len(ballots[0].AdditionalAlphas)
}

// This is to make alpha and beta compatible with the dedis/kyber/shuffle/sequences.go
alpha, beta = make([][]kyber.Point, nbrPoints), make([][]kyber.Point, nbrPoints)
for i := range alpha {
alpha[i] = make([]kyber.Point, nbrBallots)
beta[i] = make([]kyber.Point, nbrBallots)
}

for i := range ballots {
alpha[i] = ballots[i].Alpha
beta[i] = ballots[i].Beta
b := ballots[i]
alpha[0][i] = b.Alpha
beta[0][i] = b.Beta
for j := 1; j < nbrPoints; j++ {
alpha[j][i] = b.AdditionalAlphas[j-1]
beta[j][i] = b.AdditionalBetas[j-1]
}
}
return
}

// Combine creates a list of ballots from two lists of points.
func Combine(alpha, beta []kyber.Point) []*Ballot {
ballots := make([]*Ballot, len(alpha))
func Combine(alpha, beta [][]kyber.Point) []*Ballot {
ballots := make([]*Ballot, len(alpha[0]))
for i := range ballots {
ballots[i] = &Ballot{Alpha: alpha[i], Beta: beta[i]}
ballots[i] = &Ballot{Alpha: alpha[0][i], Beta: beta[0][i]}
if len(alpha) > 1 {
ballots[i].AdditionalAlphas = make([]kyber.Point, len(alpha)-1)
ballots[i].AdditionalBetas = make([]kyber.Point, len(alpha)-1)
for j := range ballots[i].AdditionalAlphas {
ballots[i].AdditionalAlphas[j] = alpha[j+1][i]
ballots[i].AdditionalBetas[j] = beta[j+1][i]
}
}
}
return ballots
}

// CreateShuffleProof takes the alphas and betas of a Ballot, shuffles them, and then returns the result
// as well as a proof.
// If the alphas are of length one, it uses the algorithm before July 2023.
// If the alphas are longer, it uses the shuffle.SequencesShuffle.
func CreateShuffleProof(alphas, betas [][]kyber.Point, pubKey kyber.Point) ([][]kyber.Point, [][]kyber.Point, []byte, error) {
// Protect from missing input.
for i := range alphas {
for j := range alphas[i] {
if alphas[i][j] == nil {
alphas[i][j] = cothority.Suite.Point().Null()
}
}
}
for i := range betas {
for j := range betas[i] {
if betas[i][j] == nil {
betas[i][j] = cothority.Suite.Point().Null()
}
}
}

// Backward compatible handling of creating the shuffle proof
if len(alphas) == 1 {
g, d, prov := shuffle.Shuffle(cothority.Suite, nil, pubKey, alphas[0], betas[0], random.New())
shuffleProof, err := proof.HashProve(cothority.Suite, "", prov)
return [][]kyber.Point{g}, [][]kyber.Point{d}, shuffleProof, err
}

// New shuffle proof using `shuffle.SequencesShuffle`
g, d, provSeq := shuffle.SequencesShuffle(cothority.Suite, nil, pubKey, alphas, betas, random.New())
// Creating a prover taken from the `shuffle.Shuffle` method
beta := make([]kyber.Scalar, len(alphas))
for i := 0; i < len(alphas); i++ {
beta[i] = cothority.Suite.Scalar().Pick(random.New())
}
prov, err := provSeq(beta)
shuffleProof, err := proof.HashProve(cothority.Suite, "", prov)
return g, d, shuffleProof, err
}

// CreateBallot returns a ballot for a user. It makes sure that enough alphas and betas are initialized,
// even if the user votes for less than maxChoices candidates.
func CreateBallot(maxChoices int, pubKey kyber.Point, user uint32, elected []uint32) Ballot {
totalBufs := (maxChoices-1)/9 + 1
k := make([]kyber.Point, totalBufs)
c := make([]kyber.Point, totalBufs)
for buf := range k {
end := (buf + 1) * CandidatesPerPoint
if end > maxChoices {
end = maxChoices
}
if end > len(elected) {
end = len(elected)
}
start := buf * CandidatesPerPoint
var electedSlice []uint32
if start < maxChoices && start < len(elected) {
electedSlice = elected[start:end]
}
log.Printf("electedSlice for buf %d is: %x", buf, electedSlice)
k[buf], c[buf] = Encrypt(pubKey, scipersToBuf(electedSlice))
}
ballot := Ballot{
User: user,
Alpha: k[0],
Beta: c[0],
}
if maxChoices > CandidatesPerPoint {
ballot.AdditionalAlphas = k[1:]
ballot.AdditionalBetas = c[1:]
}
log.Printf("ballot is: %+v", ballot)
return ballot
}

// GenerateSignature creates a USELESS signature which should be replaced for any follow-up implementation.
// It merely signs the ID of the chain, and as such does not provide any guarantee that the user signing
// actually wanted to do anything with regard to the additional data...
// Also, if the signature fails, this method panics.
func GenerateSignature(private kyber.Scalar, ID []byte, sciper uint32) []byte {
message := ID
for _, c := range strconv.Itoa(int(sciper)) {
d, _ := strconv.Atoi(string(c))
message = append(message, byte(d))
}
sig, err := schnorr.Sign(cothority.Suite, private, message)
if err != nil {
panic("cannot sign:" + err.Error())
}
return sig
}

func scipersToBuf(scipers []uint32) []byte {
var buf = make([]byte, len(scipers)*3)
for i := range buf {
s := scipers[i/3] >> ((i % 3) * 8)
buf[i] = byte(s & 0xff)
}
return buf
}
43 changes: 36 additions & 7 deletions evoting/lib/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,50 @@ func TestSplit(t *testing.T) {
ballots := genBox(X, 2).Ballots

a, b := Split(ballots)
assert.Equal(t, ballots[0].Alpha, a[0])
assert.Equal(t, ballots[0].Beta, b[0])
assert.Equal(t, ballots[1].Alpha, a[1])
assert.Equal(t, ballots[1].Beta, b[1])
assert.Equal(t, ballots[0].Alpha, a[0][0])
assert.Equal(t, ballots[0].Beta, b[0][0])
assert.Equal(t, ballots[1].Alpha, a[0][1])
assert.Equal(t, ballots[1].Beta, b[0][1])
}

func TestCombine(t *testing.T) {
_, X1 := RandomKeyPair()
_, X2 := RandomKeyPair()

a, b := []kyber.Point{X1, X1}, []kyber.Point{X2, X2}
a, b := [][]kyber.Point{{X1, X2}, {X2, X1}}, [][]kyber.Point{{X2, X1}, {X1, X2}}
ballots := Combine(a, b)

assert.Equal(t, X1, ballots[0].Alpha)
assert.Equal(t, X1, ballots[1].Alpha)
assert.Equal(t, X2, ballots[1].Alpha)
assert.Equal(t, X2, ballots[0].Beta)
assert.Equal(t, X2, ballots[1].Beta)
assert.Equal(t, X1, ballots[1].Beta)
assert.Equal(t, X2, ballots[0].AdditionalAlphas[0])
assert.Equal(t, X1, ballots[1].AdditionalAlphas[0])
assert.Equal(t, X1, ballots[0].AdditionalBetas[0])
assert.Equal(t, X2, ballots[1].AdditionalBetas[0])
}

func TestScipersToBuf(t *testing.T) {
scipers := []uint32{0x100000, 0x100001, 0x100002}

buf := scipersToBuf(scipers[0:1])
assert.Equal(t, 3, len(buf))
assert.Equal(t, uint8(0x0), buf[0])
assert.Equal(t, uint8(0x0), buf[1])
assert.Equal(t, uint8(0x10), buf[2])

buf = scipersToBuf(scipers[0:2])
assert.Equal(t, 6, len(buf))

buf = scipersToBuf(scipers)
assert.Equal(t, 9, len(buf))
assert.Equal(t, uint8(0x0), buf[0])
assert.Equal(t, uint8(0x0), buf[1])
assert.Equal(t, uint8(0x10), buf[2])
assert.Equal(t, uint8(0x1), buf[3])
assert.Equal(t, uint8(0x0), buf[4])
assert.Equal(t, uint8(0x10), buf[5])
assert.Equal(t, uint8(0x2), buf[6])
assert.Equal(t, uint8(0x0), buf[7])
assert.Equal(t, uint8(0x10), buf[8])
}
16 changes: 12 additions & 4 deletions evoting/lib/elgamal.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package lib

import (
"errors"
"go.dedis.ch/onet/v3/log"

"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/proof"
Expand Down Expand Up @@ -31,10 +32,17 @@ func Decrypt(private kyber.Scalar, K, C kyber.Point) kyber.Point {
}

// Verify performs verifies the proof of a Neff shuffle.
func Verify(tag []byte, public kyber.Point, x, y, v, w []kyber.Point) error {
if len(x) < 2 || len(y) < 2 || len(v) < 2 || len(w) < 2 {
func Verify(tag []byte, public kyber.Point, x, y, v, w [][]kyber.Point) error {
if len(x[0]) < 2 || len(y[0]) < 2 || len(v[0]) < 2 || len(w[0]) < 2 {
return errors.New("cannot verify less than 2 points")
}
verifier := shuffle.Verifier(cothority.Suite, nil, public, x, y, v, w)
return proof.HashVerify(cothority.Suite, "", verifier, tag)
if len(x) == 1 {
verifier := shuffle.Verifier(cothority.Suite, nil, public, x[0], y[0], v[0], w[0])
return proof.HashVerify(cothority.Suite, "", verifier, tag)
}

// TODO: This really should be possible. But the current implementation of shuffle.SequencesShuffle doesn't
// implement the verification! Or at least I cannot see how it's done...
log.Warn("Cannot verify mixes for more than 9 candidates!")
return nil
}
8 changes: 7 additions & 1 deletion evoting/lib/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,12 @@ func (t *Transaction) Verify(genesis skipchain.SkipBlockID, s *skipchain.Service
} else if !election.IsUser(t.User) {
return errors.New("cast error: user not part")
}
if election.MaxChoices > CandidatesPerPoint {
addLen := (election.MaxChoices - 1) / CandidatesPerPoint
if len(t.Ballot.AdditionalAlphas) != addLen || len(t.Ballot.AdditionalBetas) != addLen {
return errors.New("ballot error: not enough Additional{Alphas,Betas}")
}
}
return nil
} else if t.Mix != nil {
election, err := GetElection(s, genesis, false, t.User)
Expand Down Expand Up @@ -258,7 +264,7 @@ func (t *Transaction) Verify(genesis skipchain.SkipBlockID, s *skipchain.Service
}

// check if Mix is valid
var x, y []kyber.Point
var x, y [][]kyber.Point
if len(mixes) == 0 {
// verify against Boxes
boxes, err := election.Box(s)
Expand Down
14 changes: 11 additions & 3 deletions evoting/protocol/decrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,16 @@ func (d *Decrypt) HandlePrompt(prompt MessagePromptDecrypt) error {
err := func() error {
mix := mixes[len(mixes)-1]
points := make([]kyber.Point, len(mix.Ballots))
additionalPoints := make([]lib.PartialAdditional, len(mix.Ballots))
for i := range points {
points[i] = lib.Decrypt(d.Secret.V, mix.Ballots[i].Alpha, mix.Ballots[i].Beta)
ballot := mix.Ballots[i]
points[i] = lib.Decrypt(d.Secret.V, ballot.Alpha, ballot.Beta)
additionalPoints[i].AdditionalPoints = make([]kyber.Point, len(ballot.AdditionalAlphas))
for j := range additionalPoints[i].AdditionalPoints {
additionalPoints[i].AdditionalPoints[j] = lib.Decrypt(d.Secret.V, ballot.AdditionalAlphas[j], ballot.AdditionalBetas[j])
}
}

index := -1
for i, node := range d.Election.Roster.List {
if node.Public.Equal(d.Public()) {
Expand All @@ -117,8 +124,9 @@ func (d *Decrypt) HandlePrompt(prompt MessagePromptDecrypt) error {
}

partial = &lib.Partial{
Points: points,
NodeID: d.ServerIdentity().ID,
Points: points,
NodeID: d.ServerIdentity().ID,
AdditionalPoints: additionalPoints,
}
data, err := d.ServerIdentity().Public.MarshalBinary()
if err != nil {
Expand Down
19 changes: 8 additions & 11 deletions evoting/protocol/decrypt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import (
"time"

"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/proof"
"go.dedis.ch/kyber/v3/shuffle"
"go.dedis.ch/kyber/v3/sign/schnorr"
"go.dedis.ch/kyber/v3/util/random"
"go.dedis.ch/onet/v3"
"go.dedis.ch/onet/v3/log"
"go.dedis.ch/onet/v3/network"
Expand Down Expand Up @@ -109,19 +106,19 @@ func runDecrypt(t *testing.T, n int) {
mixes := make([]*lib.Mix, n)
x, y := lib.Split(ballots)
for i := range mixes {
v, w, prover := shuffle.Shuffle(cothority.Suite, nil, key, x, y, random.New())
proof, _ := proof.HashProve(cothority.Suite, "", prover)
v, w, shuffleProof, err := lib.CreateShuffleProof(x, y, key)
require.NoError(t, err)
public := roster.Get(i).Public
data, _ := public.MarshalBinary()
sig, _ := schnorr.Sign(cothority.Suite, local.GetPrivate(nodes[i]), data)
mix := &lib.Mix{
Ballots: lib.Combine(v, w),
Proof: proof,
Proof: shuffleProof,
NodeID: roster.Get(i).ID,
Signature: sig,
}
tx = lib.NewTransaction(mix, election.Creator)
err := lib.StoreUsingWebsocket(election.ID, election.Roster, tx)
err = lib.StoreUsingWebsocket(election.ID, election.Roster, tx)
require.NoError(t, err)
x, y = v, w
}
Expand Down Expand Up @@ -194,20 +191,20 @@ func TestDecryptNodeFailure(t *testing.T) {
mixes := make([]*lib.Mix, 7)
x, y := lib.Split(ballots)
for i := range mixes {
v, w, prover := shuffle.Shuffle(cothority.Suite, nil, key, x, y, random.New())
proof, _ := proof.HashProve(cothority.Suite, "", prover)
v, w, shuffleProof, err := lib.CreateShuffleProof(x, y, key)
require.NoError(t, err)
public := roster.Get(i).Public
data, _ := public.MarshalBinary()
sig, _ := schnorr.Sign(cothority.Suite, local.GetPrivate(nodes[i]), data)
mix := &lib.Mix{
Ballots: lib.Combine(v, w),
Proof: proof,
Proof: shuffleProof,
NodeID: roster.Get(i).ID,
Signature: sig,
}
mixes[i] = mix
tx = lib.NewTransaction(mix, election.Creator)
err := lib.StoreUsingWebsocket(election.ID, election.Roster, tx)
err = lib.StoreUsingWebsocket(election.ID, election.Roster, tx)
require.NoError(t, err)
x, y = v, w
}
Expand Down
Loading

0 comments on commit c71d3f8

Please sign in to comment.