M cmd/tallyard/main.go => cmd/tallyard/main.go +2 -1
@@ 115,7 115,7 @@ func main() {
// user likely hit C-c
return
}
- el.LocalVoter.Poly = math.NewRandomPoly(uint(len(*el.FinalJoinIDs)-1), 1024, *ballot)
+ el.LocalVoter.Poly = math.NewRandomPoly(*ballot, len(*el.FinalJoinIDs), &el.LocalVoter.Input)
el.Save()
}
@@ 172,6 172,7 @@ var electionFilter = &mautrix.Filter{
election.CreateElectionMessage,
election.JoinElectionMessage,
election.StartElectionMessage,
+ election.KeysMessage,
election.EvalsMessage,
election.SumMessage,
},
M election/election.go => election/election.go +1 -1
@@ 17,7 17,7 @@ type Election struct {
FinalJoinIDs *[]id.EventID `json:"final_voters,omitempty"`
Joins map[id.EventID]*Voter `json:"joins"`
LocalVoter *LocalVoter `json:"local_voter,omitempty"`
- Result *[]byte `json:"result,omitempty"`
+ Result *[32]byte `json:"result,omitempty"`
RoomID id.RoomID `json:"room_id"`
StartID *id.EventID `json:"start_id,omitempty"`
Title string `json:"title"`
M election/event.go => election/event.go +22 -0
@@ 55,6 55,8 @@ func (store *EventStore) UnmarshalJSON(b []byte) error {
event.Type = JoinElectionMessage
case StartElectionMessage.Type:
event.Type = StartElectionMessage
+ case KeysMessage.Type:
+ event.Type = KeysMessage
case EvalsMessage.Type:
event.Type = EvalsMessage
case SumMessage.Type:
@@ 82,6 84,11 @@ type StartEvent struct {
*StartElectionContent
}
+type KeysEvent struct {
+ *event.Event
+ *KeysMessageContent
+}
+
type EvalsEvent struct {
*event.Event
*EvalsMessageContent
@@ 137,6 144,21 @@ func (store *EventStore) GetStartEvent(roomID id.RoomID, startID id.EventID) *St
}
}
+func (store *EventStore) GetKeysEvent(roomID id.RoomID, keysID id.EventID) *KeysEvent {
+ evt, err := store.getAndHandleEvent(roomID, keysID, KeysMessage)
+ if err != nil {
+ log.Warnf("an error occurred getting keys event '%s': %s", keysID, err)
+ return nil
+ }
+ if evt == nil {
+ return nil
+ }
+ return &KeysEvent{
+ evt,
+ evt.Content.Parsed.(*KeysMessageContent),
+ }
+}
+
func (store *EventStore) GetEvalsEvent(roomID id.RoomID, evalsID id.EventID) *EvalsEvent {
evt, err := store.getAndHandleEvent(roomID, evalsID, EvalsMessage)
if err != nil {
M election/map.go => election/map.go +1 -1
@@ 37,7 37,7 @@ type ElectionsMap struct {
save func(*ElectionsMap) `json:"-"`
}
-const electionsMapVersion = 5
+const electionsMapVersion = 6
func NewElectionsMap(userID id.UserID, save func(*ElectionsMap)) *ElectionsMap {
return &ElectionsMap{
M election/msg.go => election/msg.go +444 -111
@@ 1,18 1,24 @@
package election
import (
+ "bytes"
"encoding/base64"
"fmt"
- "math/big"
"reflect"
"time"
+ "github.com/consensys/gnark/backend/groth16"
+ "github.com/consensys/gnark/crypto/hash/mimc/bls381"
+ "github.com/consensys/gnark/frontend"
+ "github.com/consensys/gurvy"
+ "github.com/consensys/gurvy/bls381/fr"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/nacl/box"
"golang.org/x/mod/semver"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
+ "tallyard.xyz/math"
)
var (
@@ 32,6 38,11 @@ var (
Type: "xyz.tallyard.start",
Class: event.MessageEventType,
}
+ // contains keys needed to do zero knowledge proofs
+ KeysMessage = event.Type{
+ Type: "xyz.tallyard.keys",
+ Class: event.MessageEventType,
+ }
// indicate's user's evaluations of their polynomial using others' inputs
EvalsMessage = event.Type{
Type: "xyz.tallyard.evals",
@@ 57,6 68,7 @@ type JoinElectionContent struct {
CreateID id.EventID `json:"create_id"`
Input string `json:"input"`
PubKey string `json:"pub_key"`
+ HashSeed string `json:"hash_seed"`
}
type StartElectionContent struct {
@@ 66,12 78,32 @@ type StartElectionContent struct {
JoinIDs []id.EventID `json:"join_ids"`
}
+type KeysMessageContent struct {
+ Version string `json:"version"`
+
+ EvalProvingKeyURI id.ContentURI `json:"eval_proving_key_uri"`
+ JoinID id.EventID `json:"join_id"`
+ StartID id.EventID `json:"start_id"`
+ SumProvingKeyURI id.ContentURI `json:"sum_proving_key_uri"`
+}
+
type EvalsMessageContent struct {
Version string `json:"version"`
- Evals map[id.EventID]string `json:"evals"`
- JoinID id.EventID `json:"join_id"`
- StartID id.EventID `json:"start_id"`
+ Evals []Eval `json:"evals"`
+ JoinID id.EventID `json:"join_id"`
+ KeysIDs []id.EventID `json:"keys_ids"`
+}
+
+type Eval struct {
+ // encrypted for specific voter
+ Output string `json:"output"`
+ // public; used by everyone in summation proofs
+ OutputHash string `json:"output_hash"`
+ // encrypted for specific voter
+ POutputHash string `json:"poutput_hash"`
+ // encrypted for specific voter
+ Proof string `json:"proof"`
}
type SumMessageContent struct {
@@ 80,20 112,14 @@ type SumMessageContent struct {
EvalsIDs []id.EventID `json:"evals_ids"`
JoinID id.EventID `json:"join_id"`
Sum string `json:"sum"`
-}
-
-type ResultMessageContent struct {
- Version string `json:"version"`
-
- JoinID id.EventID `json:"join_id"`
- Result string `json:"result"`
- SumIDs []id.EventID `json:"sums"`
+ Proofs []string `json:"proofs"`
}
func init() {
event.TypeMap[CreateElectionMessage] = reflect.TypeOf(CreateElectionContent{})
event.TypeMap[JoinElectionMessage] = reflect.TypeOf(JoinElectionContent{})
event.TypeMap[StartElectionMessage] = reflect.TypeOf(StartElectionContent{})
+ event.TypeMap[KeysMessage] = reflect.TypeOf(KeysMessageContent{})
event.TypeMap[EvalsMessage] = reflect.TypeOf(EvalsMessageContent{})
event.TypeMap[SumMessage] = reflect.TypeOf(SumMessageContent{})
}
@@ 113,7 139,8 @@ func (elections *ElectionsMap) SetupEventHooks(client *mautrix.Client, syncer ma
eventStore.Processing = make(map[id.EventID]struct{})
}
- wrapper := func(f func(*event.Event) bool) func(*event.Event) bool {
+ // TODO: this is a freaking mess
+ wrapper := func(f func(*event.Event, *mautrix.Client) bool) func(*event.Event) bool {
return func(evt *event.Event) bool {
if evt.Unsigned.RedactedBecause != nil {
log.Debugf("event %s was redacted", evt.ID)
@@ 140,7 167,7 @@ func (elections *ElectionsMap) SetupEventHooks(client *mautrix.Client, syncer ma
eventStore.Processing[evt.ID] = struct{}{}
eventStore.Unlock()
}
- success := f(evt)
+ success := f(evt, client)
eventStore.Lock()
// see EventStore doc for success explanation
if success {
@@ 153,12 180,18 @@ func (elections *ElectionsMap) SetupEventHooks(client *mautrix.Client, syncer ma
return success
}
}
+ wrapperNoClient := func(f func(*event.Event) bool) func(*event.Event) bool {
+ return wrapper(func(evt *event.Event, _ *mautrix.Client) bool {
+ return f(evt)
+ })
+ }
- eventHandlers[CreateElectionMessage] = wrapper(elections.onCreateElectionMessage)
- eventHandlers[JoinElectionMessage] = wrapper(elections.onJoinElectionMessage)
- eventHandlers[StartElectionMessage] = wrapper(elections.onStartElectionMessage)
- eventHandlers[EvalsMessage] = wrapper(elections.onEvalsMessage)
- eventHandlers[SumMessage] = wrapper(elections.onSumMessage)
+ eventHandlers[CreateElectionMessage] = wrapperNoClient(elections.onCreateElectionMessage)
+ eventHandlers[JoinElectionMessage] = wrapperNoClient(elections.onJoinElectionMessage)
+ eventHandlers[StartElectionMessage] = wrapperNoClient(elections.onStartElectionMessage)
+ eventHandlers[KeysMessage] = wrapper(elections.onKeysMessage)
+ eventHandlers[EvalsMessage] = wrapperNoClient(elections.onEvalsMessage)
+ eventHandlers[SumMessage] = wrapperNoClient(elections.onSumMessage)
for eventType, handler := range eventHandlers {
func(handler func(*event.Event) bool) {
@@ 206,6 239,11 @@ func (elections *ElectionsMap) onCreateElectionMessage(evt *event.Event) (succes
return
}
+ if len(content.Candidates) < 2 || len(content.Candidates) > 5 {
+ warnf("the number of candidates must be between 2 and 5 (inclusive), but was %s", len(content.Candidates))
+ return
+ }
+
elections.AddElection(NewElection(
content.Candidates,
*evt,
@@ 216,6 254,8 @@ func (elections *ElectionsMap) onCreateElectionMessage(evt *event.Event) (succes
return true
}
+var zero *fr.Element = new(fr.Element).SetZero()
+
func (elections *ElectionsMap) onJoinElectionMessage(evt *event.Event) (success bool) {
errorf, warnf, debugf := logFuncs(fmt.Sprintf("ignoring %s's join msg (%s) since %s", evt.Sender, evt.ID, "%s"))
@@ 225,37 265,55 @@ func (elections *ElectionsMap) onJoinElectionMessage(evt *event.Event) (success
return
}
+ // Version
if incompatibleVersion(content.Version) {
debugf("the version is incompatible")
return
}
+ // CreateID
createEvt := elections.EventStore.GetCreateEvent(evt.RoomID, content.CreateID)
if createEvt == nil {
debugf("we couldn't get the create event, %s", content.CreateID)
return
}
- bytes, err := base64.StdEncoding.DecodeString(content.Input)
+ // HashSeed
+ if content.HashSeed == "" {
+ warnf("the hash seed is empty")
+ return
+ }
+ hashSeed, err := base64.StdEncoding.DecodeString(content.HashSeed)
if err != nil {
- warnf("we couldn't decode their input: %s", err)
+ warnf("we couldn't decode their hash seed: %s", err)
+ return
+ }
+ if len(hashSeed) < 32 {
+ warnf("their hash seed is fewer than 32 bytes")
return
}
- input := new(big.Int).SetBytes(bytes)
+ // Input
+ byts, err := base64.StdEncoding.DecodeString(content.Input)
+ if err != nil {
+ warnf("we couldn't decode their input: %s", err)
+ return
+ }
+ input := new(fr.Element).SetBytes(byts)
// zero is not allowed
- if input.Cmp(new(big.Int)) == 0 {
+ if input.Cmp(zero) == 0 {
warnf("their input is zero")
return
}
- bytes, err = base64.StdEncoding.DecodeString(content.PubKey)
+ // PubKey
+ byts, err = base64.StdEncoding.DecodeString(content.PubKey)
if err != nil {
warnf("we couldn't decode their public key: %s", err)
return
}
var pubKey [32]byte
- copy(pubKey[:], bytes)
+ copy(pubKey[:], byts)
el := elections.GetElection(createEvt.ID)
if el == nil {
@@ 269,7 327,7 @@ func (elections *ElectionsMap) onJoinElectionMessage(evt *event.Event) (success
defer el.Save()
defer el.Unlock()
- el.Joins[evt.ID] = NewVoter(input, evt, &pubKey)
+ el.Joins[evt.ID] = NewVoter(input, evt, &pubKey, hashSeed)
return true
}
@@ 308,7 366,7 @@ func (elections *ElectionsMap) onStartElectionMessage(evt *event.Event) (success
}
if createEvt.Sender != evt.Sender {
- warnf("they didn't create the election")
+ warnf("they didn't create the election; %s did", createEvt.Sender)
return
}
@@ 331,6 389,126 @@ func (elections *ElectionsMap) onStartElectionMessage(evt *event.Event) (success
el.StartID = &evt.ID
el.FinalJoinIDs = &content.JoinIDs
+ for i, joinID := range *el.FinalJoinIDs {
+ // TODO: gross
+ joinIndex := i
+ el.Joins[joinID].JoinIDIndex = &joinIndex
+ }
+
+ return true
+}
+
+func (elections *ElectionsMap) onKeysMessage(evt *event.Event, client *mautrix.Client) (success bool) {
+ errorf, warnf, debugf := logFuncs(fmt.Sprintf("ignoring %s's keys msg (%s) since %s", evt.Sender, evt.ID, "%s"))
+
+ content, ok := evt.Content.Parsed.(*KeysMessageContent)
+ if !ok {
+ warnf("we couldn't cast message content to KeysMessageContent")
+ return
+ }
+
+ // Version
+ if incompatibleVersion(content.Version) {
+ debugf("the version is incompatible")
+ return
+ }
+
+ joinEvt := elections.EventStore.GetJoinEvent(evt.RoomID, content.JoinID)
+ if joinEvt == nil {
+ debugf("we couldn't get the join event, %s", content.JoinID)
+ return
+ }
+ if evt.Sender != joinEvt.Sender {
+ warnf("they did not send the join event; %s did", joinEvt.Sender)
+ return
+ }
+
+ // process the start event so that JoinIDIndex is availabe below
+ if elections.EventStore.GetStartEvent(evt.RoomID, content.StartID) == nil {
+ debugf("we couldn't process the start event, %s", content.StartID)
+ return
+ }
+
+ el := elections.GetElection(joinEvt.CreateID)
+ if el == nil {
+ // should never happen because we retrieved the join event above
+ errorf("election %s doesn't exist", joinEvt.ID)
+ return
+ }
+
+ var evalProvingKey groth16.ProvingKey
+ {
+ // yes, I know we should be using Download here
+ byts, err := client.DownloadBytes(content.EvalProvingKeyURI)
+ if err != nil {
+ errorf("couldn't download eval proving key: %s", err)
+ return
+ }
+ tryEvalPk:
+ var buf bytes.Buffer
+ _, err = buf.Write(byts)
+ if err != nil {
+ errorf("couldn't write to eval bytes buffer: %s")
+ return
+ }
+ evalProvingKey = groth16.NewProvingKey(gurvy.BLS381)
+ _, err = evalProvingKey.ReadFrom(&buf)
+ if err != nil {
+ log.Errorf("couldn't read eval key from downloaded eval proving keys file: %s", err)
+ log.Debug("trying again...")
+ goto tryEvalPk
+ }
+ }
+
+ var sumProvingKey groth16.ProvingKey
+ {
+ byts, err := client.DownloadBytes(content.SumProvingKeyURI)
+ if err != nil {
+ errorf("couldn't download sum proving key: %s", err)
+ return
+ }
+ trySumPk:
+ var buf bytes.Buffer
+ _, err = buf.Write(byts)
+ if err != nil {
+ errorf("couldn't write to sum bytes buffer: %s")
+ return
+ }
+ sumProvingKey = groth16.NewProvingKey(gurvy.BLS381)
+ _, err = sumProvingKey.ReadFrom(&buf)
+ if err != nil {
+ log.Errorf("couldn't read sum key from downloaded sum keys file: %s", err)
+ log.Debug("trying again...")
+ goto trySumPk
+ }
+ }
+
+ var voter *Voter
+ {
+ var exists bool
+ // join should exist because we processed the start event above
+ if voter, exists = el.Joins[content.JoinID]; !exists {
+ warnf("the join event (%s) does not point to the election", content.JoinID)
+ return
+ }
+ if voter.JoinIDIndex == nil {
+ warnf("the join ID (%s) was not included in the start event (%s)", content.JoinID, content.StartID)
+ return
+ }
+ }
+
+ if voter.KeysID != nil {
+ warnf("voter already sent a keys message")
+ return
+ }
+
+ el.Lock()
+ defer el.Save()
+ defer el.Unlock()
+
+ voter.EvalProvingKey = &evalProvingKey
+ voter.SumProvingKey = &sumProvingKey
+ voter.KeysID = &evt.ID
return true
}
@@ 344,46 522,52 @@ func (elections *ElectionsMap) onEvalsMessage(evt *event.Event) (success bool) {
return
}
+ // Version
if incompatibleVersion(content.Version) {
debugf("the version is incompatible")
return
}
- startEvt := elections.EventStore.GetStartEvent(evt.RoomID, content.StartID)
- if startEvt == nil {
- debugf("we couldn't get the start event, %s", content.StartID)
- return
- }
-
+ // JoinID
joinEvt := elections.EventStore.GetJoinEvent(evt.RoomID, content.JoinID)
if joinEvt == nil {
debugf("we couldn't get the join event, %s", content.JoinID)
return
}
+ if evt.Sender != joinEvt.Sender {
+ warnf("they did not send the join event; %s did", joinEvt.Sender)
+ return
+ }
- // ensure keys of Evals are in JoinIDs of start event
- if len(content.Evals) != len(startEvt.JoinIDs) {
- warnf("the number of evals is wrong (%s instead of %s)",
- len(content.Evals), len(startEvt.JoinIDs))
+ el := elections.GetElection(joinEvt.CreateID)
+ if el == nil {
+ // should never happen because we got the start event above
+ errorf("election %s doesn't exist", joinEvt.CreateID)
+ return
+ }
+
+ // KeysIDs
+ if len(content.KeysIDs) != len(*el.FinalJoinIDs) {
+ warnf("the number of keys IDs is wrong (%s instead of %s)",
+ len(content.KeysIDs), len(*el.FinalJoinIDs))
return
}
- for _, joinID := range startEvt.JoinIDs {
- if _, exists := content.Evals[joinID]; !exists {
- warnf("it doesn't include an eval for join %s", joinID)
+ for i, keysID := range content.KeysIDs {
+ keysEvent := elections.EventStore.GetKeysEvent(evt.RoomID, keysID)
+ if keysEvent == nil {
+ debugf("we couldn't get the keys event, %s", keysID)
+ return
+ }
+ if keysEvent.JoinID != (*el.FinalJoinIDs)[i] {
+ warnf("the join ID (%s) of a key (%s) does not match the start event's join ID (%s)",
+ keysEvent.JoinID, keysID, (*el.FinalJoinIDs)[i])
return
}
- }
- // is the voter's join ID in the start event's list of join IDs?
- if _, exists := content.Evals[joinEvt.ID]; !exists {
- warnf("the join event %s is not listed in the start event's join IDs", joinEvt.ID)
- return
}
- el := elections.GetElection(startEvt.CreateID)
- if el == nil {
- // should never happen because we retrieved the start/join
- // events above
- errorf("election %s doesn't exist", startEvt.CreateID)
+ if len(content.Evals) != len(*el.FinalJoinIDs) {
+ warnf("the number of evals is wrong (%s instead of %s)",
+ len(content.Evals), len(*el.FinalJoinIDs))
return
}
@@ 393,54 577,147 @@ func (elections *ElectionsMap) onEvalsMessage(evt *event.Event) (success bool) {
voter := el.Joins[joinEvt.ID]
if voter == nil {
- // should never happen because we called getJoinEvent above
+ // should never happen because we called GetJoinEvent above
errorf("voter %s doesn't exist", joinEvt.ID)
return
}
-
+ if voter.JoinIDIndex == nil {
+ warnf("the voter's join ID was not included in the start event")
+ return
+ }
if voter.EvalsID != nil {
warnf("voter submitted multiple evals events")
return
}
-
voter.EvalsID = &evt.ID
+ // we're not participating in this election
if el.LocalVoter == nil {
return true
+ } else if el.LocalVoter.JoinIDIndex == nil {
+ warnf("we didn't join the election in time (or the election creator excluded us)")
+ return
}
+ ourEval := content.Evals[*el.LocalVoter.JoinIDIndex]
- encodedEncryptedOutput, exists := content.Evals[el.LocalVoter.JoinEvt.ID]
- // If our ID doesn't exist in the keys of evalsEvtContent.Evals, our
- // JoinID wasn't included in the startEvtContent.JoinIDs (since we
- // checked that the two are equivalent above). I'm checking membership
- // in Evals instead of startEvt.JoinIDs because maps are easier than
- // slices for that.
- if !exists {
- debugf("we didn't join the election in time (or the election creator excluded us)")
- return
+ // decrypt output
+ var output *fr.Element
+ {
+ encryptedOutput, err := base64.StdEncoding.DecodeString(ourEval.Output)
+ if err != nil {
+ warnf("couldn't decode the encrypted output for us: %s", err)
+ return
+ }
+ var decryptNonce [24]byte
+ copy(decryptNonce[:], encryptedOutput[:24])
+ decryptedOutput, ok := box.Open(nil, encryptedOutput[24:], &decryptNonce, &voter.PubKey, &el.LocalVoter.PrivKey)
+ if !ok {
+ warnf("couldn't decrypt eval output for us")
+ return
+ }
+ output = new(fr.Element).SetBytes(decryptedOutput)
}
- encryptedOutput, err := base64.StdEncoding.DecodeString(encodedEncryptedOutput)
- if err != nil {
- warnf("couldn't decode %s's encrypted output: %s", evt.Sender, err)
- return
+
+ // decrypt proof
+ var proof groth16.Proof
+ {
+ encryptedProof, err := base64.StdEncoding.DecodeString(ourEval.Proof)
+ if err != nil {
+ warnf("couldn't decode the encrypted proof for us: %s", err)
+ return
+ }
+ var decryptNonce [24]byte
+ copy(decryptNonce[:], encryptedProof[:24])
+ decryptedProofBytes, ok := box.Open(nil, encryptedProof[24:], &decryptNonce, &voter.PubKey, &el.LocalVoter.PrivKey)
+ if !ok {
+ warnf("couldn't decrypt eval proof for us")
+ return
+ }
+ proof = groth16.NewProof(gurvy.BLS381)
+ _, err = proof.ReadFrom(bytes.NewBuffer(decryptedProofBytes))
+ if err != nil {
+ errorf("couldn't read proof from bytes buffer: %s", err)
+ return
+ }
}
- var decryptNonce [24]byte
- copy(decryptNonce[:], encryptedOutput[:24])
- decryptedOutput, ok := box.Open(nil, encryptedOutput[24:], &decryptNonce, &voter.PubKey, &el.LocalVoter.PrivKey)
- if !ok {
- warnf("couldn't decrypt eval for us")
- return
+
+ // decrypt poutputHash
+ var poutputHash []byte
+ {
+ encryptedPOutputHash, err := base64.StdEncoding.DecodeString(ourEval.POutputHash)
+ if err != nil {
+ warnf("couldn't decode the encrypted poutput hash for us: %s", err)
+ return
+ }
+ var decryptNonce [24]byte
+ copy(decryptNonce[:], encryptedPOutputHash[:24])
+ poutputHash, ok = box.Open(nil, encryptedPOutputHash[24:], &decryptNonce, &voter.PubKey, &el.LocalVoter.PrivKey)
+ if !ok {
+ warnf("couldn't decrypt poutput hash for us")
+ return
+ }
+ }
+
+ outputHashes := make([][]byte, len(*el.FinalJoinIDs))
+ var err error
+ for i, eval := range content.Evals {
+ outputHashes[i], err = base64.StdEncoding.DecodeString(eval.OutputHash)
+ if err != nil {
+ warnf("we couldn't decode the %d th output hash", i)
+ return
+ }
+ }
+
+ // verify proof
+ {
+ var publicCircuit math.EvalCircuit
+ publicCircuit.Coeffs = make([]frontend.Variable, len(*el.FinalJoinIDs)-1)
+ publicCircuit.BallotBits = make([]frontend.Variable, len(el.Candidates)*len(el.Candidates))
+ publicCircuit.Input.Assign(&el.LocalVoter.Input)
+ publicCircuit.Output.Assign(output)
+ publicCircuit.PInput.Assign(&voter.Input)
+ publicCircuit.POutputHash.Assign(poutputHash)
+ publicCircuit.HashSeed = el.LocalVoter.HashSeed
+ if el.LocalVoter.EvalVerifyingKey == nil {
+ // should never happen because we processed all keys
+ // events above
+ errorf("our evals verifying key is nil")
+ return
+ }
+ err = groth16.Verify(proof, *el.LocalVoter.EvalVerifyingKey, &publicCircuit)
+ if err != nil {
+ warnf("poly eval proof verification failed: %s", err)
+ return
+ }
}
- voter.Eval = new(big.Int).SetBytes(decryptedOutput)
+ // ensure our output hashes to the given hash
+ {
+ outputBytes := output.Bytes()
+ outputHash, err := bls381.Sum(el.LocalVoter.HashSeed, outputBytes[:])
+ if err != nil {
+ errorf("couldn't hash output: %s", err)
+ return
+ }
+ // el.LocalVoter.JoinIDIndex checked for nilness above
+ if bytes.Compare(outputHashes[*el.LocalVoter.JoinIDIndex], outputHash) != 0 {
+ warnf("our given output did not hash to the given hash")
+ return
+ }
+ }
+
+ voter.Output = output
+ voter.OutputHashes = &outputHashes
+
+ // calculate our sum if we have everyone's outputs
for _, voterID := range *el.FinalJoinIDs {
- if el.Joins[voterID].Eval == nil {
+ if el.Joins[voterID].Output == nil {
return true
}
}
- sum := big.NewInt(0)
+ sum := new(fr.Element).SetZero()
for _, voterID := range *el.FinalJoinIDs {
- sum.Add(sum, el.Joins[voterID].Eval)
+ sum.Add(sum, el.Joins[voterID].Output)
}
el.LocalVoter.Sum = sum
@@ 456,6 733,7 @@ func (elections *ElectionsMap) onSumMessage(evt *event.Event) (success bool) {
return
}
+ // Version
if incompatibleVersion(content.Version) {
debugf("the version is incompatible")
return
@@ 466,50 744,35 @@ func (elections *ElectionsMap) onSumMessage(evt *event.Event) (success bool) {
debugf("we couldn't get the join event, %s", content.JoinID)
return
}
+ if evt.Sender != joinEvt.Sender {
+ warnf("they did not send the join event; %s did", joinEvt.Sender)
+ return
+ }
if len(content.EvalsIDs) == 0 {
warnf("evals length is zero")
return
}
- joinIDs := make(map[id.EventID]id.EventID)
- var startID id.EventID
+ el := elections.GetElection(joinEvt.CreateID)
+ if el == nil {
+ // should never happen because we retrieved the start/join
+ // events above
+ errorf("election %s doesn't exist", joinEvt.CreateID)
+ return
+ }
- for _, evalsID := range content.EvalsIDs {
+ for i, evalsID := range content.EvalsIDs {
evalsEvt := elections.EventStore.GetEvalsEvent(evt.RoomID, evalsID)
if evalsEvt == nil {
debugf("we couldn't get an evals event, %s", evalsID)
return
}
-
- // ensure all evals have the same start ID
- if startID == "" {
- startID = evalsEvt.StartID
- } else if evalsEvt.StartID != startID {
- warnf("at least two evals have different startIDs (%s, %s, ...)", startID, evalsEvt.StartID)
- return
- }
-
- // ensure no two evals use the same join ID. This'll also catch
- // duplicates in EvalsIDs
- if same, exists := joinIDs[evalsEvt.JoinID]; exists {
- warnf("at least two evals use the same join ID (%s, %s, ...)", same, evalsID)
+ if evalsEvt.JoinID != (*el.FinalJoinIDs)[i] {
+ warnf("the join ID (%s) of an evals (%s) does not match the start event's join ID (%s)",
+ evalsEvt.JoinID, evalsID, (*el.FinalJoinIDs)[i])
return
}
-
- joinIDs[evalsEvt.JoinID] = evalsID
-
- // Remember, we don't need to check that the evals's join ID is
- // in the start event because we successfully retrieved the
- // evals event above. Recursion is wonderful.
- }
-
- el := elections.GetElection(joinEvt.CreateID)
- if el == nil {
- // should never happen because we retrieved the start/join
- // events above
- errorf("election %s doesn't exist", joinEvt.CreateID)
- return
}
el.Lock()
@@ 528,14 791,84 @@ func (elections *ElectionsMap) onSumMessage(evt *event.Event) (success bool) {
return
}
- bytes, err := base64.StdEncoding.DecodeString(content.Sum)
- if err != nil {
- warnf("we couldn't decode their sum: %s", err)
- return
+ var sum *fr.Element
+ {
+ sumBytes, err := base64.StdEncoding.DecodeString(content.Sum)
+ if err != nil {
+ warnf("we couldn't decode their sum: %s", err)
+ return
+ }
+ sum = new(fr.Element).SetBytes(sumBytes)
+ }
+
+ var proof groth16.Proof
+ {
+ encrypted, err := base64.StdEncoding.DecodeString(content.Proofs[*el.LocalVoter.JoinIDIndex])
+ if err != nil {
+ warnf("couldn't decode sum proof for us: %s", err)
+ return
+ }
+ var nonce [24]byte
+ copy(nonce[:], encrypted[:24])
+ decrypted, ok := box.Open(nil, encrypted[24:], &nonce, &voter.PubKey, &el.LocalVoter.PrivKey)
+ if !ok {
+ warnf("couldn't decrypt sum proof for us: %s", err)
+ return
+ }
+ var buf bytes.Buffer
+ _, err = buf.Write(decrypted)
+ if err != nil {
+ errorf("couldn't write decrypted sum proof to buffer: %s", err)
+ return
+ }
+ proof = groth16.NewProof(gurvy.BLS381)
+ _, err = proof.ReadFrom(&buf)
+ if err != nil {
+ errorf("couldn't read decrypted sum proof from buffer: %s", err)
+ return
+ }
+ }
+
+ // verify proof
+ {
+ var publicCircuit math.SumCircuit
+ n := len(*el.FinalJoinIDs)
+
+ hashSeeds := make([]string, n)
+ hashSelects := make([]frontend.Variable, n)
+ outputHashes := make([]frontend.Variable, n)
+ voterJoinIDIndex := *voter.JoinIDIndex
+ for i, joinID := range *el.FinalJoinIDs {
+ evaler := el.Joins[joinID]
+ hashSeeds[i] = evaler.HashSeed
+ if i == voterJoinIDIndex {
+ hashSelects[i].Assign(1)
+ } else {
+ hashSelects[i].Assign(0)
+ }
+ outputHashes[i].Assign((*evaler.OutputHashes)[voterJoinIDIndex])
+ }
+ publicCircuit.HashSeeds = hashSeeds
+ publicCircuit.HashSelects = hashSelects
+ publicCircuit.OutputHashes = outputHashes
+ publicCircuit.Sum.Assign(sum)
+ publicCircuit.Outputs = make([]frontend.Variable, n)
+
+ if el.LocalVoter.SumVerifyingKey == nil {
+ // should never happen because we processed all keys
+ // events above
+ errorf("our sum verifying key is nil")
+ return
+ }
+ err := groth16.Verify(proof, *el.LocalVoter.SumVerifyingKey, &publicCircuit)
+ if err != nil {
+ warnf("sum proof verification failed: %s", err)
+ return
+ }
}
voter.SumID = &evt.ID
- voter.Sum = new(big.Int).SetBytes(bytes)
+ voter.Sum = sum
// if we've recieved all sums, calculate the election result!
for _, voterID := range *el.FinalJoinIDs {
M election/result.go => election/result.go +22 -36
@@ 2,7 2,6 @@ package election
import (
"fmt"
- "math/big"
"sort"
"time"
@@ 13,16 12,14 @@ import (
func (el *Election) calculateResult() {
// this assumes all sums have been received and successfully validated
// and processed
- M := constructPolyMatrix(el)
- M.RREF()
- constant := M[0][len(M[0])-1]
- if !constant.IsInt() {
- panic("constant term is not an integer")
+
+ points := make([]math.Point, len(*el.FinalJoinIDs))
+ for i, joinID := range *el.FinalJoinIDs {
+ points[i][0] = &el.Joins[joinID].Input
+ points[i][1] = el.Joins[joinID].Sum
}
- result := constant.Num().Bytes()
- // number of bytes we need to insert at the front since they're zero
- diff := (len(el.Candidates) * len(el.Candidates)) - len(result)
- result = append(make([]byte, diff), result...)
+
+ result := math.LagrangeEvalAtZero(points).Bytes()
el.Result = &result
}
@@ 33,16 30,25 @@ func (el *Election) PrintResults() {
time.Sleep(time.Millisecond * 200)
}
}
- result := *el.Result
candidates := el.Candidates
- log.Debugf("result: %v", result)
+ var getRank func(i, j int) byte
+ {
+ result := *el.Result
+ log.Debugf("result: %v", result)
+ n := len(candidates)
+ l := len(result)
+ getRank = func(i, j int) byte {
+ return result[l - n*n + i*n+j]
+ }
+ }
+
fmt.Println("=== Results ===")
- n := len(candidates)
+
for i, cand := range candidates {
for j, vs := range candidates {
if i != j {
- fmt.Printf("%s over %s: %d\n", cand, vs, result[i*n+j])
+ fmt.Printf("%s over %s: %d\n", cand, vs, getRank(i, j))
}
}
}
@@ 69,8 75,8 @@ func (el *Election) PrintResults() {
p[i] = make([]int, len(candidates))
for j := range candidates {
if i != j {
- if result[i*n+j] > result[j*n+i] {
- p[i][j] = int(result[i*n+j])
+ if getRank(i, j) > getRank(j, i) {
+ p[i][j] = int(getRank(i, j))
} else {
p[i][j] = 0
}
@@ 113,23 119,3 @@ func (el *Election) PrintResults() {
}
}
}
-
-func constructPolyMatrix(el *Election) math.Matrix {
- mat := make(math.Matrix, len(*el.FinalJoinIDs))
-
- i := 0
- for _, voterJoinId := range *el.FinalJoinIDs {
- voter := el.Joins[voterJoinId]
- mat[i] = make([]big.Rat, len(mat)+1) // includes column for sum
- row := mat[i]
- row[0].SetInt64(1)
- var j int64
- for j = 1; j <= int64(len(*el.FinalJoinIDs)-1); j++ {
- row[j].SetInt(new(big.Int).Exp(&voter.Input, big.NewInt(j), nil))
- }
- row[j].SetInt(voter.Sum)
- i++
- }
-
- return mat
-}
M election/version.go => election/version.go +1 -1
@@ 1,3 1,3 @@
package election
-const Version string = "v0.3.0"
+const Version string = "v0.4.0"
M election/voter.go => election/voter.go +305 -37
@@ 1,13 1,19 @@
package election
import (
+ "bytes"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"io"
- "math/big"
+ "github.com/consensys/gnark/backend/groth16"
+ "github.com/consensys/gnark/backend/r1cs"
+ "github.com/consensys/gnark/crypto/hash/mimc/bls381"
+ "github.com/consensys/gnark/frontend"
+ "github.com/consensys/gurvy"
+ "github.com/consensys/gurvy/bls381/fr"
"golang.org/x/crypto/nacl/box"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
@@ 16,27 22,39 @@ import (
)
type Voter struct {
- Input big.Int `json:"input"`
- JoinEvt event.Event `json:"join_evt"`
- PubKey [32]byte `json:"pub_key"`
-
- Eval *big.Int `json:"eval,omitempty"`
- EvalsID *id.EventID `json:"evals_id,omitempty"`
- Sum *big.Int `json:"sum,omitempty"`
- SumID *id.EventID `json:"sum_id,omitempty"`
+ Input fr.Element `json:"input"`
+ JoinEvt event.Event `json:"join_evt"`
+ PubKey [32]byte `json:"pub_key"`
+ HashSeed string `json:"hash_seed"`
+
+ JoinIDIndex *int `json:"join_id_index,omitempty"`
+ Output *fr.Element `json:"output,omitempty"`
+ OutputHashes *[][]byte `json:"output_hashes,omitempty"`
+ KeysID *id.EventID `json:"keys_id,omitempty"`
+ EvalsID *id.EventID `json:"evals_id,omitempty"`
+ Sum *fr.Element `json:"sum,omitempty"`
+ SumID *id.EventID `json:"sum_id,omitempty"`
+
+ EvalProvingKey *groth16.ProvingKey `json:"eval_proving_key"`
+ SumProvingKey *groth16.ProvingKey `json:"sum_proving_key"`
}
type LocalVoter struct {
*Voter
- Poly *math.Poly `json:"poly,omitempty"`
- PrivKey [32]byte `json:"priv_key"`
+
+ PrivKey [32]byte `json:"priv_key"`
+
+ EvalVerifyingKey *groth16.VerifyingKey `json:"eval_verifying_key"`
+ Poly *math.Poly `json:"poly,omitempty"`
+ SumVerifyingKey *groth16.VerifyingKey `json:"sum_verifying_key"`
}
-func NewVoter(input *big.Int, joinEvt *event.Event, pubKey *[32]byte) *Voter {
+func NewVoter(input *fr.Element, joinEvt *event.Event, pubKey *[32]byte, hashSeed []byte) *Voter {
return &Voter{
- Input: *new(big.Int).Set(input),
- JoinEvt: *joinEvt,
- PubKey: *pubKey,
+ Input: *new(fr.Element).Set(input),
+ JoinEvt: *joinEvt,
+ PubKey: *pubKey,
+ HashSeed: string(hashSeed),
}
}
@@ 71,23 89,30 @@ func (el *Election) JoinElection(client *mautrix.Client, eventStore *EventStore)
return err
}
- input, err := math.RandomBigInt(1024, false)
+ input, err := new(fr.Element).SetRandom()
if err != nil {
return err
}
+ inputBytes := input.Bytes()
+
+ var seedBytes [32]byte
+ if _, err := io.ReadFull(rand.Reader, seedBytes[:]); err != nil {
+ return fmt.Errorf("couldn't read random bytes: %s", err)
+ }
resp, err := client.SendMessageEvent(el.RoomID, JoinElectionMessage, JoinElectionContent{
- Version: Version,
+ Version: Version,
+
CreateID: el.CreateEvt.ID,
- Input: base64.StdEncoding.EncodeToString(input.Bytes()),
+ Input: base64.StdEncoding.EncodeToString(inputBytes[:]),
PubKey: base64.StdEncoding.EncodeToString((*pubKey)[:]),
+ HashSeed: base64.StdEncoding.EncodeToString(seedBytes[:]),
})
if err != nil {
- return err
+ return fmt.Errorf("couldn't send join messages: %s", err)
}
- joinEvt := eventStore.GetJoinEvent(el.RoomID, resp.EventID)
- if joinEvt == nil {
+ if eventStore.GetJoinEvent(el.RoomID, resp.EventID) == nil {
return fmt.Errorf("couldn't process our own join event, %s", resp.EventID)
}
@@ 95,7 120,7 @@ func (el *Election) JoinElection(client *mautrix.Client, eventStore *EventStore)
defer el.Save()
defer el.Unlock()
- el.LocalVoter = NewLocalVoter(el.Joins[joinEvt.ID], privKey)
+ el.LocalVoter = NewLocalVoter(el.Joins[resp.EventID], privKey)
return nil
}
@@ 134,14 159,133 @@ func (el *Election) StartElection(client *mautrix.Client, eventStore *EventStore
return err
}
- startEvt := eventStore.GetStartEvent(el.RoomID, resp.EventID)
- if startEvt == nil {
+ if eventStore.GetStartEvent(el.RoomID, resp.EventID) == nil {
return fmt.Errorf("couldn't process our own start event, %s", resp.EventID)
}
return nil
}
+func (el *Election) SendProvingKeys(client *mautrix.Client, eventStore *EventStore) error {
+ // eval proving key
+ var (
+ evalProvingKeyURI id.ContentURI
+ evalVerifyingKey groth16.VerifyingKey
+ )
+ {
+ var evalCircuit math.EvalCircuit
+ evalCircuit.Coeffs = make([]frontend.Variable, len(*el.FinalJoinIDs)-1)
+ evalCircuit.BallotBits = make([]frontend.Variable, len(el.Candidates)*len(el.Candidates))
+ evalCircuit.HashSeed = el.LocalVoter.HashSeed
+ r1cs, err := frontend.Compile(gurvy.BLS381, &evalCircuit)
+ if err != nil {
+ return fmt.Errorf("couldn't compile eval circuit: %s", err)
+ }
+ var evalProvingKey groth16.ProvingKey
+ evalProvingKey, evalVerifyingKey, err = groth16.Setup(r1cs)
+ if err != nil {
+ return fmt.Errorf("couldn't setup r1cs: %s", err)
+ }
+
+ var evalPkBytes bytes.Buffer
+ // TODO: change back to WriteTo. There seems to be a bug in
+ // gnark/gurvy (or a misunderstanding on my part) where
+ // decompression fails
+ // ignoring ...'s keys msg ($tsSEYHiouPdJlaTqAOG2q_4t9MZa0OGHtJPD95jOvAg) since couldn't read sum key from bytes buffer: invalid compressed coordinate: square root doesn't exist
+ // The file was successfully processed on one side (who
+ // coincidentally was the sender) but not on the other
+ // NOTE: it's plausible this was fixed in gurvy (now gnark-crypto) 0.4.0:
+ //
+ // point.SetBytes can now be called concurently with same byte slice input
+ //
+ // https://github.com/ConsenSys/gnark-crypto/releases/tag/v0.4.0
+ evalProvingKey.WriteRawTo(&evalPkBytes)
+ uploadResp, err := client.UploadMedia(mautrix.ReqUploadMedia{
+ Content: &evalPkBytes,
+ ContentLength: int64(evalPkBytes.Len()),
+ ContentType: "application/octet-stream",
+ })
+ if err != nil {
+ return fmt.Errorf("couldn't upload eval proving key: %s", err)
+ }
+ evalProvingKeyURI = uploadResp.ContentURI
+ }
+
+ // sum proving key
+ var (
+ sumProvingKeyURI id.ContentURI
+ sumVerifyingKey groth16.VerifyingKey
+ )
+ {
+ var sumCircuit math.SumCircuit
+ numVoters := len(*el.FinalJoinIDs)
+ hashSeeds := make([]string, numVoters)
+ for i, joinID := range *el.FinalJoinIDs {
+ hashSeeds[i] = el.Joins[joinID].HashSeed
+ }
+ sumCircuit.HashSeeds = hashSeeds
+ sumCircuit.HashSelects = make([]frontend.Variable, numVoters)
+ sumCircuit.OutputHashes = make([]frontend.Variable, numVoters)
+ sumCircuit.Outputs = make([]frontend.Variable, numVoters)
+
+ r1cs, err := frontend.Compile(gurvy.BLS381, &sumCircuit)
+ if err != nil {
+ return fmt.Errorf("couldn't compile sum circuit: %s", err)
+ }
+
+ var sumProvingKey groth16.ProvingKey
+ sumProvingKey, sumVerifyingKey, err = groth16.Setup(r1cs)
+ if err != nil {
+ return fmt.Errorf("couldn't setup r1cs: %s", err)
+ }
+
+ var sumPkBytes bytes.Buffer
+ // TODO: see above
+ sumProvingKey.WriteRawTo(&sumPkBytes)
+ uploadResp, err := client.UploadMedia(mautrix.ReqUploadMedia{
+ Content: &sumPkBytes,
+ ContentLength: int64(sumPkBytes.Len()),
+ ContentType: "application/octet-stream",
+ })
+ if err != nil {
+ return fmt.Errorf("couldn't upload sum proving key: %s", err)
+ }
+ sumProvingKeyURI = uploadResp.ContentURI
+ }
+
+ resp, err := client.SendMessageEvent(el.RoomID, KeysMessage, KeysMessageContent{
+ Version: Version,
+
+ EvalProvingKeyURI: evalProvingKeyURI,
+ JoinID: el.LocalVoter.JoinEvt.ID,
+ StartID: *el.StartID,
+ SumProvingKeyURI: sumProvingKeyURI,
+ })
+ if err != nil {
+ return fmt.Errorf("couldn't send keys message event: %s", err)
+ }
+
+ if eventStore.GetKeysEvent(el.RoomID, resp.EventID) == nil {
+ return fmt.Errorf("couldn't process our own keys event, %s", resp.EventID)
+ }
+
+ if el.LocalVoter.EvalProvingKey == nil {
+ return fmt.Errorf("our eval proving key wasn't set during processing")
+ }
+ if el.LocalVoter.SumProvingKey == nil {
+ return fmt.Errorf("our sum proving key wasn't set during processing")
+ }
+
+ el.Lock()
+ defer el.Save()
+ defer el.Unlock()
+
+ el.LocalVoter.EvalVerifyingKey = &evalVerifyingKey
+ el.LocalVoter.SumVerifyingKey = &sumVerifyingKey
+
+ return nil
+}
+
func (el *Election) SendEvals(client *mautrix.Client, eventStore *EventStore) error {
el.RLock()
if el.StartID == nil {
@@ 149,24 293,77 @@ func (el *Election) SendEvals(client *mautrix.Client, eventStore *EventStore) er
return errors.New("SendEvals called before election started")
}
- evals := make(map[id.EventID]string)
- for _, joinID := range *el.FinalJoinIDs {
+ evals := make([]Eval, len(*el.FinalJoinIDs))
+ keysIDs := make([]id.EventID, len(*el.FinalJoinIDs))
+ for i, joinID := range *el.FinalJoinIDs {
voter := el.Joins[joinID]
- eval := el.LocalVoter.Poly.Eval(&voter.Input)
- var nonce [24]byte
- if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
+
+ output, poutputHash, proof := el.LocalVoter.Poly.EvalAndProve(&voter.Input, *voter.EvalProvingKey, voter.HashSeed)
+
+ // encrypt output
+ var outputBytes [32]byte
+ var encryptedOutput []byte
+ {
+ var outputNonce [24]byte
+ if _, err := io.ReadFull(rand.Reader, outputNonce[:]); err != nil {
+ el.RUnlock()
+ return fmt.Errorf("couldn't read random bytes for output nonce: %s", err)
+ }
+ outputBytes = output.Bytes()
+ encryptedOutput = box.Seal(outputNonce[:], outputBytes[:], &outputNonce, &voter.PubKey, &el.LocalVoter.PrivKey)
+ }
+
+ // hash output
+ outputHash, err := bls381.Sum(voter.HashSeed, outputBytes[:])
+ if err != nil {
el.RUnlock()
- return err
+ return fmt.Errorf("couldn't hash output: %s", err)
+ }
+
+ // hash and encrypt poutput
+ var encryptedPOutputHash []byte
+ {
+ var poutputNonce [24]byte
+ if _, err := io.ReadFull(rand.Reader, poutputNonce[:]); err != nil {
+ el.RUnlock()
+ return fmt.Errorf("couldn't read random bytes for poutput nonce: %s", err)
+ }
+ encryptedPOutputHash = box.Seal(poutputNonce[:], poutputHash, &poutputNonce, &voter.PubKey, &el.LocalVoter.PrivKey)
+ }
+
+ // encrypt proof
+ var encryptedProof []byte
+ {
+ var proofNonce [24]byte
+ if _, err := io.ReadFull(rand.Reader, proofNonce[:]); err != nil {
+ el.RUnlock()
+ return fmt.Errorf("couldn't read random bytes for proof nonce: %s", err)
+ }
+ var proofBuffer bytes.Buffer
+ _, err := proof.WriteTo(&proofBuffer)
+ if err != nil {
+ el.RUnlock()
+ return fmt.Errorf("couldn't write evals proof to buffer: %s", err)
+ }
+ encryptedProof = box.Seal(proofNonce[:], proofBuffer.Bytes(), &proofNonce, &voter.PubKey, &el.LocalVoter.PrivKey)
}
- encrypted := box.Seal(nonce[:], eval.Bytes(), &nonce, &voter.PubKey, &el.LocalVoter.PrivKey)
- evals[voter.JoinEvt.ID] = base64.StdEncoding.EncodeToString(encrypted)
+
+ evals[i] = Eval{
+ Output: base64.StdEncoding.EncodeToString(encryptedOutput),
+ OutputHash: base64.StdEncoding.EncodeToString(outputHash),
+ POutputHash: base64.StdEncoding.EncodeToString(encryptedPOutputHash),
+ Proof: base64.StdEncoding.EncodeToString(encryptedProof),
+ }
+
+ keysIDs[i] = *voter.KeysID
}
resp, err := client.SendMessageEvent(el.RoomID, EvalsMessage, EvalsMessageContent{
Version: Version,
+
Evals: evals,
JoinID: el.LocalVoter.JoinEvt.ID,
- StartID: *el.StartID,
+ KeysIDs: keysIDs,
})
el.RUnlock()
if err != nil {
@@ 188,17 385,88 @@ func (el *Election) SendSum(client *mautrix.Client, eventStore *EventStore) erro
return errors.New("SendSum called before local sum was calculated")
}
- var evalsIDs []id.EventID
- for _, joinID := range *el.FinalJoinIDs {
+ numVoters := len(*el.FinalJoinIDs)
+
+ evalsIDs := make([]id.EventID, numVoters)
+ hashSeeds := make([]string, numVoters)
+ for i, joinID := range *el.FinalJoinIDs {
voter := el.Joins[joinID]
- evalsIDs = append(evalsIDs, *voter.EvalsID)
+ evalsIDs[i] = *voter.EvalsID
+ hashSeeds[i] = voter.HashSeed
}
+ var r1cs r1cs.R1CS
+ {
+ var circuit math.SumCircuit
+ circuit.HashSeeds = hashSeeds
+ circuit.HashSelects = make([]frontend.Variable, numVoters)
+ circuit.OutputHashes = make([]frontend.Variable, numVoters)
+ circuit.Outputs = make([]frontend.Variable, numVoters)
+ var err error
+ r1cs, err = frontend.Compile(gurvy.BLS381, &circuit)
+ if err != nil {
+ el.RUnlock()
+ return fmt.Errorf("couldn't compile sum circuit: %s", err)
+ }
+ }
+
+ proofs := make([]string, numVoters)
+ {
+ hashSelects := make([]frontend.Variable, numVoters)
+ outputHashes := make([]frontend.Variable, numVoters)
+ outputs := make([]frontend.Variable, numVoters)
+
+ var witness math.SumCircuit
+ witness.HashSeeds = hashSeeds
+ witness.HashSelects = hashSelects
+ witness.OutputHashes = outputHashes
+ witness.Sum.Assign(el.LocalVoter.Sum)
+ witness.Outputs = outputs
+
+ ourJoinIDIndex := *el.LocalVoter.JoinIDIndex
+ for i, joinID := range *el.FinalJoinIDs {
+ if i == ourJoinIDIndex {
+ hashSelects[i].Assign(1)
+ } else {
+ hashSelects[i].Assign(0)
+ }
+ evaler := el.Joins[joinID]
+ outputHashForUs := (*evaler.OutputHashes)[ourJoinIDIndex]
+ outputHashes[i].Assign(outputHashForUs)
+ outputs[i].Assign(evaler.Output)
+ }
+
+ for i, joinID := range *el.FinalJoinIDs {
+ voter := *el.Joins[joinID]
+ proof, err := groth16.Prove(r1cs, *voter.SumProvingKey, &witness)
+ if err != nil {
+ el.RUnlock()
+ return fmt.Errorf("couldn't prove sum: %s", err)
+ }
+ var nonce [24]byte
+ if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
+ el.RUnlock()
+ return fmt.Errorf("couldn't read random bytes for sum proof nonce: %s", err)
+ }
+ var buf bytes.Buffer
+ _, err = proof.WriteTo(&buf)
+ if err != nil {
+ el.RUnlock()
+ return fmt.Errorf("couldn't write sum proof to buffer: %s", err)
+ }
+ encryptedProof := box.Seal(nonce[:], buf.Bytes(), &nonce, &voter.PubKey, &el.LocalVoter.PrivKey)
+ proofs[i] = base64.StdEncoding.EncodeToString(encryptedProof)
+ }
+ }
+
+ sumBytes := el.LocalVoter.Sum.Bytes()
resp, err := client.SendMessageEvent(el.RoomID, SumMessage, SumMessageContent{
Version: Version,
+
EvalsIDs: evalsIDs,
JoinID: el.LocalVoter.JoinEvt.ID,
- Sum: base64.StdEncoding.EncodeToString(el.LocalVoter.Sum.Bytes()),
+ Sum: base64.StdEncoding.EncodeToString(sumBytes[:]),
+ Proofs: proofs,
})
el.RUnlock()
if err != nil {
M go.mod => go.mod +2 -1
@@ 4,8 4,9 @@ go 1.13
require (
github.com/cbergoon/merkletree v0.2.0
+ github.com/consensys/gnark v0.3.8
+ github.com/consensys/gurvy v0.3.8
github.com/gdamore/tcell/v2 v2.1.0
- github.com/kr/pretty v0.1.0 // indirect
github.com/kyoh86/xdg v1.2.0
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/magefile/mage v1.11.0 // indirect
M go.sum => go.sum +410 -8
@@ 1,4 1,48 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
+github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=
+github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4=
+github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
+github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
+github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
+github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
+github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
+github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
+github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
+github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
+github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
+github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
+github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
+github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
+github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
@@ 11,115 55,473 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/cbergoon/merkletree v0.2.0 h1:Bttqr3OuoiZEo4ed1L7fTasHka9II+BF9fhBfbNEEoQ=
github.com/cbergoon/merkletree v0.2.0/go.mod h1:5c15eckUgiucMGDOCanvalj/yJnD+KAZj1qyJtRW5aM=
+github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U=
+github.com/consensys/bavard v0.1.7/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ=
+github.com/consensys/bavard v0.1.8-0.20210105233146-c16790d2aa8b/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ=
+github.com/consensys/gnark v0.3.8 h1:4t8tdqDLOAWRRycw4dsmI29v+ESii0up+vHAekn/Sgw=
+github.com/consensys/gnark v0.3.8/go.mod h1:ZBBVbSG3McqjLh6ek5sHu/CXbQhx5bQJFxfqV7YR8Es=
+github.com/consensys/goff v0.3.8/go.mod h1:sVd8qmGLPDVaNkeEc54IvC7/dGxuZdJBbo792ZLD5Ng=
+github.com/consensys/goff v0.3.10/go.mod h1:xTldOBEHmFiYS0gPXd3NsaEqZWlnmeWcRLWgD3ba3xc=
+github.com/consensys/gurvy v0.3.6/go.mod h1:PoRINoNBSpC1+qDcIgtH3EMHJkq+3CM1BGGcovuuOcU=
+github.com/consensys/gurvy v0.3.8 h1:H2hvjvT2OFMgdMn5ZbhXqHt+F8DJ2clZW7Vmc0kFFxc=
+github.com/consensys/gurvy v0.3.8/go.mod h1:sN75xnsiD593XnhbhvG2PkOy194pZBzqShWF/kwuW/g=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
+github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
+github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/ethereum/go-ethereum v1.9.24/go.mod h1:JIfVb6esrqALTExdz9hRYvrP0xBDf6wCncIu1hNwHpM=
+github.com/ethereum/go-ethereum v1.9.25/go.mod h1:vMkFiYLHI4tgPw4k2j4MHKoovchFE8plZ0M9VMk4/oM=
+github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ=
+github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
+github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
-github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591 h1:0WWUDZ1oxq7NxVyGo8M3KI5jbkiwNAdZFFzAdC68up4=
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
github.com/gdamore/tcell/v2 v2.1.0 h1:UnSmozHgBkQi2PGsFr+rpdXuAPRRucMegpQp3Z3kDro=
github.com/gdamore/tcell/v2 v2.1.0/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
+github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc=
+github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
+github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
+github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
+github.com/kilic/bls12-381 v0.0.0-20201104083100-a288617c07f1/go.mod h1:gcwDl9YLyNc3H3wmPXamu+8evD8TYUa6BjTsWnvdn7A=
+github.com/kilic/bls12-381 v0.0.0-20201226121925-69dacb279461/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/kyoh86/xdg v1.2.0 h1:CERuT/ShdTDj+A2UaX3hQ3mOV369+Sj+wyn2nIRIIkI=
github.com/kyoh86/xdg v1.2.0/go.mod h1:/mg8zwu1+qe76oTFUBnyS7rJzk7LLC0VGEzJyJ19DHs=
+github.com/leanovate/gopter v0.2.8/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8=
+github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
+github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
-github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls=
github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
+github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
+github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/tview v0.0.0-20210217110421-8a8f78a6dd01 h1:rtCzDXdaqhiRakJsz0bUj+3sOUjw82bJDcJrAzQ0u+M=
github.com/rivo/tview v0.0.0-20210217110421-8a8f78a6dd01/go.mod h1:n2q/ydglZJ1kqxiNrnYO+FaX1H14vA0wKyIo953QakU=
-github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
+github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
+github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw=
+github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
+github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees=
+github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
+github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191025090151-53bf42e6b339/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b h1:lAZ0/chPUDWwjqosYR0X4M490zQhMsiJ4K3DbA7o+3g=
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
+gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
maunium.net/go/maulogger/v2 v2.2.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
maunium.net/go/mautrix v0.8.3 h1:nKGdARVCf2w7thEN5GEbAjYrlLjKLX44jOdB1h+BV7U=
maunium.net/go/mautrix v0.8.3/go.mod h1:LPbb/DeAmtOPKnGbJazL9g11cO3mMAaEbLE8udd98BU=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
A math/lagrange.go => math/lagrange.go +31 -0
@@ 0,0 1,31 @@
+package math
+
+import "github.com/consensys/gurvy/bls381/fr"
+
+// p[0] is x coordinate, p[1] is y coordinate
+type Point [2]*fr.Element
+
+// uses https://en.wikipedia.org/wiki/Lagrange_polynomial#Definition but returns
+// the evaluation at zero (the only one we care about)
+func LagrangeEvalAtZero(points []Point) *fr.Element {
+ zero := new(fr.Element).SetZero()
+
+ sum := new(fr.Element).SetZero()
+ tmp := new(fr.Element)
+
+ for j, pointj := range points {
+ term := new(fr.Element).Set(pointj[1])
+ for k, pointk := range points {
+ if k == j {
+ continue
+ }
+ // numerator
+ term.Mul(term, tmp.Sub(zero, pointk[0]))
+ // denominator
+ term.Div(term, tmp.Sub(pointj[0], pointk[0]))
+ }
+ sum.Add(sum, term)
+ }
+
+ return sum
+}
A math/lagrange_test.go => math/lagrange_test.go +81 -0
@@ 0,0 1,81 @@
+package math
+
+import (
+ "testing"
+
+ "github.com/consensys/gurvy/bls381/fr"
+)
+
+func TestWikipedia(t *testing.T) {
+ // https://en.wikipedia.org/wiki/Lagrange_polynomial#Example_1
+ points := make([]Point, 3)
+ points[0][0] = new(fr.Element).SetUint64(1)
+ points[0][1] = new(fr.Element).SetUint64(1)
+ points[1][0] = new(fr.Element).SetUint64(2)
+ points[1][1] = new(fr.Element).SetUint64(4)
+ points[2][0] = new(fr.Element).SetUint64(3)
+ points[2][1] = new(fr.Element).SetUint64(9)
+ r := LagrangeEvalAtZero(points)
+ if !r.Equal(new(fr.Element).SetZero()) {
+ t.Fail()
+ }
+
+ // https://en.wikipedia.org/wiki/Lagrange_polynomial#Example_2
+ points = make([]Point, 4)
+ points[0][0] = new(fr.Element).SetUint64(1)
+ points[0][1] = new(fr.Element).SetUint64(1)
+ points[1][0] = new(fr.Element).SetUint64(2)
+ points[1][1] = new(fr.Element).SetUint64(8)
+ points[2][0] = new(fr.Element).SetUint64(3)
+ points[2][1] = new(fr.Element).SetUint64(27)
+ points[3][0] = new(fr.Element).SetUint64(4)
+ points[3][1] = new(fr.Element).SetUint64(64)
+ if !r.Equal(new(fr.Element).SetZero()) {
+ t.Fail()
+ }
+}
+
+func TestRandomPoly(t *testing.T) {
+ // a simple ballot where the first candidate is ranked above the second
+ ballot := make([][]byte, 2)
+ ballot[0] = make([]byte, 2)
+ ballot[1] = make([]byte, 2)
+ ballot[0][1] = 1
+
+ // pinput doesn't matter
+genpinput:
+ pinput, err := new(fr.Element).SetRandom()
+ if err != nil {
+ panic(err)
+ }
+ if pinput.IsZero() {
+ goto genpinput
+ }
+
+ zero := new(fr.Element).SetZero()
+ // vary the number of points
+ for n := 2; n <= 20; n++ {
+ poly := NewRandomPoly(ballot, n, pinput)
+ points := make([]Point, n)
+ for i := 0; i < n; i++ {
+ genx:
+ x, err := new(fr.Element).SetRandom()
+ // TODO: check that x doesn't repeat (very unlikely)
+ if err != nil {
+ panic(err)
+ }
+ if x.IsZero() {
+ goto genx
+ }
+ y := poly.eval(x)
+ points[i][0] = x
+ points[i][1] = y
+ }
+ lagrangeZeroOutput := LagrangeEvalAtZero(points)
+ actualZeroOutput := poly.eval(zero)
+ if lagrangeZeroOutput.Cmp(actualZeroOutput) != 0 {
+ t.Fatalf("lagrange zero output %s does not equal %s",
+ lagrangeZeroOutput, actualZeroOutput)
+ }
+ }
+}
D math/linalg.go => math/linalg.go +0 -73
@@ 1,73 0,0 @@
-package math
-
-import (
- "math/big"
-)
-
-type Matrix [][]big.Rat
-
-func (M Matrix) Rows() int {
- return len(M)
-}
-
-func (M Matrix) Cols() int {
- if len(M) == 0 {
- return 0
- }
- return len(M[0])
-}
-
-func (M Matrix) RREF() {
- lead := 0
- zero := big.NewRat(0, 1)
-
- for r := 0; r < M.Rows(); r++ {
- if lead >= M.Cols() {
- return
- }
- i := r
- for M[i][lead].Cmp(zero) == 0 {
- i++
- if M.Rows() == i {
- i = r
- lead++
- if M.Cols() == lead {
- return
- }
- }
- }
- M[i], M[r] = M[r], M[i]
- f := &big.Rat{}
- f.Inv(&M[r][lead])
- for j := range M[r] {
- M[r][j].Mul(&M[r][j], f)
- }
- for i = 0; i < M.Rows(); i++ {
- if i != r {
- f.Set(&M[i][lead])
- for j, e := range M[r] {
- M[i][j].Sub(&M[i][j], new(big.Rat).Mul(&e, f))
- }
- }
- }
- lead++
- }
-}
-
-// assumes `M' and `other' are valid matrices
-func (M Matrix) Equals(other Matrix) bool {
- if len(M) != len(other) {
- return false
- }
- if len(M) > 0 && len(M[0]) != len(other[0]) {
- return false
- }
- for r := range M {
- for c := range M[r] {
- if M[r][c].Cmp(&other[r][c]) != 0 {
- return false
- }
- }
- }
- return true
-}
D math/linalg_test.go => math/linalg_test.go +0 -36
@@ 1,36 0,0 @@
-package math
-
-import (
- "math/big"
- "testing"
-)
-
-func TestRREF(t *testing.T) {
- mat := Matrix{
- {*big.NewRat( 1, 1), *big.NewRat(2, 1), *big.NewRat(-1, 1), *big.NewRat( -4, 1)},
- {*big.NewRat( 2, 1), *big.NewRat(3, 1), *big.NewRat(-1, 1), *big.NewRat(-11, 1)},
- {*big.NewRat(-2, 1), *big.NewRat(0, 1), *big.NewRat(-3, 1), *big.NewRat( 22, 1)},
- }
- mat.RREF()
- result := Matrix{
- {*big.NewRat(1, 1), *big.NewRat(0, 1), *big.NewRat(0, 1), *big.NewRat(-8, 1)},
- {*big.NewRat(0, 1), *big.NewRat(1, 1), *big.NewRat(0, 1), *big.NewRat( 1, 1)},
- {*big.NewRat(0, 1), *big.NewRat(0, 1), *big.NewRat(1, 1), *big.NewRat(-2, 1)},
- }
- if !mat.Equals(result) {
- t.Fail()
- }
-
- mat = Matrix{
- {*big.NewRat(1, 1), *big.NewRat(3, 1), *big.NewRat(-1, 1)},
- {*big.NewRat(0, 1), *big.NewRat(1, 1), *big.NewRat(7, 1)},
- }
- mat.RREF()
- result = Matrix{
- {*big.NewRat(1, 1), *big.NewRat(0, 1), *big.NewRat(-22, 1)},
- {*big.NewRat(0, 1), *big.NewRat(1, 1), *big.NewRat( 7, 1)},
- }
- if !mat.Equals(result) {
- t.Fail()
- }
-}
M math/poly.go => math/poly.go +140 -43
@@ 1,69 1,166 @@
package math
import (
- "crypto/rand"
- "math"
- "math/big"
+ "fmt"
+
+ "github.com/consensys/gnark/backend/groth16"
+ "github.com/consensys/gnark/backend/r1cs"
+ "github.com/consensys/gnark/crypto/hash/mimc/bls381"
+ "github.com/consensys/gnark/frontend"
+ "github.com/consensys/gurvy"
+ "github.com/consensys/gurvy/bls381/fr"
)
type Poly struct {
- Constant *big.Int `json:"constant"`
- Coefs []*big.Int `json:"coefs"`
+ Ballot [][]byte `json:"ballot"`
+ Coeffs []*fr.Element `json:"coeffs"`
+ PInput *fr.Element `json:"pinput"`
+
+ cache *polyCache `json:"-"`
}
-func RandomBigInt(numBytes uint, allowAllZeros bool) (*big.Int, error) {
- randBytes := make([]byte, numBytes)
- _, err := rand.Read(randBytes)
- if err != nil {
- return nil, err
- }
- bi := new(big.Int).SetBytes(randBytes)
- if !allowAllZeros && bi.Cmp(big.NewInt(0)) == 0 {
- return RandomBigInt(numBytes, allowAllZeros)
- }
- return bi, nil
+type polyCache struct {
+ ballotBits []*fr.Element
+ constant *fr.Element
+ outputs map[*fr.Element]outputCache
+ poutput *fr.Element
}
-func NewPoly(coefficients []*big.Int, ballot []byte) *Poly {
- constant := new(big.Int).SetBytes(ballot)
- return &Poly{constant, coefficients}
+type outputCache struct{
+ output *fr.Element
+ poutputHash []byte
+ proof groth16.Proof
}
-// NewRandomPoly generates a random `degree' degree polynomial, using at least
-// `entropy' bits of entropy for the random coefficients and inserting `ballot'
-// as the constant term.
-func NewRandomPoly(degree uint, entropy uint, ballot []byte) *Poly {
- coefficients := make([]*big.Int, degree)
+// NewRandomPoly generates a random polynomial of degree numVoters-1, using
+// `ballot' as the constant term
+func NewRandomPoly(ballot [][]byte, numVoters int, pinput *fr.Element) *Poly {
+ coeffs := make([]*fr.Element, numVoters - 1)
+
+ var err error
+ for i := range coeffs {
+ coeffs[i], err = new(fr.Element).SetRandom()
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ return &Poly{ballot, coeffs, pinput, nil}
+}
- // number of bits per coefficient
- numBits := uint(math.Ceil(float64(entropy) / float64(degree)))
+func (p *Poly) setupCache() {
+ p.cache = &polyCache{}
- // number of bytes per coefficient
- numBytes := numBits / 8
- if numBits%8 > 0 {
- numBytes += 1
+ // lay out ballot bits slice and compute constant term
+ n := len(p.Ballot)
+ ballotBits := make([]*fr.Element, n*n)
+ ballot1DBytes := make([]byte, 0, n*n)
+ for i, row := range p.Ballot {
+ ballot1DBytes = append(ballot1DBytes, row...)
+ for j, bool := range row {
+ if bool == 0 {
+ ballotBits[i * n + j] = new(fr.Element).SetZero()
+ } else if bool == 1 {
+ ballotBits[i * n + j] = new(fr.Element).SetOne()
+ } else {
+ panic(fmt.Sprintf("invalid ballot byte value: %d", bool))
+ }
+ }
}
+ p.cache.ballotBits = ballotBits
+ p.cache.constant = new(fr.Element).SetBytes(ballot1DBytes)
- var err error
- for i := range coefficients {
- coefficients[i], err = RandomBigInt(numBytes, false)
+ // malloc outputs map
+ p.cache.outputs = make(map[*fr.Element]outputCache)
+
+ // calculate verifier's output
+ poutput := new(fr.Element).Set(p.cache.constant)
+ term := new(fr.Element)
+ for i, coeff := range p.Coeffs {
+ term.Set(coeff)
+ // <= because we want there to be at least one multiplication
+ for j := 0; j <= i; j++ {
+ term.Mul(term, p.PInput)
+ }
+ poutput.Add(poutput, term)
+ }
+ p.cache.poutput = poutput
+
+}
+
+func (p *Poly) EvalAndProve(input *fr.Element, provingKey groth16.ProvingKey, hashSeed string) (*fr.Element, []byte, groth16.Proof) {
+ if p.cache == nil {
+ p.setupCache()
+ } else if outputCache, exists := p.cache.outputs[input]; exists {
+ return outputCache.output, outputCache.poutputHash, outputCache.proof
+ }
+
+ // compile R1CS
+ var r1cs r1cs.R1CS
+ {
+ var circuit EvalCircuit
+ circuit.Coeffs = make([]frontend.Variable, len(p.Coeffs))
+ circuit.BallotBits = make([]frontend.Variable, len(p.Ballot)*len(p.Ballot[0]))
+ circuit.HashSeed = hashSeed
+ var err error
+ r1cs, err = frontend.Compile(gurvy.BLS381, &circuit)
if err != nil {
panic(err)
}
}
- return NewPoly(coefficients, ballot)
-}
+ output := p.eval(input)
-func (p *Poly) Eval(input *big.Int) *big.Int {
- res := new(big.Int).Set(p.Constant)
+ var witness EvalCircuit
+ // public
+ witness.Input.Assign(input)
+ witness.Output.Assign(output)
+ witness.PInput.Assign(p.PInput)
+ var poutputHash []byte
+ {
+ poutputBytes := p.cache.poutput.Bytes()
+ var err error
+ poutputHash, err = bls381.Sum(hashSeed, poutputBytes[:])
+ if err != nil {
+ panic(err)
+ }
+ witness.POutputHash.Assign(poutputHash)
+ }
+ witness.HashSeed = hashSeed
+ // private
+ witness.BallotBits = make([]frontend.Variable, len(p.Ballot)*len(p.Ballot[0]))
+ for i, bit := range p.cache.ballotBits {
+ witness.BallotBits[i].Assign(bit)
+ }
+ witness.Coeffs = make([]frontend.Variable, len(p.Coeffs))
+ for i, coeff := range p.Coeffs {
+ witness.Coeffs[i].Assign(coeff)
+ }
+ witness.Constant.Assign(p.cache.constant)
+ witness.POutput.Assign(p.cache.poutput)
- for i, coef := range p.Coefs {
- degree := big.NewInt(int64(i + 1))
- term := new(big.Int).Exp(input, degree, nil)
- term.Mul(term, coef)
- res.Add(res, term)
+ proof, err := groth16.Prove(r1cs, provingKey, &witness)
+ if err != nil {
+ panic(err)
}
- return res
+ p.cache.outputs[input] = outputCache{output, poutputHash, proof}
+ return output, poutputHash, proof
+}
+
+func (p *Poly) eval(input *fr.Element) *fr.Element {
+ if p.cache == nil {
+ p.setupCache()
+ }
+ output := new(fr.Element).Set(p.cache.constant)
+ term := new(fr.Element)
+ for i, coeff := range p.Coeffs {
+ term.Set(coeff)
+ // <= because we want there to be at least one multiplication
+ for j := 0; j <= i; j++ {
+ term.Mul(term, input)
+ }
+ output.Add(output, term)
+ }
+ return output
}
A math/zk.go => math/zk.go +117 -0
@@ 0,0 1,117 @@
+package math
+
+import (
+ "errors"
+
+ "github.com/consensys/gnark/frontend"
+ "github.com/consensys/gnark/std/hash/mimc"
+ "github.com/consensys/gurvy"
+)
+
+type EvalCircuit struct {
+ Input frontend.Variable `gnark:",public"`
+ Output frontend.Variable `gnark:",public"`
+ // prover's input
+ PInput frontend.Variable `gnark:",public"`
+ POutputHash frontend.Variable `gnark:",public"`
+ HashSeed string
+
+ // must be created with length numCandidates^2
+ BallotBits []frontend.Variable `gnark:",private"`
+ // must be created with length numVoters - 1
+ Coeffs []frontend.Variable `gnark:",private"`
+ Constant frontend.Variable `gnark:",private"`
+ // prover's hidden output for their own polynomial
+ POutput frontend.Variable `gnark:",private"`
+}
+
+func (circuit *EvalCircuit) Define(curveID gurvy.ID, cs *frontend.ConstraintSystem) error {
+ // prove the input yields the given output
+ output := circuit.Constant
+ poutput := circuit.Constant
+ for i, coeff := range circuit.Coeffs {
+ term := coeff
+ vterm := coeff
+ // <= because we want there to be at least one multiplication
+ for j := 0; j <= i; j++ {
+ term = cs.Mul(term, circuit.Input)
+ vterm = cs.Mul(vterm, circuit.PInput)
+ }
+ output = cs.Add(output, term)
+ poutput = cs.Add(poutput, vterm)
+ }
+ cs.AssertIsEqual(output, circuit.Output)
+ cs.AssertIsEqual(poutput, circuit.POutput)
+
+ // prove the constant is valid (i.e. a string of bits separated by 7
+ // zeros)
+ // TODO: support more than 255 voters (limit from usage of byte)
+ zero := cs.Constant(0)
+ shift := cs.Constant(1 << 8)
+ slot := cs.Constant(1)
+ constructedConstant := zero
+ for i := len(circuit.BallotBits)-1; i >= 0; i-- {
+ bit := circuit.BallotBits[i]
+ cs.AssertIsBoolean(bit)
+ constructedConstant = cs.Add(constructedConstant, cs.Select(bit, slot, zero))
+ slot = cs.Mul(slot, shift)
+ }
+ cs.AssertIsEqual(constructedConstant, circuit.Constant)
+
+ mimc, err := mimc.NewMiMC(circuit.HashSeed, gurvy.BLS381)
+ if err != nil {
+ return err
+ }
+
+ cs.AssertIsEqual(mimc.Hash(cs, circuit.POutput), circuit.POutputHash)
+
+ return nil
+}
+
+type SumCircuit struct {
+ HashSeeds []string
+ HashSelects []frontend.Variable `gnark:",public"`
+ OutputHashes []frontend.Variable `gnark:",public"`
+ Sum frontend.Variable `gnark:",public"`
+
+ Outputs []frontend.Variable `gnark:",private"`
+}
+
+func (circuit *SumCircuit) Define(curveID gurvy.ID, cs *frontend.ConstraintSystem) error {
+ if len(circuit.HashSeeds) != len(circuit.HashSelects) {
+ return errors.New("len(circuit.HashSeeds) != len(circuit.HashSelects)")
+ }
+ if len(circuit.HashSelects) != len(circuit.OutputHashes) {
+ return errors.New("len(circuit.HashSelects) != len(circuit.OutputHashes)")
+ }
+ if len(circuit.OutputHashes) != len(circuit.Outputs) {
+ return errors.New("len(circuit.OutputHashes) != len(circuit.Outputs)")
+ }
+
+ // prove the secret outputs hash to public hashes
+ zero := cs.Constant(0)
+ // TODO: this seems verify inefficient
+ for i, hashSeed := range circuit.HashSeeds {
+ mimc, err := mimc.NewMiMC(hashSeed, gurvy.BLS381)
+ if err != nil {
+ return err
+ }
+ bit := circuit.HashSelects[i]
+ for j, output := range circuit.Outputs {
+ cs.AssertIsEqual(
+ cs.Select(bit, mimc.Hash(cs, output), zero),
+ cs.Select(bit, circuit.OutputHashes[j], zero),
+ )
+ }
+ }
+
+ // prove that sum of outputs yields Sum
+ sum := zero
+ for _, output := range circuit.Outputs {
+ sum = cs.Add(sum, output)
+ }
+
+ cs.AssertIsEqual(sum, circuit.Sum)
+
+ return nil
+}
M ui/tui.go => ui/tui.go +55 -29
@@ 232,8 232,10 @@ func CreateElectionTUI() (title string, candidates []election.Candidate) {
n := 2
app := newTallyardApplication()
plus := func() {
- form.AddInputField(fmt.Sprintf("%d.", n+1), "", 50, nil, nil)
- n++
+ if n < 5 {
+ form.AddInputField(fmt.Sprintf("%d.", n+1), "", 50, nil, nil)
+ n++
+ }
}
minus := func() {
// TODO: ensure from joiner that there are at least two
@@ 274,7 276,7 @@ func CreateElectionTUI() (title string, candidates []election.Candidate) {
return title, candidates
}
-func ElectionWaitTUI(client *mautrix.Client, el *election.Election, eventStore *election.EventStore) error {
+func ElectionWaitTUI(client *mautrix.Client, el *election.Election, eventStore *election.EventStore) (err error) {
votersTextView := tview.NewTextView()
frame := tview.NewFrame(votersTextView)
frame.SetTitle(el.Title).SetBorder(true)
@@ 286,20 288,21 @@ func ElectionWaitTUI(client *mautrix.Client, el *election.Election, eventStore *
if event.Key() == tcell.KeyEnter {
frame.Clear()
frame.AddText("Starting election...", false, tview.AlignCenter, tcell.ColorWhite)
- err := el.StartElection(client, eventStore)
- if err != nil {
- panic(err)
- }
+ err = el.StartElection(client, eventStore)
}
return event
})
} else {
frame.AddText("Waiting for election to start...", false, tview.AlignCenter, tcell.ColorWhite)
}
+ if err != nil {
+ return
+ }
el.RUnlock()
update := func() {
el.RLock()
// TODO: handle duplicate joins from one UserID
+ // TODO: handle users who joined after cutoff
voters := make([]string, 0, len(el.Joins))
for _, voter := range el.Joins {
voters = append(voters, voter.JoinEvt.Sender.String())
@@ 311,28 314,59 @@ func ElectionWaitTUI(client *mautrix.Client, el *election.Election, eventStore *
votersTextView.SetText(text)
}
go func() {
+ var electionStarted bool
for app.alive {
- app.QueueUpdateDraw(update)
-
- // has the election started?
el.RLock()
- started := el.StartID != nil
- el.RUnlock()
- if started {
- app.Stop()
- break
+ if electionStarted {
+ // have we received everyone's keys?
+ receivedAllKeys := true
+ for _, joinID := range *el.FinalJoinIDs {
+ if el.Joins[joinID].KeysID == nil {
+ receivedAllKeys = false
+ break
+ }
+ }
+ el.RUnlock()
+ if receivedAllKeys {
+ app.Stop()
+ return
+ }
+ } else if el.FinalJoinIDs != nil {
+ electionStarted = true
+ el.RUnlock()
+ if el.LocalVoter.KeysID == nil {
+ err = el.SendProvingKeys(client, eventStore)
+ if err != nil {
+ app.Stop()
+ return
+ }
+ }
+ app.QueueUpdateDraw(func() {
+ frame.Clear()
+ frame.AddText("Waiting for everyone's keys...", false, tview.AlignCenter, tcell.ColorWhite)
+ update()
+ })
+ } else {
+ el.RUnlock()
+ app.QueueUpdateDraw(update)
}
time.Sleep(1 * time.Second)
}
}()
app.SetRoot(frame, true)
- err := app.Run()
- return err
+ err2 := app.Run()
+ if err != nil {
+ return err
+ }
+ if err2 != nil {
+ return err2
+ }
+ return nil
}
// displays a voting UI to the user and returns the encoded ballot
-func VoteTUI(candidates []election.Candidate) (ballot *[]byte, err error) {
+func VoteTUI(candidates []election.Candidate) (ballot *[][]byte, err error) {
app := newTallyardApplication()
form := tview.NewForm()
form.SetTitle("Vote").SetBorder(true)
@@ 398,17 432,15 @@ func (t *tallyardApplication) Run() error {
return err
}
-func getBallotFromRankings(ranks []int) []byte {
+func getBallotFromRankings(ranks []int) [][]byte {
n := len(ranks)
candidates := make([]int, n)
-
for i := 0; i < n; i++ {
candidates[i] = i
}
// sort candidates by ranking
- cr := candidateRanking{candidates, ranks}
- sort.Sort(&cr)
+ sort.Sort(&candidateRanking{candidates, ranks})
// TODO: support more than 255 voters (limit from usage of byte)
prefMatrix := make([][]byte, n)
@@ 431,13 463,7 @@ func getBallotFromRankings(ranks []int) []byte {
candidates = candidates[i:]
}
- // convert 2D array into 1D array
- barray := make([]byte, 0, n*n)
- for _, row := range prefMatrix {
- barray = append(barray, row...)
- }
-
- return barray
+ return prefMatrix
}
type candidateRanking struct {