~edwargix/tallyard

f3db4d6e97e358ef181b9aaf8f2cf260ca1a0f31 — David Florness 4 years ago fe0b04a
Switch to Matrix
23 files changed, 1073 insertions(+), 1523 deletions(-)

M .gitignore
M Makefile
A cmd/tallyard/main.go
D election.go
A election/election.go
A election/map.go
R merkle.go => election/merkle.go
A election/msg.go
A election/version.go
A election/voter.go
M go.mod
M go.sum
D main.go
R linalg.go => math/linalg.go
R linalg_test.go => math/linalg_test.go
R poly.go => math/poly.go
M matrix/crypto_logger.go
M matrix/data.go
D matrix/main.go
M matrix/state_store.go
D ui.go
A ui/tui.go
D voter.go
M .gitignore => .gitignore +1 -1
@@ 3,4 3,4 @@
.\#*
.DS_Store
compiled/
tallyard
/tallyard

M Makefile => Makefile +7 -3
@@ 1,7 1,11 @@
GOSRC != find . -name '*.go'
GOSRC += go.mod go.sum
GO = go
RM ?= rm -f

tallyard: $(GOSRC)
	$(GO) build -o $@
tallyard: $(GOSRC) go.mod go.sum
	$(GO) build -o $@ cmd/tallyard/main.go

clean:
	$(RM) tallyard

.PHONY: clean

A cmd/tallyard/main.go => cmd/tallyard/main.go +82 -0
@@ 0,0 1,82 @@
package main

import (
	"fmt"
	"os"

	"github.com/kyoh86/xdg"
	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/event"

	"tallyard.xyz/election"
	"tallyard.xyz/matrix"
	"tallyard.xyz/ui"
)

var gobStorePath string = xdg.DataHome() + "/tallyard/gob.dat"
var electionFilter *mautrix.Filter = &mautrix.Filter{
	Room: mautrix.RoomFilter{
		Timeline: mautrix.FilterPart{
			Types: []event.Type{
				election.CreateElectionMessage,
				election.JoinElectionMessage,
				election.StartElectionMessage,
				election.EvalMessage,
				election.SumMessage,
				election.ResultMessage,
			},
		},
	},
}

func main() {
	data, err := matrix.GetData()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	client, err := mautrix.NewClient(data.Homeserver, data.UserID, data.AccessToken)
	if err != nil {
		panic(err)
	}

	localVoter := election.NewLocalVoter(client.UserID)

	elections := election.NewElectionsMap()

	syncer := client.Syncer.(*mautrix.DefaultSyncer)
	syncer.OnEvent(client.Store.(*mautrix.InMemoryStore).UpdateState)
	syncer.OnEventType(election.CreateElectionMessage, func(source mautrix.EventSource, evt *event.Event) {
		election.OnCreateElectionMessage(source, evt, elections)
	})
	syncer.OnEventType(election.JoinElectionMessage, func(source mautrix.EventSource, evt *event.Event) {
		election.OnJoinElectionMessage(source, evt, elections)
	})
	syncer.OnEventType(election.StartElectionMessage, func(source mautrix.EventSource, evt *event.Event) {
		election.OnStartElectionMessage(source, evt, elections)
	})
	syncer.OnEventType(election.EvalMessage, func(source mautrix.EventSource, evt *event.Event) {
		election.OnEvalMessage(source, evt, elections, localVoter)
	})
	syncer.OnEventType(election.SumMessage, func(source mautrix.EventSource, evt *event.Event) {
		election.OnSumMessage(source, evt, elections)
	})
	syncer.OnEventType(election.ResultMessage, func(source mautrix.EventSource, evt *event.Event) {
		election.OnResultMessage(source, evt, elections)
	})

	go func() {
		res, err := client.CreateFilter(electionFilter)
		if err != nil {
			panic(err)
		}
		client.Store.SaveFilterID(client.UserID, res.FilterID)
		err = client.Sync()
		if err != nil {
			panic(err)
		}
	}()

	ui.TUI(client, elections, localVoter)
}

D election.go => election.go +0 -88
@@ 1,88 0,0 @@
package main

import (
	"fmt"
	"strings"
	"sync"

	"github.com/cbergoon/merkletree"
	"github.com/libp2p/go-libp2p"
	"github.com/libp2p/go-libp2p-core/peer"
	"github.com/mr-tron/base58/base58"
)

type Election struct {
	sync.RWMutex

	Candidates      []Candidate
	// for slave: signifies when election was closed by master
	//
	// for master: signifies when user hits ENTER to close the election
	//
	// the number of peers known by master is passed through it
	close           chan<- int
	// used by handleCmd to prevent closing election more than once
	closed          bool
	electionKey     string
	localVoter      *LocalVoter
	masterID        peer.ID
	merkleRoot      []byte
	remoteVoters    map[peer.ID]*Voter
	rendezvousNonce Nonce
}

func NewElectionWithCandidates(candidates []Candidate) *Election {
	localVoter := NewLocalVoter(libp2p.RandomIdentity)
	e := &Election{
		Candidates:   candidates,
		localVoter:   localVoter,
		masterID:     localVoter.ID(),
		remoteVoters: make(map[peer.ID]*Voter),
	}
	e.rendezvousNonce = NewNonce()
	content := []merkletree.Content{e.rendezvousNonce}
	for _, cand := range e.Candidates {
		content = append(content, cand)
	}
	optionsMerkle, err := merkletree.NewTree(content)
	if err != nil {
		panic(err)
	}
	e.merkleRoot = optionsMerkle.MerkleRoot()
	e.electionKey = fmt.Sprintf("%s0%s",
		base58.Encode(e.merkleRoot),
		localVoter.ID())
	return e
}

func NewElectionWithElectionKey(electionKey string) *Election {
	var (
		candidates      []Candidate
		localVoter      *LocalVoter
		rendezvousNonce Nonce
	)
	localVoter = NewLocalVoter(libp2p.RandomIdentity)
	e := &Election{
		Candidates:      candidates,
		electionKey:     electionKey,
		localVoter:      localVoter,
		remoteVoters:    make(map[peer.ID]*Voter),
		rendezvousNonce: rendezvousNonce,
	}
	zeroi := strings.IndexByte(electionKey, '0')
	if zeroi == -1 {
		panic("invalid election key")
	}
	logger.Info("merkle root:", electionKey[:zeroi])
	var err error
	e.merkleRoot, err = base58.Decode(electionKey[:zeroi])
	if err != nil {
		panic(err)
	}
	e.masterID, err = peer.Decode(electionKey[zeroi+1:])
	if err != nil {
		panic(err)
	}
	logger.Info("master ID:", e.masterID)
	return e
}

A election/election.go => election/election.go +35 -0
@@ 0,0 1,35 @@
package election

import (
	"sync"

	"maunium.net/go/mautrix/id"
)

type Election struct {
	sync.RWMutex

	Candidates        []Candidate
	CreateEventId     id.EventID
	CreationTimestamp int64
	Creator           id.UserID
	RoomID            id.RoomID
	Started           bool
	Title             string
	Voters            map[id.UserID]*Voter
}

func NewElection(candidates []Candidate, createEventId id.EventID,
	creationTimestamp int64, creator id.UserID, roomID id.RoomID,
	started bool, title string) *Election {
	return &Election{
		Candidates:        candidates,
		CreateEventId:     createEventId,
		CreationTimestamp: creationTimestamp,
		Creator:           creator,
		RoomID:            roomID,
		Started:           started,
		Title:             title,
		Voters:            make(map[id.UserID]*Voter),
	}
}

A election/map.go => election/map.go +50 -0
@@ 0,0 1,50 @@
package election

import (
	"sort"
	"sync"

	"maunium.net/go/mautrix/id"
)

type ElectionsMap struct{
	sync.RWMutex
	M map[id.EventID]*Election
	L []*Election // sorted list of elections by CreationTimestamp (newest to oldest)
}

func NewElectionsMap() *ElectionsMap {
	return &ElectionsMap{
		M: make(map[id.EventID]*Election),
		L: make([]*Election, 0),
	}
}

func (em *ElectionsMap) Get(eventID id.EventID) *Election {
	em.RLock()
	defer em.RUnlock()
	return em.M[eventID]
}

func (em *ElectionsMap) GetOk(eventID id.EventID) (*Election, bool) {
	em.RLock()
	defer em.RUnlock()
	el, ok := em.M[eventID]
	return el, ok
}

func (em *ElectionsMap) Set(eventID id.EventID, el *Election) {
	em.Lock()
	defer em.Unlock()

	em.M[eventID] = el

	i := sort.Search(len(em.L), func(i int) bool {
		return em.L[i].CreationTimestamp < el.CreationTimestamp
	})
	newList := make([]*Election, len(em.L) + 1)
	copy(newList[:i], em.L[:i])
	newList[i] = el
	copy(newList[i+1:], em.L[i:])
	em.L = newList
}

R merkle.go => election/merkle.go +1 -22
@@ 1,11 1,8 @@
package main
package election

