diff --git a/contracts/evoting/controller/action.go b/contracts/evoting/controller/action.go index b0b2a69b6..b2d04d026 100644 --- a/contracts/evoting/controller/action.go +++ b/contracts/evoting/controller/action.go @@ -425,7 +425,8 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { return xerrors.Errorf(getFormErr, err) } - encryptedBallots := form.Suffragia.Ciphervotes + suff, err := form.Suffragia(serdecontext, service.GetStore()) + encryptedBallots := suff.Ciphervotes dela.Logger.Info().Msg("Length encrypted ballots: " + strconv.Itoa(len(encryptedBallots))) dela.Logger.Info().Msgf("Ballot of user1: %s", encryptedBallots[0]) dela.Logger.Info().Msgf("Ballot of user2: %s", encryptedBallots[1]) @@ -485,7 +486,8 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { logFormStatus(form) dela.Logger.Info().Msg("Number of shuffled ballots : " + strconv.Itoa(len(form.ShuffleInstances))) - dela.Logger.Info().Msg("Number of encrypted ballots : " + strconv.Itoa(len(form.Suffragia.Ciphervotes))) + suff, err = form.Suffragia(serdecontext, service.GetStore()) + dela.Logger.Info().Msg("Number of encrypted ballots : " + strconv.Itoa(len(suff.Ciphervotes))) // ###################################### REQUEST PUBLIC SHARES ############ diff --git a/contracts/evoting/evoting.go b/contracts/evoting/evoting.go index 4edf4e078..69b63b796 100644 --- a/contracts/evoting/evoting.go +++ b/contracts/evoting/evoting.go @@ -91,7 +91,6 @@ func (e evotingCommand) createForm(snap store.Snapshot, step execution.Step) err Status: types.Initial, // Pubkey is set by the opening command BallotSize: tx.Configuration.MaxBallotSize(), - Suffragia: types.Suffragia{}, PubsharesUnits: units, ShuffleInstances: []types.ShuffleInstance{}, DecryptedBallots: []types.Ballot{}, @@ -103,7 +102,8 @@ func (e evotingCommand) createForm(snap store.Snapshot, step execution.Step) err PromFormStatus.WithLabelValues(form.FormID).Set(float64(form.Status)) - formBuf, err := form.Serialize(e.context) + //formBuf, err := form.Serialize(e.context) + formBuf, err := e.context.Marshal(form) if err != nil { return xerrors.Errorf("failed to marshal Form : %v", err) } @@ -231,7 +231,10 @@ func (e evotingCommand) castVote(snap store.Snapshot, step execution.Step) error len(tx.Ballot), form.ChunksPerBallot()) } - form.Suffragia.CastVote(tx.UserID, tx.Ballot) + err = form.CastVote(e.context, snap, tx.UserID, tx.Ballot) + if err != nil { + return xerrors.Errorf("couldn't cast vote: %v", err) + } //core.PrintTimer("Serializing") formBuf, err := form.Serialize(e.context) @@ -245,7 +248,7 @@ func (e evotingCommand) castVote(snap store.Snapshot, step execution.Step) error return xerrors.Errorf("failed to set value: %v", err) } - PromFormBallots.WithLabelValues(form.FormID).Set(float64(len(form.Suffragia.Ciphervotes))) + PromFormBallots.WithLabelValues(form.FormID).Set(float64(form.BallotCount)) return nil } @@ -363,7 +366,11 @@ func (e evotingCommand) shuffleBallots(snap store.Snapshot, step execution.Step) var ciphervotes []types.Ciphervote if tx.Round == 0 { - ciphervotes = form.Suffragia.Ciphervotes + suff, err := form.Suffragia(e.context, snap) + if err != nil { + return xerrors.Errorf("couldn't get ballots: %v", err) + } + ciphervotes = suff.Ciphervotes } else { // get the form's last shuffled ballots lastIndex := len(form.ShuffleInstances) - 1 @@ -471,7 +478,7 @@ func (e evotingCommand) closeForm(snap store.Snapshot, step execution.Step) erro return xerrors.Errorf("the form is not open, current status: %d", form.Status) } - if len(form.Suffragia.Ciphervotes) <= 1 { + if form.BallotCount <= 1 { return xerrors.Errorf("at least two ballots are required") } diff --git a/contracts/evoting/json/forms.go b/contracts/evoting/json/forms.go index 40e99e8b0..2e08d93a0 100644 --- a/contracts/evoting/json/forms.go +++ b/contracts/evoting/json/forms.go @@ -1,6 +1,7 @@ package json import ( + "encoding/hex" "encoding/json" "github.com/c4dt/d-voting/contracts/evoting/types" @@ -35,9 +36,14 @@ func (formFormat) Encode(ctx serde.Context, message serde.Message) ([]byte, erro } } - suffragia, err := encodeSuffragia(ctx, m.Suffragia) - if err != nil { - return nil, xerrors.Errorf("failed to encode suffragia: %v", err) + suffragias := make([]string, len(m.SuffragiaIDs)) + for i, suf := range m.SuffragiaIDs { + suffragias[i] = hex.EncodeToString(suf) + } + + suffragiaHashes := make([]string, len(m.SuffragiaHashes)) + for i, sufH := range m.SuffragiaHashes { + suffragiaHashes[i] = hex.EncodeToString(sufH) } shuffleInstances, err := encodeShuffleInstances(ctx, m.ShuffleInstances) @@ -62,7 +68,8 @@ func (formFormat) Encode(ctx serde.Context, message serde.Message) ([]byte, erro Status: uint16(m.Status), Pubkey: pubkey, BallotSize: m.BallotSize, - Suffragia: suffragia, + Suffragias: suffragias, + SuffragiaHashes: suffragiaHashes, ShuffleInstances: shuffleInstances, ShuffleThreshold: m.ShuffleThreshold, PubsharesUnits: pubsharesUnits, @@ -100,9 +107,20 @@ func (formFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) } } - suffragia, err := decodeSuffragia(ctx, formJSON.Suffragia) - if err != nil { - return nil, xerrors.Errorf("failed to decode suffragia: %v", err) + suffragias := make([][]byte, len(formJSON.Suffragias)) + for i, suff := range formJSON.Suffragias { + suffragias[i], err = hex.DecodeString(suff) + if err != nil { + return nil, xerrors.Errorf("failed to decode suffragia-address: %v", err) + } + } + + suffragiaHashes := make([][]byte, len(formJSON.SuffragiaHashes)) + for i, suffH := range formJSON.SuffragiaHashes { + suffragiaHashes[i], err = hex.DecodeString(suffH) + if err != nil { + return nil, xerrors.Errorf("failed to decode suffragia-hash: %v", err) + } } shuffleInstances, err := decodeShuffleInstances(ctx, formJSON.ShuffleInstances) @@ -132,7 +150,8 @@ func (formFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) Status: types.Status(formJSON.Status), Pubkey: pubKey, BallotSize: formJSON.BallotSize, - Suffragia: suffragia, + SuffragiaIDs: suffragias, + SuffragiaHashes: suffragiaHashes, ShuffleInstances: shuffleInstances, ShuffleThreshold: formJSON.ShuffleThreshold, PubsharesUnits: pubSharesSubmissions, @@ -157,7 +176,11 @@ type FormJSON struct { // to pad smaller ballots such that all ballots cast have the same size BallotSize int - Suffragia SuffragiaJSON + // Suffragias are the hex-encoded addresses of the Suffragia storages. + Suffragias []string + + // SuffragiaHashes are the hex-encoded sha256-hashes of the ballots in every Suffragia. + SuffragiaHashes []string // ShuffleInstances is all the shuffles, along with their proof and identity // of shuffler. @@ -179,60 +202,12 @@ type FormJSON struct { RosterBuf []byte } -// SuffragiaJSON defines the JSON representation of a suffragia. -type SuffragiaJSON struct { - UserIDs []string - Ciphervotes []json.RawMessage -} - -func encodeSuffragia(ctx serde.Context, suffragia types.Suffragia) (SuffragiaJSON, error) { - ciphervotes := make([]json.RawMessage, len(suffragia.Ciphervotes)) - - for i, ciphervote := range suffragia.Ciphervotes { - buff, err := ciphervote.Serialize(ctx) - if err != nil { - return SuffragiaJSON{}, xerrors.Errorf("failed to serialize ciphervote: %v", err) - } - - ciphervotes[i] = buff - } - return SuffragiaJSON{ - UserIDs: suffragia.UserIDs, - Ciphervotes: ciphervotes, - }, nil -} - -func decodeSuffragia(ctx serde.Context, suffragiaJSON SuffragiaJSON) (types.Suffragia, error) { - var res types.Suffragia - fac := ctx.GetFactory(types.CiphervoteKey{}) - - factory, ok := fac.(types.CiphervoteFactory) - if !ok { - return res, xerrors.Errorf("invalid ciphervote factory: '%T'", fac) +func encodeSuffragias(suffragias [][]byte) []string { + ret := make([]string, len(suffragias)) + for i, suf := range suffragias { + ret[i] = hex.EncodeToString(suf) } - - ciphervotes := make([]types.Ciphervote, len(suffragiaJSON.Ciphervotes)) - - for i, ciphervoteJSON := range suffragiaJSON.Ciphervotes { - msg, err := factory.Deserialize(ctx, ciphervoteJSON) - if err != nil { - return res, xerrors.Errorf("failed to deserialize ciphervote json: %v", err) - } - - ciphervote, ok := msg.(types.Ciphervote) - if !ok { - return res, xerrors.Errorf("wrong type: '%T'", msg) - } - - ciphervotes[i] = ciphervote - } - - res = types.Suffragia{ - UserIDs: suffragiaJSON.UserIDs, - Ciphervotes: ciphervotes, - } - - return res, nil + return ret } // ShuffleInstanceJSON defines the JSON representation of a shuffle instance diff --git a/contracts/evoting/json/mod.go b/contracts/evoting/json/mod.go index 900c1e5ce..c9845338a 100644 --- a/contracts/evoting/json/mod.go +++ b/contracts/evoting/json/mod.go @@ -9,6 +9,7 @@ import ( func init() { types.RegisterFormFormat(serde.FormatJSON, formFormat{}) + types.RegisterSuffragiaFormat(serde.FormatJSON, suffragiaFormat{}) types.RegisterCiphervoteFormat(serde.FormatJSON, ciphervoteFormat{}) types.RegisterTransactionFormat(serde.FormatJSON, transactionFormat{}) } diff --git a/contracts/evoting/json/suffragia.go b/contracts/evoting/json/suffragia.go new file mode 100644 index 000000000..424c83910 --- /dev/null +++ b/contracts/evoting/json/suffragia.go @@ -0,0 +1,97 @@ +package json + +import ( + "encoding/json" + + "github.com/c4dt/d-voting/contracts/evoting/types" + "go.dedis.ch/dela/serde" + "golang.org/x/xerrors" +) + +type suffragiaFormat struct{} + +func (suffragiaFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) { + switch m := msg.(type) { + case types.Suffragia: + sJson, err := encodeSuffragia(ctx, m) + if err != nil { + return nil, xerrors.Errorf("couldn't encode suffragia: %v", err) + } + + buff, err := ctx.Marshal(&sJson) + if err != nil { + return nil, xerrors.Errorf("failed to marshal form: %v", err) + } + + return buff, nil + default: + return nil, xerrors.Errorf("Unknown format: %T", msg) + } +} + +func (suffragiaFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { + var sJson SuffragiaJSON + + err := ctx.Unmarshal(data, &sJson) + if err != nil { + return nil, xerrors.Errorf("failed to unmarshal form: %v", err) + } + + return decodeSuffragia(ctx, sJson) +} + +// SuffragiaJSON defines the JSON representation of a suffragia. +type SuffragiaJSON struct { + UserIDs []string + Ciphervotes []json.RawMessage +} + +func encodeSuffragia(ctx serde.Context, suffragia types.Suffragia) (SuffragiaJSON, error) { + ciphervotes := make([]json.RawMessage, len(suffragia.Ciphervotes)) + + for i, ciphervote := range suffragia.Ciphervotes { + buff, err := ciphervote.Serialize(ctx) + if err != nil { + return SuffragiaJSON{}, xerrors.Errorf("failed to serialize ciphervote: %v", err) + } + + ciphervotes[i] = buff + } + return SuffragiaJSON{ + UserIDs: suffragia.UserIDs, + Ciphervotes: ciphervotes, + }, nil +} + +func decodeSuffragia(ctx serde.Context, suffragiaJSON SuffragiaJSON) (types.Suffragia, error) { + var res types.Suffragia + fac := ctx.GetFactory(types.CiphervoteKey{}) + + factory, ok := fac.(types.CiphervoteFactory) + if !ok { + return res, xerrors.Errorf("invalid ciphervote factory: '%T'", fac) + } + + ciphervotes := make([]types.Ciphervote, len(suffragiaJSON.Ciphervotes)) + + for i, ciphervoteJSON := range suffragiaJSON.Ciphervotes { + msg, err := factory.Deserialize(ctx, ciphervoteJSON) + if err != nil { + return res, xerrors.Errorf("failed to deserialize ciphervote json: %v", err) + } + + ciphervote, ok := msg.(types.Ciphervote) + if !ok { + return res, xerrors.Errorf("wrong type: '%T'", msg) + } + + ciphervotes[i] = ciphervote + } + + res = types.Suffragia{ + UserIDs: suffragiaJSON.UserIDs, + Ciphervotes: ciphervotes, + } + + return res, nil +} diff --git a/contracts/evoting/mod.go b/contracts/evoting/mod.go index b431bc445..0c221a370 100644 --- a/contracts/evoting/mod.go +++ b/contracts/evoting/mod.go @@ -1,6 +1,8 @@ package evoting import ( + "fmt" + dvoting "github.com/c4dt/d-voting" "github.com/c4dt/d-voting/contracts/evoting/types" "github.com/c4dt/d-voting/services/dkg" @@ -202,6 +204,7 @@ func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { case CmdCreateForm: err = c.cmd.createForm(snap, step) if err != nil { + fmt.Printf("Error: %+v\n", err) return xerrors.Errorf("failed to create form: %v", err) } case CmdOpenForm: diff --git a/contracts/evoting/mod_test.go b/contracts/evoting/mod_test.go index 5f9f615cc..22baa5261 100644 --- a/contracts/evoting/mod_test.go +++ b/contracts/evoting/mod_test.go @@ -295,11 +295,13 @@ func TestCommand_CastVote(t *testing.T) { form, ok := message.(types.Form) require.True(t, ok) - require.Len(t, form.Suffragia.Ciphervotes, 1) - require.True(t, castVote.Ballot.Equal(form.Suffragia.Ciphervotes[0])) + require.Len(t, form.BallotCount, 1) + suff, err := form.Suffragia(ctx, snap) + require.NoError(t, err) + require.True(t, castVote.Ballot.Equal(suff.Ciphervotes[0])) - require.Equal(t, castVote.UserID, form.Suffragia.UserIDs[0]) - require.Equal(t, float64(len(form.Suffragia.Ciphervotes)), testutil.ToFloat64(PromFormBallots)) + require.Equal(t, castVote.UserID, suff.UserIDs[0]) + require.Equal(t, float64(form.BallotCount), testutil.ToFloat64(PromFormBallots)) } func TestCommand_CloseForm(t *testing.T) { @@ -364,8 +366,8 @@ func TestCommand_CloseForm(t *testing.T) { err = cmd.closeForm(snap, makeStep(t, FormArg, string(data))) require.EqualError(t, err, "at least two ballots are required") - dummyForm.Suffragia.CastVote("dummyUser1", types.Ciphervote{}) - dummyForm.Suffragia.CastVote("dummyUser2", types.Ciphervote{}) + require.NoError(t, dummyForm.CastVote(ctx, snap, "dummyUser1", types.Ciphervote{})) + require.NoError(t, dummyForm.CastVote(ctx, snap, "dummyUser2", types.Ciphervote{})) formBuf, err = dummyForm.Serialize(ctx) require.NoError(t, err) @@ -697,7 +699,6 @@ func TestCommand_ShuffleBallotsFormatErrors(t *testing.T) { form.Pubkey = pubKey shuffleBallots.Round = 0 form.ShuffleInstances = make([]types.ShuffleInstance, 0) - form.Suffragia.Ciphervotes = make([]types.Ciphervote, 0) data, err = shuffleBallots.Serialize(ctx) require.NoError(t, err) @@ -713,9 +714,9 @@ func TestCommand_ShuffleBallotsFormatErrors(t *testing.T) { // > With only one shuffled ballot the shuffling can't happen - form.Suffragia.CastVote("user1", types.Ciphervote{ + require.NoError(t, form.CastVote(ctx, snap, "user1", types.Ciphervote{ types.EGPair{K: suite.Point(), C: suite.Point()}, - }) + })) data, err = shuffleBallots.Serialize(ctx) require.NoError(t, err) @@ -1118,7 +1119,6 @@ func initFormAndContract() (types.Form, Contract) { FormID: fakeFormID, Status: 0, Pubkey: nil, - Suffragia: types.Suffragia{}, ShuffleInstances: make([]types.ShuffleInstance, 0), DecryptedBallots: nil, ShuffleThreshold: 0, @@ -1156,12 +1156,13 @@ func initGoodShuffleBallot(t *testing.T, k int) (types.Form, types.ShuffleBallot shuffleBallots.Round = 0 form.ShuffleInstances = make([]types.ShuffleInstance, 0) + snap := fake.InMemorySnapshot{} for i := 0; i < k; i++ { ballot := types.Ciphervote{types.EGPair{ K: Ks[i], C: Cs[i], }} - form.Suffragia.CastVote(fmt.Sprintf("user%d", i), ballot) + form.CastVote(ctx, &snap, fmt.Sprintf("user%d", i), ballot) } // Valid Signature of shuffle diff --git a/contracts/evoting/types/election.go b/contracts/evoting/types/election.go index 896bcd77b..0a2f176ac 100644 --- a/contracts/evoting/types/election.go +++ b/contracts/evoting/types/election.go @@ -1,10 +1,15 @@ package types import ( + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "fmt" "io" "go.dedis.ch/dela/core/ordering/cosipbft/authority" ctypes "go.dedis.ch/dela/core/ordering/cosipbft/types" + "go.dedis.ch/dela/core/store" "go.dedis.ch/dela/serde" "go.dedis.ch/dela/serde/registry" "go.dedis.ch/kyber/v3" @@ -38,6 +43,8 @@ const ( Canceled Status = 6 ) +const BallotsPerBlock = 100 + // formFormat contains the supported formats for the form. Right now // only JSON is supported. var formFormat = registry.NewSimpleRegistry() @@ -67,8 +74,19 @@ type Form struct { // to pad smaller ballots such that all ballots cast have the same size BallotSize int - // Suffragia is a map from User ID to their encrypted ballot - Suffragia Suffragia + // SuffragiaIDs holds a slice of IDs to slices of SuffragiaIDs. + // This is to optimize the time it takes to (De)serialize a Form. + SuffragiaIDs [][]byte + + // BallotCount is the total number of ballots cast, including double + // ballots. + BallotCount uint32 + + // SuffragiaHashes holds a slice of hashes to all SuffragiaIDs. + // LG: not really sure if this is needed. In case a Form has also to be + // proven to be correct outside the nodes, the hashes are definitely + // needed. + SuffragiaHashes [][]byte // ShuffleInstances is all the shuffles, along with their proof and identity // of shuffler. @@ -128,6 +146,7 @@ func (e FormFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, ctx = serde.WithFactory(ctx, CiphervoteKey{}, e.ciphervoteFac) ctx = serde.WithFactory(ctx, ctypes.RosterKey{}, e.rosterFac) + fmt.Printf("Data is: %s\n", data) message, err := format.Decode(ctx, data) if err != nil { return nil, xerrors.Errorf("failed to decode: %v", err) @@ -146,6 +165,73 @@ func (e *Form) ChunksPerBallot() int { return e.BallotSize/29 + 1 } +// CastVote stores the new vote in the memory. +func (s *Form) CastVote(ctx serde.Context, st store.Snapshot, userID string, ciphervote Ciphervote) error { + var suff Suffragia + var blockID []byte + if s.BallotCount%BallotsPerBlock == 0 { + // Need to create a random ID for storing the ballots. + // H( formID | ballotcount ) + // should be random enough, even if it's previsible. + id, err := hex.DecodeString(s.FormID) + if err != nil { + return xerrors.Errorf("couldn't decode formID: %v", err) + } + h := sha256.New() + h.Write(id) + binary.LittleEndian.PutUint32(id, s.BallotCount) + blockID = h.Sum(id[0:4]) + err = st.Set(blockID, []byte{}) + if err != nil { + return xerrors.Errorf("couldn't store new ballot block: %v", err) + } + s.SuffragiaIDs = append(s.SuffragiaIDs, blockID) + s.SuffragiaHashes = append(s.SuffragiaHashes, []byte{}) + } else { + blockID = s.SuffragiaIDs[len(s.SuffragiaIDs)-1] + buf, err := st.Get(blockID) + if err != nil { + return xerrors.Errorf("couldn't get ballots block: %v", err) + } + err = ctx.Unmarshal(buf, &suff) + if err != nil { + return xerrors.Errorf("couldn't unmarshal ballots block: %v", err) + } + } + + suff.CastVote(userID, ciphervote) + buf, err := suff.Serialize(ctx) + if err != nil { + return xerrors.Errorf("couldn't marshal ballots block: %v", err) + } + err = st.Set(blockID, buf) + s.BallotCount += 1 + return nil +} + +// Suffragia returns all ballots from the storage. This should only +// be called rarely, as it might take a long time. +// It overwrites ballots cast by the same user and keeps only +// the latest ballot. +func (s *Form) Suffragia(ctx serde.Context, rd store.Readable) (Suffragia, error) { + var suff Suffragia + for _, id := range s.SuffragiaIDs { + buf, err := rd.Get(id) + if err != nil { + return suff, xerrors.Errorf("couldn't get ballot block: %v", err) + } + var suffTmp Suffragia + err = ctx.Unmarshal(buf, &suffTmp) + if err != nil { + return suff, xerrors.Errorf("couldn't unmarshal ballot block: %v", err) + } + for i, uid := range suffTmp.UserIDs { + suff.CastVote(uid, suffTmp.Ciphervotes[i]) + } + } + return suff, nil +} + // RandomVector is a slice of kyber.Scalar (encoded) which is used to prove // and verify the proof of a shuffle type RandomVector [][]byte @@ -239,80 +325,6 @@ func (c *Configuration) IsValid() bool { return true } -type Suffragia struct { - UserIDs []string - Ciphervotes []Ciphervote -} - -// CastVote adds a new vote and its associated user or updates a user's vote. -func (s *Suffragia) CastVote(userID string, ciphervote Ciphervote) { - for i, u := range s.UserIDs { - if u == userID { - s.Ciphervotes[i] = ciphervote - return - } - } - - s.UserIDs = append(s.UserIDs, userID) - s.Ciphervotes = append(s.Ciphervotes, ciphervote.Copy()) -} - -// CiphervotesFromPairs transforms two parallel lists of EGPoints to a list of -// Ciphervotes. -func CiphervotesFromPairs(X, Y [][]kyber.Point) ([]Ciphervote, error) { - if len(X) != len(Y) { - return nil, xerrors.Errorf("X and Y must have same length: %d != %d", - len(X), len(Y)) - } - - if len(X) == 0 { - return nil, xerrors.Errorf("ElGamal pairs are empty") - } - - NQ := len(X) // sequence size - k := len(X[0]) // number of votes - res := make([]Ciphervote, k) - - for i := 0; i < k; i++ { - x := make([]kyber.Point, NQ) - y := make([]kyber.Point, NQ) - - for j := 0; j < NQ; j++ { - x[j] = X[j][i] - y[j] = Y[j][i] - } - - ciphervote, err := ciphervoteFromPairs(x, y) - if err != nil { - return nil, xerrors.Errorf("failed to init from ElGamal pairs: %v", err) - } - - res[i] = ciphervote - } - - return res, nil -} - -// ciphervoteFromPairs transforms two parallel lists of EGPoints to a list of -// ElGamal pairs. -func ciphervoteFromPairs(ks []kyber.Point, cs []kyber.Point) (Ciphervote, error) { - if len(ks) != len(cs) { - return Ciphervote{}, xerrors.Errorf("ks and cs must have same length: %d != %d", - len(ks), len(cs)) - } - - res := make(Ciphervote, len(ks)) - - for i := range ks { - res[i] = EGPair{ - K: ks[i], - C: cs[i], - } - } - - return res, nil -} - // Pubshare represents a public share. type Pubshare kyber.Point diff --git a/contracts/evoting/types/suffragia.go b/contracts/evoting/types/suffragia.go new file mode 100644 index 000000000..aacbfa899 --- /dev/null +++ b/contracts/evoting/types/suffragia.go @@ -0,0 +1,119 @@ +package types + +import ( + "crypto/sha256" + + "go.dedis.ch/dela/serde" + "go.dedis.ch/dela/serde/registry" + "go.dedis.ch/kyber/v3" + "golang.org/x/xerrors" +) + +// suffragiaFormat contains the supported formats for the form. Right now +// only JSON is supported. +var suffragiaFormat = registry.NewSimpleRegistry() + +// RegisterSuffragiaFormat registers the engine for the provided format +func RegisterSuffragiaFormat(format serde.Format, engine serde.FormatEngine) { + suffragiaFormat.Register(format, engine) +} + +type Suffragia struct { + UserIDs []string + Ciphervotes []Ciphervote +} + +// Serialize implements the serde.Message +func (s Suffragia) Serialize(ctx serde.Context) ([]byte, error) { + format := formFormat.Get(ctx.GetFormat()) + + data, err := format.Encode(ctx, s) + if err != nil { + return nil, xerrors.Errorf("failed to encode form: %v", err) + } + + return data, nil +} + +// CastVote adds a new vote and its associated user or updates a user's vote. +func (s *Suffragia) CastVote(userID string, ciphervote Ciphervote) { + for i, u := range s.UserIDs { + if u == userID { + s.Ciphervotes[i] = ciphervote + return + } + } + + s.UserIDs = append(s.UserIDs, userID) + s.Ciphervotes = append(s.Ciphervotes, ciphervote.Copy()) +} + +// Hash returns the hash of this list of ballots. +func (s *Suffragia) Hash(ctx serde.Context) ([]byte, error) { + h := sha256.New() + for i, u := range s.UserIDs { + h.Write([]byte(u)) + buf, err := s.Ciphervotes[i].Serialize(ctx) + if err != nil { + return nil, xerrors.Errorf("couldn't serialize ciphervote: %v", err) + } + h.Write(buf) + } + return h.Sum(nil), nil +} + +// CiphervotesFromPairs transforms two parallel lists of EGPoints to a list of +// Ciphervotes. +func CiphervotesFromPairs(X, Y [][]kyber.Point) ([]Ciphervote, error) { + if len(X) != len(Y) { + return nil, xerrors.Errorf("X and Y must have same length: %d != %d", + len(X), len(Y)) + } + + if len(X) == 0 { + return nil, xerrors.Errorf("ElGamal pairs are empty") + } + + NQ := len(X) // sequence size + k := len(X[0]) // number of votes + res := make([]Ciphervote, k) + + for i := 0; i < k; i++ { + x := make([]kyber.Point, NQ) + y := make([]kyber.Point, NQ) + + for j := 0; j < NQ; j++ { + x[j] = X[j][i] + y[j] = Y[j][i] + } + + ciphervote, err := ciphervoteFromPairs(x, y) + if err != nil { + return nil, xerrors.Errorf("failed to init from ElGamal pairs: %v", err) + } + + res[i] = ciphervote + } + + return res, nil +} + +// ciphervoteFromPairs transforms two parallel lists of EGPoints to a list of +// ElGamal pairs. +func ciphervoteFromPairs(ks []kyber.Point, cs []kyber.Point) (Ciphervote, error) { + if len(ks) != len(cs) { + return Ciphervote{}, xerrors.Errorf("ks and cs must have same length: %d != %d", + len(ks), len(cs)) + } + + res := make(Ciphervote, len(ks)) + + for i := range ks { + res[i] = EGPair{ + K: ks[i], + C: cs[i], + } + } + + return res, nil +} diff --git a/internal/testing/fake/election.go b/internal/testing/fake/election.go index 5481a3f58..7b53d4764 100644 --- a/internal/testing/fake/election.go +++ b/internal/testing/fake/election.go @@ -4,14 +4,17 @@ import ( "strconv" "github.com/c4dt/d-voting/contracts/evoting/types" + "go.dedis.ch/dela/core/store" + "go.dedis.ch/dela/serde" "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/suites" "go.dedis.ch/kyber/v3/util/random" + "golang.org/x/xerrors" ) var suite = suites.MustFind("Ed25519") -func NewForm(formID string) types.Form { +func NewForm(ctx serde.Context, snapshot store.Snapshot, formID string) (types.Form, error) { k := 3 Ks, Cs, pubKey := NewKCPointsMarshalled(k) @@ -23,12 +26,9 @@ func NewForm(formID string) types.Form { De: "", }, }, - FormID: formID, - Status: types.Closed, - Pubkey: pubKey, - Suffragia: types.Suffragia{ - Ciphervotes: []types.Ciphervote{}, - }, + FormID: formID, + Status: types.Closed, + Pubkey: pubKey, ShuffleInstances: []types.ShuffleInstance{}, DecryptedBallots: nil, ShuffleThreshold: 1, @@ -39,10 +39,13 @@ func NewForm(formID string) types.Form { K: Ks[i], C: Cs[i], } - form.Suffragia.CastVote("dummyUser"+strconv.Itoa(i), types.Ciphervote{ballot}) + err := form.CastVote(ctx, snapshot, "dummyUser"+strconv.Itoa(i), types.Ciphervote{ballot}) + if err != nil { + return form, xerrors.Errorf("couldn't cast vote: %v", err) + } } - return form + return form, nil } func NewKCPointsMarshalled(k int) ([]kyber.Point, []kyber.Point, kyber.Point) { diff --git a/proxy/election.go b/proxy/election.go index 8743ae439..017982747 100644 --- a/proxy/election.go +++ b/proxy/election.go @@ -432,6 +432,13 @@ func (h *form) Form(w http.ResponseWriter, r *http.Request) { roster = append(roster, iter.GetNext().String()) } + suff, err := form.Suffragia(h.context, h.orderingSvc.GetStore()) + if err != nil { + http.Error(w, "couldn't get ballots: "+err.Error(), + http.StatusInternalServerError) + return + } + response := ptypes.GetFormResponse{ FormID: string(form.FormID), Configuration: form.Configuration, @@ -441,7 +448,7 @@ func (h *form) Form(w http.ResponseWriter, r *http.Request) { Roster: roster, ChunksPerBallot: form.ChunksPerBallot(), BallotSize: form.BallotSize, - Voters: form.Suffragia.UserIDs, + Voters: suff.UserIDs, } txnmanager.SendResponse(w, response) diff --git a/services/dkg/pedersen/mod_test.go b/services/dkg/pedersen/mod_test.go index af236b9ec..53d13dfaf 100644 --- a/services/dkg/pedersen/mod_test.go +++ b/services/dkg/pedersen/mod_test.go @@ -196,10 +196,6 @@ func TestPedersen_SyncDB(t *testing.T) { formID1 := "deadbeef51" formID2 := "deadbeef52" - // Start some forms - fake.NewForm(formID1) - fake.NewForm(formID2) - // Initialize a Pedersen p := NewPedersen(fake.Mino{}, &fake.Service{}, &fake.Pool{}, fake.Factory{}, fake.Signer{}) @@ -210,7 +206,8 @@ func TestPedersen_SyncDB(t *testing.T) { require.NoError(t, err) // Only Setup the first actor - a1.Setup() + _, err = a1.Setup() + require.NoError(t, err) // Create a new DKG map and fill it with data dkgMap := fake.NewInMemoryDB() @@ -487,7 +484,8 @@ func TestPedersen_Scenario(t *testing.T) { roster := authority.FromAuthority(fake.NewAuthorityFromMino(fake.NewSigner, minos...)) - form := fake.NewForm(formID) + st := fake.InMemorySnapshot{} + form, err := fake.NewForm(serdecontext, &st, formID) form.Roster = roster service := fake.NewService(formID, form, serdecontext) @@ -526,10 +524,11 @@ func TestPedersen_Scenario(t *testing.T) { K: Ks[i], C: Cs[i], }} - form.Suffragia.CastVote("dummyUser"+strconv.Itoa(i), ballot) + require.NoError(t, form.CastVote(serdecontext, &st, "dummyUser"+strconv.Itoa(i), ballot)) } - shuffledBallots := form.Suffragia.Ciphervotes + suff, err := form.Suffragia(serdecontext, &st) + shuffledBallots := suff.Ciphervotes shuffleInstance := etypes.ShuffleInstance{ShuffledBallots: shuffledBallots} form.ShuffleInstances = append(form.ShuffleInstances, shuffleInstance) diff --git a/services/shuffle/neff/handler.go b/services/shuffle/neff/handler.go index 0fc00f228..5196582f5 100644 --- a/services/shuffle/neff/handler.go +++ b/services/shuffle/neff/handler.go @@ -108,7 +108,7 @@ func (h *Handler) handleStartShuffle(formID string) error { return xerrors.Errorf("the form must be closed: (%v)", form.Status) } - tx, err := makeTx(h.context, &form, h.txmngr, h.shuffleSigner) + tx, err := h.makeTx(&form) if err != nil { return xerrors.Errorf("failed to make tx: %v", err) } @@ -149,10 +149,13 @@ func (h *Handler) handleStartShuffle(formID string) error { } } -func makeTx(ctx serde.Context, form *etypes.Form, manager txn.Manager, - shuffleSigner crypto.Signer) (txn.Transaction, error) { +// tx, err := h.makeTx(h.context, &form, h.txmngr, h.shuffleSigner) +// func makeTx(ctx serde.Context, form *etypes.Form, manager txn.Manager, +// +// shuffleSigner crypto.Signer) (txn.Transaction, error) { +func (h *Handler) makeTx(form *etypes.Form) (txn.Transaction, error) { - shuffledBallots, getProver, err := getShuffledBallots(form) + shuffledBallots, getProver, err := h.getShuffledBallots(form) if err != nil { return nil, xerrors.Errorf("failed to get shuffled ballots: %v", err) } @@ -163,17 +166,17 @@ func makeTx(ctx serde.Context, form *etypes.Form, manager txn.Manager, ShuffledBallots: shuffledBallots, } - h := sha256.New() + hash := sha256.New() - err = shuffleBallots.Fingerprint(h) + err = shuffleBallots.Fingerprint(hash) if err != nil { return nil, xerrors.Errorf("failed to get fingerprint: %v", err) } - hash := h.Sum(nil) + seed := hash.Sum(nil) // Generate random vector and proof - semiRandomStream, err := evoting.NewSemiRandomStream(hash) + semiRandomStream, err := evoting.NewSemiRandomStream(seed) if err != nil { return nil, xerrors.Errorf("could not create semi-random stream: %v", err) } @@ -204,17 +207,17 @@ func makeTx(ctx serde.Context, form *etypes.Form, manager txn.Manager, } // Sign the shuffle: - signature, err := shuffleSigner.Sign(hash) + signature, err := h.shuffleSigner.Sign(seed) if err != nil { return nil, xerrors.Errorf("could not sign the shuffle : %v", err) } - encodedSignature, err := signature.Serialize(ctx) + encodedSignature, err := signature.Serialize(h.context) if err != nil { return nil, xerrors.Errorf("could not encode signature as []byte : %v ", err) } - publicKey, err := shuffleSigner.GetPublicKey().MarshalBinary() + publicKey, err := h.shuffleSigner.GetPublicKey().MarshalBinary() if err != nil { return nil, xerrors.Errorf("could not unmarshal public key from nodeSigner: %v", err) } @@ -223,7 +226,7 @@ func makeTx(ctx serde.Context, form *etypes.Form, manager txn.Manager, shuffleBallots.PublicKey = publicKey shuffleBallots.Signature = encodedSignature - data, err := shuffleBallots.Serialize(ctx) + data, err := shuffleBallots.Serialize(h.context) if err != nil { return nil, xerrors.Errorf("failed to serialize shuffle ballots: %v", err) } @@ -242,7 +245,7 @@ func makeTx(ctx serde.Context, form *etypes.Form, manager txn.Manager, Value: data, } - tx, err := manager.Make(args...) + tx, err := h.txmngr.Make(args...) if err != nil { if err != nil { return nil, xerrors.Errorf("failed to use manager: %v", err.Error()) @@ -253,7 +256,7 @@ func makeTx(ctx serde.Context, form *etypes.Form, manager txn.Manager, } // getShuffledBallots returns the shuffled ballots with the shuffling proof. -func getShuffledBallots(form *etypes.Form) ([]etypes.Ciphervote, +func (h *Handler) getShuffledBallots(form *etypes.Form) ([]etypes.Ciphervote, func(e []kyber.Scalar) (proof.Prover, error), error) { round := len(form.ShuffleInstances) @@ -261,7 +264,11 @@ func getShuffledBallots(form *etypes.Form) ([]etypes.Ciphervote, var ciphervotes []etypes.Ciphervote if round == 0 { - ciphervotes = form.Suffragia.Ciphervotes + suff, err := form.Suffragia(h.context, h.service.GetStore()) + if err != nil { + return nil, nil, xerrors.Errorf("couldn't get ballots: %v", err) + } + ciphervotes = suff.Ciphervotes } else { ciphervotes = form.ShuffleInstances[round-1].ShuffledBallots } diff --git a/services/shuffle/neff/mod_test.go b/services/shuffle/neff/mod_test.go index 1d7cef573..e8e854eb4 100644 --- a/services/shuffle/neff/mod_test.go +++ b/services/shuffle/neff/mod_test.go @@ -52,10 +52,14 @@ func TestNeffShuffle_Shuffle(t *testing.T) { rosterLen := 2 roster := authority.FromAuthority(fake.NewAuthority(rosterLen, fake.NewSigner)) - form := fake.NewForm(formID) + st := fake.InMemorySnapshot{} + form, err := fake.NewForm(serdecontext, &st, formID) + require.NoError(t, err) form.Roster = roster - shuffledBallots := append([]etypes.Ciphervote{}, form.Suffragia.Ciphervotes...) + suff, err := form.Suffragia(serdecontext, &st) + require.NoError(t, err) + shuffledBallots := append([]etypes.Ciphervote{}, suff.Ciphervotes...) form.ShuffleInstances = append(form.ShuffleInstances, etypes.ShuffleInstance{ShuffledBallots: shuffledBallots}) form.ShuffleThreshold = 1