Skip to content

Commit

Permalink
Adding evoting-admin commands
Browse files Browse the repository at this point in the history
Showing all elections
Allow voting if the private key is known
  • Loading branch information
ineiti committed Jun 28, 2023
1 parent 10a13c0 commit 1d37c7e
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 37 deletions.
23 changes: 17 additions & 6 deletions evoting/api.go
Expand Up @@ -3,6 +3,7 @@
package evoting

import (
"go.dedis.ch/cothority/v3/skipchain"
"go.dedis.ch/onet/v3"

"go.dedis.ch/cothority/v3"
Expand All @@ -16,16 +17,17 @@ type Client struct {
*onet.Client
// If LookupURL is set, use it for SCIPER lookups (for tests).
LookupURL string
Roster *onet.Roster
}

// NewClient instantiates a new evoting.Client.
func NewClient() *Client {
return &Client{Client: onet.NewClient(cothority.Suite, ServiceName)}
func NewClient(roster *onet.Roster) *Client {
return &Client{Client: onet.NewClient(cothority.Suite, ServiceName), Roster: roster}
}

// Ping a random server which increments the nonce.
func (c *Client) Ping(roster *onet.Roster, nonce uint32) (*Ping, error) {
dest := roster.RandomServerIdentity()
func (c *Client) Ping(nonce uint32) (*Ping, error) {
dest := c.Roster.RandomServerIdentity()
reply := &Ping{}
if err := c.SendProtobuf(dest, &Ping{Nonce: nonce}, reply); err != nil {
return nil, err
Expand All @@ -34,8 +36,17 @@ func (c *Client) Ping(roster *onet.Roster, nonce uint32) (*Ping, error) {
}

// LookupSciper returns information about a sciper number.
func (c *Client) LookupSciper(roster *onet.Roster, sciper string) (reply *LookupSciperReply, err error) {
func (c *Client) LookupSciper(sciper string) (reply *LookupSciperReply, err error) {
reply = &LookupSciperReply{}
err = c.SendProtobuf(roster.RandomServerIdentity(), &LookupSciper{Sciper: sciper, LookupURL: c.LookupURL}, reply)
err = c.SendProtobuf(c.Roster.RandomServerIdentity(), &LookupSciper{Sciper: sciper, LookupURL: c.LookupURL}, reply)
return
}

// Reconstruct returns the reconstructed votes.
// If the election is not yet finished, it will return an error
func (c *Client) Reconstruct(id skipchain.SkipBlockID) (rr ReconstructReply, err error) {
err = c.SendProtobuf(c.Roster.List[0], &Reconstruct{
ID: id,
}, &rr)
return
}
4 changes: 2 additions & 2 deletions evoting/api_test.go
Expand Up @@ -23,7 +23,7 @@ func TestPing(t *testing.T) {

_, roster, _ := local.GenTree(3, true)

c := evoting.NewClient()
r, _ := c.Ping(roster, 0)
c := evoting.NewClient(roster)
r, _ := c.Ping(0)
assert.Equal(t, uint32(1), r.Nonce)
}
156 changes: 127 additions & 29 deletions evoting/evoting-admin/app.go
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/json"
"flag"
"fmt"
"go.dedis.ch/cothority/v3/evoting/lib"
"os"
"strconv"
"strings"
Expand All @@ -23,56 +24,104 @@ import (
)

var (
argRoster = flag.String("roster", "", "path to roster toml file")
argAdmins = flag.String("admins", "", "list of admin users")
argPin = flag.String("pin", "", "service pin")
argKey = flag.String("key", "", "public key of authentication server")
argID = flag.String("id", "", "ID of the master chain to modify (optional)")
argUser = flag.Int("user", 0, "The SCIPER of an existing admin of this chain")
argSig = flag.String("sig", "", "A signature proving that you can login to Tequila with the given SCIPER.")
argShow = flag.Bool("show", false, "Show the current Master config")
argDumpVoters = flag.Bool("dumpvoters", false, "Dump a list of voters for election skipchain specified with -id (ballot de-duplication has already been taken into account, order is preserved)")
argDumpElection = flag.Bool("dumpelection", false, "Dump the current election config for the election specified with -id.")
argJSON = flag.Bool("json", false, "Dump in json mode.")
argLoad = flag.String("load", "", "Load the specified json file to modify the election specified with -id.")
argRoster = flag.String("roster", "", "path to roster toml file")
argAdmins = flag.String("admins", "", "list of admin users")
argPin = flag.String("pin", "", "service pin")
argKey = flag.String("key", "", "public key of authentication server")
argPrivate = flag.String("private", "", "private key of authentication server for voting")
argID = flag.String("id", "", "ID of the master chain to modify (optional)")
argUser = flag.Int("user", 0, "The SCIPER of an existing admin of this chain")
argSig = flag.String("sig", "", "A signature proving that you can login to Tequila with the given SCIPER.")
argShow = flag.Bool("show", false, "Show the current Master config")
argDumpVoters = flag.Bool("dumpvoters", false, "Dump a list of voters for election skipchain specified with -id (ballot de-duplication has already been taken into account, order is preserved)")
argDumpElection = flag.Bool("dumpelection", false, "Dump the current election config for the election specified with -id.")
argJSON = flag.Bool("json", false, "Dump in json mode.")
argLoad = flag.String("load", "", "Load the specified json file to modify the election specified with -id.")
argVoteCandidates = flag.String("voteCandidates", "", "Coma delimited list of SCIPERs to vote for")
argDebug = flag.Int("debug", 0, "Debugging level")
)

func main() {
flag.Parse()

if *argDebug > 0 {
log.SetDebugVisible(*argDebug)
}

if *argRoster == "" {
log.Fatal("Roster argument (-roster) is required for create, update, or show.")
}
roster, err := parseRoster(*argRoster)
if err != nil {
log.Fatal("cannot parse roster: ", err)
}
client := onet.NewClient(cothority.Suite, evoting.ServiceName)
cl := evoting.NewClient(roster)

var pub kyber.Point
var priv kyber.Scalar
if *argPrivate != "" {
// If we get the private key, calculate the corresponding public key
b, err := hex.DecodeString(*argPrivate)
log.ErrFatal(err, "while parsing private key")

log.Lvl1("Setting the private key")
priv = cothority.Suite.Scalar()
err = priv.UnmarshalBinary(b)
log.ErrFatal(err, "while unmarshalling private key")
pub = cothority.Suite.Point().Mul(priv, nil)
} else if *argKey != "" {
// If we only get the public key
pub, err = parseKey(*argKey)
if err != nil {
log.Fatal("cannot parse key: ", err)
}
} else {
// If we get no key, create them again
kp := key.NewKeyPair(cothority.Suite)
priv = kp.Private
pub = kp.Public
}

if *argShow {
if *argID == "" {
log.Fatal("Please give ID of master chain")
}
id, err := hex.DecodeString(*argID)
if err != nil {
log.Fatal("id decode", err)
}
request := &evoting.GetElections{Master: id}
if priv != nil && *argUser > 0 {
request.User = uint32(*argUser)
request.Signature = lib.GenerateSignature(priv, id, request.User)
} else {
fmt.Println("You can give '-private PRIVATE_KEY -user ADMINID' for a list of available elections to this user")
}
reply := &evoting.GetElectionsReply{}
client := onet.NewClient(cothority.Suite, evoting.ServiceName)
if err = client.SendProtobuf(roster.List[0], request, reply); err != nil {
log.Fatal("get elections request: ", err)
}
m := reply.Master
fmt.Printf(" Admins: %v\n", m.Admins)
fmt.Printf(" Roster: %v\n", m.Roster.List)
fmt.Printf(" Key: %v\n", m.Key)
for _, election := range reply.Elections {
fmt.Printf("\nElection: %s\n", election.Name)
fmt.Printf(" ID: %x\n", election.ID)
}
return
}

if *argDumpVoters {
if *argID == "" {
log.Fatal("Please give ID of master chain")
}
id, err := hex.DecodeString(*argID)
if err != nil {
log.Fatal("id decode", err)
}
reply := &evoting.GetBoxReply{}
client := onet.NewClient(cothority.Suite, evoting.ServiceName)
if err = client.SendProtobuf(roster.List[0], &evoting.GetBox{ID: id}, reply); err != nil {
log.Fatal("get box request: ", err)
}
Expand All @@ -84,12 +133,14 @@ func main() {
}

if *argDumpElection {
if *argID == "" {
log.Fatal("Please give an ID")
}
id, err := hex.DecodeString(*argID)
if err != nil {
log.Fatal("id decode", err)
}
reply := &evoting.GetBoxReply{}
client := onet.NewClient(cothority.Suite, evoting.ServiceName)
if err = client.SendProtobuf(roster.List[0], &evoting.GetBox{ID: id}, reply); err != nil {
log.Fatal("get box request: ", err)
}
Expand Down Expand Up @@ -123,19 +174,34 @@ func main() {
out.WriteTo(os.Stdout)
} else {
fmt.Println(reply.Election)
if reply.Election.Stage == lib.Decrypted {
reconstructReply, err := cl.Reconstruct(id)
log.ErrFatal(err, "While getting reconstructed votes")
for i, p := range reconstructReply.Points {
d, _ := p.Data()
fmt.Printf("Vote %d with data %x", i, d)
for _, p := range reconstructReply.AdditionalPoints[i].AdditionalPoints {
d, _ := p.Data()
fmt.Printf(",%x", d)
}
fmt.Println()
}
}
}
return
}

if *argLoad != "" {
if *argID == "" {
log.Fatal("Please give ID of master chain")
}
id, err := hex.DecodeString(*argID)
if err != nil {
log.Fatal("id decode", err)
}

// Look up the election
reply := &evoting.GetBoxReply{}
client := onet.NewClient(cothority.Suite, evoting.ServiceName)
if err = client.SendProtobuf(roster.List[0], &evoting.GetBox{ID: id}, reply); err != nil {
log.Fatal("get box request:", err)
}
Expand Down Expand Up @@ -194,6 +260,50 @@ func main() {
return
}

if len(*argVoteCandidates) > 0 {
voteUser := uint32(*argUser)
if len(*argID) != 64 {
log.Fatal("Need an ID of the election chain in hex form of 32 bytes")
}
voteID, err := hex.DecodeString(*argID)
log.ErrFatal(err, "Wrong ID for election chain")

log.Lvl1("Getting election information from the chain")
reply := &evoting.GetBoxReply{}
if err = client.SendProtobuf(roster.List[0], &evoting.GetBox{ID: voteID}, reply); err != nil {
log.Fatal("get box request:", err)
}

if len(*argVoteCandidates) < 6 {
log.Fatal("Need at least one candidate to vote for")
}
voteCandidatesStrings := strings.Split(*argVoteCandidates, ",")
voteCandidates := make([]uint32, len(voteCandidatesStrings))
for i, c := range voteCandidatesStrings {
cand, err := strconv.Atoi(c)
log.ErrFatal(err, "Wrong SCIPER ID for candidate")
voteCandidates[i] = uint32(cand)
}

if priv == nil {
log.Fatal("Need the private key to vote")
}

fmt.Printf("Votes are: %+v\n", voteCandidates)

ballot := lib.CreateBallot(reply.Election.MaxChoices, reply.Election.Key, voteUser, voteCandidates)
request := &evoting.Cast{
ID: voteID,
User: voteUser,
Ballot: &ballot,
Signature: lib.GenerateSignature(priv, reply.Election.Master, voteUser),
}
replyCast := &evoting.CastReply{}
log.ErrFatal(client.SendProtobuf(roster.List[0], request, replyCast))
log.Info("Successfully cast this vote")
return
}

if *argAdmins == "" {
log.Fatal("Admin list (-admins) must have at least one id.")
}
Expand All @@ -207,18 +317,6 @@ func main() {
log.Fatal("pin must be set for create and update operations.")
}

var pub kyber.Point
if *argKey != "" {
pub, err = parseKey(*argKey)
if err != nil {
log.Fatal("cannot parse key: ", err)
}
} else {
kp := key.NewKeyPair(cothority.Suite)
log.Infof("Auth-server private key: %v", kp.Private)
pub = kp.Public
}

request := &evoting.Link{Pin: *argPin, Roster: roster, Key: pub, Admins: admins}
if *argID != "" {
id, err := hex.DecodeString(*argID)
Expand All @@ -241,11 +339,11 @@ func main() {
}
reply := &evoting.LinkReply{}

client := onet.NewClient(cothority.Suite, evoting.ServiceName)
if err = client.SendProtobuf(roster.List[0], request, reply); err != nil {
log.Fatal("link request: ", err)
}

log.Infof("Auth-server private key: %v", priv)
log.Infof("Auth-server public key: %v", pub)
log.Infof("Master ID: %x", reply.ID)
}
Expand Down

0 comments on commit 1d37c7e

Please sign in to comment.