import (
	"bytes"
	"crypto/rand"
	"crypto/sha256"
	"fmt"
	"os"

	"github.com/cbergoon/merkletree"
	"github.com/mr-tron/base58/base58"


@@ 51,21 48,3 @@ func (n Nonce) CalculateHash() ([]byte, error) {
func (n Nonce) Equals(other merkletree.Content) (bool, error) {
	return n == other.(Nonce), nil
}

func verifyElectionInfo(election *Election, merkleRoot []byte) {
	content := []merkletree.Content{election.rendezvousNonce}
	var err error
	for _, eo := range election.Candidates {
		content = append(content, eo)
	}
	optionsMerkle, err := merkletree.NewTree(content)
	if err != nil {
		panic(err)
	}
	if bytes.Compare(optionsMerkle.MerkleRoot(), merkleRoot) == 0 {
		fmt.Println("election info verification succeeded!")
	} else {
		fmt.Println("election info verification failed; exiting")
		os.Exit(1)
	}
}

A election/msg.go => election/msg.go +224 -0
@@ 0,0 1,224 @@
package election

import (
	"encoding/base64"
	"math/big"
	"reflect"

	log "github.com/sirupsen/logrus"
	"golang.org/x/crypto/nacl/box"
	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/event"
	"maunium.net/go/mautrix/id"
)

var (
	// sent when user wants to create an election
	CreateElectionMessage = event.Type{
		Type:  "xyz.tallyard.new",
		Class: event.MessageEventType,
	}
	// sent when a user wants to join an election
	JoinElectionMessage = event.Type{
		Type:  "xyz.tallyard.join",
		Class: event.MessageEventType,
	}
	// sent when a user wants to start a previously-create election. The
	// user who sends this message must be the one who created the election
	StartElectionMessage = event.Type{
		Type:  "xyz.tallyard.start",
		Class: event.MessageEventType,
	}
	// indicate's user's evaluation of their polynomial using others' inputs
	EvalMessage = event.Type{
		Type:  "xyz.tallyard.eval",
		Class: event.MessageEventType,
	}
	// indicates a user's individual summation
	SumMessage = event.Type{
		Type:  "xyz.tallyard.sum",
		Class: event.MessageEventType,
	}
	// everyone announces their result at the end
	ResultMessage = event.Type{
		Type:  "xyz.tallyard.result",
		Class: event.MessageEventType,
	}
)

type CreateElectionContent struct {
	Candidates []Candidate `json:"candidates"`
	Title      string      `json:"title"`
	Version    string      `json:"version"`
}

type JoinElectionContent struct {
	CreateEventId id.EventID `json:"create_event_id"`
	Input         string     `json:"input"`
	NaclPublicKey string     `json:"nacl_public_key"`
}

type StartElectionContent struct {
	CreateEventId id.EventID  `json:"create_event_id"`
	Voters        []id.UserID `json:"voters"`
}

type EvalMessageContent struct {
	CreateEventId id.EventID           `json:"create_event_id"`
	Outputs       map[id.UserID]string `json:"outputs"`
}

type SumMessageContent struct {
	CreateEventId id.EventID `json:"create_event_id"`
	Sum           string     `json:"sum"`
}

type ResultMessageContent struct {
	CreateEventId id.EventID `json:"create_event_id"`
	Result        string     `json:"result"`
}

func init() {
	event.TypeMap[CreateElectionMessage] = reflect.TypeOf(CreateElectionContent{})
	event.TypeMap[JoinElectionMessage]   = reflect.TypeOf(JoinElectionContent{})
	event.TypeMap[StartElectionMessage]  = reflect.TypeOf(StartElectionContent{})
	event.TypeMap[EvalMessage]           = reflect.TypeOf(EvalMessageContent{})
	event.TypeMap[SumMessage]            = reflect.TypeOf(SumMessageContent{})
	event.TypeMap[ResultMessage]         = reflect.TypeOf(ResultMessageContent{})
}

func DebugCB(source mautrix.EventSource, evt *event.Event) {
	return
	// fmt.Printf("%[5]d: <%[1]s> %[4]s (%[2]s/%[3]s)\n", evt.Sender, evt.Type.String(), evt.ID, evt.Content.AsMessage().Body, source)
}

func OnCreateElectionMessage(source mautrix.EventSource, evt *event.Event, elections *ElectionsMap) {
	DebugCB(source, evt)
	// TODO: check version
	content, ok := evt.Content.Parsed.(*CreateElectionContent)
	if !ok {
		log.Warnf("ignoring %s's create since we couldn't cast message content to CreateElectionContent", evt.Sender)
		return
	}
	elections.Set(evt.ID, NewElection(content.Candidates, evt.ID,
		evt.Timestamp, evt.Sender, evt.RoomID, false, content.Title))
}

func OnJoinElectionMessage(source mautrix.EventSource, evt *event.Event, elections *ElectionsMap) {
	DebugCB(source, evt)
	content, ok := evt.Content.Parsed.(*JoinElectionContent)
	if !ok {
		log.Warnf("ignoring %s's join since we couldn't cast message content to JoinElectionContent", evt.Sender)
		return
	}
	bytes, err := base64.StdEncoding.DecodeString(content.Input)
	if err != nil {
		log.Warnf("ignoring %s's join since we couldn't decode their input", evt.Sender)
		return
	}
	input := new(big.Int).SetBytes(bytes)
	el := elections.Get(content.CreateEventId)
	el.Lock()
	defer el.Unlock()
	if el.Started {
		log.Warnf("ignoring %s's join since the election has already started", evt.Sender)
		return
	}
	var pubKey [32]byte
	bytes, err = base64.StdEncoding.DecodeString(content.NaclPublicKey)
	if err != nil {
		log.Warnf("ignoring %s's join since we couldn't decode their public key", evt.Sender)
		return
	}
	copy(pubKey[:], bytes)
	el.Voters[evt.Sender] = NewVoter(evt.Sender, input, &pubKey)
}

func OnStartElectionMessage(source mautrix.EventSource, evt *event.Event, elections *ElectionsMap) {
	DebugCB(source, evt)
	content, ok := evt.Content.Parsed.(*StartElectionContent)
	if !ok {
		log.Warnf("ignoring %s's election start since we couldn't cast message content to StartElectionContent", evt.Sender)
		return
	}
	el := elections.Get(content.CreateEventId)
	el.Lock()
	defer el.Unlock()
	if evt.Sender != el.Creator {
		log.Warnf("ignoring %s's election start since they didn't start the election", evt.Sender)
		return
	}
	el.Started = true
}

func OnEvalMessage(source mautrix.EventSource, evt *event.Event, elections *ElectionsMap, localVoter *LocalVoter) {
	DebugCB(source, evt)
	content, ok := evt.Content.Parsed.(*EvalMessageContent)
	if !ok {
		log.Warn("ignoring eval message since we couldn't cast message content to EvalMessageContent")
		return
	}
	encodedEncryptedOutput, ok := content.Outputs[localVoter.UserID]
	if !ok {
		log.Errorf("our user ID was not included in an eval message! The election will be unable to finish; blame %s", evt.Sender)
		return
	}
	encryptedOutput, err := base64.StdEncoding.DecodeString(encodedEncryptedOutput)
	if err != nil {
		log.Errorf("couldn't decode encrypted output: %s", err)
		return
	}
	el := elections.Get(content.CreateEventId)
	el.Lock()
	defer el.Unlock()
	voter := el.Voters[evt.Sender]

	var decryptNonce [24]byte
	copy(decryptNonce[:], encryptedOutput[:24])
	decryptedOutput, ok := box.Open(nil, encryptedOutput[24:], &decryptNonce, voter.PubKey, localVoter.PrivKey)

	if !ok {
		log.Errorf("decryption error")
		return
	}

	voter.Output = new(big.Int).SetBytes(decryptedOutput)
}

func OnSumMessage(source mautrix.EventSource, evt *event.Event, elections *ElectionsMap) {
	DebugCB(source, evt)
	content, ok := evt.Content.Parsed.(*SumMessageContent)
	if !ok {
		log.Warnf("ignoring %s's sum since we couldn't cast message content to SumMessageContent", evt.Sender)
		return
	}
	sum, ok := new(big.Int).SetString(content.Sum, 64)
	if !ok {
		log.Warnf("ignoring %s's sum since we couldn't base64 decode the sum", evt.Sender)
		return
	}
	el := elections.Get(content.CreateEventId)
	el.Lock()
	defer el.Unlock()
	voter := el.Voters[evt.Sender]
	voter.Sum = sum
}

func OnResultMessage(source mautrix.EventSource, evt *event.Event, elections *ElectionsMap) {
	DebugCB(source, evt)
	content, ok := evt.Content.Parsed.(*ResultMessageContent)
	if !ok {
		log.Warnf("ignoring %s's result since we couldn't cast message content to ResultMessageContent", evt.Sender)
		return
	}
	result, ok := new(big.Int).SetString(content.Result, 64)
	if !ok {
		log.Warnf("ignoring %s's result since we couldn't base64 decode the result", evt.Sender)
		return
	}
	el := elections.Get(content.CreateEventId)
	el.Lock()
	defer el.Unlock()
	voter := el.Voters[evt.Sender]
	voter.Result = result
}

A election/version.go => election/version.go +3 -0
@@ 0,0 1,3 @@
package election

const Version string = "0.0.0"

A election/voter.go => election/voter.go +305 -0
@@ 0,0 1,305 @@
package election

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"math/big"

	log "github.com/sirupsen/logrus"
	"golang.org/x/crypto/nacl/box"
	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/id"
	"tallyard.xyz/math"
)

type Voter struct {
	Input  *big.Int
	Output *big.Int
	PubKey *[32]byte
	Result *big.Int
	Sum    *big.Int
	UserID id.UserID
}

type LocalVoter struct {
	*Voter
	PrivKey *[32]byte
	ballot  []byte
	poly    *math.Poly
}

func NewVoter(userID id.UserID, input *big.Int, pubKey *[32]byte) *Voter {
	return &Voter{
		UserID: userID,
		Input:  input,
		PubKey: pubKey,
	}
}

func NewLocalVoter(userID id.UserID) *LocalVoter {
	pubKey, privKey, err := box.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}
	input, err := math.RandomBigInt(1024, false)
	if err != nil {
		panic(err)
	}
	return &LocalVoter{
		Voter:   NewVoter(userID, input, pubKey),
		PrivKey: privKey,
	}
}

func CreateElection(client *mautrix.Client, candidates []Candidate, title string, roomID id.RoomID) (id.EventID, error) {
	resp, err := client.SendMessageEvent(roomID, CreateElectionMessage, CreateElectionContent{
		Candidates: candidates,
		Title:      title,
		Version:    Version,
	})
	return resp.EventID, err
}

func StartElection(client *mautrix.Client, election *Election) error {
	// TODO check that we created the election
	election.RLock()
	defer election.RUnlock()
	voters := make([]id.UserID, 0, len(election.Voters))
	for userID := range election.Voters {
		voters = append(voters, userID)
	}
	_, err := client.SendMessageEvent(election.RoomID, StartElectionMessage, StartElectionContent{
		CreateEventId: election.CreateEventId,
		Voters:        voters,
	})
	return err
}

func (localVoter *LocalVoter) JoinElection(client *mautrix.Client, election *Election) error {
	election.RLock()
	defer election.RUnlock()
	_, err := client.SendMessageEvent(election.RoomID, JoinElectionMessage, JoinElectionContent{
		CreateEventId: election.CreateEventId,
		Input:         base64.StdEncoding.EncodeToString(localVoter.Input.Bytes()),
		NaclPublicKey: base64.StdEncoding.EncodeToString((*localVoter.PubKey)[:]),
	})
	return err
}

