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