diff --git a/contracts/evoting/admin_test.go b/contracts/evoting/admin_test.go new file mode 100644 index 000000000..425da2fc4 --- /dev/null +++ b/contracts/evoting/admin_test.go @@ -0,0 +1,76 @@ +package evoting + +import ( + "github.com/c4dt/d-voting/contracts/evoting/types" + "github.com/stretchr/testify/require" + "go.dedis.ch/dela/serde" + sjson "go.dedis.ch/dela/serde/json" + "testing" +) + +var ctxAdminTest serde.Context + +var formFacAdminTest serde.Factory +var transactionFacAdminTest serde.Factory + +func init() { + ciphervoteFac := types.CiphervoteFactory{} + formFacAdminTest = types.NewFormFactory(ciphervoteFac, fakeAuthorityFactory{}) + transactionFacAdminTest = types.NewTransactionFactory(ciphervoteFac) + + ctxAdminTest = sjson.NewContext() +} + +// This test create an Admin Form structure which is then serialized and +// deserialized to check whether these operations work as intended. +// Serialization/Deserialization of an AdminList should not change its values. +func TestAdmin_Serde(t *testing.T) { + initialAdminList := []int{111111, 222222, 333333, 123456} + + adminList := types.AdminList{AdminList: initialAdminList} + + value, err := adminList.Serialize(ctxAdminTest) + + require.NoError(t, err) + + // deserialization + deserializedAdminList := types.AdminList{} + + msgs, err := deserializedAdminList.Deserialize(ctxAdminTest, value) + + require.NoError(t, err) + + updatedAdminList := msgs.(types.AdminList) + + require.Equal(t, initialAdminList, updatedAdminList.AdminList) +} + +func TestAdmin_AddAdminAndRemoveAdmin(t *testing.T) { + initialAdminList := []int{} + + myTestID := "123456" + + adminList := types.AdminList{AdminList: initialAdminList} + + res, err := adminList.GetAdminIndex(myTestID) + require.Equal(t, -1, res) + require.NoError(t, err) + + err = adminList.AddAdmin(myTestID) + require.NoError(t, err) + res, err = adminList.GetAdminIndex(myTestID) + require.Equal(t, 0, res) + require.NoError(t, err) + + err = adminList.RemoveAdmin(myTestID) + require.ErrorContains(t, err, "cannot remove this Admin because it is the only one remaining") + + err = adminList.AddAdmin("654321") + require.NoError(t, err) + + err = adminList.RemoveAdmin(myTestID) + require.NoError(t, err) + res, err = adminList.GetAdminIndex(myTestID) + require.Equal(t, -1, res) + require.NoError(t, err) +} diff --git a/contracts/evoting/controller/action.go b/contracts/evoting/controller/action.go index 1b674e716..2d262ffbc 100644 --- a/contracts/evoting/controller/action.go +++ b/contracts/evoting/controller/action.go @@ -48,9 +48,12 @@ const ( contentType = "application/json" formPath = "/evoting/forms" // FormPathSlash is the path to the form with a trailing slash - FormPathSlash = formPath + "/" - formIDPath = FormPathSlash + "{formID}" - transactionSlash = "/evoting/transactions/" + FormPathSlash = formPath + "/" + formIDPath = FormPathSlash + "{formID}" + transactionSlash = "/evoting/transactions/" + + evotingPathSlash = "/evoting/" + transactionPath = transactionSlash + "{token}" unexpectedStatus = "unexpected status: %s, body: %s" failRetrieveDecryption = "failed to retrieve decryption key: %v" @@ -169,12 +172,19 @@ func (a *RegisterAction) Execute(ctx node.Context) error { return xerrors.Errorf("failed to unmarshal proxy key: %v", err) } - transactionManager := txnmanager.NewTransactionManager(mngr, p, sjson.NewContext(), proxykey, blocks, signer) + transactionManager := txnmanager.NewTransactionManager(mngr, p, sjson.NewContext(), proxykey, blocks, signer, validation) ep := eproxy.NewForm(ordering, p, sjson.NewContext(), formFac, proxykey, transactionManager) router := mux.NewRouter() + router.HandleFunc(evotingPathSlash+"addadmin", ep.AddAdmin).Methods("POST") + router.HandleFunc(evotingPathSlash+"removeadmin", ep.RemoveAdmin).Methods("POST") + router.HandleFunc(evotingPathSlash+"adminlist", ep.AdminList).Methods("GET") + router.HandleFunc(formIDPath+"/addowner", ep.AddOwnerToForm).Methods("POST") + router.HandleFunc(formIDPath+"/removeowner", ep.RemoveOwnerToForm).Methods("POST") + router.HandleFunc(formIDPath+"/addvoter", ep.AddVoterToForm).Methods("POST") + router.HandleFunc(formIDPath+"/removevoter", ep.RemoveVoterToForm).Methods("POST") router.HandleFunc(formPath, ep.NewForm).Methods("POST") router.HandleFunc(formPath, ep.Forms).Methods("GET") router.HandleFunc(formPath, eproxy.AllowCORS).Methods("OPTIONS") @@ -188,6 +198,7 @@ func (a *RegisterAction) Execute(ctx node.Context) error { router.NotFoundHandler = http.HandlerFunc(eproxy.NotFoundHandler) router.MethodNotAllowedHandler = http.HandlerFunc(eproxy.NotAllowedHandler) + proxy.RegisterHandler(evotingPathSlash, router.ServeHTTP) proxy.RegisterHandler(formPath, router.ServeHTTP) proxy.RegisterHandler(FormPathSlash, router.ServeHTTP) proxy.RegisterHandler(transactionSlash, router.ServeHTTP) @@ -352,8 +363,8 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { } castVoteRequest := ptypes.CastVoteRequest{ - UserID: "user1", - Ballot: ballot1, + VoterID: "user1", + Ballot: ballot1, } signed, err := createSignedRequest(secret, castVoteRequest) @@ -377,8 +388,8 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { } castVoteRequest = ptypes.CastVoteRequest{ - UserID: "user2", - Ballot: ballot2, + VoterID: "user2", + Ballot: ballot2, } signed, err = createSignedRequest(secret, castVoteRequest) @@ -402,8 +413,8 @@ func (a *scenarioTestAction) Execute(ctx node.Context) error { } castVoteRequest = ptypes.CastVoteRequest{ - UserID: "user3", - Ballot: ballot3, + VoterID: "user3", + Ballot: ballot3, } signed, err = createSignedRequest(secret, castVoteRequest) @@ -593,7 +604,7 @@ func setupSimpleForm(ctx node.Context, secret kyber.Scalar, proxyAddr1 string, createSimpleFormRequest := ptypes.CreateFormRequest{ Configuration: configuration, - AdminID: "adminId", + UserID: "UserID", } signed, err := createSignedRequest(secret, createSimpleFormRequest) diff --git a/contracts/evoting/evoting.go b/contracts/evoting/evoting.go index 082597c31..5ac952d30 100644 --- a/contracts/evoting/evoting.go +++ b/contracts/evoting/evoting.go @@ -33,6 +33,9 @@ const ( shufflingProtocolName = "PairShuffle" errGetTransaction = "failed to get transaction: %v" errGetForm = "failed to get form: %v" + errIsRole = "failed check the permission: %v" + errNoOwnerPerms = "The user %v doesn't have the Owner permission on the form." + errNoVoterPerms = "The user %v doesn't have the Voter permission on the form." errWrongTx = "wrong type of transaction: %T" ) @@ -45,6 +48,13 @@ type evotingCommand struct { prover prover } +type Role int + +const ( + Voters Role = iota + 1 + Owners +) + type prover func(suite proof.Suite, protocolName string, verifier proof.Verifier, proof []byte) error // createForm implements commands. It performs the CREATE_FORM command @@ -65,6 +75,15 @@ func (e evotingCommand) createForm(snap store.Snapshot, step execution.Step) err return xerrors.Errorf("failed to get roster") } + // Check if has Admin Right to create a form + isAdmin, _, err := e.fetchAdmin(snap, tx.UserID) + if err != nil { + return err + } + if !isAdmin { + return xerrors.Errorf("The performing user is not an admin.") + } + roster, err := e.rosterFac.AuthorityOf(e.context, rosterBuf) if err != nil { return xerrors.Errorf("failed to get roster: %v", err) @@ -85,6 +104,16 @@ func (e evotingCommand) createForm(snap store.Snapshot, step execution.Step) err Indexes: make([]int, 0), } + // Initial owner is the creator + owners := make([]int, 1) + + sciperInt, err := types.SciperToInt(tx.UserID) + if err != nil { + return xerrors.Errorf("failed to convert SCIPER to integer: %v", err) + } + + owners[0] = sciperInt + form := types.Form{ FormID: hex.EncodeToString(formIDBuf), Configuration: tx.Configuration, @@ -98,6 +127,8 @@ func (e evotingCommand) createForm(snap store.Snapshot, step execution.Step) err // that 1/3 of the participants go away, the form will never end. Roster: roster, ShuffleThreshold: threshold.ByzantineThreshold(roster.Len()), + Owners: owners, + Voters: make([]int, 0), } PromFormStatus.WithLabelValues(form.FormID).Set(float64(form.Status)) @@ -112,8 +143,16 @@ func (e evotingCommand) createForm(snap store.Snapshot, step execution.Step) err return xerrors.Errorf("failed to set value: %v", err) } - // Update the form metadata store + err = updateFormMetadataStore(snap, form.FormID) + if err != nil { + return xerrors.Errorf("failed to update the metadata in the store: %v", err) + } + return nil +} + +// updateFormMetadataStore Update the form metadata store +func updateFormMetadataStore(snap store.Snapshot, formID string) error { formsMetadataBuf, err := snap.Get([]byte(FormsMetadataKey)) if err != nil { return xerrors.Errorf("failed to get key '%s': %v", formsMetadataBuf, err) @@ -130,7 +169,7 @@ func (e evotingCommand) createForm(snap store.Snapshot, step execution.Step) err } } - err = formsMetadata.FormsIDs.Add(form.FormID) + err = formsMetadata.FormsIDs.Add(formID) if err != nil { return xerrors.Errorf("couldn't add new form: %v", err) } @@ -144,7 +183,6 @@ func (e evotingCommand) createForm(snap store.Snapshot, step execution.Step) err if err != nil { return xerrors.Errorf("failed to set value: %v", err) } - return nil } @@ -167,6 +205,15 @@ func (e evotingCommand) openForm(snap store.Snapshot, step execution.Step) error return xerrors.Errorf(errGetForm, err) } + isOwner, err := e.isRole(form, tx.UserID, Owners) + if err != nil { + return xerrors.Errorf(errIsRole, err) + } + + if !isOwner { + return xerrors.Errorf(errNoOwnerPerms, tx.UserID) + } + if form.Status != types.Initial { return xerrors.Errorf("the form was opened before, current status: %d", form.Status) } @@ -225,12 +272,21 @@ func (e evotingCommand) castVote(snap store.Snapshot, step execution.Step) error return xerrors.Errorf("the form is not open, current status: %d", form.Status) } + isOwner, err := e.isRole(form, tx.VoterID, Voters) + if err != nil { + return xerrors.Errorf(errIsRole, err) + } + + if !isOwner { + return xerrors.Errorf(errNoVoterPerms, tx.VoterID) + } + if len(tx.Ballot) != form.ChunksPerBallot() { return xerrors.Errorf("the ballot has unexpected length: %d != %d", len(tx.Ballot), form.ChunksPerBallot()) } - err = form.CastVote(e.context, snap, tx.UserID, tx.Ballot) + err = form.CastVote(e.context, snap, tx.VoterID, tx.Ballot) if err != nil { return xerrors.Errorf("couldn't cast vote: %v", err) } @@ -278,6 +334,15 @@ func (e evotingCommand) shuffleBallots(snap store.Snapshot, step execution.Step) form.Status, types.Closed) } + isOwner, err := e.isRole(form, tx.UserID, Owners) + if err != nil { + return xerrors.Errorf(errIsRole, err) + } + + if !isOwner { + return xerrors.Errorf(errNoOwnerPerms, tx.UserID) + } + // Round starts at 0 expectedRound := len(form.ShuffleInstances) @@ -476,6 +541,15 @@ func (e evotingCommand) closeForm(snap store.Snapshot, step execution.Step) erro return xerrors.Errorf("the form is not open, current status: %d", form.Status) } + isOwner, err := e.isRole(form, tx.UserID, Owners) + if err != nil { + return xerrors.Errorf(errIsRole, err) + } + + if !isOwner { + return xerrors.Errorf(errNoOwnerPerms, tx.UserID) + } + if form.BallotCount <= 1 { return xerrors.Errorf("at least two ballots are required") } @@ -631,6 +705,15 @@ func (e evotingCommand) combineShares(snap store.Snapshot, step execution.Step) " current status: %d", form.Status) } + isOwner, err := e.isRole(form, tx.UserID, Owners) + if err != nil { + return xerrors.Errorf(errIsRole, err) + } + + if !isOwner { + return xerrors.Errorf(errNoOwnerPerms, tx.UserID) + } + allPubShares := form.PubsharesUnits.Pubshares shufflesSize := len(form.ShuffleInstances) @@ -699,6 +782,15 @@ func (e evotingCommand) cancelForm(snap store.Snapshot, step execution.Step) err return xerrors.Errorf(errGetForm, err) } + isOwner, err := e.isRole(form, tx.UserID, Owners) + if err != nil { + return xerrors.Errorf(errIsRole, err) + } + + if !isOwner { + return xerrors.Errorf(errNoOwnerPerms, tx.UserID) + } + form.Status = types.Canceled PromFormStatus.WithLabelValues(form.FormID).Set(float64(form.Status)) @@ -733,37 +825,283 @@ func (e evotingCommand) deleteForm(snap store.Snapshot, step execution.Step) err return xerrors.Errorf(errGetForm, err) } + isOwner, err := e.isRole(form, tx.UserID, Owners) + if err != nil { + return xerrors.Errorf(errIsRole, err) + } + + if !isOwner { + return xerrors.Errorf(errNoOwnerPerms, tx.UserID) + } + err = snap.Delete(formID) if err != nil { return xerrors.Errorf("failed to delete form: %v", err) } - // Update the form metadata store + err = updateFormMetadataStore(snap, form.FormID) + if err != nil { + return xerrors.Errorf("failed to update the metadata in the store: %v", err) + } - formsMetadataBuf, err := snap.Get([]byte(FormsMetadataKey)) + return nil +} + +// manageAdminList implements commands. It performs the ADD or REMOVE ADMIN command +func (e evotingCommand) manageAdminList(snap store.Snapshot, step execution.Step) error { + msg, err := e.getTransaction(step.Current) if err != nil { - return xerrors.Errorf("failed to get key '%s': %v", formsMetadataBuf, err) + return xerrors.Errorf(errGetTransaction, err) } - if len(formsMetadataBuf) == 0 { - return nil + var list types.AdminList + + h := sha256.New() + h.Write([]byte(AdminListId)) + formIDBuf := h.Sum(nil) + + txAddAdmin, okAddAdmin := msg.(types.AddAdmin) + txRemoveAdmin, okRemoveAdmin := msg.(types.RemoveAdmin) + + if okAddAdmin { + isAdmin, listRetrieved, err := e.fetchAdmin(snap, txAddAdmin.PerformingUserID) + list = listRetrieved + if err != nil { + // Exact string matching of the error + if err.Error() != "failed to get the AdminList: No list found" { + return xerrors.Errorf("failed to get AdminList: %v", err) + } + + // Trust On First Use System -> if no AdminList, will create one by default. + + intSciper, err := types.SciperToInt(txAddAdmin.TargetUserID) + if err != nil { + return xerrors.Errorf("Invalid Sciper: %v", err) + } + + err = initializeAdminList(snap, intSciper, e.context) + if err != nil { + return xerrors.Errorf("Failed to initialize admin list: %v", err) + } + + // Adding the initial admin is performed by the initialize Admin List + // Therefore return + return nil + } + if !isAdmin { + return xerrors.Errorf("The performing user is not an admin.") + } + + err = list.AddAdmin(txAddAdmin.TargetUserID) + if err != nil { + return xerrors.Errorf("couldn't add admin: %v", err) + } + } else if okRemoveAdmin { + isAdmin, listRetrieved, err := e.fetchAdmin(snap, txRemoveAdmin.PerformingUserID) + list = listRetrieved + if err != nil { + return err + } + if !isAdmin { + return xerrors.Errorf("The performing user is not an admin.") + } + + err = list.RemoveAdmin(txRemoveAdmin.TargetUserID) + if err != nil { + return xerrors.Errorf("couldn't remove admin: %v", err) + } + } else { + return xerrors.Errorf(errWrongTx, msg) } - var formsMetadata types.FormsMetadata + formBuf, err := list.Serialize(e.context) + if err != nil { + return xerrors.Errorf("failed to marshal Form : %v", err) + } - err = json.Unmarshal(formsMetadataBuf, &formsMetadata) + err = snap.Set(formIDBuf, formBuf) if err != nil { - return xerrors.Errorf("failed to unmarshal FormsMetadata: %v", err) + return xerrors.Errorf("failed to set value: %v", err) } - formsMetadata.FormsIDs.Remove(form.FormID) + return nil +} - formMetadataJSON, err := json.Marshal(formsMetadata) +// isRole check whether the txPerformingUser has the role in the provided form +func (e evotingCommand) isRole(form types.Form, txPerformingUser string, role Role) (bool, error) { + sciperInt, err := types.SciperToInt(txPerformingUser) if err != nil { - return xerrors.Errorf("failed to marshal FormsMetadata: %v", err) + return false, xerrors.Errorf("Failed to convert SCIPER to int: %v", err) } - err = snap.Set([]byte(FormsMetadataKey), formMetadataJSON) + if role == Voters { + for i := 0; i < len(form.Voters); i++ { + if form.Voters[i] == sciperInt { + return true, nil + } + } + } else if role == Owners { + for i := 0; i < len(form.Owners); i++ { + if form.Owners[i] == sciperInt { + return true, nil + } + } + } + + return false, nil +} + +// fetchAdmin Check whether a user is in an Admin List +func (e evotingCommand) fetchAdmin(snap store.Snapshot, txPerformingUser string) (bool, types.AdminList, error) { + // If it found the AdminList + // Check that the performing user is Admin + form, err := e.getAdminList(snap) + if err != nil { + return false, types.AdminList{}, err + } + + performingUserPerm, err := form.GetAdminIndex(txPerformingUser) + if err != nil { + return false, form, xerrors.Errorf("couldn't retrieve admin permission of the performing user: %v", err) + } + + if performingUserPerm < 0 { + return false, form, nil + } + return true, form, nil +} + +// initializeAdminList initialize an AdminList on the blockchain. It is called the first time that +// we attempt to add an admin. +func initializeAdminList(snap store.Snapshot, initialAdmin int, ctx serde.Context) error { + h := sha256.New() + h.Write([]byte(AdminListId)) + formIDBuf := h.Sum(nil) + + adminList := types.AdminList{ + AdminList: []int{initialAdmin}, + } + + formBuf, err := adminList.Serialize(ctx) + if err != nil { + return xerrors.Errorf("failed to marshal AdminList : %v", err) + } + + err = snap.Set(formIDBuf, formBuf) + if err != nil { + return xerrors.Errorf("failed to set value: %v", err) + } + + err = updateFormMetadataStore(snap, hex.EncodeToString(formIDBuf)) + if err != nil { + return xerrors.Errorf("failed to update the metadata in the store: %v", err) + } + + return nil +} + +// manageVotersForm implements commands. +// It performs the ADD or REMOVE VOTERS/OWNERS command +func (e evotingCommand) manageOwnersVotersForm(snap store.Snapshot, step execution.Step) error { + msg, err := e.getTransaction(step.Current) + if err != nil { + return xerrors.Errorf(errGetTransaction, err) + } + + var form types.Form + var formID []byte + + txAddVoter, okAddVoter := msg.(types.AddVoter) + txRemoveVoter, okRemoveVoter := msg.(types.RemoveVoter) + txAddOwner, okAddOwner := msg.(types.AddOwner) + txRemoveOwner, okRemoveOwner := msg.(types.RemoveOwner) + + if okAddVoter { + form, formID, err = e.getForm(txAddVoter.FormID, snap) + if err != nil { + return xerrors.Errorf(errGetForm, err) + } + + isOwner, err := e.isRole(form, txAddVoter.PerformingUserID, Owners) + if err != nil { + return xerrors.Errorf(errIsRole, err) + } + + if !isOwner { + return xerrors.Errorf(errNoOwnerPerms, txAddVoter.PerformingUserID) + } + + err = form.AddVoter(txAddVoter.TargetUserID) + if err != nil { + return xerrors.Errorf("couldn't add voter: %v", err) + } + } else if okRemoveVoter { + form, formID, err = e.getForm(txRemoveVoter.FormID, snap) + if err != nil { + return xerrors.Errorf(errGetForm, err) + } + + isOwner, err := e.isRole(form, txRemoveVoter.PerformingUserID, Owners) + if err != nil { + return xerrors.Errorf(errIsRole, err) + } + + if !isOwner { + return xerrors.Errorf(errNoOwnerPerms, txRemoveVoter.PerformingUserID) + } + + err = form.RemoveVoter(txRemoveVoter.TargetUserID) + if err != nil { + return xerrors.Errorf("couldn't remove voter: %v", err) + } + } else if okAddOwner { + form, formID, err = e.getForm(txAddOwner.FormID, snap) + if err != nil { + return xerrors.Errorf(errGetForm, err) + } + + isOwner, err := e.isRole(form, txAddOwner.PerformingUserID, Owners) + if err != nil { + return xerrors.Errorf(errIsRole, err) + } + + if !isOwner { + return xerrors.Errorf(errNoOwnerPerms, txAddOwner.PerformingUserID) + } + + err = form.AddOwner(txAddOwner.TargetUserID) + if err != nil { + return xerrors.Errorf("couldn't add owner: %v", err) + } + } else if okRemoveOwner { + form, formID, err = e.getForm(txRemoveOwner.FormID, snap) + if err != nil { + return xerrors.Errorf(errGetForm, err) + } + + isOwner, err := e.isRole(form, txRemoveOwner.PerformingUserID, Owners) + if err != nil { + return xerrors.Errorf(errIsRole, err) + } + + if !isOwner { + return xerrors.Errorf(errNoOwnerPerms, txRemoveOwner.PerformingUserID) + } + + err = form.RemoveOwner(txRemoveOwner.TargetUserID) + if err != nil { + return xerrors.Errorf("couldn't remove owner: %v", err) + } + } else { + return xerrors.Errorf(errWrongTx, msg) + } + + formBuf, err := form.Serialize(e.context) + if err != nil { + return xerrors.Errorf("failed to marshal Form : %v", err) + } + + err = snap.Set(formID, formBuf) if err != nil { return xerrors.Errorf("failed to set value: %v", err) } @@ -856,6 +1194,20 @@ func (e evotingCommand) getForm(formIDHex string, return form, formIDBuf, nil } +// getAdminList gets the AdminList from the snap. Returns the form ID NOT hex +// encoded. +func (e evotingCommand) getAdminList(snap store.Snapshot) (types.AdminList, error) { + + var form types.AdminList + + form, err := types.AdminListFromStore(e.context, e.adminListFac, snap, AdminListId) + if err != nil { + return form, xerrors.Errorf("failed to get the AdminList: %v", err) + } + + return form, nil +} + // getTransaction extracts the argument from the transaction. func (e evotingCommand) getTransaction(tx txn.Transaction) (serde.Message, error) { buff := tx.GetArg(FormArg) diff --git a/contracts/evoting/json/adminList.go b/contracts/evoting/json/adminList.go new file mode 100644 index 000000000..e74981d6f --- /dev/null +++ b/contracts/evoting/json/adminList.go @@ -0,0 +1,45 @@ +package json + +import ( + "github.com/c4dt/d-voting/contracts/evoting/types" + "go.dedis.ch/dela/serde" + "golang.org/x/xerrors" +) + +type adminListFormat struct{} + +func (adminListFormat) Encode(ctx serde.Context, message serde.Message) ([]byte, error) { + adminList, ok := message.(types.AdminList) + if !ok { + return nil, xerrors.Errorf("Unknown format: %T", message) + } + + adminListJSON := AdminListJSON{ + AdminList: adminList.AdminList, + } + + buff, err := ctx.Marshal(&adminListJSON) + if err != nil { + return nil, xerrors.Errorf("failed to marshal form: %v", err) + } + + return buff, nil +} + +func (adminListFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) { + var adminListJSON AdminListJSON + + err := ctx.Unmarshal(data, &adminListJSON) + if err != nil { + return nil, xerrors.Errorf("failed to unmarshal form: %v", err) + } + + return types.AdminList{ + AdminList: adminListJSON.AdminList, + }, nil +} + +type AdminListJSON struct { + // List of SCIPER with admin rights + AdminList []int +} diff --git a/contracts/evoting/json/forms.go b/contracts/evoting/json/forms.go index 2640cfd80..7a0fc7f84 100644 --- a/contracts/evoting/json/forms.go +++ b/contracts/evoting/json/forms.go @@ -76,6 +76,8 @@ func (formFormat) Encode(ctx serde.Context, message serde.Message) ([]byte, erro PubsharesUnits: pubsharesUnits, DecryptedBallots: m.DecryptedBallots, RosterBuf: rosterBuf, + Owners: m.Owners, + Voters: m.Voters, } buff, err := ctx.Marshal(&formJSON) @@ -159,6 +161,8 @@ func (formFormat) Decode(ctx serde.Context, data []byte) (serde.Message, error) PubsharesUnits: pubSharesSubmissions, DecryptedBallots: formJSON.DecryptedBallots, Roster: roster, + Owners: formJSON.Owners, + Voters: formJSON.Voters, }, nil } @@ -206,6 +210,12 @@ type FormJSON struct { // authority.Authority. RosterBuf []byte + + // Store the list of admins SCIPER that are Owners of the form. + Owners []int + + // Store the list of SCIPER of user that are Voters on the form. + Voters []int } // ShuffleInstanceJSON defines the JSON representation of a shuffle instance diff --git a/contracts/evoting/json/mod.go b/contracts/evoting/json/mod.go index c9845338a..6dc23f385 100644 --- a/contracts/evoting/json/mod.go +++ b/contracts/evoting/json/mod.go @@ -12,4 +12,5 @@ func init() { types.RegisterSuffragiaFormat(serde.FormatJSON, suffragiaFormat{}) types.RegisterCiphervoteFormat(serde.FormatJSON, ciphervoteFormat{}) types.RegisterTransactionFormat(serde.FormatJSON, transactionFormat{}) + types.RegisterAdminListFormat(serde.FormatJSON, adminListFormat{}) } diff --git a/contracts/evoting/json/suffragia.go b/contracts/evoting/json/suffragia.go index 424c83910..221a06511 100644 --- a/contracts/evoting/json/suffragia.go +++ b/contracts/evoting/json/suffragia.go @@ -42,7 +42,7 @@ func (suffragiaFormat) Decode(ctx serde.Context, data []byte) (serde.Message, er // SuffragiaJSON defines the JSON representation of a suffragia. type SuffragiaJSON struct { - UserIDs []string + VoterIDs []string Ciphervotes []json.RawMessage } @@ -58,7 +58,7 @@ func encodeSuffragia(ctx serde.Context, suffragia types.Suffragia) (SuffragiaJSO ciphervotes[i] = buff } return SuffragiaJSON{ - UserIDs: suffragia.UserIDs, + VoterIDs: suffragia.VoterIDs, Ciphervotes: ciphervotes, }, nil } @@ -89,7 +89,7 @@ func decodeSuffragia(ctx serde.Context, suffragiaJSON SuffragiaJSON) (types.Suff } res = types.Suffragia{ - UserIDs: suffragiaJSON.UserIDs, + VoterIDs: suffragiaJSON.VoterIDs, Ciphervotes: ciphervotes, } diff --git a/contracts/evoting/json/transaction.go b/contracts/evoting/json/transaction.go index 1093416df..495da3cec 100644 --- a/contracts/evoting/json/transaction.go +++ b/contracts/evoting/json/transaction.go @@ -21,13 +21,14 @@ func (transactionFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, e case types.CreateForm: ce := CreateFormJSON{ Configuration: t.Configuration, - AdminID: t.AdminID, + UserID: t.UserID, } m = TransactionJSON{CreateForm: &ce} case types.OpenForm: oe := OpenFormJSON{ FormID: t.FormID, + UserID: t.UserID, } m = TransactionJSON{OpenForm: &oe} @@ -39,7 +40,7 @@ func (transactionFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, e cv := CastVoteJSON{ FormID: t.FormID, - UserID: t.UserID, + VoterID: t.VoterID, Ciphervote: ballot, } @@ -71,6 +72,7 @@ func (transactionFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, e Proof: t.Proof, Signature: t.Signature, PublicKey: t.PublicKey, + UserID: t.UserID, } m = TransactionJSON{ShuffleBallots: &sb} @@ -118,6 +120,52 @@ func (transactionFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, e } m = TransactionJSON{DeleteForm: &de} + case types.AddAdmin: + aa := AddAdminJSON{ + PerformingUserID: t.PerformingUserID, + TargetUserID: t.TargetUserID, + } + + m = TransactionJSON{AddAdmin: &aa} + case types.RemoveAdmin: + ra := RemoveAdminJSON{ + PerformingUserID: t.PerformingUserID, + TargetUserID: t.TargetUserID, + } + + m = TransactionJSON{RemoveAdmin: &ra} + case types.AddOwner: + addOwner := AddOwnerJSON{ + FormID: t.FormID, + TargetUserID: t.TargetUserID, + PerformingUserID: t.PerformingUserID, + } + + m = TransactionJSON{AddOwner: &addOwner} + case types.RemoveOwner: + removeOwner := RemoveOwnerJSON{ + FormID: t.FormID, + TargetUserID: t.TargetUserID, + PerformingUserID: t.PerformingUserID, + } + + m = TransactionJSON{RemoveOwner: &removeOwner} + case types.AddVoter: + addVoter := AddVoterJSON{ + FormID: t.FormID, + TargetUserID: t.TargetUserID, + PerformingUserID: t.PerformingUserID, + } + + m = TransactionJSON{AddVoter: &addVoter} + case types.RemoveVoter: + removeVoter := RemoveVoterJSON{ + FormID: t.FormID, + TargetUserID: t.TargetUserID, + PerformingUserID: t.PerformingUserID, + } + + m = TransactionJSON{RemoveVoter: &removeVoter} default: return nil, xerrors.Errorf("unknown type: '%T", msg) } @@ -143,11 +191,12 @@ func (transactionFormat) Decode(ctx serde.Context, data []byte) (serde.Message, case m.CreateForm != nil: return types.CreateForm{ Configuration: m.CreateForm.Configuration, - AdminID: m.CreateForm.AdminID, + UserID: m.CreateForm.UserID, }, nil case m.OpenForm != nil: return types.OpenForm{ FormID: m.OpenForm.FormID, + UserID: m.OpenForm.UserID, }, nil case m.CastVote != nil: msg, err := decodeCastVote(ctx, *m.CastVote) @@ -189,6 +238,40 @@ func (transactionFormat) Decode(ctx serde.Context, data []byte) (serde.Message, return types.DeleteForm{ FormID: m.DeleteForm.FormID, }, nil + case m.AddAdmin != nil: + return types.AddAdmin{ + TargetUserID: m.AddAdmin.TargetUserID, + PerformingUserID: m.AddAdmin.PerformingUserID, + }, nil + case m.RemoveAdmin != nil: + return types.RemoveAdmin{ + TargetUserID: m.RemoveAdmin.TargetUserID, + PerformingUserID: m.RemoveAdmin.PerformingUserID, + }, nil + case m.AddOwner != nil: + return types.AddOwner{ + FormID: m.AddOwner.FormID, + TargetUserID: m.AddOwner.TargetUserID, + PerformingUserID: m.AddOwner.PerformingUserID, + }, nil + case m.RemoveOwner != nil: + return types.RemoveOwner{ + FormID: m.RemoveOwner.FormID, + TargetUserID: m.RemoveOwner.TargetUserID, + PerformingUserID: m.RemoveOwner.PerformingUserID, + }, nil + case m.AddVoter != nil: + return types.AddVoter{ + FormID: m.AddVoter.FormID, + TargetUserID: m.AddVoter.TargetUserID, + PerformingUserID: m.AddVoter.PerformingUserID, + }, nil + case m.RemoveVoter != nil: + return types.RemoveVoter{ + FormID: m.RemoveVoter.FormID, + TargetUserID: m.RemoveVoter.TargetUserID, + PerformingUserID: m.RemoveVoter.PerformingUserID, + }, nil } return nil, xerrors.Errorf("empty type: %s", data) @@ -206,23 +289,30 @@ type TransactionJSON struct { CombineShares *CombineSharesJSON `json:",omitempty"` CancelForm *CancelFormJSON `json:",omitempty"` DeleteForm *DeleteFormJSON `json:",omitempty"` + AddAdmin *AddAdminJSON `json:",omitempty"` + RemoveAdmin *RemoveAdminJSON `json:",omitempty"` + AddOwner *AddOwnerJSON `json:",omitempty"` + RemoveOwner *RemoveOwnerJSON `json:",omitempty"` + AddVoter *AddVoterJSON `json:",omitempty"` + RemoveVoter *RemoveVoterJSON `json:",omitempty"` } // CreateFormJSON is the JSON representation of a CreateForm transaction type CreateFormJSON struct { Configuration types.Configuration - AdminID string + UserID string } // OpenFormJSON is the JSON representation of a OpenForm transaction type OpenFormJSON struct { FormID string + UserID string } // CastVoteJSON is the JSON representation of a CastVote transaction type CastVoteJSON struct { FormID string - UserID string + VoterID string Ciphervote json.RawMessage } @@ -241,6 +331,7 @@ type ShuffleBallotsJSON struct { Proof []byte Signature []byte PublicKey []byte + UserID string } type RegisterPubSharesJSON struct { @@ -268,6 +359,52 @@ type DeleteFormJSON struct { FormID string } +// AdminList + +// AddAdminJSON is the JSON representation of a AddAdmin transaction +type AddAdminJSON struct { + TargetUserID string + PerformingUserID string +} + +// RemoveAdminJSON is the JSON representation of a RemoveAdmin transaction +type RemoveAdminJSON struct { + TargetUserID string + PerformingUserID string +} + +// OwnerForm + +// AddOwnerJSON is the JSON representation of a AddOwner transaction +type AddOwnerJSON struct { + FormID string + TargetUserID string + PerformingUserID string +} + +// RemoveOwnerJSON is the JSON representation of a RemoveOwner transaction +type RemoveOwnerJSON struct { + FormID string + TargetUserID string + PerformingUserID string +} + +// VoterForm + +// AddVoterJSON is the JSON representation of a AddVoter transaction +type AddVoterJSON struct { + FormID string + TargetUserID string + PerformingUserID string +} + +// RemoveVoterJSON is the JSON representation of a RemoveVoter transaction +type RemoveVoterJSON struct { + FormID string + TargetUserID string + PerformingUserID string +} + func decodeCastVote(ctx serde.Context, m CastVoteJSON) (serde.Message, error) { factory := ctx.GetFactory(types.CiphervoteKey{}) if factory == nil { @@ -285,9 +422,9 @@ func decodeCastVote(ctx serde.Context, m CastVoteJSON) (serde.Message, error) { } return types.CastVote{ - FormID: m.FormID, - UserID: m.UserID, - Ballot: ciphervote, + FormID: m.FormID, + VoterID: m.VoterID, + Ballot: ciphervote, }, nil } @@ -321,6 +458,7 @@ func decodeShuffleBallots(ctx serde.Context, m ShuffleBallotsJSON) (serde.Messag Proof: m.Proof, Signature: m.Signature, PublicKey: m.PublicKey, + UserID: m.UserID, }, nil } diff --git a/contracts/evoting/mod.go b/contracts/evoting/mod.go index b431bc445..f9aeb87b1 100644 --- a/contracts/evoting/mod.go +++ b/contracts/evoting/mod.go @@ -84,6 +84,8 @@ const ( // credentialAllCommand defines the credential command that is allowed to // perform all commands. credentialAllCommand = "all" + + AdminListId = ContractUID + "AdminList" ) // commands defines the commands of the evoting contract. Using an interface @@ -98,6 +100,8 @@ type commands interface { combineShares(snap store.Snapshot, step execution.Step) error cancelForm(snap store.Snapshot, step execution.Step) error deleteForm(snap store.Snapshot, step execution.Step) error + manageAdminList(snap store.Snapshot, step execution.Step) error + manageOwnersVotersForm(snap store.Snapshot, step execution.Step) error } // Command defines a type of command for the value contract @@ -125,6 +129,21 @@ const ( // CmdDeleteForm is the command to delete a form CmdDeleteForm Command = "DELETE_FORM" + + // CmdAddAdmin is the command to add an admin to the system + CmdAddAdmin Command = "ADD_ADMIN" + // CmdRemoveAdmin is the command to remove an admin to the system + CmdRemoveAdmin Command = "REMOVE_ADMIN" + + // CmdAddOwnerForm is the command to add an Owner to a form + CmdAddOwnerForm Command = "ADD_OWNER" + // CmdRemoveOwnerForm is the command to remove an Owner to a form + CmdRemoveOwnerForm Command = "REMOVE_OWNER" + + // CmdAddVoterForm is the command to add an Voter to a form + CmdAddVoterForm Command = "ADD_VOTER" + // CmdRemoveVoterForm is the command to remove an Voter to a form + CmdRemoveVoterForm Command = "REMOVE_VOTER" ) // NewCreds creates new credentials for a evoting contract execution. We might @@ -153,6 +172,7 @@ type Contract struct { context serde.Context formFac serde.Factory + adminListFac serde.Factory rosterFac authority.Factory transactionFac serde.Factory } @@ -166,6 +186,7 @@ func NewContract(srvc access.Service, ciphervoteFac := types.CiphervoteFactory{} formFac := types.NewFormFactory(ciphervoteFac, rosterFac) transactionFac := types.NewTransactionFactory(ciphervoteFac) + adminListFac := types.AdminListFactory{} contract := Contract{ access: srvc, @@ -174,6 +195,7 @@ func NewContract(srvc access.Service, context: ctx, formFac: formFac, + adminListFac: adminListFac, rosterFac: rosterFac, transactionFac: transactionFac, } @@ -244,6 +266,36 @@ func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { if err != nil { return xerrors.Errorf("failed to delete form: %v", err) } + case CmdAddAdmin: + err := c.cmd.manageAdminList(snap, step) + if err != nil { + return xerrors.Errorf("failed to add admin: %v", err) + } + case CmdRemoveAdmin: + err := c.cmd.manageAdminList(snap, step) + if err != nil { + return xerrors.Errorf("failed to remove admin: %v", err) + } + case CmdAddOwnerForm: + err := c.cmd.manageOwnersVotersForm(snap, step) + if err != nil { + return xerrors.Errorf("failed to add owner: %v", err) + } + case CmdRemoveOwnerForm: + err := c.cmd.manageOwnersVotersForm(snap, step) + if err != nil { + return xerrors.Errorf("failed to remove owner: %v", err) + } + case CmdAddVoterForm: + err := c.cmd.manageOwnersVotersForm(snap, step) + if err != nil { + return xerrors.Errorf("failed to add voter: %v", err) + } + case CmdRemoveVoterForm: + err := c.cmd.manageOwnersVotersForm(snap, step) + if err != nil { + return xerrors.Errorf("failed to remove voter: %v", err) + } default: return xerrors.Errorf("unknown command: %s", cmd) } diff --git a/contracts/evoting/mod_test.go b/contracts/evoting/mod_test.go index adf46e2a4..8c4d80df8 100644 --- a/contracts/evoting/mod_test.go +++ b/contracts/evoting/mod_test.go @@ -32,6 +32,7 @@ import ( var dummyFormIDBuff = []byte("dummyID") var fakeFormID = hex.EncodeToString(dummyFormIDBuff) var fakeCommonSigner = bls.NewSigner() +var dummyUserAdminID = "123456" const getTransactionErr = "failed to get transaction: \"evoting:arg\" not found in tx arg" const unmarshalTransactionErr = "failed to get transaction: failed to deserialize " + @@ -44,11 +45,13 @@ var invalidForm = []byte("fake form") var ctx serde.Context var formFac serde.Factory +var adminListFac serde.Factory var transactionFac serde.Factory func init() { ciphervoteFac := types.CiphervoteFactory{} formFac = types.NewFormFactory(ciphervoteFac, fakeAuthorityFactory{}) + adminListFac = types.AdminListFactory{} transactionFac = types.NewTransactionFactory(ciphervoteFac) ctx = sjson.NewContext() @@ -97,6 +100,12 @@ func TestExecute(t *testing.T) { err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, string(CmdCancelForm))) require.EqualError(t, err, fake.Err("failed to cancel form")) + err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, string(CmdAddAdmin))) + require.EqualError(t, err, fake.Err("failed to add admin")) + + err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, string(CmdRemoveAdmin))) + require.EqualError(t, err, fake.Err("failed to remove admin")) + err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, "fake")) require.EqualError(t, err, "unknown command: fake") @@ -119,8 +128,12 @@ func TestCommand_CreateForm(t *testing.T) { err: nil, } + addAdmin := types.AddAdmin{dummyUserAdminID, dummyUserAdminID} + dataAddAdmin, err := addAdmin.Serialize(ctx) + require.NoError(t, err) + createForm := types.CreateForm{ - AdminID: "dummyAdminID", + UserID: dummyUserAdminID, } data, err := createForm.Serialize(ctx) @@ -145,7 +158,11 @@ func TestCommand_CreateForm(t *testing.T) { require.EqualError(t, err, "failed to get roster") snap := fake.NewSnapshot() - step := makeStep(t, FormArg, string(data)) + step := makeStep(t, FormArg, string(dataAddAdmin)) + err = cmd.manageAdminList(snap, step) + require.NoError(t, err) + + step = makeStep(t, FormArg, string(data)) err = cmd.createForm(snap, step) require.NoError(t, err) @@ -171,12 +188,16 @@ func TestCommand_OpenForm(t *testing.T) { // TODO } +/* + Testing Scenario: + - TODO +*/ func TestCommand_CastVote(t *testing.T) { initMetrics() castVote := types.CastVote{ - FormID: fakeFormID, - UserID: "dummyUserId", + FormID: fakeFormID, + VoterID: dummyUserAdminID, Ballot: types.Ciphervote{types.EGPair{ K: suite.Point(), C: suite.Point(), @@ -186,7 +207,7 @@ func TestCommand_CastVote(t *testing.T) { data, err := castVote.Serialize(ctx) require.NoError(t, err) - dummyForm, contract := initFormAndContract() + dummyForm, contract := initFormAndContract(123456) formBuf, err := dummyForm.Serialize(ctx) require.NoError(t, err) @@ -202,15 +223,14 @@ func TestCommand_CastVote(t *testing.T) { require.EqualError(t, err, unmarshalTransactionErr) err = cmd.castVote(fake.NewBadSnapshot(), makeStep(t, FormArg, string(data))) - require.Contains(t, err.Error(), "failed to get key") + require.ErrorContains(t, err, "failed to get key") snap := fake.NewSnapshot() - err = snap.Set(dummyFormIDBuff, invalidForm) require.NoError(t, err) err = cmd.castVote(snap, makeStep(t, FormArg, string(data))) - require.Contains(t, err.Error(), deserializeErr) + require.ErrorContains(t, err, deserializeErr) err = snap.Set(dummyFormIDBuff, formBuf) require.NoError(t, err) @@ -227,6 +247,15 @@ func TestCommand_CastVote(t *testing.T) { err = snap.Set(dummyFormIDBuff, formBuf) require.NoError(t, err) + err = cmd.castVote(snap, makeStep(t, FormArg, string(data))) + require.EqualError(t, err, "The user 123456 doesn't have the Voter permission on the form.") + + addVoter := types.AddVoter{FormID: fakeFormID, TargetUserID: dummyUserAdminID, PerformingUserID: dummyUserAdminID} + dataAddVoter, err := addVoter.Serialize(ctx) + require.NoError(t, err) + err = cmd.manageOwnersVotersForm(snap, makeStep(t, FormArg, string(dataAddVoter))) + require.NoError(t, err) + err = cmd.castVote(snap, makeStep(t, FormArg, string(data))) require.EqualError(t, err, "the ballot has unexpected length: 1 != 0") @@ -283,6 +312,9 @@ func TestCommand_CastVote(t *testing.T) { data, err = castVote.Serialize(ctx) require.NoError(t, err) + err = cmd.manageOwnersVotersForm(snap, makeStep(t, FormArg, string(dataAddVoter))) + require.NoError(t, err) + err = cmd.castVote(snap, makeStep(t, FormArg, string(data))) require.NoError(t, err) @@ -300,7 +332,7 @@ func TestCommand_CastVote(t *testing.T) { require.NoError(t, err) require.True(t, castVote.Ballot.Equal(suff.Ciphervotes[0])) - require.Equal(t, castVote.UserID, suff.UserIDs[0]) + require.Equal(t, castVote.VoterID, suff.VoterIDs[0]) require.Equal(t, float64(form.BallotCount), testutil.ToFloat64(PromFormBallots)) } @@ -309,13 +341,13 @@ func TestCommand_CloseForm(t *testing.T) { closeForm := types.CloseForm{ FormID: fakeFormID, - UserID: "dummyUserId", + UserID: dummyUserAdminID, } data, err := closeForm.Serialize(ctx) require.NoError(t, err) - dummyForm, contract := initFormAndContract() + dummyForm, contract := initFormAndContract(123456) dummyForm.FormID = fakeFormID formBuf, err := dummyForm.Serialize(ctx) @@ -332,21 +364,18 @@ func TestCommand_CloseForm(t *testing.T) { require.EqualError(t, err, unmarshalTransactionErr) err = cmd.closeForm(fake.NewBadSnapshot(), makeStep(t, FormArg, string(data))) - require.Contains(t, err.Error(), "failed to get key") + require.ErrorContains(t, err, "failed to get key") snap := fake.NewSnapshot() - err = snap.Set(dummyFormIDBuff, invalidForm) require.NoError(t, err) err = cmd.closeForm(snap, makeStep(t, FormArg, string(data))) - require.Contains(t, err.Error(), deserializeErr) + require.ErrorContains(t, err, deserializeErr) err = snap.Set(dummyFormIDBuff, formBuf) require.NoError(t, err) - closeForm.UserID = hex.EncodeToString([]byte("dummyAdminID")) - data, err = closeForm.Serialize(ctx) require.NoError(t, err) @@ -366,8 +395,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") - require.NoError(t, dummyForm.CastVote(ctx, snap, "dummyUser1", types.Ciphervote{})) - require.NoError(t, dummyForm.CastVote(ctx, snap, "dummyUser2", types.Ciphervote{})) + require.NoError(t, dummyForm.CastVote(ctx, snap, "123456", types.Ciphervote{})) + require.NoError(t, dummyForm.CastVote(ctx, snap, "654321", types.Ciphervote{})) formBuf, err = dummyForm.Serialize(ctx) require.NoError(t, err) @@ -531,7 +560,7 @@ func TestCommand_ShuffleBallotsFormatErrors(t *testing.T) { require.EqualError(t, err, unmarshalTransactionErr) err = cmd.shuffleBallots(fake.NewBadSnapshot(), makeStep(t, FormArg, string(data))) - require.Contains(t, err.Error(), "failed to get key") + require.ErrorContains(t, err, "failed to get key") // Wrong form id format snap := fake.NewSnapshot() @@ -540,7 +569,7 @@ func TestCommand_ShuffleBallotsFormatErrors(t *testing.T) { require.NoError(t, err) err = cmd.shuffleBallots(snap, makeStep(t, FormArg, string(data))) - require.Contains(t, err.Error(), deserializeErr) + require.ErrorContains(t, err, deserializeErr) // Form not closed err = snap.Set(dummyFormIDBuff, formBuf) @@ -739,7 +768,7 @@ func TestCommand_RegisterPubShares(t *testing.T) { data, err := registerPubShares.Serialize(ctx) require.NoError(t, err) - form, contract := initFormAndContract() + form, contract := initFormAndContract(123456) form.FormID = fakeFormID formBuf, err := form.Serialize(ctx) @@ -756,15 +785,14 @@ func TestCommand_RegisterPubShares(t *testing.T) { require.EqualError(t, err, unmarshalTransactionErr) err = cmd.registerPubshares(fake.NewBadSnapshot(), makeStep(t, FormArg, string(data))) - require.Contains(t, err.Error(), "failed to get key") + require.ErrorContains(t, err, "failed to get key") snap := fake.NewSnapshot() - err = snap.Set(dummyFormIDBuff, invalidForm) require.NoError(t, err) err = cmd.registerPubshares(snap, makeStep(t, FormArg, string(data))) - require.Contains(t, err.Error(), deserializeErr) + require.ErrorContains(t, err, deserializeErr) err = snap.Set(dummyFormIDBuff, formBuf) require.NoError(t, err) @@ -935,13 +963,13 @@ func TestCommand_RegisterPubShares(t *testing.T) { func TestCommand_DecryptBallots(t *testing.T) { decryptBallot := types.CombineShares{ FormID: fakeFormID, - UserID: hex.EncodeToString([]byte("dummyUserId")), + UserID: dummyUserAdminID, } data, err := decryptBallot.Serialize(ctx) require.NoError(t, err) - dummyForm, contract := initFormAndContract() + dummyForm, contract := initFormAndContract(123456) formBuf, err := dummyForm.Serialize(ctx) require.NoError(t, err) @@ -957,21 +985,18 @@ func TestCommand_DecryptBallots(t *testing.T) { require.EqualError(t, err, unmarshalTransactionErr) err = cmd.combineShares(fake.NewBadSnapshot(), makeStep(t, FormArg, string(data))) - require.Contains(t, err.Error(), "failed to get key") + require.ErrorContains(t, err, "failed to get key") snap := fake.NewSnapshot() - err = snap.Set(dummyFormIDBuff, invalidForm) require.NoError(t, err) err = cmd.combineShares(snap, makeStep(t, FormArg, string(data))) - require.Contains(t, err.Error(), deserializeErr) + require.ErrorContains(t, err, deserializeErr) err = snap.Set(dummyFormIDBuff, formBuf) require.NoError(t, err) - decryptBallot.UserID = hex.EncodeToString([]byte("dummyAdminID")) - data, err = decryptBallot.Serialize(ctx) require.NoError(t, err) @@ -1034,13 +1059,13 @@ func TestCommand_DecryptBallots(t *testing.T) { func TestCommand_CancelForm(t *testing.T) { cancelForm := types.CancelForm{ FormID: fakeFormID, - UserID: "dummyUserId", + UserID: dummyUserAdminID, } data, err := cancelForm.Serialize(ctx) require.NoError(t, err) - dummyForm, contract := initFormAndContract() + dummyForm, contract := initFormAndContract(123456) dummyForm.FormID = fakeFormID formBuf, err := dummyForm.Serialize(ctx) @@ -1057,21 +1082,18 @@ func TestCommand_CancelForm(t *testing.T) { require.EqualError(t, err, unmarshalTransactionErr) err = cmd.cancelForm(fake.NewBadSnapshot(), makeStep(t, FormArg, string(data))) - require.Contains(t, err.Error(), "failed to get key") + require.ErrorContains(t, err, "failed to get key") snap := fake.NewSnapshot() - err = snap.Set(dummyFormIDBuff, invalidForm) require.NoError(t, err) err = cmd.cancelForm(snap, makeStep(t, FormArg, string(data))) - require.Contains(t, err.Error(), deserializeErr) + require.ErrorContains(t, err, deserializeErr) err = snap.Set(dummyFormIDBuff, formBuf) require.NoError(t, err) - cancelForm.UserID = hex.EncodeToString([]byte("dummyAdminID")) - data, err = cancelForm.Serialize(ctx) require.NoError(t, err) @@ -1095,6 +1117,421 @@ func TestRegisterContract(t *testing.T) { RegisterContract(native.NewExecution(), Contract{}) } +// =============== +// Admin Form Test + +/* + Testing Scenario: + - Initialize Contract & Form + - Perform error handling test + - 123456 add admin 123456 + - 123456 remove admin 123456 -> error because only one left + - 777777 add admin 777777 -> error because not an admin + - 123456 add admin 777777 + - 123456 remove admin 123456 +*/ +func TestCommand_AdminList(t *testing.T) { + initMetrics() + + dummyForm, contract := initFormAndContract(123456) + dummyForm.FormID = fakeFormID + + // Initialize the command handler to post on the ledger + cmd := evotingCommand{ + Contract: &contract, + } + + // We define a dummy userID which we are going to add admin permission. + dummyUID := "123456" + dummyUID2 := "777777" + + // We initialize the command to add permission. + addAdmin := types.AddAdmin{dummyUID, dummyUID} + data, err := addAdmin.Serialize(ctx) + require.NoError(t, err) + + // The following test are there to check error handling + + // Checking that if no AdminList is on the blockchain, + // It won't be able to find the transaction. + err = cmd.manageAdminList(fake.NewSnapshot(), makeStep(t)) + require.EqualError(t, err, getTransactionErr) + + // Checking that providing a dummy data as argument, the form will not + // recognize it and won't be able to unmarshal it. + err = cmd.manageAdminList(fake.NewSnapshot(), makeStep(t, FormArg, "dummy")) + require.EqualError(t, err, unmarshalTransactionErr) + + // Checking that given a Blockchain that always returns an error, + // it will not be able to create the AdminList on the store. + err = cmd.manageAdminList(fake.NewBadSnapshot(), makeStep(t, FormArg, string(data))) + require.ErrorContains(t, err, "failed to get AdminList") + + snap := fake.NewSnapshot() + + // General ID for AdminList + h := sha256.New() + h.Write([]byte(AdminListId)) + formIDBuf := h.Sum(nil) + + // before adding admin lets check the AdminList does not exist + res, err := snap.Get(formIDBuf) + require.NoError(t, err) + + _, err = adminListFac.Deserialize(ctx, res) + require.ErrorContains(t, err, "failed to unmarshal form") + + // Now Let's add our admin + data, err = addAdmin.Serialize(ctx) + require.NoError(t, err) + + // We perform below the command on the ledger + err = cmd.manageAdminList(snap, makeStep(t, FormArg, string(data))) + require.NoError(t, err) + + // Now we want to remove its admin privilege. + // Will be a failure cause only one left + // Initialization of the command + removeAdmin := types.RemoveAdmin{ + TargetUserID: dummyUID, + PerformingUserID: dummyUID, + } + data, err = removeAdmin.Serialize(ctx) + require.NoError(t, err) + + // Publish the command on the ledger. + err = cmd.manageAdminList(snap, makeStep(t, FormArg, string(data))) + require.ErrorContains(t, err, "couldn't remove admin") + + // We try to add a second admin but the performing user + // does not have the permission + addAdmin2 := types.AddAdmin{dummyUID2, dummyUID2} + data2, err := addAdmin2.Serialize(ctx) + require.NoError(t, err) + + err = cmd.manageAdminList(snap, makeStep(t, FormArg, string(data2))) + require.ErrorContains(t, err, "The performing user is not an admin") + + // Now we add another admin but with a performing user that is already admin + addAdmin2 = types.AddAdmin{dummyUID2, dummyUID} + data2, err = addAdmin2.Serialize(ctx) + require.NoError(t, err) + + err = cmd.manageAdminList(snap, makeStep(t, FormArg, string(data2))) + require.NoError(t, err) + + // We retrieve the form on the ledger + res, err = snap.Get(formIDBuf) + require.NoError(t, err) + + message, err := adminListFac.Deserialize(ctx, res) + require.NoError(t, err) + + adminList, ok := message.(types.AdminList) + require.True(t, ok) + + // We check that our dummy User is now admin + // (if not admin return -1; else return admin index in AdminList). + dummyUserIDIndex, _ := adminList.GetAdminIndex(dummyUID) + require.True(t, dummyUserIDIndex > -1) + + // Now we want to remove its admin privilege. + // Initialization of the command + removeAdmin = types.RemoveAdmin{dummyUID, dummyUID} + data, err = removeAdmin.Serialize(ctx) + require.NoError(t, err) + + // Publish the command on the ledger. + err = cmd.manageAdminList(snap, makeStep(t, FormArg, string(data))) + require.NoError(t, err) + + // We retrieve the Admin Form from the ledger. + res, err = snap.Get(formIDBuf) + require.NoError(t, err) + + message, err = adminListFac.Deserialize(ctx, res) + require.NoError(t, err) + + adminList, ok = message.(types.AdminList) + require.True(t, ok) + + // We check that now our dummy user is not admin anymore (return -1) + dummyUserIDIndex, _ = adminList.GetAdminIndex(dummyUID) + + require.True(t, dummyUserIDIndex == -1) +} + +/* + Testing Scenario: + - We initialize a form with user 123456 as owner + - We check that 123456 is owner + - We try to remove it. -> failure cause is the only owner. + - We add a second user: 234567 + - Now we can remove ownership to: 123456 +*/ +func TestCommand_OwnerForm(t *testing.T) { + dummyUser := 654321 + + removeOwner := types.RemoveOwner{ + FormID: fakeFormID, + TargetUserID: dummyUserAdminID, + PerformingUserID: dummyUserAdminID, + } + + // Test Serialization of RemoveOwner command + dataRemove, err := removeOwner.Serialize(ctx) + require.NoError(t, err) + + // Initialize the form and contract chain + dummyForm, contract := initFormAndContract(123456) + dummyForm.FormID = fakeFormID + + // Test the serialization of the Ledger + formBuf, err := dummyForm.Serialize(ctx) + require.NoError(t, err) + + cmd := evotingCommand{ + Contract: &contract, + } + + // The following test are there to check error handling + + // Checking that if no AdminList is on the blockchain, + // It won't be able to find the transaction. + err = cmd.manageOwnersVotersForm(fake.NewSnapshot(), makeStep(t)) + require.EqualError(t, err, getTransactionErr) + + // Checking that providing a dummy data as argument, the form will not + // recognize it and won't be able to unmarshal it. + err = cmd.manageOwnersVotersForm(fake.NewSnapshot(), makeStep(t, FormArg, "dummy")) + require.EqualError(t, err, unmarshalTransactionErr) + + // Checking that given a Blockchain that always returns an error with + // a Remove cmd, it will not be able to retrieve the Form on the store. + err = cmd.manageOwnersVotersForm(fake.NewBadSnapshot(), makeStep(t, FormArg, string(dataRemove))) + require.ErrorContains(t, err, "failed to get key") + + // Checking that given the form set in the Snapshot which is invalid, then it + // will not be able to deserialize the Form to perform the command. + snap := fake.NewSnapshot() + err = snap.Set(dummyFormIDBuff, invalidForm) + require.NoError(t, err) + err = cmd.manageOwnersVotersForm(snap, makeStep(t, FormArg, string(dataRemove))) + require.ErrorContains(t, err, deserializeErr) + + // ==== + // Now that we've performed check on the error, let's + // reset everything to perform the real test + // ==== + + err = snap.Set(dummyFormIDBuff, formBuf) + require.NoError(t, err) + + // Let's see if the owner was set successfully at the form creation + res, err := snap.Get(dummyFormIDBuff) + require.NoError(t, err) + + message, err := formFac.Deserialize(ctx, res) + require.NoError(t, err) + + form, ok := message.(types.Form) + require.True(t, ok) + + // We check that now our dummy user is an owner (return 0) + dummyUserOwnerIndex, _ := form.GetOwnerIndex(dummyUserAdminID) + require.True(t, dummyUserOwnerIndex == 0) + + // Now let's remove it + + // We perform the Remove command on the ledger + // but it fails because it is the only owner. + err = cmd.manageOwnersVotersForm(snap, makeStep(t, FormArg, string(dataRemove))) + require.ErrorContains(t, err, "cannot remove this owner because it is the only one remaining for this form") + + // So first let's add a second owner + addOwner2 := types.AddOwner{ + FormID: fakeFormID, + TargetUserID: strconv.Itoa(dummyUser), + PerformingUserID: dummyUserAdminID, + } + + // Test Serialization of AddOwner command + dataAdd2, err := addOwner2.Serialize(ctx) + require.NoError(t, err) + + // We perform below the command on the ledger + err = cmd.manageOwnersVotersForm(snap, makeStep(t, FormArg, string(dataAdd2))) + require.NoError(t, err) + + res, err = snap.Get(dummyFormIDBuff) + require.NoError(t, err) + + message, err = formFac.Deserialize(ctx, res) + require.NoError(t, err) + + form, ok = message.(types.Form) + require.True(t, ok) + + // We check that now our second dummy user is also an owner (return 0). + secondDummyUserOwnerIndex, _ := form.GetOwnerIndex(strconv.Itoa(dummyUser)) + require.True(t, secondDummyUserOwnerIndex == 1) + + // Now remove successfully the first one. + err = cmd.manageOwnersVotersForm(snap, makeStep(t, FormArg, string(dataRemove))) + require.NoError(t, err) + + // Let's retrieve the form to check whether it worked + res, err = snap.Get(dummyFormIDBuff) + require.NoError(t, err) + + message, err = formFac.Deserialize(ctx, res) + require.NoError(t, err) + + form, ok = message.(types.Form) + require.True(t, ok) + + // We check that now our first dummy user is not an owner anymore (return -1) + dummyUserOwnerIndex, _ = form.GetOwnerIndex(dummyUserAdminID) + require.True(t, dummyUserOwnerIndex == -1) + // But that the second one is still an admin (return != -1) + secondDummyUserOwnerIndex, _ = form.GetOwnerIndex(strconv.Itoa(dummyUser)) + require.True(t, secondDummyUserOwnerIndex != -1) +} + +/* + Testing Scenario: + - We check that the voter field is empty + - We add user: 123456 + - We check that 123456 is voter + - We remove 123456 from the voter list. +*/ +func TestCommand_VoterForm(t *testing.T) { + addVoter := types.AddVoter{ + FormID: fakeFormID, + TargetUserID: dummyUserAdminID, + PerformingUserID: dummyUserAdminID, + } + + // Test Serialization of AddVoter command + dataAdd, err := addVoter.Serialize(ctx) + require.NoError(t, err) + + removeVoter := types.RemoveVoter{ + FormID: fakeFormID, + TargetUserID: dummyUserAdminID, + PerformingUserID: dummyUserAdminID, + } + + // Test Serialization of RemoveVoter command + dataRemove, err := removeVoter.Serialize(ctx) + require.NoError(t, err) + + // Initialize the form and contract chain + dummyForm, contract := initFormAndContract(123456) + dummyForm.FormID = fakeFormID + + // Test the serialization of the Ledger + formBuf, err := dummyForm.Serialize(ctx) + require.NoError(t, err) + + // Create an evoting command. + cmd := evotingCommand{ + Contract: &contract, + } + + // The following test are there to check error handling + + // Checking that if no AdminList is on the blockchain, + // It won't be able to find the transaction. + err = cmd.manageOwnersVotersForm(fake.NewSnapshot(), makeStep(t)) + require.EqualError(t, err, getTransactionErr) + + // Checking that providing a dummy data as argument, the form will not + // recognize it and won't be able to unmarshal it. + err = cmd.manageOwnersVotersForm(fake.NewSnapshot(), makeStep(t, FormArg, "dummy")) + require.EqualError(t, err, unmarshalTransactionErr) + + // Checking that given a Blockchain that always returns an error with + // an Add cmd, it will not be able to retrieve the Form on the store. + err = cmd.manageOwnersVotersForm(fake.NewBadSnapshot(), makeStep(t, FormArg, string(dataAdd))) + require.ErrorContains(t, err, "failed to get key") + + // Checking that given a Blockchain that always returns an error with + // a Remove cmd, it will not be able to retrieve the Form on the store. + err = cmd.manageOwnersVotersForm(fake.NewBadSnapshot(), makeStep(t, FormArg, string(dataRemove))) + require.ErrorContains(t, err, "failed to get key") + + snap := fake.NewSnapshot() + + // Checking that given the form set in the Snapshot which is invalid, then it + // will not be able to deserialize the Form to perform the command. + err = snap.Set(dummyFormIDBuff, invalidForm) + require.NoError(t, err) + err = cmd.manageOwnersVotersForm(snap, makeStep(t, FormArg, string(dataAdd))) + require.ErrorContains(t, err, deserializeErr) + + // ==== + // Now that we've performed check on the error, let's + // reset everything to perform the real test + // ==== + + err = snap.Set(dummyFormIDBuff, formBuf) + require.NoError(t, err) + + // We retrieve the Admin Form from the ledger. + res, err := snap.Get(dummyFormIDBuff) + require.NoError(t, err) + + message, err := formFac.Deserialize(ctx, res) + require.NoError(t, err) + + form, ok := message.(types.Form) + require.True(t, ok) + + // We check that now our dummy user is not a voter yet (return -1) + dummyUserVoterIndex, _ := form.GetVoterIndex(dummyUserAdminID) + require.True(t, dummyUserVoterIndex == -1) + + // We perform the Add command on the ledger + err = cmd.manageOwnersVotersForm(snap, makeStep(t, FormArg, string(dataAdd))) + require.NoError(t, err) + + // We then check that the Add command was a success + res, err = snap.Get(dummyFormIDBuff) + require.NoError(t, err) + + message, err = formFac.Deserialize(ctx, res) + require.NoError(t, err) + + form, ok = message.(types.Form) + require.True(t, ok) + + // We check that now our dummy user is an owner (return 0) + dummyUserVoterIndex, _ = form.GetVoterIndex(dummyUserAdminID) + + require.True(t, dummyUserVoterIndex == 0) + + // Now let's remove it + + // We perform below the command on the ledger + err = cmd.manageOwnersVotersForm(snap, makeStep(t, FormArg, string(dataRemove))) + require.NoError(t, err) + + res, err = snap.Get(dummyFormIDBuff) + require.NoError(t, err) + + message, err = formFac.Deserialize(ctx, res) + require.NoError(t, err) + + form, ok = message.(types.Form) + require.True(t, ok) + + // We check that now our dummy user is an owner (return 0) + dummyUserVoterIndex, _ = form.GetVoterIndex(dummyUserAdminID) + + require.True(t, dummyUserVoterIndex == -1) +} + // ----------------------------------------------------------------------------- // Utility functions @@ -1105,7 +1542,7 @@ func initMetrics() { PromFormPubShares.Reset() } -func initFormAndContract() (types.Form, Contract) { +func initFormAndContract(initialOwner int) (types.Form, Contract) { fakeDkg := fakeDKG{ actor: fakeDkgActor{}, err: nil, @@ -1119,6 +1556,7 @@ func initFormAndContract() (types.Form, Contract) { DecryptedBallots: nil, ShuffleThreshold: 0, Roster: fake.Authority{}, + Owners: []int{initialOwner}, } service := fakeAccess{err: fake.GetError()} @@ -1200,9 +1638,10 @@ func initBadShuffleBallot(sizeOfForm int) (types.Form, types.ShuffleBallots, Con ShuffledBallots: shuffledBallots, Proof: nil, PublicKey: FakePubKeyMarshalled, + UserID: dummyUserAdminID, } - form, contract := initFormAndContract() + form, contract := initFormAndContract(123456) return form, shuffleBallots, contract } @@ -1331,6 +1770,14 @@ type fakeCmd struct { err error } +func (c fakeCmd) manageAdminList(snap store.Snapshot, step execution.Step) error { + return c.err +} + +func (c fakeCmd) manageOwnersVotersForm(snap store.Snapshot, step execution.Step) error { + return c.err +} + func (c fakeCmd) createForm(snap store.Snapshot, step execution.Step) error { return c.err } diff --git a/contracts/evoting/types/admin.go b/contracts/evoting/types/admin.go new file mode 100644 index 000000000..c520c4be7 --- /dev/null +++ b/contracts/evoting/types/admin.go @@ -0,0 +1,146 @@ +package types + +import ( + "crypto/sha256" + "go.dedis.ch/dela/core/store" + "go.dedis.ch/dela/serde" + "go.dedis.ch/dela/serde/registry" + "golang.org/x/xerrors" +) + +var adminListFormat = registry.NewSimpleRegistry() + +func RegisterAdminListFormat(format serde.Format, engine serde.FormatEngine) { + adminListFormat.Register(format, engine) +} + +type AdminList struct { + // List of SCIPER with admin rights + AdminList []int +} + +func (adminList AdminList) Serialize(ctx serde.Context) ([]byte, error) { + format := adminListFormat.Get(ctx.GetFormat()) + + data, err := format.Encode(ctx, adminList) + if err != nil { + return nil, xerrors.Errorf("Failed to encode AdminList: %v", err) + } + + return data, nil +} + +func (adminList AdminList) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { + format := adminListFormat.Get(ctx.GetFormat()) + + message, err := format.Decode(ctx, data) + if err != nil { + return nil, xerrors.Errorf("Failed to decode: %v", err) + } + + return message, nil +} + +// AddAdmin add a new admin to the system. +func (adminList *AdminList) AddAdmin(userID string) error { + sciperInt, err := SciperToInt(userID) + if err != nil { + return xerrors.Errorf("Failed to convert SCIPER to int: %v", err) + } + + index, err := adminList.GetAdminIndex(userID) + if err != nil { + return err + } + + if index > -1 { + return xerrors.Errorf("The user %v is already an admin", userID) + } + + adminList.AdminList = append(adminList.AdminList, sciperInt) + + return nil +} + +// GetAdminIndex return the index of admin if userID is one, else return -1 +func (adminList *AdminList) GetAdminIndex(userID string) (int, error) { + sciperInt, err := SciperToInt(userID) + if err != nil { + return -1, xerrors.Errorf("Failed to convert SCIPER to int: %v", err) + } + + for i := 0; i < len(adminList.AdminList); i++ { + if adminList.AdminList[i] == sciperInt { + return i, nil + } + } + + return -1, nil +} + +// RemoveAdmin add a new admin to the system. +func (adminList *AdminList) RemoveAdmin(userID string) error { + index, err := adminList.GetAdminIndex(userID) + if err != nil { + return xerrors.Errorf("Failed to retrieve the admin from the Admin List: %v", err) + } + + if index < 0 { + return xerrors.Errorf("Error while retrieving the index of the element.") + } + + // We don't want to have a form without any Admin. + if len(adminList.AdminList) <= 1 { + return xerrors.Errorf("Error, cannot remove this Admin because it is the " + + "only one remaining.") + } + + adminList.AdminList = append(adminList.AdminList[:index], adminList.AdminList[index+1:]...) + return nil +} + +func AdminListFromStore(ctx serde.Context, adminListFac serde.Factory, store store.Readable, adminListId string) (AdminList, error) { + adminList := AdminList{} + + h := sha256.New() + h.Write([]byte(adminListId)) + adminListIDBuf := h.Sum(nil) + + adminListBuf, err := store.Get(adminListIDBuf) + if err != nil { + return adminList, xerrors.Errorf("While getting data for list: %v", err) + } + if len(adminListBuf) == 0 { + return adminList, xerrors.Errorf("No list found") + } + + message, err := adminListFac.Deserialize(ctx, adminListBuf) + if err != nil { + return adminList, xerrors.Errorf("failed to deserialize AdminList: %v", err) + } + + adminList, ok := message.(AdminList) + if !ok { + return adminList, xerrors.Errorf("Wrong message type: %T", message) + } + + return adminList, nil +} + +// AdminListFactory provides the mean to deserialize a AdminList. It naturally +// uses the formFormat. +// +// - implements serde.Factory +type AdminListFactory struct{} + +// Deserialize implements serde.Factory +func (AdminListFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { + format := adminListFormat.Get(ctx.GetFormat()) + + message, err := format.Decode(ctx, data) + if err != nil { + return nil, xerrors.Errorf("failed to decode: %v", err) + } + + return message, nil +} diff --git a/contracts/evoting/types/election.go b/contracts/evoting/types/election.go index bc2d41383..43f3294f9 100644 --- a/contracts/evoting/types/election.go +++ b/contracts/evoting/types/election.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "fmt" "io" + "strconv" "go.dedis.ch/dela/core/ordering/cosipbft/authority" ctypes "go.dedis.ch/dela/core/ordering/cosipbft/types" @@ -112,13 +113,19 @@ type Form struct { // authority.Authority. Roster authority.Authority + + // Store the list of admins SCIPER that are Owners of the form. + Owners []int + + // Store the list of SCIPER of user that are Voters on the form. + Voters []int } // Serialize implements serde.Message -func (e Form) Serialize(ctx serde.Context) ([]byte, error) { +func (form Form) Serialize(ctx serde.Context) ([]byte, error) { format := formFormat.Get(ctx.GetFormat()) - data, err := format.Encode(ctx, e) + data, err := format.Encode(ctx, form) if err != nil { return nil, xerrors.Errorf("failed to encode form: %v", err) } @@ -144,11 +151,11 @@ func NewFormFactory(cf serde.Factory, rf authority.Factory) FormFactory { } // Deserialize implements serde.Factory -func (e FormFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { +func (formFactory FormFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { format := formFormat.Get(ctx.GetFormat()) - ctx = serde.WithFactory(ctx, CiphervoteKey{}, e.ciphervoteFac) - ctx = serde.WithFactory(ctx, ctypes.RosterKey{}, e.rosterFac) + ctx = serde.WithFactory(ctx, CiphervoteKey{}, formFactory.ciphervoteFac) + ctx = serde.WithFactory(ctx, ctypes.RosterKey{}, formFactory.rosterFac) message, err := format.Decode(ctx, data) if err != nil { @@ -193,38 +200,38 @@ func FormFromStore(ctx serde.Context, formFac serde.Factory, formIDHex string, // ChunksPerBallot returns the number of chunks of El Gamal pairs needed to // represent an encrypted ballot, knowing that one chunk is 29 bytes at most. -func (e *Form) ChunksPerBallot() int { - if e.BallotSize%29 == 0 { - return e.BallotSize / 29 +func (form *Form) ChunksPerBallot() int { + if form.BallotSize%29 == 0 { + return form.BallotSize / 29 } - return e.BallotSize/29 + 1 + return form.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 { +func (form *Form) CastVote(ctx serde.Context, st store.Snapshot, userID string, ciphervote Ciphervote) error { var suff Suffragia var blockID []byte - if s.BallotCount%BallotsPerBlock == 0 { + if form.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) + id, err := hex.DecodeString(form.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) + binary.LittleEndian.PutUint32(id, form.BallotCount) blockID = h.Sum(id[0:4])[:32] 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{}) + form.SuffragiaIDs = append(form.SuffragiaIDs, blockID) + form.SuffragiaHashes = append(form.SuffragiaHashes, []byte{}) } else { - blockID = s.SuffragiaIDs[len(s.SuffragiaIDs)-1] + blockID = form.SuffragiaIDs[len(form.SuffragiaIDs)-1] buf, err := st.Get(blockID) if err != nil { return xerrors.Errorf("couldn't get ballots block: %v", err) @@ -243,7 +250,7 @@ func (s *Form) CastVote(ctx serde.Context, st store.Snapshot, userID string, cip for i := uint32(1); i < BallotsPerBlock; i++ { suff.CastVote(fmt.Sprintf("%s-%d", userID, i), ciphervote) } - s.BallotCount += BallotsPerBlock - 1 + form.BallotCount += BallotsPerBlock - 1 } buf, err := suff.Serialize(ctx) if err != nil { @@ -253,7 +260,7 @@ func (s *Form) CastVote(ctx serde.Context, st store.Snapshot, userID string, cip if err != nil { xerrors.Errorf("couldn't set new ballots block: %v", err) } - s.BallotCount += 1 + form.BallotCount += 1 return nil } @@ -261,9 +268,9 @@ func (s *Form) CastVote(ctx serde.Context, st store.Snapshot, userID string, cip // 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) { +func (form *Form) Suffragia(ctx serde.Context, rd store.Readable) (Suffragia, error) { var suff Suffragia - for _, id := range s.SuffragiaIDs { + for _, id := range form.SuffragiaIDs { buf, err := rd.Get(id) if err != nil { return suff, xerrors.Errorf("couldn't get ballot block: %v", err) @@ -275,7 +282,7 @@ func (s *Form) Suffragia(ctx serde.Context, rd store.Readable) (Suffragia, error return suff, xerrors.Errorf("couldn't unmarshal ballots block in cast: %v", err) } suffTmp := msg.(Suffragia) - for i, uid := range suffTmp.UserIDs { + for i, uid := range suffTmp.VoterIDs { suff.CastVote(uid, suffTmp.Ciphervotes[i]) } } @@ -287,10 +294,10 @@ func (s *Form) Suffragia(ctx serde.Context, rd store.Readable) (Suffragia, error type RandomVector [][]byte // Unmarshal returns the native type of a random vector -func (r RandomVector) Unmarshal() ([]kyber.Scalar, error) { - e := make([]kyber.Scalar, len(r)) +func (randomVector RandomVector) Unmarshal() ([]kyber.Scalar, error) { + e := make([]kyber.Scalar, len(randomVector)) - for i, v := range r { + for i, v := range randomVector { scalar := suite.Scalar() err := scalar.UnmarshalBinary(v) if err != nil { @@ -304,15 +311,15 @@ func (r RandomVector) Unmarshal() ([]kyber.Scalar, error) { // LoadFromScalars marshals a given vector of scalars into the current // RandomVector -func (r *RandomVector) LoadFromScalars(e []kyber.Scalar) error { - *r = make([][]byte, len(e)) +func (randomVector *RandomVector) LoadFromScalars(e []kyber.Scalar) error { + *randomVector = make([][]byte, len(e)) for i, scalar := range e { v, err := scalar.MarshalBinary() if err != nil { return xerrors.Errorf("could not marshal random vector: %v", err) } - (*r)[i] = v + (*randomVector)[i] = v } return nil @@ -339,9 +346,9 @@ type Configuration struct { } // MaxBallotSize returns the maximum number of bytes required to store a ballot -func (c *Configuration) MaxBallotSize() int { +func (configuration *Configuration) MaxBallotSize() int { size := 0 - for _, subject := range c.Scaffold { + for _, subject := range configuration.Scaffold { size += subject.MaxEncodedSize() } return size @@ -349,8 +356,8 @@ func (c *Configuration) MaxBallotSize() int { // GetQuestion finds the question associated to a given ID and returns it. // Returns nil if no question found. -func (c *Configuration) GetQuestion(ID ID) Question { - for _, subject := range c.Scaffold { +func (configuration *Configuration) GetQuestion(ID ID) Question { + for _, subject := range configuration.Scaffold { question := subject.GetQuestion(ID) if question != nil { @@ -363,11 +370,11 @@ func (c *Configuration) GetQuestion(ID ID) Question { // IsValid returns true if and only if the whole configuration is coherent and // valid. -func (c *Configuration) IsValid() bool { +func (configuration *Configuration) IsValid() bool { // serves as a set to check each ID is unique uniqueIDs := make(map[ID]bool) - for _, subject := range c.Scaffold { + for _, subject := range configuration.Scaffold { if !subject.isValid(uniqueIDs) { return false } @@ -384,8 +391,8 @@ type Pubshare kyber.Point type PubsharesUnit [][]Pubshare // Fingerprint implements serde.Fingerprinter -func (p PubsharesUnit) Fingerprint(writer io.Writer) error { - for _, ballotShares := range p { +func (pubshareUnit PubsharesUnit) Fingerprint(writer io.Writer) error { + for _, ballotShares := range pubshareUnit { for _, pubShare := range ballotShares { _, err := pubShare.MarshalTo(writer) if err != nil { @@ -409,3 +416,108 @@ type PubsharesUnits struct { // PubsharesUnit Indexes []int } + +// AddVoter add a new voter to the form. +func (form *Form) AddVoter(userID string) error { + sciperInt, err := SciperToInt(userID) + if err != nil { + return xerrors.Errorf("failed to convert SCIPER to integer: %v", err) + } + + form.Voters = append(form.Voters, sciperInt) + + return nil +} + +// GetVoterIndex return the index of voter if userID is one, else return -1 +func (form *Form) GetVoterIndex(userID string) (int, error) { + sciperInt, err := SciperToInt(userID) + if err != nil { + return -1, xerrors.Errorf("failed to convert SCIPER to integer: %v", err) + } + + for i := 0; i < len(form.Voters); i++ { + if form.Voters[i] == sciperInt { + return i, nil + } + } + + return -1, nil +} + +// RemoveVoter remove a voter to the form. +func (form *Form) RemoveVoter(userID string) error { + index, err := form.GetVoterIndex(userID) + if err != nil { + return xerrors.Errorf("Failed GetVoterIndex: %v", err) + } + + if index < 0 { + return xerrors.Errorf("Error while retrieving the index of the element.") + } + + form.Voters = append(form.Voters[:index], form.Voters[index+1:]...) + return nil +} + +// AddOwner add a new owner to the form. +func (form *Form) AddOwner(userID string) error { + sciperInt, err := SciperToInt(userID) + if err != nil { + return xerrors.Errorf("failed to convert SCIPER to integer: %v", err) + } + + form.Owners = append(form.Owners, sciperInt) + + return nil +} + +// GetOwnerIndex return the index of owner if userID is one, else return -1 +func (form *Form) GetOwnerIndex(userID string) (int, error) { + sciperInt, err := SciperToInt(userID) + if err != nil { + return -1, xerrors.Errorf("failed to convert SCIPER to integer: %v", err) + } + + for i := 0; i < len(form.Owners); i++ { + if form.Owners[i] == sciperInt { + return i, nil + } + } + + return -1, nil +} + +// RemoveOwner remove an owner from the form. +func (form *Form) RemoveOwner(userID string) error { + index, err := form.GetOwnerIndex(userID) + if err != nil { + return xerrors.Errorf("Failed GetOwnerIndex: %v", err) + } + + if index < 0 { + return xerrors.Errorf("Error while retrieving the index of the element.") + } + + // We don't want to have a form without any Owners. + if len(form.Owners) <= 1 { + return xerrors.Errorf("Error, cannot remove this owner because it is the " + + "only one remaining for this form") + } + + form.Owners = append(form.Owners[:index], form.Owners[index+1:]...) + return nil +} + +func SciperToInt(userID string) (int, error) { + sciperInt, err := strconv.Atoi(userID) + if err != nil { + return 0, xerrors.Errorf("Failed to convert SCIPER to an INT: %v", err) + } + + if sciperInt < 100000 || sciperInt > 999999 { + return 0, xerrors.Errorf("SCIPER %v is out of range.", sciperInt) + } + + return sciperInt, nil +} diff --git a/contracts/evoting/types/suffragia.go b/contracts/evoting/types/suffragia.go index c8a507440..62f25bc65 100644 --- a/contracts/evoting/types/suffragia.go +++ b/contracts/evoting/types/suffragia.go @@ -19,7 +19,7 @@ func RegisterSuffragiaFormat(format serde.Format, engine serde.FormatEngine) { } type Suffragia struct { - UserIDs []string + VoterIDs []string Ciphervotes []Ciphervote } @@ -36,22 +36,22 @@ func (s Suffragia) Serialize(ctx serde.Context) ([]byte, error) { } // 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 { +func (s *Suffragia) CastVote(voterID string, ciphervote Ciphervote) { + for i, u := range s.VoterIDs { + if u == voterID { s.Ciphervotes[i] = ciphervote return } } - s.UserIDs = append(s.UserIDs, userID) + s.VoterIDs = append(s.VoterIDs, voterID) 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 { + for i, u := range s.VoterIDs { h.Write([]byte(u)) buf, err := s.Ciphervotes[i].Serialize(ctx) if err != nil { diff --git a/contracts/evoting/types/transactions.go b/contracts/evoting/types/transactions.go index 48ff3733d..97a462d51 100644 --- a/contracts/evoting/types/transactions.go +++ b/contracts/evoting/types/transactions.go @@ -30,8 +30,8 @@ type FormsMetadata struct { type FormIDs []string // Contains checks if el is present. Return < 0 if not. -func (e FormIDs) Contains(el string) int { - for i, e1 := range e { +func (formID FormIDs) Contains(el string) int { + for i, e1 := range formID { if e1 == el { return i } @@ -41,21 +41,21 @@ func (e FormIDs) Contains(el string) int { } // Add adds a form ID or returns an error if already present -func (e *FormIDs) Add(id string) error { - if e.Contains(id) >= 0 { +func (formID *FormIDs) Add(id string) error { + if formID.Contains(id) >= 0 { return xerrors.Errorf("id %q already exist", id) } - *e = append(*e, id) + *formID = append(*formID, id) return nil } // Remove removes a form ID from the list, if it exists -func (e *FormIDs) Remove(id string) { - i := e.Contains(id) +func (formID *FormIDs) Remove(id string) { + i := formID.Contains(id) if i >= 0 { - *e = append((*e)[:i], (*e)[i+1:]...) + *formID = append((*formID)[:i], (*formID)[i+1:]...) } } @@ -74,10 +74,10 @@ func NewTransactionFactory(cf serde.Factory) TransactionFactory { } // Deserialize implements serde.Factory -func (t TransactionFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { +func (transactionFactory TransactionFactory) Deserialize(ctx serde.Context, data []byte) (serde.Message, error) { format := transactionFormats.Get(ctx.GetFormat()) - ctx = serde.WithFactory(ctx, CiphervoteKey{}, t.ciphervoteFac) + ctx = serde.WithFactory(ctx, CiphervoteKey{}, transactionFactory.ciphervoteFac) message, err := format.Decode(ctx, data) if err != nil { @@ -92,14 +92,15 @@ func (t TransactionFactory) Deserialize(ctx serde.Context, data []byte) (serde.M // - implements serde.Message type CreateForm struct { Configuration Configuration - AdminID string + // UserID of the owner that is performing the action + UserID string } // Serialize implements serde.Message -func (ce CreateForm) Serialize(ctx serde.Context) ([]byte, error) { +func (createForm CreateForm) Serialize(ctx serde.Context) ([]byte, error) { format := transactionFormats.Get(ctx.GetFormat()) - data, err := format.Encode(ctx, ce) + data, err := format.Encode(ctx, createForm) if err != nil { return nil, xerrors.Errorf("failed to encode create form: %v", err) } @@ -113,13 +114,15 @@ func (ce CreateForm) Serialize(ctx serde.Context) ([]byte, error) { type OpenForm struct { // FormID is hex-encoded FormID string + // UserID of the owner that is performing the action + UserID string } // Serialize implements serde.Message -func (oe OpenForm) Serialize(ctx serde.Context) ([]byte, error) { +func (openForm OpenForm) Serialize(ctx serde.Context) ([]byte, error) { format := transactionFormats.Get(ctx.GetFormat()) - data, err := format.Encode(ctx, oe) + data, err := format.Encode(ctx, openForm) if err != nil { return nil, xerrors.Errorf("failed to encode open form: %v", err) } @@ -132,16 +135,16 @@ func (oe OpenForm) Serialize(ctx serde.Context) ([]byte, error) { // - implements serde.Message type CastVote struct { // FormID is hex-encoded - FormID string - UserID string - Ballot Ciphervote + FormID string + VoterID string + Ballot Ciphervote } // Serialize implements serde.Message -func (cv CastVote) Serialize(ctx serde.Context) ([]byte, error) { +func (castVote CastVote) Serialize(ctx serde.Context) ([]byte, error) { format := transactionFormats.Get(ctx.GetFormat()) - data, err := format.Encode(ctx, cv) + data, err := format.Encode(ctx, castVote) if err != nil { return nil, xerrors.Errorf("failed to encode cast vote: %v", err) } @@ -155,14 +158,15 @@ func (cv CastVote) Serialize(ctx serde.Context) ([]byte, error) { type CloseForm struct { // FormID is hex-encoded FormID string + // UserID of the owner that is performing the action UserID string } // Serialize implements serde.Message -func (ce CloseForm) Serialize(ctx serde.Context) ([]byte, error) { +func (closeForm CloseForm) Serialize(ctx serde.Context) ([]byte, error) { format := transactionFormats.Get(ctx.GetFormat()) - data, err := format.Encode(ctx, ce) + data, err := format.Encode(ctx, closeForm) if err != nil { return nil, xerrors.Errorf("failed to encode close form: %v", err) } @@ -186,15 +190,17 @@ type ShuffleBallots struct { // Signature is the signature of the result of HashShuffle() with the private // key corresponding to PublicKey Signature []byte - //PublicKey is the public key of the signer. + // PublicKey is the public key of the signer. PublicKey []byte + // UserID of the owner that is performing the action + UserID string } // Serialize implements serde.Message -func (sb ShuffleBallots) Serialize(ctx serde.Context) ([]byte, error) { +func (shuffleBallots ShuffleBallots) Serialize(ctx serde.Context) ([]byte, error) { format := transactionFormats.Get(ctx.GetFormat()) - data, err := format.Encode(ctx, sb) + data, err := format.Encode(ctx, shuffleBallots) if err != nil { return nil, xerrors.Errorf("failed to encode shuffle ballots: %v", err) } @@ -218,13 +224,17 @@ type RegisterPubShares struct { Signature []byte // PublicKey is the public key of the signer PublicKey []byte + + // TODO Is is needed + // UserID of the owner that is performing the action + // UserID string } // Serialize implements serde.Message -func (rp RegisterPubShares) Serialize(ctx serde.Context) ([]byte, error) { +func (registerPubShares RegisterPubShares) Serialize(ctx serde.Context) ([]byte, error) { format := transactionFormats.Get(ctx.GetFormat()) - data, err := format.Encode(ctx, rp) + data, err := format.Encode(ctx, registerPubShares) if err != nil { return nil, xerrors.Errorf("failed to encode register pubShares: %v", err) } @@ -239,14 +249,15 @@ func (rp RegisterPubShares) Serialize(ctx serde.Context) ([]byte, error) { type CombineShares struct { // FormID is hex-encoded FormID string + // UserID of the owner that is performing the action UserID string } // Serialize implements serde.Message -func (db CombineShares) Serialize(ctx serde.Context) ([]byte, error) { +func (combineShares CombineShares) Serialize(ctx serde.Context) ([]byte, error) { format := transactionFormats.Get(ctx.GetFormat()) - data, err := format.Encode(ctx, db) + data, err := format.Encode(ctx, combineShares) if err != nil { return nil, xerrors.Errorf("failed to encode decrypt ballot: %v", err) } @@ -260,14 +271,15 @@ func (db CombineShares) Serialize(ctx serde.Context) ([]byte, error) { type CancelForm struct { // FormID is hex-encoded FormID string + // UserID of the owner that is performing the action UserID string } // Serialize implements serde.Message -func (ce CancelForm) Serialize(ctx serde.Context) ([]byte, error) { +func (cancelForm CancelForm) Serialize(ctx serde.Context) ([]byte, error) { format := transactionFormats.Get(ctx.GetFormat()) - data, err := format.Encode(ctx, ce) + data, err := format.Encode(ctx, cancelForm) if err != nil { return nil, xerrors.Errorf("failed to encode cancel form: %v", err) } @@ -281,13 +293,15 @@ func (ce CancelForm) Serialize(ctx serde.Context) ([]byte, error) { type DeleteForm struct { // FormID is hex-encoded FormID string + // UserID of the owner that is performing the action + UserID string } // Serialize implements serde.Message -func (ce DeleteForm) Serialize(ctx serde.Context) ([]byte, error) { +func (deleteForm DeleteForm) Serialize(ctx serde.Context) ([]byte, error) { format := transactionFormats.Get(ctx.GetFormat()) - data, err := format.Encode(ctx, ce) + data, err := format.Encode(ctx, deleteForm) if err != nil { return nil, xerrors.Errorf("failed to encode cancel form: %v", err) } @@ -308,13 +322,13 @@ func RandomID() (string, error) { // Fingerprint implements serde.Fingerprinter. If creates a fingerprint only // based on the formID and the shuffled ballots. -func (sb ShuffleBallots) Fingerprint(writer io.Writer) error { - _, err := writer.Write([]byte(sb.FormID)) +func (shuffleBallots ShuffleBallots) Fingerprint(writer io.Writer) error { + _, err := writer.Write([]byte(shuffleBallots.FormID)) if err != nil { return xerrors.Errorf("failed to write the form ID: %v", err) } - for _, ballot := range sb.ShuffledBallots { + for _, ballot := range shuffleBallots.ShuffledBallots { err := ballot.FingerPrint(writer) if err != nil { return xerrors.Errorf("failed to fingerprint shuffled ballot: %v", err) @@ -325,21 +339,149 @@ func (sb ShuffleBallots) Fingerprint(writer io.Writer) error { } // Fingerprint implements serde.Fingerprinter -func (rp RegisterPubShares) Fingerprint(writer io.Writer) error { - _, err := writer.Write([]byte(rp.FormID)) +func (registerPubShares RegisterPubShares) Fingerprint(writer io.Writer) error { + _, err := writer.Write([]byte(registerPubShares.FormID)) if err != nil { return xerrors.Errorf("failed to write the form ID: %v", err) } - _, err = writer.Write([]byte(strconv.Itoa(rp.Index))) + _, err = writer.Write([]byte(strconv.Itoa(registerPubShares.Index))) if err != nil { return xerrors.Errorf("failed to write the pubShare index: %v", err) } - err = rp.Pubshares.Fingerprint(writer) + err = registerPubShares.Pubshares.Fingerprint(writer) if err != nil { return xerrors.Errorf("failed to fingerprint pubShares: %V", err) } return nil } + +// AddAdmin defines the transaction to Add an Admin +// +// - implements serde.Message +type AddAdmin struct { + TargetUserID string + PerformingUserID string +} + +// Serialize implements serde.Message +func (addAdmin AddAdmin) Serialize(ctx serde.Context) ([]byte, error) { + format := transactionFormats.Get(ctx.GetFormat()) + + data, err := format.Encode(ctx, addAdmin) + if err != nil { + return nil, xerrors.Errorf("failed to encode Add Admin: %v", err) + } + + return data, nil +} + +// RemoveAdmin defines the transaction to Remove an Admin +// +// - implements serde.Message +type RemoveAdmin struct { + TargetUserID string + PerformingUserID string +} + +// Serialize implements serde.Message +func (removeAdmin RemoveAdmin) Serialize(ctx serde.Context) ([]byte, error) { + format := transactionFormats.Get(ctx.GetFormat()) + + data, err := format.Encode(ctx, removeAdmin) + if err != nil { + return nil, xerrors.Errorf("failed to encode remove admin: %v", err) + } + + return data, nil +} + +// AddVoter defines the transaction to Add an Voter +// +// - implements serde.Message +type AddVoter struct { + // FormID is hex-encoded + FormID string + TargetUserID string + PerformingUserID string +} + +// Serialize implements serde.Message +func (addVoter AddVoter) Serialize(ctx serde.Context) ([]byte, error) { + format := transactionFormats.Get(ctx.GetFormat()) + + data, err := format.Encode(ctx, addVoter) + if err != nil { + return nil, xerrors.Errorf("failed to encode Add Voter: %v", err) + } + + return data, nil +} + +// RemoveVoter defines the transaction to Remove an Voter +// +// - implements serde.Message +type RemoveVoter struct { + // FormID is hex-encoded + FormID string + TargetUserID string + PerformingUserID string +} + +// Serialize implements serde.Message +func (removeVoter RemoveVoter) Serialize(ctx serde.Context) ([]byte, error) { + format := transactionFormats.Get(ctx.GetFormat()) + + data, err := format.Encode(ctx, removeVoter) + if err != nil { + return nil, xerrors.Errorf("failed to encode Remove Admin: %v", err) + } + + return data, nil +} + +// AddOwner defines the transaction to Add an Owner +// +// - implements serde.Message +type AddOwner struct { + // FormID is hex-encoded + FormID string + TargetUserID string + PerformingUserID string +} + +// Serialize implements serde.Message +func (addOwner AddOwner) Serialize(ctx serde.Context) ([]byte, error) { + format := transactionFormats.Get(ctx.GetFormat()) + + data, err := format.Encode(ctx, addOwner) + if err != nil { + return nil, xerrors.Errorf("failed to encode Add Owner: %v", err) + } + + return data, nil +} + +// RemoveOwner defines the transaction to Remove an Owner +// +// - implements serde.Message +type RemoveOwner struct { + // FormID is hex-encoded + FormID string + TargetUserID string + PerformingUserID string +} + +// Serialize implements serde.Message +func (removeOwner RemoveOwner) Serialize(ctx serde.Context) ([]byte, error) { + format := transactionFormats.Get(ctx.GetFormat()) + + data, err := format.Encode(ctx, removeOwner) + if err != nil { + return nil, xerrors.Errorf("failed to encode Remove Owner: %v", err) + } + + return data, nil +} diff --git a/docs/api.md b/docs/api.md index 4186c2104..fc989f297 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,6 +1,6 @@ # API documentation -_Documentation Last Review: 11.04.2022_ +_Documentation Last Review: 27.06.2024_ ## Regular workflow: @@ -183,7 +183,7 @@ Return: ```json { - "UserID": "", + "VoterID": "", "Ballot": [ { "K": "", @@ -274,7 +274,7 @@ Return: ``` -# SC?: Form cancel 🔐 +# SC7: Form cancel 🔐 | | | | ------ | ------------------------- | @@ -291,6 +291,7 @@ Return: Return: `200 OK` + ```json { "Status": 0, @@ -298,7 +299,7 @@ Return: } ``` -# SC?: Form delete +# SC8: Form delete | | | | ------- | -------------------------- | @@ -317,6 +318,7 @@ formID: Return: `200 OK` + ```json { "Status": 0, @@ -324,7 +326,7 @@ Return: } ``` -# SC?: Form infos from all forms +# SC9: Form infos from all forms | | | | ------ | ---------------- | @@ -349,6 +351,106 @@ Return: } ``` +# SC10: Add an owner to a form 🔐 + +| | | +| ------ |-----------------------------------| +| URL | `/evoting/form/{formID}/addowner` | +| Method | `POST` | +| Input | `application/json` | +```json +{ + "TargetUserID": "", + "PerformingUserID": "" +} +``` + +Return: + +`200 OK` + +```json +{ + "Status": 0, + "Token": "" +} +``` + +# SC11: Remove an owner from the form 🔐 + +| | | +|--------|--------------------------------------| +| URL | `/evoting/form/{formID}/removeowner` | +| Method | `POST` | +| Input | `application/json` | +```json +{ + "TargetUserID": "", + "PerformingUserID": "" +} +``` + +Return: + +`200 OK` + +```json +{ + "Status": 0, + "Token": "" +} +``` + +# SC12: Add a voter to the Form 🔐 + +| | | +| ------ |-----------------------------------| +| URL | `/evoting/form/{formID}/addvoter` | +| Method | `POST` | +| Input | `application/json` | +```json +{ + "TargetUserID": "", + "PerformingUserID": "" +} +``` + +Return: + +`200 OK` + +```json +{ + "Status": 0, + "Token": "" +} +``` + +# SC13: Remove a voter from the Form 🔐 + +| | | +|--------|--------------------------------------| +| URL | `/evoting/form/{formID}/removevoter` | +| Method | `POST` | +| Input | `application/json` | +```json +{ + "TargetUserID": "", + "PerformingUserID": "" +} +``` + +Return: + +`200 OK` + +```json +{ + "Status": 0, + "Token": "" +} +``` + # DK1: DKG init 🔐 | | | @@ -467,3 +569,76 @@ Status can be: - 2: transaction not included The token is an updated version of the token in the URL that can be used to check again the status of the transaction if it is not yet included. + +# A1: Add an admin to the AdminList 🔐 + +| | | +| ------ |---------------------| +| URL | `/evoting/addadmin` | +| Method | `POST` | +| Input | `application/json` | +```json +{ + "TargetUserID": "", + "PerformingUserID": "" +} +``` + +Return: + +`200 OK` + +```json +{ + "Status": 0, + "Token": "" +} +``` + +# A2: Remove an admin from the AdminList 🔐 + +| | | +| ------ |------------------------| +| URL | `/evoting/removeadmin` | +| Method | `POST` | +| Input | `application/json` | + +```json +{ + "TargetUserID": "", + "PerformingUserID": "" +} +``` + +Return: + +`200 OK` + +```json +{ + "Status": 0, + "Token": "" +} +``` + +# A3: Get the AdminList + + + +| | | +| ------ |----------------------| +| URL | `/evoting/adminlist` | +| Method | `GET` | +| Input | | + +Return: + +`200 OK` + +```json +{ + "", "", "..." +} +``` + + diff --git a/docs/smart_contract.md b/docs/smart_contract.md index 52a2dd1a6..0adff392c 100644 --- a/docs/smart_contract.md +++ b/docs/smart_contract.md @@ -71,7 +71,7 @@ This transaction requires the following 3 parameters: 1. `actor` of type dkg.Actor 2. `formID` (see Create Form above) -3. `userID` of the voter +3. `VoterID` of the voter 4. `vote` to be casted Key / Value pairs sent in the transaction in order to create a form: @@ -88,7 +88,7 @@ where: evoting.CastVoteArg = "evoting:cast_vote" castVoteBuf = a marshalled version of types.CastVoteTransaction{ FormID: hex.EncodeToString(formID), - UserID: userID, + VoterID: VoterID, Ballot: ballot, // a vote encrypted by the actor } evoting.CmdArg = "evoting:command" diff --git a/docs/verifiability_doc.md b/docs/verifiability_doc.md index 156214119..64fd5eea6 100755 --- a/docs/verifiability_doc.md +++ b/docs/verifiability_doc.md @@ -20,6 +20,7 @@ The current d-voting [latest commit](https://github.com/c4dt/d-voting/commit/39a Note over User: encrypt ballot via Elgamal encryption using electionPubKey Note over User, Backend: data = encrypted ballot Note over Backend: check role and sign payload. + Note over Backend: add voterID inside payload. Note over Backend: add userID inside payload. Note over Backend: sign = kyber.sign.schnorr.sign(edCurve, scalar, hash); Backend ->>+ NodeX: POST /evoting/elections/ @@ -96,7 +97,7 @@ sequenceDiagram Note over User: generate hash of encrypted ballot and show to user Note over User, Backend: data = encrypted ballot Note over Backend: check role and sign payload. - Note over Backend: add userID inside payload. + Note over Backend: add VoterID inside payload. Note over Backend: sign = kyber.sign.schnorr.sign(edCurve, scalar, hash); Backend ->>+ NodeX: POST /evoting/elections/ Note over Backend, NodeX: data: {"Payload": dataStrB64, "Signature": ""} diff --git a/integration/ballot.go b/integration/ballot.go index 9f24b8c44..23c6012ec 100644 --- a/integration/ballot.go +++ b/integration/ballot.go @@ -44,7 +44,7 @@ func ballotIsNull(ballot types.Ballot) bool { // castVotesRandomly chooses numberOfVotes predefined ballots randomly // and cast them func castVotesRandomly(m txManager, actor dkg.Actor, form types.Form, - numberOfVotes int) ([]types.Ballot, error) { + numberOfVotes int, ownerID string) ([]types.Ballot, error) { possibleBallots := []string{ string("select:" + encodeID("bb") + ":0,0,1,0\n" + @@ -57,6 +57,32 @@ func castVotesRandomly(m txManager, actor dkg.Actor, form types.Form, votes := make([]types.Ballot, numberOfVotes) + for i := 0; i < numberOfVotes; i++ { + voterID := strconv.Itoa(i+1) + "11111" + voterID = voterID[:6] + addVoter := types.AddVoter{ + FormID: form.FormID, + TargetUserID: voterID, + PerformingUserID: ownerID, + } + + data, err := addVoter.Serialize(serdecontext) + if err != nil { + return nil, xerrors.Errorf("failed to serialize add voter: %v", err) + } + + args := []txn.Arg{ + {Key: native.ContractArg, Value: []byte(evoting.ContractName)}, + {Key: evoting.FormArg, Value: data}, + {Key: evoting.CmdArg, Value: []byte(evoting.CmdAddVoterForm)}, + } + + _, err = m.addAndWait(args...) + if err != nil { + return nil, xerrors.Errorf(addAndWaitErr, err) + } + } + for i := 0; i < numberOfVotes; i++ { randomIndex := rand.Intn(len(possibleBallots)) vote := possibleBallots[randomIndex] @@ -66,12 +92,37 @@ func castVotesRandomly(m txManager, actor dkg.Actor, form types.Form, return nil, xerrors.Errorf("failed to marshallBallot: %v", err) } - userID := "user " + strconv.Itoa(i) + /* + For the voters permission verification, we need a voter id. As this method + does not use a fix number of voters, we need a way to generate these voters' + id. We decided to use the ith voter as an id. + As the sciper range from 100000 to 999999 and as we don't know how many + voters they will be created in the method, we pad the ith number with + 1's, and then we truncate to take the first six number. + + example run + if it generates 10 voters, i is going to vary from 0 to 9 + with the +1 + from 1 to 10 + we are going to get id: + 1 11111 + 2 11111 + 3 11111 + 4 11111 + 5 11111 + 6 11111 + 7 11111 + 8 11111 + 9 11111 + 10 11111 -> truncate to 6 digits: 101111 + */ + voterID := strconv.Itoa(i+1) + "11111" + voterID = voterID[:6] castVote := types.CastVote{ - FormID: form.FormID, - UserID: userID, - Ballot: ciphervote, + FormID: form.FormID, + VoterID: voterID, + Ballot: ciphervote, } data, err := castVote.Serialize(serdecontext) @@ -122,12 +173,12 @@ func castBadVote(m txManager, actor dkg.Actor, form types.Form, numberOfBadVotes return xerrors.Errorf("failed to marshallBallot: %v", err) } - userID := "badUser " + strconv.Itoa(i) + voterID := "badUser " + strconv.Itoa(i) castVote := types.CastVote{ - FormID: form.FormID, - UserID: userID, - Ballot: ciphervote, + FormID: form.FormID, + VoterID: voterID, + Ballot: ciphervote, } data, err := castVote.Serialize(serdecontext) @@ -183,13 +234,14 @@ func marshallBallot(vote io.Reader, actor dkg.Actor, chunks int) (types.Ciphervo return ballot, nil } -func decryptBallots(m txManager, actor dkg.Actor, form types.Form) error { +func decryptBallots(m txManager, actor dkg.Actor, form types.Form, userID string) error { if form.Status != types.PubSharesSubmitted { return xerrors.Errorf("cannot decrypt: not all pubShares submitted") } decryptBallots := types.CombineShares{ FormID: form.FormID, + UserID: userID, } data, err := decryptBallots.Serialize(serdecontext) @@ -348,8 +400,8 @@ func castVotesLoad(numVotesPerSec, numSec, BallotSize, chunksPerBallot int, form idx := i*numVotesPerSec + j randomproxy := proxyArray[rand.Intn(proxyCount)] castVoteRequest := ptypes.CastVoteRequest{ - UserID: "user" + strconv.Itoa(idx), - Ballot: ballot, + VoterID: "user" + strconv.Itoa(idx), + Ballot: ballot, } // cast asynchrounously and increment includedVoteCount // if the cast was succesfull @@ -450,8 +502,8 @@ func castVotesScenario(numVotes, BallotSize, chunksPerBallot int, formID, conten require.NoError(t, err) castVoteRequest := ptypes.CastVoteRequest{ - UserID: "user" + strconv.Itoa(i+1), - Ballot: ballot, + VoterID: "user" + strconv.Itoa(i+1), + Ballot: ballot, } randomproxy := proxyArray[rand.Intn(len(proxyArray))] diff --git a/integration/form.go b/integration/form.go index 502af5b5d..090b54335 100644 --- a/integration/form.go +++ b/integration/form.go @@ -33,6 +33,29 @@ func encodeID(ID string) types.ID { return types.ID(base64.StdEncoding.EncodeToString([]byte(ID))) } +// for integration tests +func addAdmin(m txManager, admin string) error { + addAdmin := types.AddAdmin{admin, admin} + + data, err := addAdmin.Serialize(serdecontext) + if err != nil { + return xerrors.Errorf("failed to serialize: %v", err) + } + + args := []txn.Arg{ + {Key: native.ContractArg, Value: []byte(evoting.ContractName)}, + {Key: evoting.FormArg, Value: data}, + {Key: evoting.CmdArg, Value: []byte(evoting.CmdAddAdmin)}, + } + + _, err = m.addAndWait(args...) + if err != nil { + return xerrors.Errorf(addAndWaitErr, err) + } + + return nil +} + // for integration tests func createForm(m txManager, title string, admin string) ([]byte, error) { // Define the configuration : @@ -40,7 +63,7 @@ func createForm(m txManager, title string, admin string) ([]byte, error) { createForm := types.CreateForm{ Configuration: configuration, - AdminID: admin, + UserID: admin, } data, err := createForm.Serialize(serdecontext) @@ -75,7 +98,7 @@ func createFormScenario(contentType, proxy string, secret kyber.Scalar, t *testi createSimpleFormRequest := ptypes.CreateFormRequest{ Configuration: configuration, - AdminID: "adminId", + UserID: "adminId", } signed, err := createSignedRequest(secret, createSimpleFormRequest) @@ -110,9 +133,10 @@ func createFormScenario(contentType, proxy string, secret kyber.Scalar, t *testi } // for integration tests -func openForm(m txManager, formID []byte) error { +func openForm(m txManager, formID []byte, userID string) error { openForm := &types.OpenForm{ FormID: hex.EncodeToString(formID), + UserID: userID, } data, err := openForm.Serialize(serdecontext) diff --git a/integration/integration_test.go b/integration/integration_test.go index 4833a3663..73877fc87 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -34,7 +34,8 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { return func(t *testing.T) { t.Parallel() - adminID := "first admin" + // Dummy UserID in SCIPER format + adminID := "123456" // ##### SETUP ENV ##### @@ -63,6 +64,9 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { } // ##### CREATE FORM ##### + err = addAdmin(m, adminID) + require.NoError(t, err) + formID, err := createForm(m, "Three votes form", adminID) require.NoError(t, err) @@ -71,9 +75,8 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { // ##### SETUP DKG ##### actor, err := initDkg(nodes, formID, m.m) require.NoError(t, err) - // ##### OPEN FORM ##### - err = openForm(m, formID) + err = openForm(m, formID, adminID) require.NoError(t, err) formFac := types.NewFormFactory(types.CiphervoteFactory{}, nodes[0].GetRosterFac()) @@ -82,7 +85,7 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { form, err := getForm(formFac, formID, nodes[0].GetOrdering()) require.NoError(t, err) - castedVotes, err := castVotesRandomly(m, actor, form, numVotes) + castedVotes, err := castVotesRandomly(m, actor, form, numVotes, adminID) require.NoError(t, err) fmt.Println("casted votes:", castedVotes) @@ -103,7 +106,7 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { time.Sleep(time.Second * 1) t.Logf("shuffling") - err = sActor.Shuffle(formID) + err = sActor.Shuffle(formID, adminID) require.NoError(t, err) err = waitForStatus(types.ShuffledBallots, formFac, formID, nodes, @@ -128,7 +131,7 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { form, err = getForm(formFac, formID, nodes[0].GetOrdering()) t.Logf("PubsharesUnit: %v", form.PubsharesUnits) require.NoError(t, err) - err = decryptBallots(m, actor, form) + err = decryptBallots(m, actor, form, adminID) require.NoError(t, err) err = waitForStatus(types.ResultAvailable, formFac, formID, nodes, @@ -167,13 +170,15 @@ func getIntegrationTest(numNodes, numVotes int) func(*testing.T) { fmt.Println("test done") } + } func getIntegrationTestCrash(numNodes, numVotes, failingNodes int) func(*testing.T) { return func(t *testing.T) { t.Parallel() - adminID := "first admin" + // Dummy UserID in SCIPER format + adminID := "123456" // ##### SETUP ENV ##### @@ -202,6 +207,9 @@ func getIntegrationTestCrash(numNodes, numVotes, failingNodes int) func(*testing } // ##### CREATE FORM ##### + err = addAdmin(m, adminID) + require.NoError(t, err) + formID, err := createForm(m, "Three votes form", adminID) require.NoError(t, err) @@ -212,7 +220,7 @@ func getIntegrationTestCrash(numNodes, numVotes, failingNodes int) func(*testing require.NoError(t, err) // ##### OPEN FORM ##### - err = openForm(m, formID) + err = openForm(m, formID, adminID) require.NoError(t, err) formFac := types.NewFormFactory(types.CiphervoteFactory{}, nodes[0].GetRosterFac()) @@ -233,7 +241,7 @@ func getIntegrationTestCrash(numNodes, numVotes, failingNodes int) func(*testing err = closeNodes(crashNodeList) require.NoError(t, err) - castedVotes, err := castVotesRandomly(m, actor, form, numVotes) + castedVotes, err := castVotesRandomly(m, actor, form, numVotes, adminID) require.NoError(t, err) fmt.Println("casted votes:", castedVotes) @@ -254,7 +262,7 @@ func getIntegrationTestCrash(numNodes, numVotes, failingNodes int) func(*testing time.Sleep(time.Second * 1) t.Logf("shuffling") - err = sActor.Shuffle(formID) + err = sActor.Shuffle(formID, adminID) // If the number of failing nodes is greater // than the threshold, the shuffle will fail @@ -291,7 +299,7 @@ func getIntegrationTestCrash(numNodes, numVotes, failingNodes int) func(*testing t.Logf("PubsharesUnit: %v", form.PubsharesUnits) require.NoError(t, err) // Heisenbug: https://github.com/c4dt/d-voting/issues/90 - err = decryptBallots(m, actor, form) + err = decryptBallots(m, actor, form, adminID) require.NoError(t, err) err = waitForStatus(types.ResultAvailable, formFac, formID, nodes, @@ -321,7 +329,8 @@ func getIntegrationTestCrash(numNodes, numVotes, failingNodes int) func(*testing func getIntegrationBenchmark(numNodes, numVotes int) func(*testing.B) { return func(b *testing.B) { - adminID := "first admin" + // Dummy UserID in SCIPER format + adminID := "123456" // ##### SETUP ENV ##### @@ -348,6 +357,9 @@ func getIntegrationBenchmark(numNodes, numVotes int) func(*testing.B) { } // ##### CREATE FORM ##### + err = addAdmin(m, adminID) + require.NoError(b, err) + formID, err := createForm(m, "Three votes form", adminID) require.NoError(b, err) @@ -358,7 +370,7 @@ func getIntegrationBenchmark(numNodes, numVotes int) func(*testing.B) { require.NoError(b, err) // ##### OPEN FORM ##### - err = openForm(m, formID) + err = openForm(m, formID, adminID) require.NoError(b, err) formFac := types.NewFormFactory(types.CiphervoteFactory{}, nodes[0].GetRosterFac()) @@ -367,7 +379,7 @@ func getIntegrationBenchmark(numNodes, numVotes int) func(*testing.B) { form, err := getForm(formFac, formID, nodes[0].GetOrdering()) require.NoError(b, err) - castedVotes, err := castVotesRandomly(m, actor, form, numVotes) + castedVotes, err := castVotesRandomly(m, actor, form, numVotes, adminID) require.NoError(b, err) fmt.Println("casted votes:", castedVotes) @@ -386,7 +398,7 @@ func getIntegrationBenchmark(numNodes, numVotes int) func(*testing.B) { time.Sleep(time.Second * 1) - err = sActor.Shuffle(formID) + err = sActor.Shuffle(formID, adminID) require.NoError(b, err) err = waitForStatus(types.ShuffledBallots, formFac, formID, nodes, @@ -410,7 +422,7 @@ func getIntegrationBenchmark(numNodes, numVotes int) func(*testing.B) { form, err = getForm(formFac, formID, nodes[0].GetOrdering()) b.Logf("PubsharesUnit: %v", form.PubsharesUnits) require.NoError(b, err) - err = decryptBallots(m, actor, form) + err = decryptBallots(m, actor, form, adminID) require.NoError(b, err) err = waitForStatus(types.ResultAvailable, formFac, formID, nodes, diff --git a/integration/performance_test.go b/integration/performance_test.go index 9d4e542a9..1c8a294c8 100644 --- a/integration/performance_test.go +++ b/integration/performance_test.go @@ -87,7 +87,7 @@ func customVotesScenario(b *testing.B, stuffing bool) { require.NoError(b, err) // ##### OPEN FORM ##### - err = openForm(m, formID) + err = openForm(m, formID, adminID) require.NoError(b, err) formFac := types.NewFormFactory(types.CiphervoteFactory{}, nodes[0].GetRosterFac()) @@ -117,7 +117,7 @@ func customVotesScenario(b *testing.B, stuffing bool) { require.NoError(b, err) b.Logf("shuffling") - err = sActor.Shuffle(formID) + err = sActor.Shuffle(formID, adminID) require.NoError(b, err) durationShuffling := b.Elapsed() @@ -146,7 +146,7 @@ func customVotesScenario(b *testing.B, stuffing bool) { require.NoError(b, err) b.ResetTimer() - err = decryptBallots(m, actor, form) + err = decryptBallots(m, actor, form, adminID) require.NoError(b, err) durationDecrypt := b.Elapsed() @@ -219,7 +219,7 @@ func createFormNChunks(m txManager, title types.Title, admin string, numChunks i createForm := types.CreateForm{ Configuration: configuration, - AdminID: admin, + UserID: admin, } data, err := createForm.Serialize(serdecontext) @@ -272,12 +272,12 @@ func castVotesNChunks(m txManager, actor dkg.Actor, form types.Form, start := time.Now() for i := 0; i < numberOfVotes; i++ { - userID := "user " + strconv.Itoa(i) + voterID := "user " + strconv.Itoa(i) castVote := types.CastVote{ - FormID: form.FormID, - UserID: userID, - Ballot: ballot, + FormID: form.FormID, + VoterID: voterID, + Ballot: ballot, } data, err := castVote.Serialize(serdecontext) diff --git a/integration/votes_test.go b/integration/votes_test.go index 207798f18..fe151a9e9 100644 --- a/integration/votes_test.go +++ b/integration/votes_test.go @@ -69,7 +69,7 @@ func getIntegrationTestBadVote(numNodes, numVotes, numBadVotes int) func(*testin require.NoError(t, err) // ##### OPEN FORM ##### - err = openForm(m, formID) + err = openForm(m, formID, adminID) require.NoError(t, err) formFac := types.NewFormFactory(types.CiphervoteFactory{}, nodes[0].GetRosterFac()) @@ -80,7 +80,7 @@ func getIntegrationTestBadVote(numNodes, numVotes, numBadVotes int) func(*testin // cast a vote with wrong answers: Should not be taken into account - _, err = castVotesRandomly(m, actor, form, numVotes-numBadVotes) + _, err = castVotesRandomly(m, actor, form, numVotes-numBadVotes, adminID) require.NoError(t, err) err = castBadVote(m, actor, form, numBadVotes) @@ -102,7 +102,7 @@ func getIntegrationTestBadVote(numNodes, numVotes, numBadVotes int) func(*testin time.Sleep(time.Second * 1) t.Logf("shuffling") - err = sActor.Shuffle(formID) + err = sActor.Shuffle(formID, adminID) require.NoError(t, err) err = waitForStatus(types.ShuffledBallots, formFac, formID, nodes, @@ -127,7 +127,7 @@ func getIntegrationTestBadVote(numNodes, numVotes, numBadVotes int) func(*testin form, err = getForm(formFac, formID, nodes[0].GetOrdering()) t.Logf("PubsharesUnit: %v", form.PubsharesUnits) require.NoError(t, err) - err = decryptBallots(m, actor, form) + err = decryptBallots(m, actor, form, adminID) require.NoError(t, err) err = waitForStatus(types.ResultAvailable, formFac, formID, nodes, @@ -207,7 +207,7 @@ func getIntegrationTestRevote(numNodes, numVotes, numRevotes int) func(*testing. require.NoError(t, err) // ##### OPEN FORM ##### - err = openForm(m, formID) + err = openForm(m, formID, adminID) require.NoError(t, err) formFac := types.NewFormFactory(types.CiphervoteFactory{}, nodes[0].GetRosterFac()) @@ -216,10 +216,10 @@ func getIntegrationTestRevote(numNodes, numVotes, numRevotes int) func(*testing. form, err := getForm(formFac, formID, nodes[0].GetOrdering()) require.NoError(t, err) - _, err = castVotesRandomly(m, actor, form, numVotes) + _, err = castVotesRandomly(m, actor, form, numVotes, adminID) require.NoError(t, err) - castedVotes, err := castVotesRandomly(m, actor, form, numRevotes) + castedVotes, err := castVotesRandomly(m, actor, form, numRevotes, adminID) require.NoError(t, err) fmt.Println("casted votes:", castedVotes) @@ -240,7 +240,7 @@ func getIntegrationTestRevote(numNodes, numVotes, numRevotes int) func(*testing. time.Sleep(time.Second * 1) t.Logf("shuffling") - err = sActor.Shuffle(formID) + err = sActor.Shuffle(formID, adminID) require.NoError(t, err) err = waitForStatus(types.ShuffledBallots, formFac, formID, nodes, @@ -265,7 +265,7 @@ func getIntegrationTestRevote(numNodes, numVotes, numRevotes int) func(*testing. form, err = getForm(formFac, formID, nodes[0].GetOrdering()) t.Logf("PubsharesUnit: %v", form.PubsharesUnits) require.NoError(t, err) - err = decryptBallots(m, actor, form) + err = decryptBallots(m, actor, form, adminID) require.NoError(t, err) err = waitForStatus(types.ResultAvailable, formFac, formID, nodes, diff --git a/proxy/election.go b/proxy/election.go index 7429e04ce..d60499725 100644 --- a/proxy/election.go +++ b/proxy/election.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "sync" "github.com/c4dt/d-voting/contracts/evoting" @@ -19,7 +20,6 @@ import ( "go.dedis.ch/dela/core/txn/pool" "go.dedis.ch/dela/serde" "go.dedis.ch/kyber/v3" - "go.dedis.ch/kyber/v3/sign/schnorr" "golang.org/x/xerrors" ) @@ -37,14 +37,23 @@ func NewForm(srv ordering.Service, p pool.Pool, logger := dela.Logger.With().Timestamp().Str("role", "evoting-proxy").Logger() + // Compute the ID of the admin list id + // We need it to filter the send list of form + h := sha256.New() + h.Write([]byte(evoting.AdminListId)) + adminListIDBuf := h.Sum(nil) + adminListID := hex.EncodeToString(adminListIDBuf) + return &form{ logger: logger, orderingSvc: srv, context: ctx, formFac: fac, + adminFac: types.AdminListFactory{}, mngr: txnManaxer, pool: p, pk: pk, + adminListID: adminListID, } } @@ -58,13 +67,15 @@ type form struct { logger zerolog.Logger context serde.Context formFac serde.Factory + adminFac serde.Factory mngr txnmanager.Manager pool pool.Pool pk kyber.Point + adminListID string } // NewForm implements proxy.Proxy -func (h *form) NewForm(w http.ResponseWriter, r *http.Request) { +func (form *form) NewForm(w http.ResponseWriter, r *http.Request) { var req ptypes.CreateFormRequest // get the signed request @@ -75,7 +86,7 @@ func (h *form) NewForm(w http.ResponseWriter, r *http.Request) { } // get the request and verify the signature - err = signed.GetAndVerify(h.pk, &req) + err = signed.GetAndVerify(form.pk, &req) if err != nil { InternalError(w, r, getSignedErr(err), nil) return @@ -83,11 +94,11 @@ func (h *form) NewForm(w http.ResponseWriter, r *http.Request) { createForm := types.CreateForm{ Configuration: req.Configuration, - AdminID: req.AdminID, + UserID: req.UserID, } // serialize the transaction - data, err := createForm.Serialize(h.context) + data, err := createForm.Serialize(form.context) if err != nil { http.Error(w, "failed to marshal CreateFormTransaction: "+err.Error(), http.StatusInternalServerError) @@ -95,7 +106,7 @@ func (h *form) NewForm(w http.ResponseWriter, r *http.Request) { } // create the transaction and add it to the pool - txnID, blockIdx, err := h.mngr.SubmitTxn(r.Context(), evoting.CmdCreateForm, evoting.FormArg, data) + txnID, blockIdx, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdCreateForm, evoting.FormArg, data) if err != nil { http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) return @@ -107,7 +118,7 @@ func (h *form) NewForm(w http.ResponseWriter, r *http.Request) { formID := hash.Sum(nil) // create it to get the token - transactionClientInfo, err := h.mngr.CreateTransactionResult(txnID, blockIdx, txnmanager.UnknownTransactionStatus) + transactionClientInfo, err := form.mngr.CreateTransactionResult(txnID, blockIdx, txnmanager.UnknownTransactionStatus) if err != nil { http.Error(w, "failed to create transaction info: "+err.Error(), http.StatusInternalServerError) return @@ -126,7 +137,7 @@ func (h *form) NewForm(w http.ResponseWriter, r *http.Request) { } // NewFormVote implements proxy.Proxy -func (h *form) NewFormVote(w http.ResponseWriter, r *http.Request) { +func (form *form) NewFormVote(w http.ResponseWriter, r *http.Request) { var req ptypes.CastVoteRequest // get the signed request @@ -137,7 +148,7 @@ func (h *form) NewFormVote(w http.ResponseWriter, r *http.Request) { } // get the request and verify the signature - err = signed.GetAndVerify(h.pk, &req) + err = signed.GetAndVerify(form.pk, &req) if err != nil { InternalError(w, r, getSignedErr(err), nil) return @@ -153,7 +164,7 @@ func (h *form) NewFormVote(w http.ResponseWriter, r *http.Request) { formID := vars["formID"] - elecMD, err := h.getFormsMetadata() + elecMD, err := form.getFormsMetadata() if err != nil { http.Error(w, "failed to get form metadata", http.StatusNotFound) return @@ -192,13 +203,13 @@ func (h *form) NewFormVote(w http.ResponseWriter, r *http.Request) { } castVote := types.CastVote{ - FormID: formID, - UserID: req.UserID, - Ballot: ciphervote, + FormID: formID, + VoterID: req.VoterID, + Ballot: ciphervote, } // serialize the vote - data, err := castVote.Serialize(h.context) + data, err := castVote.Serialize(form.context) if err != nil { http.Error(w, "failed to marshal CastVoteTransaction: "+err.Error(), http.StatusInternalServerError) @@ -206,15 +217,15 @@ func (h *form) NewFormVote(w http.ResponseWriter, r *http.Request) { } // create the transaction and add it to the pool - txnID, lastBlock, err := h.mngr.SubmitTxn(r.Context(), evoting.CmdCastVote, evoting.FormArg, data) + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdCastVote, evoting.FormArg, data) if err != nil { - h.logger.Err(err).Msg("failed to submit txn") + form.logger.Err(err).Msg("failed to submit txn") http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) return } // send the transaction's information - err = h.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) + err = form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) if err != nil { http.Error(w, "couldn't send transaction info: "+err.Error(), http.StatusInternalServerError) return @@ -222,7 +233,7 @@ func (h *form) NewFormVote(w http.ResponseWriter, r *http.Request) { } // EditForm implements proxy.Proxy -func (h *form) EditForm(w http.ResponseWriter, r *http.Request) { +func (form *form) EditForm(w http.ResponseWriter, r *http.Request) { var req ptypes.UpdateFormRequest // get the signed request @@ -233,7 +244,7 @@ func (h *form) EditForm(w http.ResponseWriter, r *http.Request) { } // get the request and verify the signature - err = signed.GetAndVerify(h.pk, &req) + err = signed.GetAndVerify(form.pk, &req) if err != nil { InternalError(w, r, getSignedErr(err), nil) return @@ -249,7 +260,7 @@ func (h *form) EditForm(w http.ResponseWriter, r *http.Request) { formID := vars["formID"] - elecMD, err := h.getFormsMetadata() + elecMD, err := form.getFormsMetadata() if err != nil { http.Error(w, "failed to get form metadata", http.StatusNotFound) return @@ -263,13 +274,13 @@ func (h *form) EditForm(w http.ResponseWriter, r *http.Request) { switch req.Action { case "open": - h.openForm(formID, w, r) + form.openForm(formID, req.UserID, w, r) case "close": - h.closeForm(formID, w, r) + form.closeForm(formID, req.UserID, w, r) case "combineShares": - h.combineShares(formID, w, r) + form.combineShares(formID, req.UserID, w, r) case "cancel": - h.cancelForm(formID, w, r) + form.cancelForm(formID, req.UserID, w, r) default: BadRequestError(w, r, xerrors.Errorf("invalid action: %s", req.Action), nil) return @@ -278,13 +289,14 @@ func (h *form) EditForm(w http.ResponseWriter, r *http.Request) { // openForm allows opening a form, which sets the public key based on // the DKG actor. -func (h *form) openForm(formID string, w http.ResponseWriter, r *http.Request) { +func (form *form) openForm(formID string, userID string, w http.ResponseWriter, r *http.Request) { openForm := types.OpenForm{ FormID: formID, + UserID: userID, } // serialize the transaction - data, err := openForm.Serialize(h.context) + data, err := openForm.Serialize(form.context) if err != nil { http.Error(w, "failed to marshal OpenFormTransaction: "+err.Error(), http.StatusInternalServerError) @@ -292,25 +304,26 @@ func (h *form) openForm(formID string, w http.ResponseWriter, r *http.Request) { } // create the transaction and add it to the pool - txnID, lastBlock, err := h.mngr.SubmitTxn(r.Context(), evoting.CmdOpenForm, evoting.FormArg, data) + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdOpenForm, evoting.FormArg, data) if err != nil { http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) return } // send the transaction's informations - h.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) } // closeForm closes a form. -func (h *form) closeForm(formIDHex string, w http.ResponseWriter, r *http.Request) { +func (form *form) closeForm(formIDHex string, userID string, w http.ResponseWriter, r *http.Request) { closeForm := types.CloseForm{ FormID: formIDHex, + UserID: userID, } // serialize the transaction - data, err := closeForm.Serialize(h.context) + data, err := closeForm.Serialize(form.context) if err != nil { http.Error(w, "failed to marshal CloseFormTransaction: "+err.Error(), http.StatusInternalServerError) @@ -318,27 +331,27 @@ func (h *form) closeForm(formIDHex string, w http.ResponseWriter, r *http.Reques } // create the transaction and add it to the pool - txnID, lastBlock, err := h.mngr.SubmitTxn(r.Context(), evoting.CmdCloseForm, evoting.FormArg, data) + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdCloseForm, evoting.FormArg, data) if err != nil { http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) return } // send the transaction's informations - h.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) } // combineShares decrypts the shuffled ballots in a form. -func (h *form) combineShares(formIDHex string, w http.ResponseWriter, r *http.Request) { +func (form *form) combineShares(formIDHex string, userID string, w http.ResponseWriter, r *http.Request) { - form, err := types.FormFromStore(h.context, h.formFac, formIDHex, h.orderingSvc.GetStore()) + formFromStore, err := types.FormFromStore(form.context, form.formFac, formIDHex, form.orderingSvc.GetStore()) if err != nil { http.Error(w, "failed to get form: "+err.Error(), http.StatusInternalServerError) return } - if form.Status != types.PubSharesSubmitted { + if formFromStore.Status != types.PubSharesSubmitted { http.Error(w, "the submission of public shares must be over!", http.StatusUnauthorized) return @@ -346,10 +359,11 @@ func (h *form) combineShares(formIDHex string, w http.ResponseWriter, r *http.Re decryptBallots := types.CombineShares{ FormID: formIDHex, + UserID: userID, } // serialize the transaction - data, err := decryptBallots.Serialize(h.context) + data, err := decryptBallots.Serialize(form.context) if err != nil { http.Error(w, "failed to marshal decryptBallots: "+err.Error(), http.StatusInternalServerError) @@ -357,25 +371,26 @@ func (h *form) combineShares(formIDHex string, w http.ResponseWriter, r *http.Re } // create the transaction and add it to the pool - txnID, lastBlock, err := h.mngr.SubmitTxn(r.Context(), evoting.CmdCombineShares, evoting.FormArg, data) + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdCombineShares, evoting.FormArg, data) if err != nil { http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) return } // send the transaction's informations - h.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) } // cancelForm cancels a form. -func (h *form) cancelForm(formIDHex string, w http.ResponseWriter, r *http.Request) { +func (form *form) cancelForm(formIDHex string, userID string, w http.ResponseWriter, r *http.Request) { cancelForm := types.CancelForm{ FormID: formIDHex, + UserID: userID, } // serialize the transaction - data, err := cancelForm.Serialize(h.context) + data, err := cancelForm.Serialize(form.context) if err != nil { http.Error(w, "failed to marshal CancelForm: "+err.Error(), http.StatusInternalServerError) @@ -383,19 +398,19 @@ func (h *form) cancelForm(formIDHex string, w http.ResponseWriter, r *http.Reque } // create the transaction and add it to the pool - txnID, lastBlock, err := h.mngr.SubmitTxn(r.Context(), evoting.CmdCancelForm, evoting.FormArg, data) + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdCancelForm, evoting.FormArg, data) if err != nil { http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) return } // send the transaction's informations - h.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) } // Form implements proxy.Proxy. The request should not be signed because it // is fetching public data. -func (h *form) Form(w http.ResponseWriter, r *http.Request) { +func (form *form) Form(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "*") @@ -410,7 +425,7 @@ func (h *form) Form(w http.ResponseWriter, r *http.Request) { formID := vars["formID"] // get the form - form, err := types.FormFromStore(h.context, h.formFac, formID, h.orderingSvc.GetStore()) + formFromStore, err := types.FormFromStore(form.context, form.formFac, formID, form.orderingSvc.GetStore()) if err != nil { http.Error(w, xerrors.Errorf("failed to get form: %v", err).Error(), http.StatusInternalServerError) return @@ -419,8 +434,8 @@ func (h *form) Form(w http.ResponseWriter, r *http.Request) { var pubkeyBuf []byte // get the public key - if form.Pubkey != nil { - pubkeyBuf, err = form.Pubkey.MarshalBinary() + if formFromStore.Pubkey != nil { + pubkeyBuf, err = formFromStore.Pubkey.MarshalBinary() if err != nil { http.Error(w, "failed to marshal pubkey: "+err.Error(), http.StatusInternalServerError) @@ -428,14 +443,14 @@ func (h *form) Form(w http.ResponseWriter, r *http.Request) { } } - roster := make([]string, 0, form.Roster.Len()) + roster := make([]string, 0, formFromStore.Roster.Len()) - iter := form.Roster.AddressIterator() + iter := formFromStore.Roster.AddressIterator() for iter.HasNext() { roster = append(roster, iter.GetNext().String()) } - suff, err := form.Suffragia(h.context, h.orderingSvc.GetStore()) + suff, err := formFromStore.Suffragia(form.context, form.orderingSvc.GetStore()) if err != nil { http.Error(w, "couldn't get ballots: "+err.Error(), http.StatusInternalServerError) @@ -443,15 +458,15 @@ func (h *form) Form(w http.ResponseWriter, r *http.Request) { } response := ptypes.GetFormResponse{ - FormID: string(form.FormID), - Configuration: form.Configuration, - Status: uint16(form.Status), + FormID: string(formFromStore.FormID), + Configuration: formFromStore.Configuration, + Status: uint16(formFromStore.Status), Pubkey: hex.EncodeToString(pubkeyBuf), - Result: form.DecryptedBallots, + Result: formFromStore.DecryptedBallots, Roster: roster, - ChunksPerBallot: form.ChunksPerBallot(), - BallotSize: form.BallotSize, - Voters: suff.UserIDs, + ChunksPerBallot: formFromStore.ChunksPerBallot(), + BallotSize: formFromStore.BallotSize, + Voters: suff.VoterIDs, } txnmanager.SendResponse(w, response) @@ -460,11 +475,11 @@ func (h *form) Form(w http.ResponseWriter, r *http.Request) { // Forms implements proxy.Proxy. The request should not be signed because it // is fecthing public data. -func (h *form) Forms(w http.ResponseWriter, r *http.Request) { +func (form *form) Forms(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "*") - elecMD, err := h.getFormsMetadata() + elecMD, err := form.getFormsMetadata() if err != nil { InternalError(w, r, xerrors.Errorf("failed to get form metadata: %v", err), nil) return @@ -474,30 +489,32 @@ func (h *form) Forms(w http.ResponseWriter, r *http.Request) { // get the forms for i, id := range elecMD.FormsIDs { - form, err := types.FormFromStore(h.context, h.formFac, id, h.orderingSvc.GetStore()) - if err != nil { - InternalError(w, r, xerrors.Errorf("failed to get form: %v", err), nil) - return - } - - var pubkeyBuf []byte - - if form.Pubkey != nil { - pubkeyBuf, err = form.Pubkey.MarshalBinary() + if id != form.adminListID { + form, err := types.FormFromStore(form.context, form.formFac, id, form.orderingSvc.GetStore()) if err != nil { - InternalError(w, r, xerrors.Errorf("failed to marshal pubkey: %v", err), nil) + InternalError(w, r, xerrors.Errorf("failed to get form: %v", err), nil) return } - } - info := ptypes.LightForm{ - FormID: string(form.FormID), - Title: form.Configuration.Title, - Status: uint16(form.Status), - Pubkey: hex.EncodeToString(pubkeyBuf), - } + var pubkeyBuf []byte + + if form.Pubkey != nil { + pubkeyBuf, err = form.Pubkey.MarshalBinary() + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to marshal pubkey: %v", err), nil) + return + } + } - allFormsInfo[i] = info + info := ptypes.LightForm{ + FormID: string(form.FormID), + Title: form.Configuration.Title, + Status: uint16(form.Status), + Pubkey: hex.EncodeToString(pubkeyBuf), + } + + allFormsInfo[i] = info + } } response := ptypes.GetFormsResponse{Forms: allFormsInfo} @@ -507,7 +524,9 @@ func (h *form) Forms(w http.ResponseWriter, r *http.Request) { } // DeleteForm implements proxy.Proxy -func (h *form) DeleteForm(w http.ResponseWriter, r *http.Request) { +func (form *form) DeleteForm(w http.ResponseWriter, r *http.Request) { + var req ptypes.UpdateFormRequest + vars := mux.Vars(r) // check if the formID is valid @@ -518,7 +537,7 @@ func (h *form) DeleteForm(w http.ResponseWriter, r *http.Request) { formID := vars["formID"] - elecMD, err := h.getFormsMetadata() + elecMD, err := form.getFormsMetadata() if err != nil { http.Error(w, "failed to get form metadata", http.StatusNotFound) return @@ -529,49 +548,270 @@ func (h *form) DeleteForm(w http.ResponseWriter, r *http.Request) { http.Error(w, "the form does not exist", http.StatusNotFound) return } - - // auth should contain the hex-encoded signature on the hex-encoded form - // ID - auth := r.Header.Get("Authorization") - - signature, err := hex.DecodeString(auth) + + // get the signed request + signed, err := ptypes.NewSignedRequest(r.Body) if err != nil { - BadRequestError(w, r, xerrors.Errorf("failed to decode auth: %v", err), nil) + InternalError(w, r, newSignedErr(err), nil) return } - // check if the signature is valid - err = schnorr.Verify(suite, h.pk, []byte(formID), signature) + err = signed.GetAndVerify(form.pk, &req) if err != nil { - ForbiddenError(w, r, xerrors.Errorf("signature verification failed: %v", err), nil) + InternalError(w, r, getSignedErr(err), nil) return } deleteForm := types.DeleteForm{ FormID: formID, + UserID: req.UserID, } - data, err := deleteForm.Serialize(h.context) + data, err := deleteForm.Serialize(form.context) if err != nil { InternalError(w, r, xerrors.Errorf("failed to marshal DeleteForm: %v", err), nil) return } // create the transaction and add it to the pool - txnID, lastBlock, err := h.mngr.SubmitTxn(r.Context(), evoting.CmdDeleteForm, evoting.FormArg, data) + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdDeleteForm, evoting.FormArg, data) if err != nil { http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) return } // send the transaction's information - h.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) +} + +// POST /addtoadminlist +func (form *form) AddAdmin(w http.ResponseWriter, r *http.Request) { + req, err := form.getPermissionOpRequest(w, r) + if err != nil { + return + } + + addAdmin := types.AddAdmin{ + TargetUserID: req.TargetUserID, + PerformingUserID: req.PerformingUserID, + } + + data, err := addAdmin.Serialize(form.context) + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to marshal AddAdmin: %v", err), nil) + return + } + + // create the transaction and add it to the pool + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdAddAdmin, evoting.FormArg, data) + if err != nil { + http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) + return + } + + // send the transaction's information + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) +} + +// POST /removetoadminlist +func (form *form) RemoveAdmin(w http.ResponseWriter, r *http.Request) { + req, err := form.getPermissionOpRequest(w, r) + if err != nil { + return + } + + removeAdmin := types.RemoveAdmin{ + TargetUserID: req.TargetUserID, + PerformingUserID: req.PerformingUserID, + } + + data, err := removeAdmin.Serialize(form.context) + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to marshal RemoveAdmin: %v", err), nil) + return + } + + // create the transaction and add it to the pool + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdRemoveAdmin, evoting.FormArg, data) + if err != nil { + http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) + return + } + + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) +} + +// GET /adminlist +func (form *form) AdminList(w http.ResponseWriter, r *http.Request) { + adminList, err := types.AdminListFromStore(form.context, form.adminFac, form.orderingSvc.GetStore(), evoting.AdminListId) + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to get form: %v", err), nil) + return + } + + // Used to check whether it is the last SCIPER printed + count := 0 + lenAdminList := len(adminList.AdminList) + + myAdminList := "{" + for id := range adminList.AdminList { + count++ + + // If last element, does not add ', ' otherwise add ', ' + if count == lenAdminList { + myAdminList += strconv.Itoa(adminList.AdminList[id]) + } else { + myAdminList += strconv.Itoa(adminList.AdminList[id]) + ", " + } + + } + myAdminList += "}" + + txnmanager.SendResponse(w, myAdminList) +} + +// POST /forms/{formID}/addowner +func (form *form) AddOwnerToForm(w http.ResponseWriter, r *http.Request) { + req, err := form.getPermissionOpRequest(w, r) + if err != nil { + return + } + + formID, hasFailed := form.extractAndRetrieveFormID(w, r) + if hasFailed { + return + } + + addOwner := types.AddOwner{ + FormID: formID, + TargetUserID: req.TargetUserID, + PerformingUserID: req.PerformingUserID, + } + + data, err := addOwner.Serialize(form.context) + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to marshal AddOwner: %v", err), nil) + return + } + + // create the transaction and add it to the pool + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdAddOwnerForm, evoting.FormArg, data) + if err != nil { + http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) + return + } + + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) +} + +// POST /forms/{formID}/removeowner +func (form *form) RemoveOwnerToForm(w http.ResponseWriter, r *http.Request) { + req, err := form.getPermissionOpRequest(w, r) + if err != nil { + return + } + + formID, hasFailed := form.extractAndRetrieveFormID(w, r) + if hasFailed { + return + } + + removeOwner := types.RemoveOwner{ + FormID: formID, + TargetUserID: req.TargetUserID, + PerformingUserID: req.PerformingUserID, + } + + data, err := removeOwner.Serialize(form.context) + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to marshal RemoveOwner: %v", err), nil) + return + } + + // create the transaction and add it to the pool + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdRemoveOwnerForm, evoting.FormArg, data) + if err != nil { + http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) + return + } + + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) +} + +// POST /forms/{formID}/addvoter +func (form *form) AddVoterToForm(w http.ResponseWriter, r *http.Request) { + req, err := form.getPermissionOpRequest(w, r) + if err != nil { + return + } + + formID, hasFailed := form.extractAndRetrieveFormID(w, r) + if hasFailed { + return + } + + addVoter := types.AddVoter{ + FormID: formID, + TargetUserID: req.TargetUserID, + PerformingUserID: req.PerformingUserID, + } + + data, err := addVoter.Serialize(form.context) + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to marshal AddVoter: %v", err), nil) + return + } + + // create the transaction and add it to the pool + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdAddVoterForm, evoting.FormArg, data) + if err != nil { + http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) + return + } + + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) } -func (h *form) getFormsMetadata() (types.FormsMetadata, error) { +// POST /forms/{formID}/removevoter +func (form *form) RemoveVoterToForm(w http.ResponseWriter, r *http.Request) { + req, err := form.getPermissionOpRequest(w, r) + if err != nil { + return + } + + formID, hasFailed := form.extractAndRetrieveFormID(w, r) + if hasFailed { + return + } + + removeVoter := types.RemoveVoter{ + FormID: formID, + TargetUserID: req.TargetUserID, + PerformingUserID: req.PerformingUserID, + } + + data, err := removeVoter.Serialize(form.context) + if err != nil { + InternalError(w, r, xerrors.Errorf("failed to marshal RemoveVoter: %v", err), nil) + return + } + + // create the transaction and add it to the pool + txnID, lastBlock, err := form.mngr.SubmitTxn(r.Context(), evoting.CmdRemoveVoterForm, evoting.FormArg, data) + if err != nil { + http.Error(w, "failed to submit txn: "+err.Error(), http.StatusInternalServerError) + return + } + + form.mngr.SendTransactionInfo(w, txnID, lastBlock, txnmanager.UnknownTransactionStatus) +} + +// ===== HELPER ===== + +func (form *form) getFormsMetadata() (types.FormsMetadata, error) { var md types.FormsMetadata - store, err := h.orderingSvc.GetStore().Get([]byte(evoting.FormsMetadataKey)) + store, err := form.orderingSvc.GetStore().Get([]byte(evoting.FormsMetadataKey)) if err != nil { return md, nil } @@ -588,3 +828,47 @@ func (h *form) getFormsMetadata() (types.FormsMetadata, error) { return md, nil } + +func (form *form) getPermissionOpRequest(w http.ResponseWriter, r *http.Request) (ptypes.PermissionOperationRequest, error) { + var req ptypes.PermissionOperationRequest + + // get the signed request + signed, err := ptypes.NewSignedRequest(r.Body) + if err != nil { + InternalError(w, r, newSignedErr(err), nil) + return ptypes.PermissionOperationRequest{}, err + } + + // get the request and verify the signature + err = signed.GetAndVerify(form.pk, &req) + if err != nil { + InternalError(w, r, getSignedErr(err), nil) + return ptypes.PermissionOperationRequest{}, err + } + return req, err +} + +func (form *form) extractAndRetrieveFormID(w http.ResponseWriter, r *http.Request) (string, bool) { + vars := mux.Vars(r) + + // check if the formID is valid + if vars == nil || vars["formID"] == "" { + http.Error(w, fmt.Sprintf("formID not found: %v", vars), http.StatusInternalServerError) + return "", true + } + + formID := vars["formID"] + + elecMD, err := form.getFormsMetadata() + if err != nil { + http.Error(w, "failed to get form metadata", http.StatusNotFound) + return "", true + } + + // check if the form exists + if elecMD.FormsIDs.Contains(formID) < 0 { + http.Error(w, "the form does not exist", http.StatusNotFound) + return "", true + } + return formID, false +} diff --git a/proxy/generate_test.go b/proxy/generate_test.go new file mode 100644 index 000000000..8fa88d38e --- /dev/null +++ b/proxy/generate_test.go @@ -0,0 +1,51 @@ +package proxy + +import ( + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "github.com/stretchr/testify/require" + "go.dedis.ch/kyber/v3/sign/schnorr" + "testing" +) + +/* +TestGenerateSignatureAndB64Payload is a code snippet that help developer +to generate Signature and Base64 Payload to create cURL request for +debug environment. + +How to use it: +- Feel free to change the INPUT following docs/api.md +- Run the test and use the provided result as a cURL request. +*/ +func TestGenerateSignatureAndB64Payload(t *testing.T) { + // #### INPUT #### + // rawPayload must be built following docs/api.md + rawPayload := `{"TargetUserID" : "654321", "PerformingUserID" : "123456"}` + + // pk must be set according to the public key used to run the system. + pk := "6aadf480d068ac896330b726802abd0da2a5f3824f791fe8dbd4cd555e80b809" + // #### END INPUT #### + + // #### DO NOT MODIFY BELOW #### + payload := base64.URLEncoding.EncodeToString([]byte(rawPayload)) + + hash := sha256.New() + hash.Write([]byte(payload)) + md := hash.Sum(nil) + + pkhex, err := hex.DecodeString(pk) + require.NoError(t, err) + + point := suite.Scalar() + err = point.UnmarshalBinary(pkhex) + require.NoError(t, err) + + require.NoError(t, err) + + signature, err := schnorr.Sign(suite, point, md) + require.NoError(t, err) + + println("Signature: " + hex.EncodeToString(signature)) + println("Payload: " + payload) +} diff --git a/proxy/mod.go b/proxy/mod.go index dbd1cb9cb..b5e2be50f 100644 --- a/proxy/mod.go +++ b/proxy/mod.go @@ -31,6 +31,21 @@ type Form interface { Form(http.ResponseWriter, *http.Request) // DELETE /forms/{formID} DeleteForm(http.ResponseWriter, *http.Request) + // TODO CHECK CAUSE NEW -> modif according to blockchain + // POST /addadmin + AddAdmin(http.ResponseWriter, *http.Request) + // POST /removeadmin + RemoveAdmin(http.ResponseWriter, *http.Request) + // GET /adminlist + AdminList(http.ResponseWriter, *http.Request) + // POST /forms/{formID}/addowner + AddOwnerToForm(http.ResponseWriter, *http.Request) + // POST /forms/{formID}/removeowner + RemoveOwnerToForm(http.ResponseWriter, *http.Request) + // POST /forms/{formID}/addvoter + AddVoterToForm(http.ResponseWriter, *http.Request) + // POST /forms/{formID}/removevoter + RemoveVoterToForm(http.ResponseWriter, *http.Request) } // DKG defines the public HTTP API of the DKG service diff --git a/proxy/shuffle.go b/proxy/shuffle.go index ef9466890..9554cae13 100644 --- a/proxy/shuffle.go +++ b/proxy/shuffle.go @@ -57,6 +57,7 @@ func (s shuffle) EditShuffle(w http.ResponseWriter, r *http.Request) { } formID := vars["formID"] + userID := vars["userID"] formIDBuf, err := hex.DecodeString(formID) if err != nil { @@ -67,7 +68,7 @@ func (s shuffle) EditShuffle(w http.ResponseWriter, r *http.Request) { switch req.Action { // shuffle the ballots case "shuffle": - err = s.actor.Shuffle(formIDBuf) + err = s.actor.Shuffle(formIDBuf, userID) if err != nil { http.Error(w, "failed to shuffle: "+err.Error(), http.StatusInternalServerError) return diff --git a/proxy/txnmanager/transaction.go b/proxy/txnmanager/transaction.go index 0cce21e7a..f8f189629 100644 --- a/proxy/txnmanager/transaction.go +++ b/proxy/txnmanager/transaction.go @@ -7,6 +7,7 @@ import ( b64 "encoding/base64" "encoding/json" "fmt" + "go.dedis.ch/dela/core/validation" "net/http" "strconv" "sync" @@ -32,7 +33,7 @@ const ( // NewTransactionManager returns a new initialized transaction manager func NewTransactionManager(mngr txn.Manager, p pool.Pool, - ctx serde.Context, pk kyber.Point, blocks blockstore.BlockStore, signer crypto.Signer) Manager { + ctx serde.Context, pk kyber.Point, blocks blockstore.BlockStore, signer crypto.Signer, val validation.Service) Manager { logger := dela.Logger.With().Timestamp().Str("role", "proxy-txmanager").Logger() @@ -44,6 +45,7 @@ func NewTransactionManager(mngr txn.Manager, p pool.Pool, pk: pk, blocks: blocks, signer: signer, + val: val, } } @@ -60,6 +62,7 @@ type manager struct { pk kyber.Point blocks blockstore.BlockStore signer crypto.Signer + val validation.Service } // StatusHandlerGet checks if the transaction is included in the blockchain @@ -183,10 +186,19 @@ func (h *manager) checkTxnIncluded(transactionID []byte, lastBlockIdx uint64) (T } // check if the transaction is in the block - transactions := blockLink.GetBlock().GetTransactions() + transactions := blockLink.GetBlock().GetData().GetTransactionResults() //blockLink.GetBlock().GetTransactions() + for _, txn := range transactions { - if bytes.Equal(txn.GetID(), transactionID) { - return IncludedTransaction, blockLink.GetBlock().GetIndex() + + if bytes.Equal(txn.GetTransaction().GetID(), transactionID) { + accepted, reason := txn.GetStatus() + if accepted { + return IncludedTransaction, blockLink.GetBlock().GetIndex() + } else { + println(reason) + return RejectedTransaction, blockLink.GetBlock().GetIndex() + } + } } diff --git a/proxy/types/election.go b/proxy/types/election.go index a6a8b5cfc..8058b50b4 100644 --- a/proxy/types/election.go +++ b/proxy/types/election.go @@ -6,10 +6,17 @@ import ( // CreateFormRequest defines the HTTP request for creating a form type CreateFormRequest struct { - AdminID string + UserID string Configuration etypes.Configuration } +// PermissionOperationRequest defines the HTTP request for performing +// an operation request +type PermissionOperationRequest struct { + TargetUserID string + PerformingUserID string +} + // CreateFormResponse defines the HTTP response when creating a form type CreateFormResponse struct { FormID string // hex-encoded @@ -18,7 +25,7 @@ type CreateFormResponse struct { // CastVoteRequest defines the HTTP request for casting a vote type CastVoteRequest struct { - UserID string + VoterID string // Marshalled representation of Ciphervote. It contains []{K:,C:} Ballot CiphervoteJSON } @@ -35,6 +42,7 @@ type EGPairJSON struct { // UpdateFormRequest defines the HTTP request for updating a form type UpdateFormRequest struct { Action string + UserID string } // GetFormResponse defines the HTTP response when getting the form info diff --git a/services/shuffle/mod.go b/services/shuffle/mod.go index f06f0254b..9ed31ab76 100644 --- a/services/shuffle/mod.go +++ b/services/shuffle/mod.go @@ -15,5 +15,5 @@ type Shuffle interface { type Actor interface { // Shuffle must be called by ONE of the actor to shuffle the list of ElGamal // pairs. Each node represented by a player must first execute Listen(). - Shuffle(formID []byte) (err error) + Shuffle(formID []byte, userID string) (err error) } diff --git a/services/shuffle/neff/handler.go b/services/shuffle/neff/handler.go index b7382a7b8..7412a728c 100644 --- a/services/shuffle/neff/handler.go +++ b/services/shuffle/neff/handler.go @@ -70,7 +70,7 @@ func (h *Handler) Stream(out mino.Sender, in mino.Receiver) error { switch msg := msg.(type) { case types.StartShuffle: - err := h.handleStartShuffle(msg.GetFormId()) + err := h.handleStartShuffle(msg.GetFormID(), msg.GetUserID()) if err != nil { return xerrors.Errorf("failed to handle StartShuffle message: %v", err) } @@ -81,7 +81,7 @@ func (h *Handler) Stream(out mino.Sender, in mino.Receiver) error { return nil } -func (h *Handler) handleStartShuffle(formID string) error { +func (h *Handler) handleStartShuffle(formID string, userID string) error { dela.Logger.Info().Msg("Starting the neff shuffle protocol ...") err := h.txmngr.Sync() @@ -108,7 +108,7 @@ func (h *Handler) handleStartShuffle(formID string) error { return xerrors.Errorf("the form must be closed: (%v)", form.Status) } - tx, err := h.makeTx(&form) + tx, err := h.makeTx(&form, userID) if err != nil { return xerrors.Errorf("failed to make tx: %v", err) } @@ -149,7 +149,7 @@ func (h *Handler) handleStartShuffle(formID string) error { } } -func (h *Handler) makeTx(form *etypes.Form) (txn.Transaction, error) { +func (h *Handler) makeTx(form *etypes.Form, userID string) (txn.Transaction, error) { shuffledBallots, getProver, err := h.getShuffledBallots(form) if err != nil { @@ -158,6 +158,7 @@ func (h *Handler) makeTx(form *etypes.Form) (txn.Transaction, error) { shuffleBallots := etypes.ShuffleBallots{ FormID: form.FormID, + UserID: userID, Round: len(form.ShuffleInstances), ShuffledBallots: shuffledBallots, } diff --git a/services/shuffle/neff/handler_test.go b/services/shuffle/neff/handler_test.go index 154b7a6f4..2ebe27e52 100644 --- a/services/shuffle/neff/handler_test.go +++ b/services/shuffle/neff/handler_test.go @@ -37,7 +37,7 @@ func TestHandler_Stream(t *testing.T) { require.EqualError(t, err, "expected StartShuffle message, got: fake.Message") receiver = fake.NewReceiver(fake.NewRecvMsg(fake.NewAddress(0), - types.NewStartShuffle("dummyID", make([]mino.Address, 0)))) + types.NewStartShuffle("dummyID", "123456", make([]mino.Address, 0)))) handler.txmngr = fake.Manager{} handler.service = &fake.Service{Forms: make(map[string]etypes.Form), BallotSnap: fake.NewSnapshot()} @@ -50,7 +50,7 @@ func TestHandler_Stream(t *testing.T) { dummyID := hex.EncodeToString([]byte("dummyId")) handler = initValidHandler(dummyID) - receiver = fake.NewReceiver(fake.NewRecvMsg(fake.NewAddress(0), types.NewStartShuffle(dummyID, make([]mino.Address, 0)))) + receiver = fake.NewReceiver(fake.NewRecvMsg(fake.NewAddress(0), types.NewStartShuffle(dummyID, "123456", make([]mino.Address, 0)))) err = handler.Stream(fake.Sender{}, receiver) require.NoError(t, err) @@ -76,7 +76,7 @@ func TestHandler_StartShuffle(t *testing.T) { handler.service = &badService handler.txmngr = fake.Manager{} - err := handler.handleStartShuffle(dummyID) + err := handler.handleStartShuffle(dummyID, "123456") require.EqualError(t, err, "failed to get form: while getting data for form: this key doesn't exist") // Form does not exist @@ -87,7 +87,7 @@ func TestHandler_StartShuffle(t *testing.T) { } handler.service = &service - err = handler.handleStartShuffle(dummyID) + err = handler.handleStartShuffle(dummyID, "123456") require.EqualError(t, err, "failed to get form: while getting data for form: this key doesn't exist") // Form still opened: @@ -107,7 +107,7 @@ func TestHandler_StartShuffle(t *testing.T) { handler.context = serdecontext handler.formFac = formFac - err = handler.handleStartShuffle(dummyID) + err = handler.handleStartShuffle(dummyID, "123456") require.EqualError(t, err, "the form must be closed: (0)") t.Skip("Doesn't work with new form because of snap needed by Form") @@ -140,7 +140,7 @@ func TestHandler_StartShuffle(t *testing.T) { C: Cs[i], }, } - form.CastVote(service.Context, snap, "dummyUser"+strconv.Itoa(i), ballot) + form.CastVote(service.Context, snap, "123456", ballot) } service = updateService(form, dummyID) @@ -155,7 +155,7 @@ func TestHandler_StartShuffle(t *testing.T) { handler.shuffleSigner = fake.NewBadSigner() - err = handler.handleStartShuffle(dummyID) + err = handler.handleStartShuffle(dummyID, "123456") require.EqualError(t, err, fake.Err("failed to make tx: could not sign the shuffle ")) // Bad common signer : @@ -168,7 +168,7 @@ func TestHandler_StartShuffle(t *testing.T) { handler.txmngr = fake.Manager{} - err = handler.handleStartShuffle(dummyID) + err = handler.handleStartShuffle(dummyID, "123456") require.EqualError(t, err, fake.Err("failed to make tx: failed to use manager")) manager := signed.NewManager(fake.NewSigner(), fakeClient{}) @@ -181,7 +181,7 @@ func TestHandler_StartShuffle(t *testing.T) { handler.service = &service handler.p = &fakePool - err = handler.handleStartShuffle(dummyID) + err = handler.handleStartShuffle(dummyID, "123456") require.NoError(t, err) // Threshold is reached : @@ -190,7 +190,7 @@ func TestHandler_StartShuffle(t *testing.T) { fakePool = fake.Pool{Service: &service} handler.service = &service - err = handler.handleStartShuffle(dummyID) + err = handler.handleStartShuffle(dummyID, "123456") require.NoError(t, err) // Service not working : @@ -200,7 +200,7 @@ func TestHandler_StartShuffle(t *testing.T) { fakePool = fake.Pool{Service: &service} handler.service = &service - err = handler.handleStartShuffle(dummyID) + err = handler.handleStartShuffle(dummyID, "123456") // all transactions got denied require.NoError(t, err) @@ -219,7 +219,7 @@ func TestHandler_StartShuffle(t *testing.T) { handler = *NewHandler(handler.me, &service, &fakePool, manager, handler.shuffleSigner, serdecontext, formFac) - err = handler.handleStartShuffle(dummyID) + err = handler.handleStartShuffle(dummyID, "123456") require.NoError(t, err) } diff --git a/services/shuffle/neff/json/mod.go b/services/shuffle/neff/json/mod.go index bc973c358..d03fa55e0 100644 --- a/services/shuffle/neff/json/mod.go +++ b/services/shuffle/neff/json/mod.go @@ -15,7 +15,8 @@ func init() { type Address []byte type StartShuffle struct { - FormId string + FormID string + UserID string Addresses []Address } @@ -60,7 +61,8 @@ func (f MsgFormat) Encode(ctx serde.Context, msg serde.Message) ([]byte, error) } startShuffle := StartShuffle{ - FormId: in.GetFormId(), + FormID: in.GetFormID(), + UserID: in.GetUserID(), Addresses: addrs, } m = Message{StartShuffle: &startShuffle} @@ -114,7 +116,7 @@ func (f MsgFormat) decodeStartShuffle(ctx serde.Context, startShuffle *StartShuf addrs[i] = fac.FromText(addr) } - s := types.NewStartShuffle(startShuffle.FormId, addrs) + s := types.NewStartShuffle(startShuffle.FormID, startShuffle.UserID, addrs) return s, nil } diff --git a/services/shuffle/neff/json/mod_test.go b/services/shuffle/neff/json/mod_test.go index 6f5bddc2a..6981bbae4 100644 --- a/services/shuffle/neff/json/mod_test.go +++ b/services/shuffle/neff/json/mod_test.go @@ -20,18 +20,18 @@ func TestMessageFormat_StartShuffle_Encode(t *testing.T) { _, err = format.Encode(ctx, fake.Message{}) require.EqualError(t, err, "unsupported message of type 'fake.Message'") - startShuffle := types.NewStartShuffle("", []mino.Address{fake.NewBadAddress()}) + startShuffle := types.NewStartShuffle("", "123456", []mino.Address{fake.NewBadAddress()}) _, err = format.Encode(ctx, startShuffle) require.EqualError(t, err, fake.Err("couldn't marshal address")) - startShuffle = types.NewStartShuffle("dummyId", []mino.Address{fake.NewAddress(0)}) + startShuffle = types.NewStartShuffle("dummyId", "123456", []mino.Address{fake.NewAddress(0)}) data, err := format.Encode(ctx, startShuffle) require.NoError(t, err) - regexp := `{"StartShuffle":{"FormId":"dummyId","Addresses":\["AAAAAA=="\]` - require.Regexp(t, regexp, string(data)) + regexp := `{"StartShuffle":{"FormID":"dummyId","UserID":"123456","Addresses":["AAAAAA=="]}}` + require.Equal(t, regexp, string(data)) } func TestMessageFormat_EndShuffle_Encode(t *testing.T) { @@ -64,6 +64,7 @@ func TestMessageFormat_StartShuffle_Decode(t *testing.T) { expected := types.NewStartShuffle( "dummyId", + "123456", []mino.Address{fake.NewAddress(0)}, ) @@ -72,7 +73,7 @@ func TestMessageFormat_StartShuffle_Decode(t *testing.T) { startShuffle, err := format.Decode(ctx, data) require.NoError(t, err) - require.Equal(t, expected.GetFormId(), startShuffle.(types.StartShuffle).GetFormId()) + require.Equal(t, expected.GetFormID(), startShuffle.(types.StartShuffle).GetFormID()) require.Len(t, startShuffle.(types.StartShuffle).GetAddresses(), len(expected.GetAddresses())) } diff --git a/services/shuffle/neff/mod.go b/services/shuffle/neff/mod.go index 3ac3220f2..9b29766a2 100644 --- a/services/shuffle/neff/mod.go +++ b/services/shuffle/neff/mod.go @@ -98,7 +98,7 @@ type Actor struct { // Shuffle must be called by ONE of the actors to shuffle the list of ElGamal // pairs. // Each node represented by a player must first execute Listen(). -func (a *Actor) Shuffle(formID []byte) error { +func (a *Actor) Shuffle(formID []byte, userID string) error { a.Lock() defer a.Unlock() @@ -133,7 +133,7 @@ func (a *Actor) Shuffle(formID []byte) error { dela.Logger.Info().Msgf("sending start shuffle to: %v", addrs) - message := types.NewStartShuffle(formIDHex, addrs) + message := types.NewStartShuffle(formIDHex, userID, addrs) errs := sender.Send(message, addrs...) err = <-errs @@ -142,7 +142,7 @@ func (a *Actor) Shuffle(formID []byte) error { //return xerrors.Errorf("failed to start shuffle: %v", err) } - err = a.waitAndCheckShuffling(message.GetFormId(), form.Roster.Len()) + err = a.waitAndCheckShuffling(message.GetFormID(), form.Roster.Len()) if err != nil { return xerrors.Errorf("failed to wait and check shuffling: %v", err) } diff --git a/services/shuffle/neff/mod_test.go b/services/shuffle/neff/mod_test.go index a21812e8d..eaa67e7d0 100644 --- a/services/shuffle/neff/mod_test.go +++ b/services/shuffle/neff/mod_test.go @@ -74,7 +74,7 @@ func TestNeffShuffle_Shuffle(t *testing.T) { formFac: etypes.NewFormFactory(etypes.CiphervoteFactory{}, fake.NewRosterFac(roster)), } - err = actor.Shuffle(formIDBuf) + err = actor.Shuffle(formIDBuf, "123456") require.EqualError(t, err, fake.Err("failed to stream")) rpc := fake.NewStreamRPC(fake.NewReceiver(), fake.NewBadSender()) @@ -89,7 +89,7 @@ func TestNeffShuffle_Shuffle(t *testing.T) { dela.Logger = zerolog.New(out) // should only output a warning - err = actor.Shuffle(formIDBuf) + err = actor.Shuffle(formIDBuf, "123456") require.NoError(t, err) require.True(t, strings.Contains(out.String(), "failed to start shuffle"), out.String()) @@ -97,7 +97,7 @@ func TestNeffShuffle_Shuffle(t *testing.T) { actor.rpc = rpc // we no longer use the receiver: - err = actor.Shuffle(formIDBuf) + err = actor.Shuffle(formIDBuf, "123456") require.NoError(t, err) recv := fake.NewReceiver(fake.NewRecvMsg(fake.NewAddress(0), types.NewEndShuffle())) @@ -105,7 +105,7 @@ func TestNeffShuffle_Shuffle(t *testing.T) { rpc = fake.NewStreamRPC(recv, fake.Sender{}) actor.rpc = rpc - err = actor.Shuffle(formIDBuf) + err = actor.Shuffle(formIDBuf, "123456") require.NoError(t, err) } diff --git a/services/shuffle/neff/types/messages.go b/services/shuffle/neff/types/messages.go index 9e7abbed6..635d7be8b 100644 --- a/services/shuffle/neff/types/messages.go +++ b/services/shuffle/neff/types/messages.go @@ -19,21 +19,28 @@ func RegisterMessageFormat(c serde.Format, f serde.FormatEngine) { // // - implements serde.Message type StartShuffle struct { - formId string + formID string + userID string addresses []mino.Address } // NewStartShuffle creates a new StartShuffle message. -func NewStartShuffle(formId string, addresses []mino.Address) StartShuffle { +func NewStartShuffle(formID string, userID string, addresses []mino.Address) StartShuffle { return StartShuffle{ - formId: formId, + formID: formID, + userID: userID, addresses: addresses, } } -// GetFormId returns the formId. -func (s StartShuffle) GetFormId() string { - return s.formId +// GetFormID returns the formId. +func (s StartShuffle) GetFormID() string { + return s.formID +} + +// GetUserID returns the formId. +func (s StartShuffle) GetUserID() string { + return s.userID } // GetAddresses returns the list of addresses. diff --git a/services/shuffle/neff/types/messages_test.go b/services/shuffle/neff/types/messages_test.go index e76dd6a73..454b296c2 100644 --- a/services/shuffle/neff/types/messages_test.go +++ b/services/shuffle/neff/types/messages_test.go @@ -17,13 +17,13 @@ func init() { } func TestStartShuffle_GetFormId(t *testing.T) { - startShuffle := NewStartShuffle("dummyId", nil) + startShuffle := NewStartShuffle("dummyId", "123456", nil) - require.Equal(t, "dummyId", startShuffle.GetFormId()) + require.Equal(t, "dummyId", startShuffle.GetFormID()) } func TestStartShuffle_GetAddresses(t *testing.T) { - startShuffle := NewStartShuffle("", []mino.Address{fake.NewAddress(0)}) + startShuffle := NewStartShuffle("", "123456", []mino.Address{fake.NewAddress(0)}) require.Len(t, startShuffle.GetAddresses(), 1) } diff --git a/web/backend/src/controllers/dela.ts b/web/backend/src/controllers/dela.ts index 8c725f9fc..876e20156 100644 --- a/web/backend/src/controllers/dela.ts +++ b/web/backend/src/controllers/dela.ts @@ -253,17 +253,20 @@ delaRouter.use('/*', (req, res) => { if (process.env.REACT_APP_RANDOMIZE_VOTE_ID === 'true') { // DEBUG: this is only for debugging and needs to be replaced before production console.warn('DEV CODE - randomizing the SCIPER ID to allow for unlimited votes'); - bodyData.UserID = makeid(10); + bodyData.VoterID = makeid(10); } else { // We must set the UserID to know who this ballot is associated to. This is // only needed to allow users to cast multiple ballots, where only the last // ballot is taken into account. To preserve anonymity, the web-backend could // translate UserIDs to another random ID. - bodyData.UserID = req.session.userId.toString(); + bodyData.VoterID = req.session.userId.toString(); } } + // UserID for permission + bodyData.UserID = req.session.userId.toString(); + const dataStr = JSON.stringify(bodyData); sendToDela(dataStr, req, res);