// func handleCmd(cmd string, rw *bufio.ReadWriter, stream network.Stream, election *Election) {
// 	localVoter := election.localVoter
// 	switch cmd {
// 	case "info":
// 		rw.WriteString(fmt.Sprintf("%s\n", election.rendezvousNonce))
// 		for _, option := range election.Candidates {
// 			rw.WriteString(fmt.Sprintf("%s\n", option))
// 		}
// 		rw.Flush()
// 	case "close":
// 		election.Lock()
// 		defer election.Unlock()
// 		if peer := stream.Conn().RemotePeer(); peer != election.masterID {
// 			log.Warning("received close command from non-master:", peer)
// 			return
// 		}
// 		if election.closed {
// 			log.Warning("election already closed")
// 			return
// 		}
// 		str, err := rw.ReadString('\n')
// 		if err != nil && err != io.EOF {
// 			panic(err)
// 		}
// 		str = stripNewline(str)
// 		numPeers, err := strconv.Atoi(str)
// 		if err != nil {
// 			panic(err)
// 		}
// 		election.close <- numPeers
// 		election.closed = true
// 	case "shake":
// 		election.Lock()
// 		defer election.Unlock()
// 		peerID := stream.Conn().RemotePeer()
// 		if election.closed {
// 			log.Warning("peer attempted to shake after "+
// 				"election was closed:", peerID)
// 			return
// 		}
// 		if _, exists := election.RemoteVoters[peerID]; exists {
// 			log.Warning("peer attempted to shake after having already done so", peerID)
// 			return
// 		}
// 		fmt.Printf("found voter: %s\n", peerID)
// 		election.RemoteVoters[peerID] = &Voter{
// 			addrInfo: peer.AddrInfo{
// 				ID:    peerID,
// 				Addrs: []multiaddr.Multiaddr{stream.Conn().RemoteMultiaddr()},
// 			},
// 		}
// 	case "eval": // peer is giving their input and requesting output from our poly
// 		localVoter.polyMu.RLock()
// 		defer localVoter.polyMu.RUnlock()
// 		if localVoter.poly == nil {
// 			log.Warning("peer attempted to eval before we had our poly:",
// 				stream.Conn().RemotePeer())
// 			return
// 		}
// 		inputb58, err := rw.ReadString('\n')
// 		if err != nil && err != io.EOF {
// 			log.Warning("unable to read input from peer during eval:",
// 				stream.Conn().RemotePeer())
// 			return
// 		}
// 		inputb58 = stripNewline(inputb58)
// 		inputBytes, err := base58.Decode(inputb58)
// 		if err != nil {
// 			log.Warning("unable to base58 decode input from peer during eval:",
// 				stream.Conn().RemotePeer())
// 			return
// 		}
// 		peer, exists := election.RemoteVoters[stream.Conn().RemotePeer()]
// 		if !exists {
// 			log.Warning("received eval command from unrecognized peer")
// 			return
// 		}
// 		peer.inputMu.Lock()
// 		defer peer.inputMu.Unlock()
// 		peer.input = new(big.Int).SetBytes(inputBytes)
// 		log.Infof("%s input: %s", peer.addrInfo.ID, peer.input)
// 		output := localVoter.poly.Eval(peer.input)
// 		rw.WriteString(base58.Encode(output.Bytes()))
// 		rw.Flush()
// 	case "sum":
// 		localVoter.sumMu.RLock()
// 		defer localVoter.sumMu.RUnlock()
// 		if localVoter.sum == nil {
// 			log.Info("peer attempted to fetch sum "+
// 				"before we computed it:", stream.Conn().RemotePeer())
// 			return
// 		}
// 		rw.WriteString(base58.Encode(localVoter.sum.Bytes()))
// 		rw.Flush()
// 	default:
// 		log.Warningf("uknown command %s", cmd)
// 	}
// }

// func (election *Election) StartVoting() {
// 	localVoter := election.localVoter

// 	var err error
// 	localVoter.inputMu.Lock()
// 	localVoter.input, err = math.RandomBigInt(128, false)
// 	localVoter.inputMu.Unlock()
// 	if err != nil {
// 		panic(err)
// 	}
// 	log.Infof("our input: %s", localVoter.input)

// 	localVoter.ballot = vote(election.Candidates)
// 	log.Infof("our ballot: %v", localVoter.ballot)

// 	// no +1 since we want degree k-1 where k is total number of voters
// 	localVoter.polyMu.Lock()
// 	localVoter.poly = math.NewRandomPoly(uint(len(election.RemoteVoters)),
// 		1024, localVoter.ballot)
// 	localVoter.polyMu.Unlock()
// 	log.Infof("our constant: %s", localVoter.poly.constant)

// 	// get outputs
// 	var wg sync.WaitGroup
// 	for _, voter := range election.RemoteVoters {
// 		wg.Add(1)
// 		go func(voter *Voter) {
// 			voter.output = voter.fetchNumber(election, "eval", base58.Encode(localVoter.input.Bytes()))
// 			logger.Infof("voter %s output: %s", voter.addrInfo.ID, voter.output)
// 			wg.Done()
// 		}(voter)
// 	}
// 	wg.Wait()

// 	// calculate sum
// 	localVoter.sumMu.Lock()
// 	localVoter.sum = localVoter.poly.Eval(localVoter.input)
// 	for _, voter := range election.RemoteVoters {
// 		localVoter.sum.Add(localVoter.sum, voter.output)
// 	}
// 	localVoter.sumMu.Unlock()
// 	logger.Infof("our sum: %s", localVoter.sum)

// 	// get sums
// 	for _, voter := range election.RemoteVoters {
// 		wg.Add(1)
// 		go func(voter *Voter) {
// 			voter.sum = voter.fetchNumber(election, "sum")
// 			logger.Infof("voter %s sum: %s",
// 				voter.addrInfo.ID, voter.sum)
// 			wg.Done()
// 		}(voter)
// 	}
// 	wg.Wait()

// 	mat := constructPolyMatrix(election)
// 	mat.RREF()

// 	constant := mat[0][len(mat[0])-1]
// 	if !constant.IsInt() {
// 		panic("constant term is not an integer")
// 	}

// 	result := constant.Num().Bytes()

// 	// number of bytes we need to insert at the front since they're zero
// 	diff := (len(election.Candidates)*len(election.Candidates)) - len(result)
// 	result = append(make([]byte, diff), result...)

// 	printResults(result, election.Candidates)

// 	// temporary
// 	select {}
// }

// func constructPolyMatrix(election *Election) Matrix {
// 	mat := make(Matrix, len(election.remoteVoters) + 1) // row for everyone (including ourselves)

// 	i := 0
// 	for _, voter := range election.RemoteVoters {
// 		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(election.RemoteVoters)); j++ {
// 			row[j].SetInt(new(big.Int).Exp(voter.input, big.NewInt(j), nil))
// 		}
// 		row[j].SetInt(voter.sum)
// 		i++
// 	}

// 	// row for ourselves
// 	mat[i] = make([]big.Rat, len(mat) + 1)
// 	row := mat[i]
// 	row[0].SetInt64(1)
// 	localVoter := election.localVoter
// 	var j int64
// 	for j = 1; j <= int64(len(election.RemoteVoters)); j++ {
// 		row[j].SetInt(new(big.Int).Exp(localVoter.input, big.NewInt(j), nil))
// 	}
// 	row[j].SetInt(localVoter.sum)

// 	return mat
// }

func printResults(result []byte, candidates []Candidate) {
	log.Infof("result: %v", result)
	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])
			}
		}
	}
}

M go.mod => go.mod +4 -6
@@ 4,16 4,14 @@ go 1.13

require (
	github.com/cbergoon/merkletree v0.2.0
	github.com/ipfs/go-log v1.0.2
	github.com/gdamore/tcell v1.3.0
	github.com/kr/pretty v0.1.0 // indirect
	github.com/kyoh86/xdg v1.2.0
	github.com/libp2p/go-libp2p v0.6.1
	github.com/libp2p/go-libp2p-core v0.5.0
	github.com/libp2p/go-libp2p-discovery v0.2.0
	github.com/libp2p/go-libp2p-kad-dht v0.5.0
	github.com/mr-tron/base58 v1.2.0
	github.com/multiformats/go-multiaddr v0.2.1
	github.com/rivo/tview v0.0.0-20200528200248-fe953220389f
	github.com/sirupsen/logrus v1.2.0
	golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
	golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect
	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
	maunium.net/go/mautrix v0.7.13
)

M go.sum => go.sum +0 -456
@@ 1,152 1,18 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8=
github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
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-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
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/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018 h1:6xT9KW8zLC5IlbaIF5Q7JNieBoACT7iW0YTxQHR0in0=
github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4=
github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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 v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU=
github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
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.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
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/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo=
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/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M=
github.com/ipfs/go-cid v0.0.5 h1:o0Ix8e/ql7Zb5UVUJEUfjsWCIY8t48++9lR8qi6oiJU=
github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog=
github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE=
github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw=
github.com/ipfs/go-datastore v0.3.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw=
github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA=
github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA=
github.com/ipfs/go-datastore v0.4.4 h1:rjvQ9+muFaJ+QZ7dN5B1MSDNQ0JVZKkkES/rMZmA8X8=
github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA=
github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk=
github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps=
github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8=
github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s=
github.com/ipfs/go-ds-badger v0.0.7/go.mod h1:qt0/fWzZDoPW6jpQeqUjR5kBfhDNB65jd9YlmAvpQBk=
github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE=
github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc=
github.com/ipfs/go-ds-leveldb v0.1.0/go.mod h1:hqAW8y4bwX5LWcCtku2rFNX3vjDZCy5LZCg+cSZvYb8=
github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s=
github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
github.com/ipfs/go-ipfs-util v0.0.1 h1:Wz9bL2wB2YBJqggkA4dD7oSmqB4cAnpNbGrlHJulv50=
github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc=
github.com/ipfs/go-log v0.0.1 h1:9XTUN/rW64BCG1YhPK9Hoy3q8nr4gOmHHBpgFdfw6Lc=
github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM=
github.com/ipfs/go-log v1.0.2 h1:s19ZwJxH8rPWzypjcDpqPLIyV7BnbLqvpli3iZoqYK0=
github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk=
github.com/ipfs/go-log/v2 v2.0.2 h1:xguurydRdfKMJjKyxNXNU8lYP0VZH1NUwJRwUorjuEw=
github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0=
github.com/ipfs/go-todocounter v0.0.2 h1:9UBngSQhylg2UDcxSAtpkT+rEWFr26hDPXVStE8LFyc=
github.com/ipfs/go-todocounter v0.0.2/go.mod h1:l5aErvQc8qKE2r7NDMjmq5UNAvuZy0rC8BHOplkWvZ4=
github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc=
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
github.com/jackpal/go-nat-pmp v1.0.1 h1:i0LektDkO1QlrTm/cSuP+PyBCDnYvjPLGl4LdWEMiaA=
github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs=
github.com/jbenet/go-cienv v0.1.0 h1:Vc/s0QbQtoxX8MwwSLWWh+xNNZvM3Lw7NsTcHrvvhMc=
github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=
github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2 h1:vhC1OXXiT9R2pczegwz6moDvuRpggaroAXhPIseh57A=
github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs=
github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY=
github.com/jbenet/goprocess v0.1.3 h1:YKyIEECS/XvcfHtBzxtjBBbWK+MbvA6dG8ASiqwvr10=
github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
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 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jvAOgROwZ3H+Y3hIDk4tbbmIjcYQ=
github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk=
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=


