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])
- }
- }
- }
-}