~edwargix/tallyard

dde4183f882cffc9d52589d9d70bde80d40f8922 — David Florness 3 years ago 6e6e0a9
Implement zero-knowledge proofs for evaluations and summations

Candidates had to be limited to 5 since 5*5=25 is the largest square less than
32, thu number of bytes in a BLS12-381 point:
https://github.com/ConsenSys/gurvy/blob/e350ead0219d4f30aa72ec85d1857b573d2f136a/bls381/fr/element.go#L596
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 {