@@ 156,256 22,31 @@ 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/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libp2p/go-addr-util v0.0.1 h1:TpTQm9cXVRVSKsYbgQ7GKc3KbbHVTnbostgGaDEP+88=
github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ=
github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ=
github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs=
github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM=
github.com/libp2p/go-conn-security-multistream v0.1.0 h1:aqGmto+ttL/uJgX0JtQI0tD21CIEy5eYd1Hlp0juHY0=
github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc=
github.com/libp2p/go-eventbus v0.1.0 h1:mlawomSAjjkk97QnYiEmHsLu7E136+2oCWSHRUvMfzQ=
github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4=
github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8=
github.com/libp2p/go-flow-metrics v0.0.2/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs=
github.com/libp2p/go-flow-metrics v0.0.3 h1:8tAs/hSdNvUiLgtlSy3mxwxWP4I9y/jlkPFT7epKdeM=
github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs=
github.com/libp2p/go-libp2p v0.5.0/go.mod h1:Os7a5Z3B+ErF4v7zgIJ7nBHNu2LYt8ZMLkTQUB3G/wA=
github.com/libp2p/go-libp2p v0.6.1 h1:mxabyJf4l6AmotDOKObwSfBNBWjL5VYXysVFLUMAuB8=
github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54=
github.com/libp2p/go-libp2p-autonat v0.1.1 h1:WLBZcIRsjZlWdAZj9CiBSvU2wQXoUOiS1Zk1tM7DTJI=
github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE=
github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro=
github.com/libp2p/go-libp2p-blankhost v0.1.4 h1:I96SWjR4rK9irDHcHq3XHN6hawCRTPUADzkJacgZLvk=
github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU=
github.com/libp2p/go-libp2p-circuit v0.1.4 h1:Phzbmrg3BkVzbqd4ZZ149JxCuUWu2wZcXf/Kr6hZJj8=
github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU=
github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco=
github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I=
github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI=
github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0=
github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g=
github.com/libp2p/go-libp2p-core v0.2.5/go.mod h1:6+5zJmKhsf7yHn1RbmYDu08qDUpIUxGdqHuEZckmZOA=
github.com/libp2p/go-libp2p-core v0.3.0 h1:F7PqduvrztDtFsAa/bcheQ3azmNo+Nq7m8hQY5GiUW8=
github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw=
github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII=
github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0=
github.com/libp2p/go-libp2p-core v0.5.0 h1:FBQ1fpq2Fo/ClyjojVJ5AKXlKhvNc/B6U0O+7AN1ffE=
github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0=
github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI=
github.com/libp2p/go-libp2p-discovery v0.2.0 h1:1p3YSOq7VsgaL+xVHPi8XAmtGyas6D2J6rWBEfz/aiY=
github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg=
github.com/libp2p/go-libp2p-kad-dht v0.5.0 h1:kDMtCftpQOL2s84/dZmw5z4NmBe6ByeDLKpcn6TcyxU=
github.com/libp2p/go-libp2p-kad-dht v0.5.0/go.mod h1:42YDfiKXzIgaIexiEQ3rKZbVPVPziLOyHpXbOCVd814=
github.com/libp2p/go-libp2p-kbucket v0.2.3 h1:XtNfN4WUy0cfeJoJgWCf1lor4Pp3kBkFJ9vQ+Zs+VUM=
github.com/libp2p/go-libp2p-kbucket v0.2.3/go.mod h1:opWrBZSWnBYPc315q497huxY3sz1t488X6OiXUEYWKA=
github.com/libp2p/go-libp2p-loggables v0.1.0 h1:h3w8QFfCt2UJl/0/NW4K829HX/0S4KD31PQ7m8UXXO8=
github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90=
github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo=
github.com/libp2p/go-libp2p-mplex v0.2.1 h1:E1xaJBQnbSiTHGI1gaBKmKhu1TUKkErKJnE8iGvirYI=
github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE=
github.com/libp2p/go-libp2p-mplex v0.2.2 h1:+Ld7YDAfVERQ0E+qqjE7o6fHwKuM0SqTzYiwN1lVVSA=
github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo=
github.com/libp2p/go-libp2p-nat v0.0.5 h1:/mH8pXFVKleflDL1YwqMg27W9GD8kjEx7NY0P6eGc98=
github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE=
github.com/libp2p/go-libp2p-netutil v0.1.0 h1:zscYDNVEcGxyUpMd0JReUZTrpMfia8PmLKcKF72EAMQ=
github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU=
github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY=
github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY=
github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI=
github.com/libp2p/go-libp2p-peerstore v0.1.4 h1:d23fvq5oYMJ/lkkbO4oTwBp/JP+I/1m5gZJobNXCE/k=
github.com/libp2p/go-libp2p-peerstore v0.1.4/go.mod h1:+4BDbDiiKf4PzpANZDAT+knVdLxvqh7hXOujessqdzs=
github.com/libp2p/go-libp2p-peerstore v0.2.0 h1:XcgJhI8WyUOCbHyRLNEX5542YNj8hnLSJ2G1InRjDhk=
github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ=
github.com/libp2p/go-libp2p-pnet v0.2.0 h1:J6htxttBipJujEjz1y0a5+eYoiPcFHhSYHH6na5f0/k=
github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA=
github.com/libp2p/go-libp2p-record v0.1.2 h1:M50VKzWnmUrk/M5/Dz99qO9Xh4vs8ijsK+7HkJvRP+0=
github.com/libp2p/go-libp2p-record v0.1.2/go.mod h1:pal0eNcT5nqZaTV7UGhqeGqxFgGdsU/9W//C8dqjQDk=
github.com/libp2p/go-libp2p-routing v0.1.0 h1:hFnj3WR3E2tOcKaGpyzfP4gvFZ3t8JkQmbapN0Ct+oU=
github.com/libp2p/go-libp2p-routing v0.1.0/go.mod h1:zfLhI1RI8RLEzmEaaPwzonRvXeeSHddONWkcTcB54nE=
github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8=
github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g=
github.com/libp2p/go-libp2p-secio v0.2.1 h1:eNWbJTdyPA7NxhP7J3c5lT97DC5d+u+IldkgCYFTPVA=
github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8=
github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4=
github.com/libp2p/go-libp2p-swarm v0.2.2 h1:T4hUpgEs2r371PweU3DuH7EOmBIdTBCwWs+FLcgx3bQ=
github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU=
github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E=
github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0=
github.com/libp2p/go-libp2p-testing v0.1.1 h1:U03z3HnGI7Ni8Xx6ONVZvUFOAzWYmolWf5W5jAOPNmU=
github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0=
github.com/libp2p/go-libp2p-transport-upgrader v0.1.1 h1:PZMS9lhjK9VytzMCW3tWHAXtKXmlURSc3ZdvwEcKCzw=
github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA=
github.com/libp2p/go-libp2p-transport-upgrader v0.2.0 h1:5EhPgQhXZNyfL22ERZTUoVp9UVVbNowWNVtELQaKCHk=
github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns=
github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8=
github.com/libp2p/go-libp2p-yamux v0.2.1 h1:Q3XYNiKCC2vIxrvUJL+Jg1kiyeEaIDNKLjgEjo3VQdI=
github.com/libp2p/go-libp2p-yamux v0.2.1/go.mod h1:1FBXiHDk1VyRM1C0aez2bCfHQ4vMZKkAQzZbkSQt5fI=
github.com/libp2p/go-libp2p-yamux v0.2.5 h1:MuyItOqz03oi8npvjgMJxgnhllJLZnO/dKVOpTZ9+XI=
github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA=
github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q=
github.com/libp2p/go-maddr-filter v0.0.5 h1:CW3AgbMO6vUvT4kf87y4N+0P8KUl2aqLYhrGyDUbLSg=
github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M=
github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0=
github.com/libp2p/go-mplex v0.1.0 h1:/nBTy5+1yRyY82YaO6HXQRnO5IAGsXTjEJaR3LdTPc0=
github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU=
github.com/libp2p/go-mplex v0.1.1 h1:huPH/GGRJzmsHR9IZJJsrSwIM5YE2gL4ssgl1YWb/ps=
github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk=
github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ=
github.com/libp2p/go-msgio v0.0.4 h1:agEFehY3zWJFUHK6SEMR7UYmk2z6kC3oeCM7ybLhguA=
github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ=
github.com/libp2p/go-nat v0.0.4 h1:KbizNnq8YIf7+Hn7+VFL/xE0eDrkPru2zIO9NMwL8UQ=
github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo=
github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0=
github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc=
github.com/libp2p/go-openssl v0.0.4 h1:d27YZvLoTyMhIN4njrkr8zMDOM4lfpHIp6A+TK9fovg=
github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc=
github.com/libp2p/go-reuseport v0.0.1 h1:7PhkfH73VXfPJYKQ6JwS5I/eVcoyYi9IMNGc6FWpFLw=
github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA=
github.com/libp2p/go-reuseport-transport v0.0.2 h1:WglMwyXyBu61CMkjCCtnmqNqnjib0GIEjMiHTwR/KN4=
github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs=
github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14=
github.com/libp2p/go-stream-muxer-multistream v0.2.0 h1:714bRJ4Zy9mdhyTLJ+ZKiROmAFwUHpeRidG+q7LTQOg=
github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc=
github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc=
github.com/libp2p/go-tcp-transport v0.1.1 h1:yGlqURmqgNA2fvzjSgZNlHcsd/IulAnKM8Ncu+vlqnw=
github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY=
github.com/libp2p/go-ws-transport v0.2.0 h1:MJCw2OrPA9+76YNRvdo1wMnSOxb9Bivj6sVFY1Xrj6w=
github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM=
github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow=
github.com/libp2p/go-yamux v1.2.3 h1:xX8A36vpXb59frIzWFdEgptLMsOANMFq2K7fPRlunYI=
github.com/libp2p/go-yamux v1.2.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow=
github.com/libp2p/go-yamux v1.3.3 h1:mWuzZRCAeTBFdynLlsYgA/EIeMOLr8XY04wa52NRhsE=
github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
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/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI=
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44=
github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=
github.com/multiformats/go-multiaddr v0.2.0 h1:lR52sFwcTCuQb6bTfnXF6zA2XfyYvyd+5a9qECv/J90=
github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4=
github.com/multiformats/go-multiaddr v0.2.1 h1:SgG/cw5vqyB5QQe5FPe2TqggU9WtrA9X4nZw7LlVqOI=
github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE=
github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q=
github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q=
github.com/multiformats/go-multiaddr-dns v0.2.0 h1:YWJoIDwLePniH7OU5hBnDZV6SWuvJqJ0YtN6pLeH9zA=
github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0=
github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q=
github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=
github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=
github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU=
github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ=
github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ=
github.com/multiformats/go-multiaddr-net v0.1.2 h1:P7zcBH9FRETdPkDrylcXVjQLQ2t1JQtNItZULWNWgeg=
github.com/multiformats/go-multiaddr-net v0.1.2/go.mod h1:QsWt3XK/3hwvNxZJp92iMQKME1qHfpYmyIjFVsSOY6Y=
github.com/multiformats/go-multiaddr-net v0.1.3 h1:q/IYAvoPKuRzGeERn3uacWgm0LIWkLZBAvO5DxSzq3g=
github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA=
github.com/multiformats/go-multibase v0.0.1 h1:PN9/v21eLywrFWdFNsFKaU04kLJzuYzmrJR+ubhT9qA=
github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs=
github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U=
github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po=
github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
github.com/multiformats/go-multihash v0.0.9/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
github.com/multiformats/go-multihash v0.0.13 h1:06x+mk/zj1FoMsgNejLpy6QTvJqlSt/BhLEy87zidlc=
github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc=
github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg=
github.com/multiformats/go-multistream v0.1.1 h1:JlAdpIFhBhGRLxe9W6Om0w++Gd6KMWoFPZL/dEnm9nI=
github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38=
github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/multiformats/go-varint v0.0.5 h1:XVZwSo04Cs3j/jS0uAEPpT3JY6DzMcVLLoWOSnCxOjg=
github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
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.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.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/rivo/tview v0.0.0-20200528200248-fe953220389f h1:tRx/LLIP2PSA7johw9xhf+6NUCLC4BbMhpGdm110MGI=
github.com/rivo/tview v0.0.0-20200528200248-fe953220389f/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84=
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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY=
github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0=
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU=
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/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 v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
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/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc=
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 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
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/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=


