~edwargix/tallyard

9903a641537bfe60e76e3dc22c422955f80b51d0 — David Florness 5 years ago 5c3956f + a00ffe0
Merge branch 'golang'
21 files changed, 1539 insertions(+), 904 deletions(-)

M .gitignore
A Makefile
M README.md
A TODO.org
D ballot-tui.rkt
D bulletin.rkt
D client.rkt
A go.mod
A go.sum
D info.rkt
A linalg.go
A linalg_test.go
A main.go
D main.rkt
A merkle.go
D merkle.rkt
A poly.go
D poly.rkt
D scribblings/tallyard.scrbl
A ui.go
A voter.go
M .gitignore => .gitignore +1 -0
@@ 3,3 3,4 @@
.\#*
.DS_Store
compiled/
tallyard

A Makefile => Makefile +7 -0
@@ 0,0 1,7 @@
GOSRC != find . -name '*.go'
GOSRC += go.mod
GO = go
RM ?= rm -f

tallyard: $(GOSRC)
	$(GO) build -o $@

M README.md => README.md +14 -70
@@ 5,11 5,12 @@ preventing double-voting.  Voter privacy is achieved via the homomorphic secret
sharing system outlined
[here](https://en.wikipedia.org/wiki/Homomorphic_secret_sharing#Example:_decentralized_voting_protocol)
and double-voting is prevented by a STARKs zero-knowledge proof as described
[here](https://vitalik.ca/general/2017/11/09/starks_part_1.html).
[here](https://vitalik.ca/general/2017/11/09/starks_part_1.html).  Voters'
computers communicate with each other directly over a peer-to-peer network.
While there is a "master" node that decides what the candidates of the election
are, it does not have any privileges once voting begins.

The zero-knowledge implementation is not entirely finished.  All of this is
planned to be done over a peer-to-peer network; however, at the moment there is a
centralized "bulletin" that relays traffic between peers.
The zero-knowledge implementation is not entirely finished.

tallyard is very much a work-in-progress and is only useful for elections where
everyone is voting simultaneously, such as a club election, [which it was


@@ 18,81 19,24 @@ for](https://mailman.mines.edu/pipermail/lug/2020-April/000573.html).

# Usage

Ensure you have the [Racket](https://racket-lang.org) programming language
installed.  Then, install tallyard's dependencies:
Ensure you have the [Go](https://golang.org) programming language
installed.  Then:

```sh
$ cd /path/to/tallyard/repo
$ raco pkg install
$ make
```

## Bulletin
# Why Go?

The bulletin is a simple Racket-powered RESTful HTTP server to which voters
(i.e. peers) connect to participate in an election.  As the bulletin is running,
it can be given commands to alter the election.

If you with to run the bulletin, you need to setup a host with an HTTP endpoint
that accepts a JSON body containing "username" and "password" keys and returns
the JSON string "success" if the corresponding "username" and "password" values
correspond to a valid login.

In the following code in bulletin.rkt, replace "AUTH-HOST-HERE" with the
aforementioned host and "/auth/route/here" with the endpoint.  I realize this is
cumbersome, and I plan to provide an easier way to configure this.

```racket
(define (auth username password)
  (equal? (hash-ref
           (json-response-body
            (post (update-ssl (update-host json-requester
                                           "AUTH-HOST-HERE")
                              #t)
                  "/auth/route/here"
                  #:data
                  (jsexpr->string (hasheq 'username username
                                          'password password))))
           'result)
          "success"))
```

The bulletin can then be started with

```sh
$ racket bulletin.rkt
```

## Voter client

If you're a voter who wants to participate, do

```sh
$ racket client.rkt
```

and enter the correct bulletin host and login information when prompted.

# Why Racket?

I've always had a soft spot for LISPs[^1] and needed an excuse to try [Typed
Racket](https://docs.racket-lang.org/ts-guide/).  tallyard began as a quick
hackathon project, but turned into the project I worked on in my free time once
I became obsessed.

[^1]: I still don't know why

However, Racket is not the ideal language for networking and fast cryptography.
Defining functional tree-like structures (like Merkle trees) in Racket is quite
ugly without something like [recursion
schemes](https://blog.sumtypeofway.com/posts/introduction-to-recursion-schemes.html),
which are not implemented in Racket yet.
- concurrency
- libp2p

# Future Work

- utilize [libp2p](https://libp2p.io/) for peer-to-peer networking.  This will
  require either rewriting tallyard it Golang or using FFIs.
- support multiple authentication systems.  First on the list is [Matrix](https://matrix.org/)
- [Matrix](https://matrix.org/) bots for voting on topics in matrix chat rooms
- support authentication systems.  First on the list is
  [Matrix](https://matrix.org/) accounts
- [Matrix](https://matrix.org) bots for voting on topics in matrix chat rooms
- finish the zero-knowledge proof implementation

# Origin of the name

A TODO.org => TODO.org +11 -0
@@ 0,0 1,11 @@
* use WriteBytes instead of WriteString
* ensure input from other voter is not 0
* proof of contact with master during shake
* yew, iced
* mutexs for all fields of voter
* shake with master
* use atomic instead of locks
* support getting inputs from peer other than owner
* make input right away to avoid lock BS
* warn about 2 voters
* enable loggers on -d flag

D ballot-tui.rkt => ballot-tui.rkt +0 -73
@@ 1,73 0,0 @@
#lang racket/base

(require racket/contract)
(require racket/list)
(require racket/set)

(require charterm)

(define/contract (get-vote candidates)
  (-> (non-empty-listof string?) (listof set?))
  (let* ([n (length candidates)]
         [prefs (make-hash (for/list ([i n]) (cons i n)))]
         [data-line 2])
    (with-charterm
      (charterm-clear-screen)
      (for ([i (in-naturals data-line)]
            [cand candidates])
        (charterm-cursor 2 i)
        (charterm-display "[ ] ")
        (charterm-display cand))
      (charterm-cursor 2 (+ 1 n data-line))
      (charterm-display "[Submit]")
      (let loop-fast-next-key ([cursor-line 0])
        (if (>= cursor-line n)
            (begin
              (charterm-cursor 2 (+ 1 n data-line))
              (charterm-display "[")
              (charterm-inverse)
              (charterm-display "Submit]")
              (charterm-normal)
              (charterm-cursor 2 (+ 1 n data-line)))
            (charterm-cursor 3 (+ data-line cursor-line)))
        (let ([keyinfo (charterm-read-keyinfo #:timeout 1)])
          (if keyinfo
              (let* ([keycode (charterm-keyinfo-keycode keyinfo)]
                     [keynum  (if (char? keycode)
                                  (char->integer keycode)
                                  #f)])
                (if (and keynum (<= 49 keynum 57))
                    (let ([val (- keynum 49)])
                      (when (< cursor-line n)
                        (hash-set! prefs cursor-line val)
                        (charterm-display (bytes keynum)))
                      (loop-fast-next-key cursor-line))
                    (case keycode
                      [(return)
                       (if (>= cursor-line n)
                           (let ([prefs (sort (hash->list prefs) < #:key cdr)])
                             (let loop ([p   (car prefs)]
                                        [rst (cdr prefs)])
                               (let-values
                                   ([(same-rank rst)
                                     (partition (λ (e)
                                                  (eqv? (cdr e) (cdr p)))
                                                rst)])
                                 (cons (set-add (list->set (map car same-rank))
                                                (car p))
                                       (if (empty? rst)
                                           empty
                                           (loop (car rst) (cdr rst)))))))
                           (loop-fast-next-key cursor-line))]
                      [(down #\j)
                       (loop-fast-next-key (min n (add1 cursor-line)))]
                      [(up #\k)
                       (when (>= cursor-line n)
                         (charterm-cursor 2 (+ 1 n data-line))
                         (charterm-display "[Submit]"))
                       (loop-fast-next-key (max 0 (sub1 cursor-line)))]
                      [else
                       (loop-fast-next-key cursor-line)])))
              (loop-fast-next-key cursor-line)))))))

(provide get-vote)

D bulletin.rkt => bulletin.rkt +0 -307
@@ 1,307 0,0 @@
#lang racket/base
(require racket/contract
         (only-in racket/format ~a)
         racket/list
         racket/match
         racket/random
         racket/set
         racket/string)
(require web-server/servlet
         web-server/servlet-env
         json)
(require simple-http)
(require readline/readline)
(require (only-in openssl/sha1 bytes->hex-string))

(define state    'registering)
(define _peers   (make-hasheq))
(define _commits (make-hash))
(define _outputs (make-hash))
(define _sums    (make-hash))

(define (reset)
  (hash-clear! _commits)
  (hash-clear! _outputs)
  (hash-clear! _sums))

(define election 'default)
(define election->candidates
  (make-hash '((default . ("Emacs" "Vim" "VSCode")))))

(define/contract (election-set! sym)
  (-> symbol? void?)
  (set! election sym)
  (when (not (hash-has-key? election->candidates
                            election))
    (hash-set! election->candidates election
               (list))))

(define transitions
  (hasheq 'committing 'voting
          'voting 'summing
          'summing 'done))

(define (transition [trans #f])
  (set! state (if trans trans (hash-ref transitions state))))

(define (server)
  (define (candidates request)
    (if (eq? state 'committing)
        (response/jsexpr (hash-ref election->candidates election))
        (response/jsexpr
         empty
         #:code 403
         #:message #"The election is currently closed")))

  (define (auth username password)
    (equal? (hash-ref
             (json-response-body
              (post (update-ssl (update-host json-requester
                                             "AUTH-HOST-HERE")
                                #t)
                    "/auth/route/here"
                    #:data
                    (jsexpr->string (hasheq 'username username
                                            'password password))))
             'result)
            "success"))

  (define (register request)
    (if (eq? state 'registering)
        (let* ([params   (bytes->jsexpr (request-post-data/raw request))]
               [username (hash-ref params 'username)]
               [password (hash-ref params 'password)]
               [input    (hash-ref params 'input)])
          (if (auth username password)
              ; check if username is already present
              (if (for/or ([(_ peer) _peers])
                    (equal? username (hash-ref peer 'username)))
                  (response/jsexpr
                   "That username is already in use"
                   #:code 403)
                  (let* ([token (crypto-random-bytes 128)]
                         [token (bytes->hex-string token)])
                    (displayln (format "~a is voting! (~a)" username input))
                    (hash-set! _peers (string->symbol token)
                               (hasheq 'input input
                                       'username username))
                    ; send the token that the peer will use for further
                    ; authorization
                    (response/jsexpr token)))
              (response/jsexpr
               "Bad Login"
               #:code 400)))
        (response/jsexpr
         "Registration is closed"
         #:code 403)))

  (define (peer-count request)
    (if (eq? state 'committing)
        (response/jsexpr (hash-count _peers))
        (response/jsexpr
         "Committing has not begun"
         #:code 403)))

  (define (commit request)
    (if (eq? state 'committing)
        (let* ([params (bytes->jsexpr (request-post-data/raw request))]
               [peer   (hash-ref _peers (string->symbol
                                         (hash-ref params 'token)))])
          (displayln params)
          (hash-set! _commits (hash-ref peer 'input)
                     (hasheq 'p (hash-ref params 'p))
                     ; TODO: D polynomial
                     ;; (hasheq 'd (hash-ref params 'd))
                     )
          ; has everyone committed?
          (when (eqv? (hash-count _commits)
                      (hash-count _peers))
            (transition))
          (response/jsexpr empty))
        (response/jsexpr "Not in the committing phase"
                         #:code 403)))

  (define (peers request)
    (if (eq? state 'voting)
        (response/jsexpr (for/list ([v (hash-values _peers)])
                           (hash-ref v 'input)))
        (response/jsexpr
         "Voting has not begun"
         #:code 403)))

  (define (outputs request)
    (if (eq? state 'voting)
        (let* ([params (bytes->jsexpr (request-post-data/raw request))]
               ; ensure this is a registered peer
               [peer   (hash-ref _peers (string->symbol
                                         (hash-ref params 'token)))])
          ; TODO: check validity of inputs
          (for ([(p v) (hash-ref params 'outputs)])
            (hash-update! _outputs
                          (symbol->string p)
                          (λ (l) (cons v l)) empty))
          ; has everyone submitted their outputs?
          (when (for/and ([(p v) _outputs])
                  (eqv? (length v)
                        (hash-count _peers)))
            (transition))
          (response/jsexpr empty))
        (response/jsexpr
         "This election is not in the voting phase"
         #:code 403)))

  (define (_values request)
    (if (eq? state 'summing)
        (let* ([params (bytes->jsexpr (request-post-data/raw request))]
               [peer   (hash-ref _peers (string->symbol
                                         (hash-ref params 'token)))])
          (response/jsexpr
           (hash-ref _outputs (hash-ref peer 'input))))
        (response/jsexpr
         "This election is not in the summing phase"
         #:code 403)))

  (define (sum request)
    (if (eq? state 'summing)
        (let* ([params (bytes->jsexpr (request-post-data/raw request))]
               [peer   (hash-ref _peers (string->symbol
                                         (hash-ref params 'token)))]
               [input  (string->symbol (hash-ref peer 'input))]
               [sum    (hash-ref params 'sum)])
          (hash-set! _sums input sum)
          (response/jsexpr
           empty))
        (response/jsexpr
         "This election is not in the summing phase"
         #:code 403)))

  (define (sums request)
    (if (eq? state 'summing)
        (if (eq? (length (hash-keys _sums))
                 (hash-count _peers))
            (begin
              (transition)
              (response/jsexpr _sums))
            (response/jsexpr
             "The sums are not yet available"
             #:code 403))
        (if (eq? state 'done)
            (response/jsexpr _sums)
            (response/jsexpr
             "Too early to request sums"
             #:code 403))))

  (define (quit request)
    (let* ([token (string->symbol
                   (hash-ref
                    (bytes->jsexpr (request-post-data/raw request))
                    'token))]
           [username (hash-ref (hash-ref _peers token) 'username)])
      (displayln (format "~a quit!" username))
      (hash-remove! _peers token)))

  (define-values (dispatch-route url-generator)
    (dispatch-rules
     [("register")   #:method "post" register  ]
     [("candidates") #:method "get"  candidates]
     [("peer-count") #:method "get"  peer-count]
     [("commit")     #:method "post" commit    ]
     [("peers")      #:method "get"  peers     ]
     [("outputs")    #:method "post" outputs   ]
     [("values")     #:method "post" _values   ]
     [("sum")        #:method "post" sum       ]
     [("sums")       #:method "get"  sums      ]
     [("quit")       #:method "post" quit      ]))

  (define/contract (internal-server-error url ex)
    (url? any/c . -> . can-be-response?)
    (response/jsexpr
     empty
     #:code 400))

  (define/contract (dispatcher req)
    (request? . -> . any)
    (if (eq? state 'closed)
        (response/jsexpr
         "Election closed / cancelled"
         #:code 410)
        (dispatch-route req)))

  (serve/servlet
   dispatcher
   #:port 1984
   #:servlet-regexp #rx""
   #:command-line? #t
   ; comment out the following to get client-side tracebacks
   #:servlet-responder internal-server-error))

(module+ main
  (void (thread server))

  (let loop ()
    (let ([line (readline "> ")])
      (when (not (eq? line eof))
        (when (not (equal? line ""))
          (add-history line))
        (with-handlers ([exn:fail? (λ (ex)
                                     (displayln (exn-message ex))
                                     (break-enabled #t)
                                     (loop))])
          (let ([sp (open-input-string line)])
            (match (read sp)
              ['add (match (read sp)
                      ['cand
                       (hash-update! election->candidates
                                     election
                                     (λ (l)
                                       (cons (~a (read sp)) l)))]
                      [(? eof-object?) (displayln "add what? options are `cand'")]
                      [else (displayln "cannot add that")])]
              ['list (match (read sp)
                       ['cands
                        (println (hash-ref election->candidates election))]
                       ['peers
                        (println (hash-values _peers))]
                       ['elections
                        (displayln (format "current: ~s" election))
                        (println (hash-keys election->candidates))]
                       [(? eof-object?)
                        (displayln "list what? options are `cands' `peers' `elections'")]
                       [else (displayln "cannot list that")])]
              ['set (match (read sp)
                      ['election (election-set!
                                  (string->symbol (~a (read sp))))]
                      [(? eof-object?)
                       (displayln "set what? options are `election'")]
                      [else (displayln "cannot set that")])]
              ['open
               (if (memq state '(closed registering))
                   (transition 'committing)
                   (displayln "election is not closed"))]
              ['close
               (transition 'closed)
               (reset)]
              ['register
               (transition 'registering)
               (reset)]
              ['kick (if (eq? state 'closed)
                         (let ([username (~a (read sp))])
                           ; only need to find the first peer with the username
                           (for/first ([(token peer) _peers]
                                       #:when (equal? (hash-ref peer 'username)
                                                      username))
                             (hash-remove! _peers token)))
                         (displayln "close the election first"))]
              ['state (displayln (symbol->string state))]
              ['waiting
               (displayln
                (set-map
                 (for/set ([p (hash-values _peers)]
                           #:when (not (hash-has-key? _commits (hash-ref p 'input))))
                   (hash-ref p 'input))
                 (λ (input)
                   (for/first ([(k v) _peers]
                               #:when (equal? (hash-ref v 'input) input))
                     (hash-ref v 'username)))))]
              [else (displayln "unknown command")])
            (loop)))))))

D client.rkt => client.rkt +0 -356
@@ 1,356 0,0 @@
#lang typed/racket
(require math/array)
(require math/matrix)
(require racket/list)
(require racket/math)
(require racket/port)

(require (only-in typed/openssl/sha1 bytes->hex-string hex-string->bytes))
(require/typed get-pass
  [get-pass (->* () (String #:in Input-Port #:out Output-Port) String)])
(require net/http-client)
(require net/uri-codec)
(require typed/json)
; TODO: all of these type declarations are horrid
(require/typed binaryio
  [bytes->integer (->* (Bytes Boolean) (Boolean Natural Natural) Integer)]
  [integer->bytes (->* (Integer Positive-Integer Boolean) (Boolean Bytes Natural) Bytes)]
  [integer-bytes-length (-> Integer Boolean Natural)])
(require/typed readline/readline
  [readline (-> String String)])
(require/typed simple-http
  [#:struct requester
   ([host : String]
    [headers : Any]
    [port : Integer]
    [ssl : Boolean]
    [type : Symbol])]
  [update-host (-> requester String requester)]
  [update-port (-> requester Integer requester)]
  [update-ssl  (-> requester Boolean requester)]
  [json-requester requester]
  [#:struct json-response
   ([status : String]
    [headers : Any]
    [body : JSExpr])]
  [get (->* (requester String) (#:params (Listof Pair)) json-response)]
  [post (->* (requester String) (#:data Any #:params (Listof Pair)) json-response)]
  [#:struct (exn:fail:network:http:error exn:fail:network) ([code : Any] [type : Any])])
(require/typed crypto
  [crypto-random-bytes (Natural -> Bytes)])
(require/typed sugar
  [members-unique? (-> (Listof Any) Boolean)])

(require "merkle.rkt")
(require "poly.rkt")
(require/typed "ballot-tui.rkt"
  [get-vote (-> (Listof String) (Listof (Setof Natural)))])

(define (integer->hex-string [n : Integer])
  (bytes->hex-string
   (integer->bytes n (assert (integer-bytes-length n #t) positive?) #t)))

(define (hex-string->integer [hs : String])
  (bytes->integer (hex-string->bytes hs) #t))

(define-syntax-rule (retry-request f)
  (let loop ([wait 0])
    (sleep wait)
    (with-handlers ([exn:fail:network:http:error?
                     (λ ([ex : exn:fail:network:http:error])
                       ; 410 indicates the election is closed/cancalled
                       (when (eqv? (exn:fail:network:http:error-code ex)
                                   410)
                         (newline)
                         (raise ex))
                       (break-enabled #t)
                       (display ".")
                       (flush-output)
                       (loop 3))])
      f)))

(define (vote-in-election
         [point    : Integer]
         [bulletin : requester]
         [token    : JSExpr])
  (display "waiting for voting to commence...")
  (define peer-count (cast
                      (json-response-body
                       (retry-request (get bulletin "/peer-count")))
                      Positive-Integer))
  (newline)

  (break-enabled #f) ; voting has begun!

  (display "retrieving candidates...")
  (define candidates (cast (json-response-body
                            (retry-request (get bulletin "/candidates")))
                           (Listof String)))
  (define cand-count (cast (length candidates) Positive-Integer))
  (newline)

  (define vote
    (let* ([vote (get-vote candidates)]
           [m    cand-count]
           [n    m]
           [arr  (array->mutable-array
                  (make-array (vector m n) 0))])
      (let loop ([rank      (car vote)]
                 [rst       (cdr vote)]
                 [remaining (set-subtract (list->set (range m)) (car vote))])
        (for ([cand rank])
          (for ([other remaining])
            (array-set! arr (vector cand other) 1)))
        (when (not (empty? rst))
          (loop (car rst) (cdr rst) (set-subtract remaining (car rst)))))
      (bytes->integer
       (for*/fold : Bytes
           ([bstr : Bytes #""])
           ([i : Natural m]
            [j : Natural n])
         (bytes-append bstr
                       (integer->bytes
                        (array-ref arr (vector i j))
                        (cast
                         (max (integer-bytes-length peer-count #t) 1)
                         Positive-Integer)
                        #t)))
       #t)))

  (displayln "committing poly...")
  (define poly (random-poly
                (cast (sub1 peer-count) Natural)
                vote))
  (post bulletin "/commit"
        #:data
        (jsexpr->string
         (hasheq
          'token token
          'p
          (bytes->hex-string
           (merkle-node-hsh
            (merkle-tree-root (poly-merkle poly)))))))

  (display "retrieving peers...")
  (define peer-inputs
    (assert (map hex-string->integer
                 (cast (json-response-body
                        (retry-request (get bulletin "/peers")))
                       (Listof String)))
            (λ ([lst : (Listof Integer)])
              (= (length lst) peer-count))))
  (newline)

  (displayln "submitting poly outputs...")
  (post bulletin "/outputs"
        #:data
        (jsexpr->string
         (hasheq
          'token token
          'outputs
          (for/hasheq : (Immutable-HashTable Symbol String)
              ([p : Integer (cast peer-inputs (Listof Integer))])
            (values (string->symbol (integer->hex-string p))
                    (integer->hex-string (poly p)))))))

  (display "summing...")
  (post bulletin "/sum"
        #:data
        (jsexpr->string
         (hasheq
          'token token
          'sum
          (integer->hex-string
           (foldl + 0
                  (map
                   hex-string->integer
                   (cast (json-response-body
                          (retry-request
                           (post bulletin "/values"
                                 #:data
                                 (jsexpr->string
                                  (hasheq 'token token)))))
                         (Listof String))))))))
  (newline)

  (break-enabled #t) ; it's ok if the client quits at this point

  (display "finding constant...")
  (define constant
    (let ([k (length peer-inputs)]
          [sums (for/hash : (Immutable-HashTable Integer Integer)
                    ([(k v)
                      (in-hash
                       (cast
                        (json-response-body
                         (retry-request (get bulletin "/sums")))
                        (Immutable-HashTable Symbol String)))])
                  (values (hex-string->integer (symbol->string k))
                          (hex-string->integer v)))])
      (cast
       (matrix-ref
        (matrix-solve
         ; M
         (for*/matrix k k ([x peer-inputs]
                           [p (range k)]) : Number
                      (expt x p))
         ; B
         (for*/matrix k 1 ([x peer-inputs]) : Number
                      (hash-ref sums x)))
        0 0)
       Integer)))
  (newline)

  (displayln ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; RESULTS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;")
  (define pairwise-prefs
    (let* ([partial-size (cast (integer-bytes-length peer-count #t)
                               Positive-Integer)]
           [bstr (integer->bytes constant
                                 (* partial-size cand-count cand-count)
                                 #t)]
           [arr (for/array: #:shape (vector cand-count cand-count)
                    ([section : Natural (in-range 0
                                                  (bytes-length bstr)
                                                  partial-size)])
                  : Integer
                  (bytes->integer bstr #t #t section
                                  (+ section partial-size)))]
           [candidates (list->vector candidates)])
      (for*/hash : (HashTable (Pairof Natural Natural) Integer)
          ([i cand-count]
           [j cand-count]
           #:when (not (eqv? i j)))
        (values (cons i j)
                (let ([prefer-i-over-j (array-ref arr (vector i j))])
                  (displayln (format "~a over ~a: ~a"
                                     (vector-ref candidates i)
                                     (vector-ref candidates j)
                                     prefer-i-over-j))
                  prefer-i-over-j)))))

  (define strongest-paths
    (let ([p : (HashTable (Pairof Natural Natural) Integer)
             (make-hash)])
      (for* ([i cand-count]
             [j cand-count]
             #:when (not (eqv? i j)))
        (hash-set! p (cons i j)
                   (if (> (hash-ref pairwise-prefs (cons i j))
                          (hash-ref pairwise-prefs (cons j i)))
                       (hash-ref pairwise-prefs (cons i j))
                       0)))
      (for* ([i cand-count]
             [j cand-count]
             #:when (not (eqv? i j))
             [k cand-count]
             #:when (and (not (eqv? i k)) (not (eqv? j k))))
        (hash-update! p (cons j k)
                      (λ ([c : Integer])
                        (cast
                         (max c (min (hash-ref p (cons j i))
                                     (hash-ref p (cons i k))))
                         Integer))))
      p))

  (define rankings
    (let loop : (Listof (Setof Natural))
         ([remaining : (Setof Natural)
                     (list->set (range cand-count))])
         (if (set-empty? remaining)
             empty
             (let ([winners
                    (for/set : (Setof Natural)
                        ([cand : Natural remaining]
                         #:when
                         (for/and : Boolean
                             ([other : Natural remaining]
                              #:when (not (eqv? cand other)))
                           (>= (hash-ref strongest-paths
                                         (cons cand other))
                               (hash-ref strongest-paths
                                         (cons other cand)))))
                      cand)])
               (cons winners (loop (set-subtract remaining winners)))))))

  (displayln "Rankings:")
  (for ([i (in-naturals 1)]
        [rank rankings])
    (for ([r rank])
      (displayln (format "~a) ~a" i (list-ref candidates r))))))

(define (wait-for-open-election [bulletin : requester])
  (let retry : Any ([wait 0])
       (sleep wait)
       (with-handlers
         ([exn:fail:network:http:error?
           (λ ([ex : exn:fail:network:http:error])
             (break-enabled #t)
             (when (eqv? 410 (exn:fail:network:http:error-code ex))
               (retry 3)))])
         (get bulletin "/candidates")
         (void))))

(define (can-we-register? [bulletin : requester]) : (Values Boolean (Option String))
  (with-handlers ([exn:fail:network:http:error?
                   (λ ([ex : exn:fail:network:http:error])
                     (if (eqv? (exn:fail:network:http:error-code ex) 400)
                         (values #t #f)
                         (values #f (exn-message ex))))])
    (post bulletin "/register" #:data (jsexpr->string empty))
    (values #t #f)))

(module+ main
  (define point (gen))

  (define bulletin
    (update-ssl
     (update-port (update-host json-requester
                               (readline "Bulletin host: "))
                  1984)
     #t))

  ; check if we're allowed to register
  (let-values ([(answer why) (can-we-register? bulletin)])
    (when (not answer)
      (displayln (format "Cannot register: ~a" why))
      (exit)))

  (define token
    (let ([username (readline "Username: ")]
          [password (get-pass "Password: ")])
      (with-handlers ([exn:fail:network:http:error?
                       (λ ([ex : exn:fail:network:http:error])
                         (newline)
                         (displayln (format "Login refused: ~a"
                                            (exn-message ex)))
                         (exit))])
        (newline)
        (displayln "retrieving token...")
        (json-response-body
         (post bulletin "/register"
               #:data
               (jsexpr->string (hasheq 'input (integer->hex-string point)
                                       'username username
                                       'password password)))))))

  ; the bulletin can have multiple elections, so loop
  (let loop ()
    (with-handlers
      ([exn:break?
        (λ ([ex : exn:break])
          (post bulletin "/quit"
                #:data
                (jsexpr->string
                 (hasheq
                  'token token))))]
       [exn:fail:network:http:error?
        (λ ([ex : exn:fail:network:http:error])
          (break-enabled #t)
          (if (eqv? 410 (exn:fail:network:http:error-code ex))
              (begin
                (displayln
                 "Election was closed; awaiting for it to reopen...")
                (wait-for-open-election bulletin))
              (raise ex)))])
      (vote-in-election point bulletin token))
    (loop)))

A go.mod => go.mod +28 -0
@@ 0,0 1,28 @@
module gitlab.com/edwargix/tallyard

require (
	github.com/cbergoon/merkletree v0.2.0
	github.com/gogo/protobuf v1.3.1
	github.com/google/uuid v1.1.1
	github.com/ipfs/go-datastore v0.4.2
	github.com/ipfs/go-log v0.0.1
	github.com/libp2p/go-libp2p v0.5.2
	github.com/libp2p/go-libp2p-autonat-svc v0.1.0
	github.com/libp2p/go-libp2p-circuit v0.1.4
	github.com/libp2p/go-libp2p-connmgr v0.2.1
	github.com/libp2p/go-libp2p-core v0.3.0
	github.com/libp2p/go-libp2p-discovery v0.2.0
	github.com/libp2p/go-libp2p-kad-dht v0.5.0
	github.com/libp2p/go-libp2p-quic-transport v0.2.3
	github.com/libp2p/go-libp2p-routing v0.1.0
	github.com/libp2p/go-libp2p-secio v0.2.1
	github.com/libp2p/go-libp2p-swarm v0.2.2
	github.com/libp2p/go-libp2p-tls v0.1.3
	github.com/mr-tron/base58 v1.2.0
	github.com/multiformats/go-multiaddr v0.2.0
	github.com/multiformats/go-multiaddr-net v0.1.2
	github.com/rivo/tview v0.0.0-20200528200248-fe953220389f
	github.com/whyrusleeping/go-logging v0.0.1
)

go 1.13

A go.sum => go.sum +468 -0
@@ 0,0 1,468 @@
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/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75/go.mod h1:uAXEEpARkRhCZfEvy/y0Jcc888f9tHCc1W7/UeEtreE=
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/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
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/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/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/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/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/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/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/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
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/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
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.2 h1:h8/n7WPzhp239kkLws+epN3Ic7YtcBPgcaXfEfdVDWM=
github.com/ipfs/go-datastore v0.4.2/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA=
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-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-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-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/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/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk=
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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
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.1.0/go.mod h1:6D/2OBauqLUoqcADOJpn9WbKqvaM07tDw68qHM0BxUM=
github.com/libp2p/go-libp2p v0.5.0/go.mod h1:Os7a5Z3B+ErF4v7zgIJ7nBHNu2LYt8ZMLkTQUB3G/wA=
github.com/libp2p/go-libp2p v0.5.2 h1:fjQUTyB7x/4XgO31OEWkJ5uFeHRgpoExlf0rXz5BO8k=
github.com/libp2p/go-libp2p v0.5.2/go.mod h1:o2r6AcpNl1eNGoiWhRtPji03NYOvZumeQ6u+X6gSxnM=
github.com/libp2p/go-libp2p-autonat v0.1.0/go.mod h1:1tLf2yXxiE/oKGtDwPYWTSYG3PtvYlJmg7NeVtPRqH8=
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-autonat-svc v0.1.0/go.mod h1:fqi8Obl/z3R4PFVLm8xFtZ6PBL9MlV/xumymRFkKq5A=
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.0/go.mod h1:Ahq4cY3V9VJcHcn1SBXjr78AbFkZeIRmfunbA7pmFh8=
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-connmgr v0.2.1/go.mod h1:JReKEFcgzSHKT9lL3rhYcUtXBs9uMIiMKJGM1tl3xJE=
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-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI=
github.com/libp2p/go-libp2p-discovery v0.1.0/go.mod h1:4F/x+aldVHjHDHuX85x1zWoFTGElt8HnoDzwkFZm29g=
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-nat v0.0.4/go.mod h1:N9Js/zVtAXqaeT99cXgTV9e75KpnWCvVOiGzlcHmBbY=
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-quic-transport v0.2.3/go.mod h1:bwW00YE7G6Spb+/yVfWDIFi3WrOXfE00KlwOMJPHeX0=
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-tls v0.1.2/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M=
github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M=
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-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-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-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.3/go.mod h1:88nUEt0k0JD45Bk93NIwDqjlhiOwOoV36GchpcVc1yI=
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.1.0/go.mod h1:rjw1MG1LU9YDC6gzmwObkPd/Sqwhw7yT74kj3raBFuo=
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/lucas-clemente/quic-go v0.14.2/go.mod h1:Vn3/Fb0/77b02SGhQk36KzOUmXgVpFfizUfW5WMaqyU=
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/marten-seemann/chacha20 v0.2.0/go.mod h1:HSdjFau7GzYRj+ahFNwsO3ouVJr1HFkWoEwNDb4TMtE=
github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI=
github.com/marten-seemann/qtls v0.4.1/go.mod h1:pxVXcHHw1pNIt8Qo0pwSYQEoZ8yYOOPXTCZLQQunvRc=
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/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-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-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.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.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
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.8.1/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/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/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/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/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
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/go-notifier v0.0.0-20170827234753-097c5d47330f/go.mod h1:cZNvX9cFybI01GriPRMXDtczuvUhgbcYr9iCGaNlRv8=
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-20180901202407-ef14215e6b30/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4=
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=
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-20190829043050-9756ffdc2472/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/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-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-20190228165749-92fc7df08ae7/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/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/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-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/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-20190904154756-749cb33beabd/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-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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/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=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

D info.rkt => info.rkt +0 -12
@@ 1,12 0,0 @@
#lang info
(define collection "tallyard")
(define deps '("base" "charterm" "crypto" "get-pass"
                      "sha" "simple-http" "sugar"))
(define build-deps '("scribble-lib" "racket-doc" "rackunit-lib"))
(define scribblings '(("scribblings/tallyard.scrbl" ())))
(define pkg-desc "Description Here")
(define version "0.0")
(define pkg-authors '(edwargix))

(define racket-launcher-names '("tallyard"))
(define racket-launcher-libraries '("client.rkt"))

A linalg.go => linalg.go +73 -0
@@ 0,0 1,73 @@
package main

import (
	"math/big"
)

type Matrix [][]big.Rat

func (M Matrix) Rows() int {
	return len(M)
}

func (M Matrix) Cols() int {
	if len(M) == 0 {
		return 0
	}
	return len(M[0])
}

func (M Matrix) RREF() {
	lead := 0
	zero := big.NewRat(0, 1)

	for r := 0; r < M.Rows(); r++ {
		if lead >= M.Cols() {
			return
		}
		i := r
		for M[i][lead].Cmp(zero) == 0 {
			i++
			if M.Rows() == i {
				i = r
				lead++
				if M.Cols() == lead {
					return
				}
			}
		}
		M[i], M[r] = M[r], M[i]
		f := &big.Rat{}
		f.Inv(&M[r][lead])
		for j := range M[r] {
			M[r][j].Mul(&M[r][j], f)
		}
		for i = 0; i < M.Rows(); i++ {
			if i != r {
				f.Set(&M[i][lead])
				for j, e := range M[r] {
					M[i][j].Sub(&M[i][j], new(big.Rat).Mul(&e, f))
				}
			}
		}
		lead++
	}
}

// assumes `M' and `other' are valid matrices
func (M Matrix) Equals(other Matrix) bool {
	if len(M) != len(other) {
		return false
	}
	if len(M) > 0 && len(M[0]) != len(other[0]) {
		return false
	}
	for r := range M {
		for c := range M[r] {
			if M[r][c].Cmp(&other[r][c]) != 0 {
				return false
			}
		}
	}
	return true
}

A linalg_test.go => linalg_test.go +36 -0
@@ 0,0 1,36 @@
package main

import (
	"math/big"
	"testing"
)

func TestRREF(t *testing.T) {
	mat := Matrix{
		{*big.NewRat( 1, 1), *big.NewRat(2, 1), *big.NewRat(-1, 1), *big.NewRat( -4, 1)},
		{*big.NewRat( 2, 1), *big.NewRat(3, 1), *big.NewRat(-1, 1), *big.NewRat(-11, 1)},
		{*big.NewRat(-2, 1), *big.NewRat(0, 1), *big.NewRat(-3, 1), *big.NewRat( 22, 1)},
	}
	mat.RREF()
	result := Matrix{
		{*big.NewRat(1, 1), *big.NewRat(0, 1), *big.NewRat(0, 1), *big.NewRat(-8, 1)},
		{*big.NewRat(0, 1), *big.NewRat(1, 1), *big.NewRat(0, 1), *big.NewRat( 1, 1)},
		{*big.NewRat(0, 1), *big.NewRat(0, 1), *big.NewRat(1, 1), *big.NewRat(-2, 1)},
	}
	if !mat.Equals(result) {
		t.Fail()
	}

	mat = Matrix{
		{*big.NewRat(1, 1), *big.NewRat(3, 1), *big.NewRat(-1, 1)},
		{*big.NewRat(0, 1), *big.NewRat(1, 1),  *big.NewRat(7, 1)},
	}
	mat.RREF()
	result = Matrix{
		{*big.NewRat(1, 1), *big.NewRat(0, 1), *big.NewRat(-22, 1)},
		{*big.NewRat(0, 1), *big.NewRat(1, 1), *big.NewRat(  7, 1)},
	}
	if !mat.Equals(result) {
		t.Fail()
	}
}

A main.go => main.go +196 -0
@@ 0,0 1,196 @@
package main

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

	"github.com/cbergoon/merkletree"
	"github.com/ipfs/go-log"
	"github.com/libp2p/go-libp2p"
	"github.com/libp2p/go-libp2p-core/host"
	"github.com/libp2p/go-libp2p-core/peer"
	"github.com/libp2p/go-libp2p-core/protocol"
	dht "github.com/libp2p/go-libp2p-kad-dht"
	routing "github.com/libp2p/go-libp2p-routing"
	"github.com/mr-tron/base58/base58"
	"github.com/rivo/tview"
	"github.com/whyrusleeping/go-logging"
)

var (
	logger          = log.Logger("tallyard")
	protocolID      = protocol.ID("/tallyard/0.0.0")
	candidates      []Candidate
	optionsMerkle   *merkletree.MerkleTree
	rendezvousNonce Nonce
	merkleRoot      []byte
	me              Me
	election        Election
)

func bootstrap() {
	var err error

	me.ctx = context.Background()
	election.remoteVoters = make(map[peer.ID]*Voter)

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

	logger.Info("host:", me.ID())
	logger.Info(me.Addrs())

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

	if election.masterID == "" { // we are the master
		fmt.Println("share this with peers:")
		fmt.Printf("%s0%s\n",
			base58.Encode(optionsMerkle.MerkleRoot()),
			me.ID())

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

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

		me.SetStreamHandler(protocolID, streamHandler)

		fmt.Println("press ENTER to solidify group of voters and start voting")
		stdReader := bufio.NewReader(os.Stdin)
		_, err := stdReader.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 := me.NewStream(me.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()
	} else { // we are a slave
		logger.Info("attempting to open stream with master peer...")
		stream, err := me.NewStream(me.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)
		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 != "" {
				candidates = append(candidates, Candidate(str))
			}
			if err == io.EOF {
				break
			}
		}
		logger.Info("done fetching election info")

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

		// 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
		me.SetStreamHandler(protocolID, streamHandler)
		findPeers(ch)
	}
}

func main() {
	log.SetAllLoggers(logging.WARNING)
	log.SetLogLevel("dht", "critical")
	log.SetLogLevel("relay", "critical")
	log.SetLogLevel("tallyard", "critical")

	app := tview.NewApplication()
	modal := tview.NewModal().
		SetText("Welcome to tallyard!").
		AddButtons([]string{"Create Election", "Join Election"}).
		SetDoneFunc(func(buttonIndex int, buttonLabel string) {
			app.Stop()
			switch buttonLabel {
			case "Create Election":
				createElection()
			case "Join Election":
				joinElection()
			}
		})
	if err := app.SetRoot(modal, false).EnableMouse(true).Run(); err != nil {
		panic(err)
	}

	bootstrap()

	startVoting()
}

D main.rkt => main.rkt +0 -1
@@ 1,1 0,0 @@
#lang racket

A merkle.go => merkle.go +71 -0
@@ 0,0 1,71 @@
package main

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

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

type Candidate string

func (eo Candidate) CalculateHash() ([]byte, error) {
	h := sha256.New()
	if _, err := h.Write([]byte(eo)); err != nil {
		return nil, err
	}
	return h.Sum(nil), nil
}

func (eo Candidate) Equals(other merkletree.Content) (bool, error) {
	return eo == other.(Candidate), nil
}

type Nonce string

func NewNonce() Nonce {
	randBytes := make([]byte, 128)
	_, err := rand.Read(randBytes)
	if err != nil {
		panic(err)
	}
	return Nonce(base58.Encode(randBytes))
}

func (n Nonce) CalculateHash() ([]byte, error) {
	h := sha256.New()
	b, err := base58.Decode(string(n))
	if err != nil {
		return nil, err
	}
	if _, err = h.Write(b); err != nil {
		return nil, err
	}
	return h.Sum(nil), nil
}

func (n Nonce) Equals(other merkletree.Content) (bool, error) {
	return n == other.(Nonce), nil
}

func verifyElectionInfo() {
	content := []merkletree.Content{rendezvousNonce}
	var err error
	for _, eo := range 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)
	}
}

D merkle.rkt => merkle.rkt +0 -40
@@ 1,40 0,0 @@
#lang typed/racket/base
(require/typed sha [sha256 (-> Bytes Bytes)])

(struct merkle-tree
  ([root    : merkle-node]
   [leaves  : (Vectorof merkle-node)]
   [content : (Vectorof Bytes)]))

(struct merkle-node
  ([parent : (Option merkle-node)]
   [hsh    : Bytes])
  #:mutable
  #:transparent)

(define (merkle [content : (Vectorof Bytes)])
  (let* ([n (vector-length content)]
         [leaves
          (build-vector (+ n (modulo n 2))
                        (λ ([i : Natural])
                          (let ([bstr (vector-ref content (min i (sub1 n)))])
                            (merkle-node #f (sha256 bstr)))))])
    (let build-level : merkle-tree
         ([nodes : (Vectorof merkle-node) leaves])
         (let ([n (vector-length nodes)])
           (if (> n 1)
               (build-level
                (for/vector : (Vectorof merkle-node)
                    ([left (in-vector nodes 0 n 2)]
                     [right (in-vector nodes 1 n 2)])
                  (let ([new-node
                         (merkle-node
                          #f
                          (sha256 (bytes-append (merkle-node-hsh left)
                                                (merkle-node-hsh right))))])
                    (set-merkle-node-parent! left new-node)
                    (set-merkle-node-parent! right new-node)
                    new-node)))
               (merkle-tree (vector-ref nodes 0) leaves content))))))

(provide (all-defined-out))

A poly.go => poly.go +65 -0
@@ 0,0 1,65 @@
package main

import (
	"crypto/rand"
	"math"
	"math/big"
)

type Poly struct {
	constant *big.Int
	coefs    []*big.Int
}

func RandomBigInt(numBytes uint, allowAllZeros bool) (*big.Int, error) {
	randBytes := make([]byte, numBytes)
	_, err := rand.Read(randBytes)
	if err != nil {
		return nil, err
	}
	bi := new(big.Int).SetBytes(randBytes)
	if bi.Cmp(big.NewInt(0)) == 0 {
		return RandomBigInt(numBytes, allowAllZeros)
	}
	return bi.SetBytes(randBytes), nil
}

// creates a random polynomial of the given degree, uses at least `entropy' bits
// of entropy for the random coefficients, and assigns the ballot to the
// constant term
func NewRandomPoly(degree uint, entropy uint, ballot []byte) *Poly {
	constant := new(big.Int).SetBytes(ballot)
	p := &Poly{constant, make([]*big.Int, degree)}

	// number of bits per coefficient
	numBits := uint(math.Ceil(float64(entropy) / float64(degree)))

	// number of bytes per coefficient
	numBytes := numBits / 8
	if numBits%8 > 0 {
		numBytes += 1
	}

	var err error
	for i := range p.coefs {
		p.coefs[i], err = RandomBigInt(numBytes, false)
		if err != nil {
			panic(err)
		}
	}

	return p
}

func (p *Poly) Eval(input *big.Int) *big.Int {
	res := new(big.Int).Set(p.constant)

	for i, coef := range p.coefs {
		degree := big.NewInt(int64(i + 1))
		term := new(big.Int).Exp(input, degree, nil)
		term.Mul(term, coef)
		res.Add(res, term)
	}

	return res
}

D poly.rkt => poly.rkt +0 -35
@@ 1,35 0,0 @@
#lang typed/racket/base
(require (only-in racket/math natural?))
(require (only-in racket/vector vector-map))
(require/typed binaryio
  [bytes->integer (->* (Bytes Boolean) (Boolean Natural Natural) Integer)]
  [integer->bytes (->* (Integer Positive-Integer Boolean)
                       (Boolean Bytes Natural) Bytes)]
  [integer-bytes-length (Integer Boolean -> Positive-Integer)])
(require/typed crypto
  [crypto-random-bytes (Natural -> Bytes)])

(require "merkle.rkt")

(define-type Poly (Integer -> Integer))

(define (gen)
  (let* ([num-bytes (assert (/ 1024 8) natural?)]
         [bstr (crypto-random-bytes num-bytes)])
    (bytes->integer bstr #t #t 0 num-bytes)))

(define (random-poly [degree : Natural] [constant : Integer]) : Poly
  (let ([coefficients (build-vector degree (λ (_) (gen)))])
    (λ ([x : Integer]) : Integer
       (cast (+ constant
                (for/sum : Integer ([i degree])
                  (* (expt x (add1 i)) (vector-ref coefficients i))))
             Integer))))

(define (poly-merkle [p : Poly]) : merkle-tree
  (merkle
   (vector-map
    (λ ([x : Integer]) (integer->bytes x (integer-bytes-length x #t) #t))
    (build-vector 100000 p))))

(provide (all-defined-out))

D scribblings/tallyard.scrbl => scribblings/tallyard.scrbl +0 -10
@@ 1,10 0,0 @@
#lang scribble/manual
@require[@for-label[tallyard
                    racket/base]]

@title{tallyard}
@author{edwargix}

@defmodule[tallyard]

Package Description Here

A ui.go => ui.go +175 -0
@@ 0,0 1,175 @@
package main

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

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

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

func joinElection() {
	app := tview.NewApplication()
	var form *tview.Form
	form = tview.NewForm().
		AddInputField("Election key:", "", 100, nil, nil).
		AddButton("Continue", func() {
			app.Stop()
			electionKey := form.GetFormItem(0).(*tview.InputField).GetText()

			zeroi := strings.IndexByte(electionKey, '0')
			var err error
			logger.Info("merkle root:", electionKey[:zeroi])
			merkleRoot, err = base58.Decode(electionKey[:zeroi])
			if err != nil {
				panic(err)
			}

			election.masterID, err = peer.Decode(electionKey[zeroi+1:])
			if err != nil {
				panic(err)
			}
			logger.Info("master ID:", election.masterID)
		})
	if err := app.SetRoot(form, true).EnableMouse(true).Run(); err != nil {
		panic(err)
	}
}

// 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 voter.go => voter.go +394 -0
@@ 0,0 1,394 @@
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 Me struct {
	Voter
	host.Host
	ctx       context.Context
	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
}

type Election struct {
	sync.RWMutex
	// for slave: signifies when election was closed by master
	//
	// for master: signifies when user hits ENTER to close the election
	//
	// the number of peers know by master is passed through it
	close        chan<- int
	closed       bool // used by handleCmd to prevent closing election more than once
	masterID     peer.ID
	remoteVoters map[peer.ID]*Voter
}

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) {
	switch cmd {
	case "info":
		rw.WriteString(fmt.Sprintf("%s\n", rendezvousNonce))
		for _, option := range 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
		close(election.close)
		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
		me.polyMu.RLock()
		defer me.polyMu.RUnlock()
		if me.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 := me.poly.Eval(peer.input)
		rw.WriteString(base58.Encode(output.Bytes()))
		rw.Flush()
	case "sum":
		me.sumMu.RLock()
		defer me.sumMu.RUnlock()
		if me.sum == nil {
			logger.Info("peer attempted to fetch sum "+
				"before we computed it:", stream.Conn().RemotePeer())
			return
		}
		rw.WriteString(base58.Encode(me.sum.Bytes()))
		rw.Flush()
	default:
		logger.Warningf("uknown command %s", cmd)
	}
}

func streamHandler(stream network.Stream) {
	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)
	stream.Close()
}

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

	fmt.Println("finding other voters...")
	peerChan, err := routingDiscovery.FindPeers(me.ctx, string(rendezvousNonce))
	if err != nil {
		panic(err)
	}
	numPeers := -1
	for {
		if numPeers != -1 && numPeers == len(election.remoteVoters) {
			break
		}
		select {
		case peer := <-peerChan:
			if peer.ID == me.ID() {
				continue
			}
			fmt.Printf("found voter: %s\n", peer.ID)
			logger.Info("connecting to:", peer)

			err = me.Connect(me.ctx, peer)

			stream, err := me.NewStream(me.ctx, peer.ID, protocolID)
			if err == nil {
				writer := bufio.NewWriter(stream)
				writer.WriteString("shake")
				writer.Flush()
				stream.Close()
				election.remoteVoters[peer.ID] = &Voter{addrInfo: peer}
			} else {
				logger.Warning("connection failed:", err)
			}
		case numPeers = <-closeElection:
			if len(election.remoteVoters) > numPeers {
				logger.Fatal("found more peers than master!")
				os.Exit(1)
			}
		}
	}
	logger.Info("done finding peers")
}

func (voter *Voter) fetchNumber(cmd string, args ...string) *big.Int {
	printErr := func(err error, msg string) {
		logger.Errorf("%s: %s while fetcing `%s'; retrying in 2 seconds",
			voter.addrInfo.ID, msg, cmd)
		time.Sleep(time.Second * 2)
	}
retry:
	stream, err := me.NewStream(me.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 startVoting() {
	var err error
	me.inputMu.Lock()
	me.input, err = RandomBigInt(128, false)
	me.inputMu.Unlock()
	if err != nil {
		panic(err)
	}
	logger.Infof("our input: %s", me.input)

	ballot := vote(candidates)
	logger.Infof("our ballot: %v", ballot)

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

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

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

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

	mat := constructPolyMatrix()
	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(candidates)*len(candidates)) - len(result)
	result = append(make([]byte, diff), result...)

	printResults(result, candidates)

	// temporary
	select {}
}

func constructPolyMatrix() 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)
	var j int64
	for j = 1; j <= int64(len(election.remoteVoters)); j++ {
		row[j].SetInt(new(big.Int).Exp(me.input, big.NewInt(j), nil))
	}
	row[j].SetInt(me.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])
			}
		}
	}
}