@@ 415,92 56,24 @@ github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8=
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/sjson v1.1.1 h1:7h1vk049Jnd5EH9NyzNiEuwYW4b5qgreBbqRC19AS3U=
github.com/tidwall/sjson v1.1.1/go.mod h1:yvVuSnpEQv5cYIrO+AT6kw4QVfd5SDZoGIS7/5+fZFs=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k=
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc=
github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM=
github.com/whyrusleeping/go-logging v0.0.1 h1:fwpzlmT0kRC/Fmd0MdmGgJG/CXIZ6gFq46FQZjprUcc=
github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE=
github.com/whyrusleeping/mafmt v1.2.8 h1:TCghSl5kkwEE0j+sU/gudyhVMRlpBin8fMBBHg59EbA=
github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA=
github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4=
github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 h1:E9S12nwJwEOXe2d6gT6qxdvqMnNq+VnSsKPgm2ZZNds=
github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI=
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA=
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
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-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/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-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/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-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
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-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-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-20181205085412-a5c9d58dba9a/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-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 h1:rOhMmluY6kLMhdnrivzec6lLgaVbMHMn2ISQXJeJ5EM=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=


@@ 510,43 83,14 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/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-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8=
gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
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=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
maunium.net/go/maulogger/v2 v2.1.1/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
maunium.net/go/mautrix v0.7.13 h1:qfnvLxvQafvLgHbdZF/+9qs9gyArYf8fUnzfQbjgQaU=
maunium.net/go/mautrix v0.7.13/go.mod h1:Jn0ijwXwMFvJFIN9IljirIVKpZQbZP/Dk7pdX2qDmXk=

D main.go => main.go +0 -224
@@ 1,224 0,0 @@
package main

import (
	"bufio"
	"context"
	"flag"
	"fmt"
	"io"
	"os"
	"sync"

	"github.com/ipfs/go-log"
	"github.com/libp2p/go-libp2p"
	"github.com/libp2p/go-libp2p-core/host"
	"github.com/libp2p/go-libp2p-core/network"
	"github.com/libp2p/go-libp2p-core/peer"
	"github.com/libp2p/go-libp2p-core/protocol"
	"github.com/libp2p/go-libp2p-core/routing"
	dht "github.com/libp2p/go-libp2p-kad-dht"
)

const protocolID = protocol.ID("/tallyard/0.0.0")

var logger = log.Logger("tallyard")

func NewLocalVoter(hostOpts ...libp2p.Option) *LocalVoter {
	localVoter := new(LocalVoter)
	localVoter.ctx = context.Background()

	routing := libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) {
		var err error
		localVoter.kdht, err = dht.New(localVoter.ctx, h)
		if err != nil {
			return localVoter.kdht, err
		}
		logger.Info("boostrapping the DHT")
		if err = localVoter.kdht.Bootstrap(localVoter.ctx); err != nil {
			panic(err)
		}
		return localVoter.kdht, err
	})

	var err error
	localVoter.Host, err = libp2p.New(localVoter.ctx, append(hostOpts, routing)...)
	if err != nil {
		panic(err)
	}

	logger.Info("host: ", localVoter.ID())
	logger.Info("addrs: ", localVoter.Addrs())

	return localVoter
}

func (localVoter *LocalVoter) Bootstrap() {
	var wg sync.WaitGroup
	for _, peerAddr := range dht.DefaultBootstrapPeers {
		peerInfo, _ := peer.AddrInfoFromP2pAddr(peerAddr)
		wg.Add(1)
		go func() {
			defer wg.Done()
			if err := localVoter.Connect(localVoter.ctx, *peerInfo); err != nil {
				logger.Warning(err)
			} else {
				logger.Info("connection established with bootstrap node:", *peerInfo)
			}
		}()
	}
	wg.Wait()
}

func (election *Election) SetupMaster() {
	fmt.Println("share this with peers:")
	fmt.Printf("%s\n", election.electionKey)

	logger.Info("waiting for incoming streams and finding voters...")

	election.Lock()
	ch := make(chan int, 1)
	election.close = ch
	go findPeers(ch, election)
	election.Unlock()

	localVoter := election.localVoter
	localVoter.SetStreamHandler(protocolID, func(stream network.Stream) {
		streamHandler(stream, election)
	})

	fmt.Println("press ENTER to solidify group of voters and start voting")
	_, err := bufio.NewReader(os.Stdin).ReadString('\n')
	if err != nil {
		panic(err)
	}

	logger.Info("ENTER has been pressed; closing election")
	election.Lock()
	n := len(election.remoteVoters)
	election.close <- n
	close(election.close)
	election.Unlock()
	election.RLock()
	for _, voter := range election.remoteVoters {
		stream, err := localVoter.NewStream(localVoter.ctx, voter.addrInfo.ID, protocolID)
		if err != nil {
			panic(err)
		}
		writer := bufio.NewWriter(stream)
		writer.WriteString(fmt.Sprintf("close\n%d", n))
		writer.Flush()
		stream.Close()
	}
	election.RUnlock()
}

func (election *Election) SetupSlave() {
	localVoter := election.localVoter

	// have candidates and nonce been loaded from elections file?
	// if not, fetch them from master
	if len(election.Candidates) == 0 || election.rendezvousNonce == "" {
		logger.Info("attempting to open stream with master peer...")
		stream, err := localVoter.NewStream(localVoter.ctx, election.masterID, protocolID)
		rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
		if err != nil {
			panic(err)
		}
		logger.Info("opened stream with master peer")

		logger.Info("fetching election info from master")
		_, err = rw.WriteString("info")
		if err != nil {
			panic(err)
		}
		rw.Flush()
		stream.Close() // only stops writing
		// first field is the rendezvous string, which is used for peer
		// discovery
		str, err := rw.ReadString('\n')
		if err != nil && err != io.EOF {
			panic(err)
		}
		str = stripNewline(str)
		election.rendezvousNonce = Nonce(str)
		// remaining fields are the candidates
		for {
			str, err := rw.ReadString('\n')
			if err != nil && err != io.EOF {
				panic(err)
			}
			str = stripNewline(str)
			if str != "" {
				election.Candidates = append(election.Candidates, Candidate(str))
			}
			if err == io.EOF {
				break
			}
		}
		logger.Info("done fetching election info")
	}

	logger.Info("checking authenticity of election info...")
	verifyElectionInfo(election, election.merkleRoot)

	// channel used to signify when election is closed
	ch := make(chan int, 1)
	election.close = ch
	// now that we have election info, begin handling streams
	localVoter.SetStreamHandler(protocolID, func(stream network.Stream) {
		streamHandler(stream, election)
	})

	findPeers(ch, election)
}

func main() {
	log.SetAllLoggers(log.LevelWarn)

	verbose := flag.Bool("v", false, "enable verbose logging for debugging")
	master := flag.Bool("m", false, "indicate that this node is the "+
		"master and the candidates are given via positional arguments")
	flag.Parse()
	if *verbose {
		log.SetLogLevel("tallyard", "info")
	} else {
		log.SetLogLevel("dht", "critical")
		log.SetLogLevel("relay", "critical")
		log.SetLogLevel("tallyard", "critical")
	}

	var election *Election

	if *master {
		// we are the master and the candidates are the positional
		// arguments
		candidates := []Candidate{}
		for _, candidate := range flag.Args() {
			candidates = append(
				candidates,
				Candidate(candidate))
		}
		// TODO: ensure at least 2 candidates
		election = NewElectionWithCandidates(candidates)
	} else if electionKey := flag.Arg(0); electionKey != "" {
		// we are a slave the and election key was given via CLI args
		election = NewElectionWithElectionKey(electionKey)
	} else {
		// create/join election via TUI
		election = tui()
		if election == nil {
			// tui form wasn't submitted (maybe the user hit ^C)
			os.Exit(0)
		}
	}

	election.localVoter.Bootstrap()

	if election.masterID == election.localVoter.ID() {
		election.SetupMaster()
	} else {
		election.SetupSlave()
	}

	election.StartVoting()
}

R linalg.go => math/linalg.go +1 -1
@@ 1,4 1,4 @@
package main
package math

import (
	"math/big"

R linalg_test.go => math/linalg_test.go +1 -1
@@ 1,4 1,4 @@
package main
package math

import (
	"math/big"

R poly.go => math/poly.go +1 -1
@@ 1,4 1,4 @@
package main
package math

import (
	"crypto/rand"

M matrix/crypto_logger.go => matrix/crypto_logger.go +1 -1
@@ 1,4 1,4 @@
package main
package matrix

import (
	log "github.com/sirupsen/logrus"

M matrix/data.go => matrix/data.go +1 -1
@@ 1,4 1,4 @@
package main
package matrix

import (
	"bufio"

D matrix/main.go => matrix/main.go +0 -139
@@ 1,139 0,0 @@
package main

import (
	"fmt"
	"os"

	"github.com/kyoh86/xdg"
	log "github.com/sirupsen/logrus"
	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/crypto"
	"maunium.net/go/mautrix/event"
)

type MockSharedStore struct{}

var StartMessage = event.Type{"xyz.tallyard.start", event.MessageEventType}
var client *mautrix.Client
var olmMachine *crypto.OlmMachine
var gobStorePath string = xdg.DataHome() + "/tallyard/gob.dat"

func debugCB(source mautrix.EventSource, evt *event.Event) {
	fmt.Printf("%[5]d: <%[1]s> %[4]s (%[2]s/%[3]s)\n", evt.Sender, evt.Type.String(), evt.ID, evt.Content.AsMessage().Body, source)
}

func joinRoomCB(source mautrix.EventSource, evt *event.Event) {
	debugCB(source, evt)

	memberEvtContent := evt.Content.AsMember()
	if memberEvtContent.Membership != event.MembershipInvite {
		return
	}

	_, err := client.JoinRoomByID(evt.RoomID)
	if err != nil {
		panic(err)
	}
}

func encryptedCB(source mautrix.EventSource, evt *event.Event) {
	debugCB(source, evt)

	encryptedEvtContent := evt.Content.AsEncrypted()
	fmt.Println(encryptedEvtContent)

	decrypted, err := olmMachine.DecryptMegolmEvent(evt)
	if err != nil {
		log.WithError(err).Warn("couldn't decrypt melogm event")
		return
	}
	debugCB(source, decrypted)
}

func main() {
	data, err := GetData()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Println(data)

	client, err = mautrix.NewClient(data.Homeserver, data.UserID, data.AccessToken)
	if err != nil {
		panic(err)
	}

	syncer := client.Syncer.(*mautrix.DefaultSyncer)
	syncer.OnEventType(event.StateAliases, debugCB)
	syncer.OnEventType(event.StateCanonicalAlias, debugCB)
	syncer.OnEventType(event.StateCreate, debugCB)
	syncer.OnEventType(event.StateJoinRules, debugCB)
	syncer.OnEventType(event.StateHistoryVisibility, debugCB)
	syncer.OnEventType(event.StateGuestAccess, debugCB)
	syncer.OnEventType(event.StateMember, joinRoomCB)
	syncer.OnEventType(event.StatePowerLevels, debugCB)
	syncer.OnEventType(event.StateRoomName, debugCB)
	syncer.OnEventType(event.StateTopic, debugCB)
	syncer.OnEventType(event.StateRoomAvatar, debugCB)
	syncer.OnEventType(event.StatePinnedEvents, debugCB)
	syncer.OnEventType(event.StateTombstone, debugCB)
	syncer.OnEventType(event.StateEncryption, debugCB)
	syncer.OnEventType(event.EventRedaction, debugCB)
	syncer.OnEventType(event.EventMessage, debugCB)
	syncer.OnEventType(event.EventEncrypted, encryptedCB)
	syncer.OnEventType(event.EventReaction, debugCB)
	syncer.OnEventType(event.EventSticker, debugCB)
	syncer.OnEventType(event.EphemeralEventReceipt, debugCB)
	syncer.OnEventType(event.EphemeralEventTyping, debugCB)
	syncer.OnEventType(event.EphemeralEventPresence, debugCB)
	syncer.OnEventType(event.AccountDataDirectChats, debugCB)
	syncer.OnEventType(event.AccountDataPushRules, debugCB)
	syncer.OnEventType(event.AccountDataRoomTags, debugCB)
	syncer.OnEventType(event.AccountDataFullyRead, debugCB)
	syncer.OnEventType(event.AccountDataIgnoredUserList, debugCB)
	syncer.OnEventType(event.ToDeviceRoomKey, debugCB)
	syncer.OnEventType(event.ToDeviceRoomKeyRequest, debugCB)
	syncer.OnEventType(event.ToDeviceForwardedRoomKey, debugCB)
	syncer.OnEventType(event.ToDeviceEncrypted, debugCB)
	syncer.OnEventType(event.ToDeviceRoomKeyWithheld, debugCB)
	syncer.OnEventType(event.ToDeviceVerificationRequest, debugCB)
	syncer.OnEventType(event.ToDeviceVerificationStart, debugCB)
	syncer.OnEventType(event.ToDeviceVerificationAccept, debugCB)
	syncer.OnEventType(event.ToDeviceVerificationKey, debugCB)
	syncer.OnEventType(event.ToDeviceVerificationMAC, debugCB)
	syncer.OnEventType(event.ToDeviceVerificationCancel, debugCB)
	syncer.OnEventType(event.ToDeviceOrgMatrixRoomKeyWithheld, debugCB)

	resp, err := client.JoinedRooms()
	fmt.Println("Joined rooms:")
	for _, joinedRoom := range resp.JoinedRooms {
		fmt.Println(joinedRoom)
	}

	gobStore, err := crypto.NewGobStore(gobStorePath)
	if err != nil {
		panic(err)
	}
	stateStore := &TallyardStateStore{mautrix.NewInMemoryStore()}
	logger := CryptoMachineLogger{}
	olmMachine = crypto.NewOlmMachine(client, logger, gobStore, stateStore)
	if err = olmMachine.Load(); err != nil {
		panic(err)
	}
	syncer.OnEventType(event.StateMember, func(_ mautrix.EventSource, evt *event.Event) {
		olmMachine.HandleMemberEvent(evt)
	})
	syncer.OnSync(func(resp *mautrix.RespSync, since string) bool {
		stateStore.UpdateStateStore(resp)
		olmMachine.ProcessSyncResponse(resp, since)
		if err := olmMachine.CryptoStore.Flush(); err != nil {
			log.WithError(err).Error("Could not flush crypto store")
		}
		return true
	});

	err = client.Sync()
	if err != nil {
		panic(err)
	}
}

M matrix/state_store.go => matrix/state_store.go +1 -1
@@ 1,4 1,4 @@
package main
package matrix

import (
	"errors"

D ui.go => ui.go +0 -188
@@ 1,188 0,0 @@
package main

import (
	"fmt"
	"os"
	"sort"
	"strconv"
	"unicode"

	"github.com/rivo/tview"
)

func tui() (election *Election) {
	app := tview.NewApplication()
	done := func(buttonIndex int, buttonLabel string) {
		app.Stop()
		switch buttonLabel {
		case "Create Election":
			candidates := getCandidatesTUI()
			// TODO: slaves should check that len candidates >= 2
			if len(candidates) == 0 {
				fmt.Printf("no candidates entered; exiting\n")
				os.Exit(0)
			}
			election = NewElectionWithCandidates(candidates)
		case "Join Election":
			electionKey := getElectionKeyTUI()
			if electionKey == "" {
				fmt.Printf("no election key given; exiting\n")
				os.Exit(0)
			}
			election = NewElectionWithElectionKey(electionKey)
		}
	}
	modal := tview.NewModal().
		SetText("Welcome to tallyard!").
		AddButtons([]string{"Create Election", "Join Election"}).
		SetDoneFunc(done)
	if err := app.SetRoot(modal, false).EnableMouse(true).Run(); err != nil {
		panic(err)
	}
	return election
}

func getCandidatesTUI() (candidates []Candidate) {
	var form *tview.Form
	n := 3
	app := tview.NewApplication()
	plus := func() {
		form.AddInputField(fmt.Sprintf("%d.", n), "", 50, nil, nil)
		n++
	}
	minus := func() {
		// TODO: ensure from joiner that there are at least two
		// candidates
		if n > 3 {
			form.RemoveFormItem(n - 2)
			n--
		}
	}
	done := func() {
		// TODO: ensure none of the candidates are empty
		app.Stop()
		for i := 0; i < n-1; i++ {
			candidates = append(candidates,
				Candidate(form.GetFormItem(i).(*tview.InputField).GetText()))
		}
	}
	form = tview.NewForm().
		AddInputField("1.", "", 50, nil, nil).
		AddInputField("2.", "", 50, nil, nil).
		AddButton("+", plus).
		AddButton("-", minus).
		AddButton("Done", done)
	if err := app.SetRoot(form, true).EnableMouse(true).Run(); err != nil {
		panic(err)
	}
	return candidates
}

func getElectionKeyTUI() (electionKey string) {
	app := tview.NewApplication()
	var form *tview.Form
	done := func() {
		app.Stop()
		electionKey = form.GetFormItem(0).(*tview.InputField).GetText()
	}
	form = tview.NewForm().
		AddInputField("Election key:", "", 100, nil, nil).
		AddButton("Continue", done)
	if err := app.SetRoot(form, true).EnableMouse(true).Run(); err != nil {
		panic(err)
	}
	return electionKey
}

// displays a voting UI to the user and returns the encoded ballot
func vote(candidates []Candidate) []byte {
	ranks := make([]int, len(candidates))
	app := tview.NewApplication()
	form := tview.NewForm()

	for _, eo := range candidates {
		// TODO: support more than 99 candidates
		form.AddInputField(string(eo), "", 2,
			func(textToCheck string, lastChar rune) bool {
				return len(textToCheck) < 3 && unicode.IsDigit(lastChar)
			}, nil)
	}

	form.AddButton("Submit", func() {
		app.Stop()
		for i := 0; i < len(candidates); i++ {
			rank, err := strconv.Atoi(form.GetFormItem(i).(*tview.InputField).GetText())
			if err != nil {
				panic(err)
			}
			ranks[i] = rank
		}
	})

	if err := app.SetRoot(form, true).EnableMouse(true).Run(); err != nil {
		panic(err)
	}

	return GetBallotFromRankings(ranks)
}

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)

	// TODO: support more than 255 voters (limit from usage of byte)
	prefMatrix := make([][]byte, n)

	for len(candidates) > 0 {
		r := ranks[candidates[0]]
		i := 1
		for i < len(candidates) && ranks[candidates[i]] == r {
			i++
		}
		// i is now index of the first candidate with worse (i.e. higher
		// in value) rank
		row := make([]byte, n)
		for j := i; j < len(candidates); j++ {
			row[candidates[j]] = 1
		}
		for j := 0; j < i; j++ {
			prefMatrix[candidates[j]] = row
		}
		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
}

type CandidateRanking struct {
	candidates []int // becomes list of candidate IDs sorted by rank
	ranks      []int // maps candidate ID to rank
}

func (cr *CandidateRanking) Len() int {
	return len(cr.ranks)
}

func (cr *CandidateRanking) Less(i, j int) bool {
	return cr.ranks[cr.candidates[i]] < cr.ranks[cr.candidates[j]]
}

func (cr *CandidateRanking) Swap(i, j int) {
	tmp := cr.candidates[i]
	cr.candidates[i] = cr.candidates[j]
	cr.candidates[j] = tmp
}

A ui/tui.go => ui/tui.go +355 -0
@@ 0,0 1,355 @@
package ui

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode"

	"github.com/gdamore/tcell"
	"github.com/rivo/tview"
	"maunium.net/go/mautrix"
	"maunium.net/go/mautrix/event"
	"maunium.net/go/mautrix/id"
	"tallyard.xyz/election"
)

func TUI(client *mautrix.Client, elections *election.ElectionsMap, localVoter *election.LocalVoter) {
	alive := true
	app := tview.NewApplication()
	resp, err := client.JoinedRooms()
	if err != nil {
		panic(err)
	}
	list := tview.NewList()
	for i, roomID := range resp.JoinedRooms {
		name := string(roomID)
		room := client.Store.LoadRoom(roomID)
		if room != nil {
			if roomNameEvent, ok := room.State[event.StateRoomName][""]; ok {
				name = roomNameEvent.Content.AsRoomName().Name
			}
		}
		list.AddItem(name, string(roomID), rune('a'+i), nil)
	}
	update := func() {
		for i, roomID := range resp.JoinedRooms {
			room := client.Store.LoadRoom(roomID)
			if room == nil {
				continue
			}
			if roomNameEvent, ok := room.State[event.StateRoomName][""]; ok {
				name := roomNameEvent.Content.AsRoomName().Name
				app.QueueUpdateDraw(func() {
					list.SetItemText(i, name, string(roomID))
				})
			}
		}
	}
	go func() {
		for alive {
			time.Sleep(1 * time.Second) // syncing right away can be slow
			update()
		}
	}()
	list.SetSelectedFunc(func(i int, _ string, _ string, _ rune) {
		app.Stop()
		alive = false
		RoomTUI(client, resp.JoinedRooms[i], elections, localVoter)
	})
	if err := app.SetRoot(list, true).SetFocus(list).Run(); err != nil {
		panic(err)
	}
	alive = false
}

func RoomTUI(client *mautrix.Client, roomID id.RoomID, elections *election.ElectionsMap, localVoter *election.LocalVoter) {
	alive := true
	app := tview.NewApplication()
	list := tview.NewList().
		AddItem("Create Election", "start a new election in this room", '+', nil)
	n := 0
	update := func() {
		elections.RLock()
		defer elections.RUnlock()
		if n == len(elections.L) {
			return
		}
		n = len(elections.L)
		for i, el := range elections.L {
			title := el.Title
			if title == "" {
				title = "<no name>"
			}
			var f func(string, string, rune, func()) *tview.List
			if i+1 < list.GetItemCount() {
				f = func(mainText string, secondaryText string, shortcut rune, selected func()) *tview.List {
					return list.InsertItem(i+1, mainText, secondaryText, shortcut, selected)
				}
			} else {
				f = list.AddItem
			}
			f(title, fmt.Sprintf("created by %s, ID: %s", el.Creator, el.CreateEventId), rune('a'+i), nil)
			i++
		}
        }
	go func() {
		for alive {
			app.QueueUpdateDraw(func() {
				update()
			})
			time.Sleep(1 * time.Second)
		}
	}()
	list.SetSelectedFunc(func(i int, _ string, _ string, _ rune) {
		app.Stop()
		alive = false

		if i > 0 {
			// user wants to join election
			elections.RLock()
			defer elections.RUnlock()
			el := elections.L[i-1]
			if joinElectionConfirmation(el) {
				_, electionMember := el.Voters[localVoter.UserID]
				if !electionMember {
					localVoter.JoinElection(client, el)
				}
				ElectionTUI(el, localVoter)
			} else {
				RoomTUI(client, roomID, elections, localVoter)
			}
			return
		}

		// user wants to create election
		title, candidates := CreateElectionTUI()
		fmt.Println("title", title)
		fmt.Println("candidates", candidates)
		eventID, err := election.CreateElection(client, candidates, title, roomID)
		if err != nil {
			panic(err)
		}
		el, ok := elections.GetOk(eventID)
		for !ok {
			time.Sleep(10 * time.Millisecond)
			el, ok = elections.GetOk(eventID)
		}
		err = localVoter.JoinElection(client, el)
		if err != nil {
			panic(err)
		}
	})
	if err := app.SetRoot(list, true).SetFocus(list).Run(); err != nil {
		panic(err)
	}
	alive = false
}

func joinElectionConfirmation(el *election.Election) (shouldJoin bool) {
	app := tview.NewApplication()

	var buttons []string
	var text string

	// TODO: handle when election starts while in modal
	el.RLock()
	if el.Started {
		buttons = []string{"Ok"}
		text = "Election has already started, sorry"
	} else {
		buttons = []string{"Yes", "No"}
		text = "Join election?"
	}
	el.RUnlock()

	modal := tview.NewModal().
		SetText(text).
		AddButtons(buttons).
		SetDoneFunc(func(_ int, buttonLabel string) {
			app.Stop()
			if buttonLabel == "Yes" {
				shouldJoin = true
			} else {
				shouldJoin = false
			}
		})
	if err := app.SetRoot(modal, false).SetFocus(modal).Run(); err != nil {
		panic(err)
	}
	return
}

func CreateElectionTUI() (title string, candidates []election.Candidate) {
	var form *tview.Form
	n := 2
	app := tview.NewApplication()
	plus := func() {
		form.AddInputField(fmt.Sprintf("%d.", n+1), "", 50, nil, nil)
		n++
	}
	minus := func() {
		// TODO: ensure from joiner that there are at least two
		// candidates
		if n > 2 {
			form.RemoveFormItem(n) // don't need -1 because of Title
			n--
		}
	}
	done := func() {
		// TODO: ensure none of the candidates are empty
		app.Stop()
		title = form.GetFormItem(0).(*tview.InputField).GetText()
		for i := 1; i <= n; i++ {
			candidates = append(candidates,
				election.Candidate(form.GetFormItem(i).(*tview.InputField).GetText()))
		}
	}
	form = tview.NewForm().
		AddInputField("Title", "", 50, nil, nil).
		AddInputField("1.", "", 50, nil, nil).
		AddInputField("2.", "", 50, nil, nil).
		AddButton("+", plus).
		AddButton("-", minus).
		AddButton("Done", done)
	if err := app.SetRoot(form, true).SetFocus(form).Run(); err != nil {
		panic(err)
	}
	return title, candidates
}

func ElectionTUI(election *election.Election, localVoter *election.LocalVoter) {
	votersTextView := tview.NewTextView()
	frame := tview.NewFrame(votersTextView)
	if election.Creator == localVoter.UserID {
		frame.AddText("Press enter to start the election", false, tview.AlignCenter, tcell.ColorWhite)
	} else {
		frame.AddText("Waiting for election to start...", false, tview.AlignCenter, tcell.ColorWhite)
	}
	app := tview.NewApplication()
	app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
		app.Stop()
		if event.Key() == tcell.KeyEnter {
			fmt.Println("ready to rock")
		}
		return event
	})
	update := func() {
		election.RLock()
		voters := make([]string, 0, len(election.Voters))
		for voterUserID := range election.Voters {
			voters = append(voters, voterUserID.String())
		}
		election.RUnlock()
		sort.Strings(voters)
		text := strings.Join(voters, "\n")
		text = "Joined voters:\n" + text
		app.QueueUpdateDraw(func() {
			votersTextView.SetText(text)
		})
	}
	go func() {
		for {
			update()
			time.Sleep(1 * time.Second)
		}
	}()
	if err := app.SetRoot(frame, true).SetFocus(frame).Run(); err != nil {
		panic(err)
	}
}

// displays a voting UI to the user and returns the encoded ballot
func Vote(candidates []election.Candidate) []byte {
	ranks := make([]int, len(candidates))
	app := tview.NewApplication()
	form := tview.NewForm()

	for _, eo := range candidates {
		// TODO: support more than 99 candidates
		form.AddInputField(string(eo), "", 2,
			func(textToCheck string, lastChar rune) bool {
				return len(textToCheck) < 3 && unicode.IsDigit(lastChar)
			}, nil)
	}

	form.AddButton("Submit", func() {
		app.Stop()
		for i := 0; i < len(candidates); i++ {
			rank, err := strconv.Atoi(form.GetFormItem(i).(*tview.InputField).GetText())
			if err != nil {
				panic(err)
			}
			ranks[i] = rank
		}
	})

	if err := app.SetRoot(form, true).SetFocus(form).Run(); err != nil {
		panic(err)
	}

	return GetBallotFromRankings(ranks)
}

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)

	// TODO: support more than 255 voters (limit from usage of byte)
	prefMatrix := make([][]byte, n)

	for len(candidates) > 0 {
		r := ranks[candidates[0]]
		i := 1
		for i < len(candidates) && ranks[candidates[i]] == r {
			i++
		}
		// i is now index of the first candidate with worse (i.e. higher
		// in value) rank
		row := make([]byte, n)
		for j := i; j < len(candidates); j++ {
			row[candidates[j]] = 1
		}
		for j := 0; j < i; j++ {
			prefMatrix[candidates[j]] = row
		}
		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
}

type CandidateRanking struct {
	candidates []int // becomes list of candidate IDs sorted by rank
	ranks      []int // maps candidate ID to rank
}

func (cr *CandidateRanking) Len() int {
	return len(cr.ranks)
}

func (cr *CandidateRanking) Less(i, j int) bool {
	return cr.ranks[cr.candidates[i]] < cr.ranks[cr.candidates[j]]
}

func (cr *CandidateRanking) Swap(i, j int) {
	tmp := cr.candidates[i]
	cr.candidates[i] = cr.candidates[j]
	cr.candidates[j] = tmp
}

D voter.go => voter.go +0 -390
@@ 1,390 0,0 @@
package main

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"math/big"
	"os"
	"strconv"
	"sync"
	"time"

	"github.com/libp2p/go-libp2p-core/host"
	"github.com/libp2p/go-libp2p-core/network"
	"github.com/libp2p/go-libp2p-core/peer"
	discovery "github.com/libp2p/go-libp2p-discovery"
	dht "github.com/libp2p/go-libp2p-kad-dht"
	"github.com/mr-tron/base58/base58"
	"github.com/multiformats/go-multiaddr"
)

type Voter struct {
	sum      *big.Int
	// may get more than 1 eval from peer; doesn't need to be RW because we
	// never serve it to peers
	inputMu  sync.Mutex
	input    *big.Int
	output   *big.Int
	addrInfo peer.AddrInfo
}

type LocalVoter struct {
	Voter
	host.Host
	ctx    context.Context
	ballot []byte
	kdht   *dht.IpfsDHT
	poly   *Poly

	// mutexs only used for atomicity; atomicity.Value sucks because we lose
	// type safety with interface{}
	polyMu  sync.RWMutex // poly is computed after ballot; don't want R/W data races
	sumMu   sync.RWMutex // sum is computed in a loop; don
	inputMu sync.RWMutex // TODO remove by generating input right away
}

func stripNewline(str string) string {
	if str == "" {
		return str
	}
	if str[len(str)-1] == '\n' {
		return str[:len(str)-1]
	}
	return str
}

func handleCmd(cmd string, rw *bufio.ReadWriter, stream network.Stream, election *Election) {
	localVoter := election.localVoter
	switch cmd {
	case "info":
		rw.WriteString(fmt.Sprintf("%s\n", election.rendezvousNonce))
		for _, option := range election.Candidates {
			rw.WriteString(fmt.Sprintf("%s\n", option))
		}
		rw.Flush()
	case "close":
		election.Lock()
		defer election.Unlock()
		if peer := stream.Conn().RemotePeer(); peer != election.masterID {
			logger.Warning("received close command from non-master:", peer)
			return
		}
		if election.closed {
			logger.Warning("election already closed")
			return
		}
		str, err := rw.ReadString('\n')
		if err != nil && err != io.EOF {
			panic(err)
		}
		str = stripNewline(str)
		numPeers, err := strconv.Atoi(str)
		if err != nil {
			panic(err)
		}
		election.close <- numPeers
		election.closed = true
	case "shake":
		election.Lock()
		defer election.Unlock()
		peerID := stream.Conn().RemotePeer()
		if election.closed {
			logger.Warning("peer attempted to shake after "+
				"election was closed:", peerID)
			return
		}
		if _, exists := election.remoteVoters[peerID]; exists {
			logger.Warning("peer attempted to shake after having already done so", peerID)
			return
		}
		fmt.Printf("found voter: %s\n", peerID)
		election.remoteVoters[peerID] = &Voter{
			addrInfo: peer.AddrInfo{
				ID:    peerID,
				Addrs: []multiaddr.Multiaddr{stream.Conn().RemoteMultiaddr()},
			},
		}
	case "eval": // peer is giving their input and requesting output from our poly
		localVoter.polyMu.RLock()
		defer localVoter.polyMu.RUnlock()
		if localVoter.poly == nil {
			logger.Warning("peer attempted to eval before we had our poly:",
				stream.Conn().RemotePeer())
			return
		}
		inputb58, err := rw.ReadString('\n')
		if err != nil && err != io.EOF {
			logger.Warning("unable to read input from peer during eval:",
				stream.Conn().RemotePeer())
			return
		}
		inputb58 = stripNewline(inputb58)
		inputBytes, err := base58.Decode(inputb58)
		if err != nil {
			logger.Warning("unable to base58 decode input from peer during eval:",
				stream.Conn().RemotePeer())
			return
		}
		peer, exists := election.remoteVoters[stream.Conn().RemotePeer()]
		if !exists {
			logger.Warning("received eval command from unrecognized peer")
			return
		}
		peer.inputMu.Lock()
		defer peer.inputMu.Unlock()
		peer.input = new(big.Int).SetBytes(inputBytes)
		logger.Infof("%s input: %s", peer.addrInfo.ID, peer.input)
		output := localVoter.poly.Eval(peer.input)
		rw.WriteString(base58.Encode(output.Bytes()))
		rw.Flush()
	case "sum":
		localVoter.sumMu.RLock()
		defer localVoter.sumMu.RUnlock()
		if localVoter.sum == nil {
			logger.Info("peer attempted to fetch sum "+
				"before we computed it:", stream.Conn().RemotePeer())
			return
		}
		rw.WriteString(base58.Encode(localVoter.sum.Bytes()))
		rw.Flush()
	default:
		logger.Warningf("uknown command %s", cmd)
	}
}

func streamHandler(stream network.Stream, election *Election) {
	logger.Info("got a new stream:", stream)
	logger.Info("remote peer:", stream.Conn().RemotePeer())
	rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))

	cmd, err := rw.ReadString('\n')
	if err != nil && err != io.EOF {
		panic(err)
	}
	cmd = stripNewline(cmd)

	logger.Info("cmd:", cmd)
	handleCmd(cmd, rw, stream, election)
	stream.Close()
}

func findPeers(closeElection <-chan int, election *Election) {
	localVoter := election.localVoter
	routingDiscovery := discovery.NewRoutingDiscovery(localVoter.kdht)
	logger.Info("announcing ourselves")
	discovery.Advertise(localVoter.ctx, routingDiscovery, string(election.rendezvousNonce))
	logger.Info("successfully announced!")

	fmt.Println("finding other voters...")
	peerChan, err := routingDiscovery.FindPeers(localVoter.ctx, string(election.rendezvousNonce))
	if err != nil {
		panic(err)
	}
	numPeers := -1
	for {
		if numPeers != -1 && numPeers == len(election.remoteVoters) {
			break
		}
		select {
		case peer := <-peerChan:
			if peer.ID == localVoter.ID() {
				continue
			}
			fmt.Printf("found voter: %s\n", peer.ID)
			logger.Info("connecting to:", peer)
			err = localVoter.Connect(localVoter.ctx, peer)
			if err != nil {
				logger.Warn("couldn't connect to peer: ", err)
				continue
			}
			stream, err := localVoter.NewStream(localVoter.ctx, peer.ID, protocolID)
			if err != nil {
				logger.Warn("couldn't open stream with peer: ", err)
				continue
			}
			writer := bufio.NewWriter(stream)
			writer.WriteString("shake")
			writer.Flush()
			stream.Close()
			election.remoteVoters[peer.ID] = &Voter{addrInfo: peer}
		case numPeers = <-closeElection:
			if len(election.remoteVoters) > numPeers {
				logger.Fatalf("found more peers than master! %d > %d",
					len(election.remoteVoters), numPeers)
				os.Exit(1)
			}
		}
	}
	logger.Info("done finding peers")
}

func (voter *Voter) fetchNumber(election *Election, cmd string, args ...string) *big.Int {
	printErr := func(err error, msg string) {
		logger.Errorf("%s: %s while fetching `%s'; retrying in 2 seconds",
			voter.addrInfo.ID, msg, cmd)
		time.Sleep(time.Second * 2)
	}
retry:
	localVoter := election.localVoter
	stream, err := localVoter.NewStream(localVoter.ctx, voter.addrInfo.ID, protocolID)
	if err != nil {
		printErr(err, "couldn't open stream")
		goto retry
	}
	rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
	out := cmd
	for _, arg := range args {
		out += "\n" + arg
	}
	_, err = rw.WriteString(out)
	if err != nil {
		printErr(err, "couldn't write to stream")
		goto retry
	}
	err = rw.Flush()
	if err != nil {
		printErr(err, "couldn't flush stream")
		goto retry
	}
	err = stream.Close() // only closes writing
	if err != nil {
		printErr(err, "couldn't close stream")
		goto retry
	}
	retB58, err := rw.ReadString('\n')
	if err != nil && err != io.EOF {
		printErr(err, "couldn't read string from stream")
		goto retry
	}
	retB58 = stripNewline(retB58)
	if retB58 == "" {
		printErr(err, "empty string")
		goto retry
	}
	retBytes, err := base58.Decode(retB58)
	if err != nil {
		printErr(err, "couldn't base58-decode contents from stream")
		goto retry
	}
	return new(big.Int).SetBytes(retBytes)
}

func (election *Election) StartVoting() {
	localVoter := election.localVoter

	var err error
	localVoter.inputMu.Lock()
	localVoter.input, err = RandomBigInt(128, false)
	localVoter.inputMu.Unlock()
	if err != nil {
		panic(err)
	}
	logger.Infof("our input: %s", localVoter.input)

	localVoter.ballot = vote(election.Candidates)
	logger.Infof("our ballot: %v", localVoter.ballot)

	// no +1 since we want degree k-1 where k is total number of voters
	localVoter.polyMu.Lock()
	localVoter.poly = NewRandomPoly(uint(len(election.remoteVoters)),
		1024, localVoter.ballot)
	localVoter.polyMu.Unlock()
	logger.Infof("our constant: %s", localVoter.poly.constant)

	// get outputs
	var wg sync.WaitGroup
	for _, voter := range election.remoteVoters {
		wg.Add(1)
		go func(voter *Voter) {
			voter.output = voter.fetchNumber(election, "eval", base58.Encode(localVoter.input.Bytes()))
			logger.Infof("voter %s output: %s", voter.addrInfo.ID, voter.output)
			wg.Done()
		}(voter)
	}
	wg.Wait()

	// calculate sum
	localVoter.sumMu.Lock()
	localVoter.sum = localVoter.poly.Eval(localVoter.input)
	for _, voter := range election.remoteVoters {
		localVoter.sum.Add(localVoter.sum, voter.output)
	}
	localVoter.sumMu.Unlock()
	logger.Infof("our sum: %s", localVoter.sum)

	// get sums
	for _, voter := range election.remoteVoters {
		wg.Add(1)
		go func(voter *Voter) {
			voter.sum = voter.fetchNumber(election, "sum")
			logger.Infof("voter %s sum: %s",
				voter.addrInfo.ID, voter.sum)
			wg.Done()
		}(voter)
	}
	wg.Wait()

	mat := constructPolyMatrix(election)
	mat.RREF()

	constant := mat[0][len(mat[0])-1]
	if !constant.IsInt() {
		panic("constant term is not an integer")
	}

	result := constant.Num().Bytes()

	// number of bytes we need to insert at the front since they're zero
	diff := (len(election.Candidates)*len(election.Candidates)) - len(result)
	result = append(make([]byte, diff), result...)

	printResults(result, election.Candidates)

	// temporary
	select {}
}

func constructPolyMatrix(election *Election) Matrix {
	mat := make(Matrix, len(election.remoteVoters) + 1) // row for everyone (including ourselves)

	i := 0
	for _, voter := range election.remoteVoters {
		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(election.remoteVoters)); j++ {
			row[j].SetInt(new(big.Int).Exp(voter.input, big.NewInt(j), nil))
		}
		row[j].SetInt(voter.sum)
		i++
	}

	// row for ourselves
	mat[i] = make([]big.Rat, len(mat) + 1)
	row := mat[i]
	row[0].SetInt64(1)
	localVoter := election.localVoter
	var j int64
	for j = 1; j <= int64(len(election.remoteVoters)); j++ {
		row[j].SetInt(new(big.Int).Exp(localVoter.input, big.NewInt(j), nil))
	}
	row[j].SetInt(localVoter.sum)

	return mat
}

func printResults(result []byte, candidates []Candidate) {
	logger.Infof("result: %v", result)
	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])
			}
		}
	}
}