Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
DisSys Inc.
bsc-shamir
Commits
50f621eb
Commit
50f621eb
authored
Jun 03, 2020
by
Anders Jensen Løvig
Browse files
Agreement: send ballot lists
parent
ff4cae2c
Pipeline
#21811
failed with stages
in 1 minute and 23 seconds
Changes
6
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
election/agreement.go
View file @
50f621eb
...
...
@@ -2,12 +2,44 @@ package election
import
(
"bsc-shamir/crypto/pedersen"
"fmt"
"sync"
"github.com/gonum/stat/combin"
"github.com/google/uuid"
)
type
BallotList
map
[
string
]
uuid
.
UUID
type
Agreement
struct
{
sync
.
Mutex
// The minimum servers required for being able to tally
required
int
// Map of ballot lists receveid from servers
ballotLists
map
[
UniqueID
]
BallotList
}
func
NewAgreement
(
required
int
)
*
Agreement
{
return
&
Agreement
{
Mutex
:
sync
.
Mutex
{},
required
:
required
,
ballotLists
:
make
(
map
[
UniqueID
]
BallotList
),
}
}
func
(
a
*
Agreement
)
AddList
(
serverID
UniqueID
,
ballotList
map
[
string
]
uuid
.
UUID
)
error
{
a
.
Lock
()
defer
a
.
Unlock
()
if
_
,
ok
:=
a
.
ballotLists
[
serverID
];
ok
{
return
fmt
.
Errorf
(
"server %s already sent ballot list"
,
serverID
)
}
a
.
ballotLists
[
serverID
]
=
ballotList
return
nil
}
type
Whitelist
map
[
uuid
.
UUID
]
pedersen
.
Proof
type
Judge
struct
{
...
...
election/ballot.go
View file @
50f621eb
...
...
@@ -9,6 +9,7 @@ import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"log"
...
...
@@ -53,6 +54,23 @@ func (ballot *Ballot) Verify(params *common.Params) bool {
return
sigma
.
NewParams
(
params
)
.
Verify
(
ballot
.
Commits
[
0
],
ballot
.
Proofs
)
}
func
(
ballot
*
Ballot
)
Hash
()
string
{
// This information
toHash
:=
struct
{
ID
uuid
.
UUID
Timestamp
time
.
Time
Commits
pedersen
.
Proof
}{
ballot
.
ID
,
ballot
.
Timestamp
,
ballot
.
Commits
,
}
data
,
_
:=
json
.
Marshal
(
toHash
)
h
:=
sha256
.
Sum256
(
data
)
return
hex
.
EncodeToString
(
h
[
:
])
}
// CreateBallots returns n ballots each containing a single share
func
CreateBallots
(
t
int
,
xs
[]
*
big
.
Int
,
vote
*
big
.
Int
)
map
[
UniqueID
]
*
Ballot
{
params
:=
common
.
DefaultParams
()
...
...
@@ -61,11 +79,12 @@ func CreateBallots(t int, xs []*big.Int, vote *big.Int) map[UniqueID]*Ballot {
proof
:=
sigma
.
NewParams
(
params
)
.
Prove
(
vote
,
binder
,
commits
[
0
])
timestamp
:=
time
.
Now
()
id
:=
uuid
.
New
()
ballots
:=
make
(
map
[
UniqueID
]
*
Ballot
)
for
_
,
share
:=
range
shares
{
ballots
[
UniqueID
(
share
.
X
.
String
())]
=
&
Ballot
{
ID
:
uuid
.
New
()
,
ID
:
id
,
Timestamp
:
timestamp
,
Share
:
share
,
Commits
:
commits
,
...
...
@@ -101,6 +120,19 @@ func (box *BallotBox) Put(ballot *Ballot) error {
return
nil
}
// ballotList returns a mapping from ballot hash to their uuid.
func
(
box
*
BallotBox
)
ballotList
()
BallotList
{
box
.
Lock
()
defer
box
.
Unlock
()
list
:=
make
(
map
[
string
]
uuid
.
UUID
)
for
id
,
ballot
:=
range
box
.
ballots
{
list
[
ballot
.
Hash
()]
=
id
}
return
list
}
// Filter ballots by returning a list of valid ballots
func
(
box
*
BallotBox
)
Filter
()
[]
uuid
.
UUID
{
good
:=
[]
uuid
.
UUID
{}
...
...
election/ballot_test.go
View file @
50f621eb
...
...
@@ -37,3 +37,22 @@ func TestBallotOne(t *testing.T) {
}
}
}
func
TestBallotHash
(
t
*
testing
.
T
)
{
ballots
:=
CreateBallots
(
5
,
[]
*
big
.
Int
{
big
.
NewInt
(
1
),
big
.
NewInt
(
2
),
big
.
NewInt
(
3
),
big
.
NewInt
(
4
),
big
.
NewInt
(
5
),
},
big
.
NewInt
(
1
))
h
:=
ballots
[
"1"
]
.
Hash
()
t
.
Log
(
"Hash"
,
h
)
for
i
,
ballot
:=
range
ballots
{
if
ballot
.
Hash
()
!=
h
{
t
.
Errorf
(
"Ballot %s have hash %s"
,
i
,
h
)
}
}
}
election/election.go
View file @
50f621eb
...
...
@@ -22,6 +22,8 @@ const (
ReasonCloseTimeout
=
"close timeout"
ReasonVotes
=
"all votes received"
ReasonAgreement
=
"majority"
ReasonTallyTimeout
=
"tally timeout"
ReasonBallotList
=
"all ballot lists received"
ReasonTally
=
"received enough tallies"
)
...
...
@@ -31,9 +33,11 @@ type Phase int
// Election phases
const
(
PhaseNotStarted
Phase
=
iota
PhaseCollect
ing
// When voters can send ballots
PhaseCollect
// When voters can send ballots
PhaseCloseWait
// When server waits after close
PhaseClosed
// When waiting for other servers to close
PhasePreTally
// When servers send ballot lists
PhaseTally
// When servers sent tallies
PhaseTallying
// When tallying
PhaseResult
// When a result is available
)
...
...
@@ -44,10 +48,12 @@ func (p Phase) String() string {
return
"PhaseNotStarted"
case
PhaseCloseWait
:
return
"PhaseCloseWait"
case
PhaseCollect
ing
:
case
PhaseCollect
:
return
"PhaseCollecting"
case
PhaseClosed
:
return
"PhaseClosed"
case
PhasePreTally
:
return
"PhasePreTally"
case
PhaseTallying
:
return
"PhaseTallying"
case
PhaseResult
:
...
...
@@ -97,8 +103,8 @@ type Election struct {
Deadline
time
.
Time
// Struct containing server status about the election.
Status
Status
//
Judge for
decid
ing
which ballots
are good
Judge
*
Judge
//
Agreement handles the agreement protocol. This
decid
es
which ballots
to tally.
Agreement
*
Agreement
// Called when the final result is available
resultCallback
func
(
*
Result
)
}
...
...
@@ -153,10 +159,12 @@ func NewElection(config *Config) *Election {
},
resultCallback
:
config
.
ResultCallback
,
Mutex
:
sync
.
Mutex
{},
Agreement
:
NewAgreement
(
config
.
RequiredServers
),
}
config
.
Server
.
OnClientMessage
(
MsgBallot
,
e
.
OnBallot
)
config
.
Server
.
OnServerMessage
(
MsgClosing
,
e
.
OnClose
)
config
.
Server
.
OnServerMessage
(
MsgTally
,
e
.
OnTally
)
config
.
Server
.
OnServerMessage
(
MsgBallotList
,
e
.
OnBallotList
)
return
e
}
...
...
@@ -183,7 +191,7 @@ func (election *Election) Start() error {
defer
election
.
Unlock
()
// Deadline reached: Close election
if
election
.
Status
.
Phase
==
PhaseCollect
ing
{
if
election
.
Status
.
Phase
==
PhaseCollect
{
election
.
nextPhase
(
ReasonDeadline
)
}
}(
election
)
...
...
@@ -196,8 +204,8 @@ func (election *Election) nextPhase(reason Reason) {
switch
election
.
Status
.
Phase
{
case
PhaseNotStarted
:
log
.
Println
(
"==================== Collecting ===================="
)
election
.
Status
.
Phase
=
PhaseCollect
ing
case
PhaseCollect
ing
:
election
.
Status
.
Phase
=
PhaseCollect
case
PhaseCollect
:
log
.
Println
(
"====================== Closed ======================"
)
// The closed phase is twofold. If the reason for closing is another
// than all votes received (ReasonVotes), then we must wait for late
...
...
@@ -214,24 +222,7 @@ func (election *Election) nextPhase(reason Reason) {
if
reason
!=
ReasonVotes
&&
election
.
Config
.
CloseSleep
!=
0
{
log
.
Printf
(
"Election: waiting %s for late ballots
\n
"
,
election
.
Config
.
CloseSleep
)
election
.
Status
.
Phase
=
PhaseCloseWait
go
func
(
e
*
Election
)
{
var
reason
Reason
// Wait for timeout or all votes received.
select
{
case
<-
time
.
After
(
time
.
Until
(
now
.
Add
(
e
.
Config
.
CloseSleep
)))
:
reason
=
ReasonCloseTimeout
case
<-
e
.
Status
.
PhaseChannel
:
reason
=
ReasonVotes
}
// Then next phase
e
.
Lock
()
defer
e
.
Unlock
()
// Actually close the election
e
.
Status
.
Phase
=
PhaseClosed
e
.
nextPhase
(
reason
)
}(
election
)
go
delay
(
election
,
now
,
ReasonCloseTimeout
,
ReasonVotes
,
PhaseClosed
)
break
}
else
{
// If we received all votes go directly to next phase
...
...
@@ -247,23 +238,38 @@ func (election *Election) nextPhase(reason Reason) {
break
}
// Else enough servers to start tallying
// If the ballot box is empty we can go directly to result
if
election
.
ballotBox
.
Size
()
!=
0
{
log
.
Println
(
"===================== Tallying ====================="
)
log
.
Println
(
"===================== Tallying ====================="
)
// Tallying is two phased. First we wait for servers to sent ballot lists.
// Then we find the largest intersection of ballot lists and tally this intersection.
election
.
Status
.
Phase
=
PhasePreTally
ballotList
:=
election
.
ballotBox
.
ballotList
()
_
=
election
.
Agreement
.
AddList
(
election
.
ServerID
,
ballotList
)
election
.
server
.
Broadcast
(
network
.
NewMessage
(
MsgBallotList
,
ballotList
))
if
election
.
Config
.
CloseSleep
!=
0
{
log
.
Printf
(
"Election: waiting %s for ballots lists
\n
"
,
election
.
Config
.
CloseSleep
)
go
delay
(
election
,
time
.
Now
(),
ReasonTallyTimeout
,
ReasonBallotList
,
PhaseTally
)
break
}
else
{
// Skip PhasePreTally
election
.
Status
.
Phase
=
PhaseTallying
election
.
Judge
=
NewJudge
(
election
.
Participants
.
RequiredServers
,
election
.
ballotBox
)
}
fallthrough
case
PhaseTally
:
election
.
Status
.
Phase
=
PhaseTallying
tally
:=
election
.
ballotBox
.
Tally
(
election
.
Judge
.
GoodIds
())
// Only tally if we have ballots
if
election
.
ballotBox
.
Size
()
!=
0
{
tally
:=
election
.
ballotBox
.
Tally
(
nil
)
election
.
Status
.
TalliedServers
[
election
.
ServerID
]
=
true
err
:=
election
.
tallyBox
.
Put
(
tally
)
if
err
!=
nil
{
panic
(
err
)
//
This s
hould n
ever
happen
panic
(
err
)
//
S
hould n
ot
happen
}
election
.
server
.
Broadcast
(
network
.
NewMessage
(
MsgTally
,
tally
))
break
}
fallthrough
case
PhaseTallying
:
log
.
Println
(
"====================== Result ======================"
)
...
...
@@ -271,6 +277,8 @@ func (election *Election) nextPhase(reason Reason) {
result
:=
election
.
createResults
()
election
.
resultCallback
(
result
)
}
log
.
Println
(
"Election: new phase:"
,
election
.
Status
.
Phase
)
}
func
(
election
*
Election
)
createResults
()
*
Result
{
...
...
@@ -283,3 +291,21 @@ func (election *Election) createResults() *Result {
Tally
:
int
(
election
.
tallyBox
.
Combine
()
.
Int64
()),
}
}
func
delay
(
e
*
Election
,
now
time
.
Time
,
r1
,
r2
Reason
,
p
Phase
)
{
var
reason
Reason
// Wait for timeout or all votes received.
select
{
case
<-
time
.
After
(
time
.
Until
(
now
.
Add
(
e
.
Config
.
CloseSleep
)))
:
reason
=
r1
case
<-
e
.
Status
.
PhaseChannel
:
reason
=
r2
}
// Then next phase
e
.
Lock
()
defer
e
.
Unlock
()
// Actually close the election
e
.
Status
.
Phase
=
p
e
.
nextPhase
(
reason
)
}
election/election_test.go
View file @
50f621eb
...
...
@@ -247,7 +247,7 @@ func TestCloseDeadline(t *testing.T) {
func
TestCloseAgreement
(
t
*
testing
.
T
)
{
_
=
testElection
(
t
,
PhaseTallying
,
func
(
e
*
Election
)
<-
chan
time
.
Time
{
e
.
Status
.
Phase
=
PhaseCollect
ing
e
.
Status
.
Phase
=
PhaseCollect
ballots
:=
CreateBallots
(
3
,
createXS
(
1
),
big
.
NewInt
(
1
))
e
.
handleBallot
(
UniqueID
(
strconv
.
Itoa
(
1
)),
ballots
[
"1"
])
...
...
@@ -261,7 +261,7 @@ func TestCloseAgreement(t *testing.T) {
func
TestCloseAllVotes
(
t
*
testing
.
T
)
{
_
=
testElection
(
t
,
PhaseClosed
,
func
(
e
*
Election
)
<-
chan
time
.
Time
{
e
.
Status
.
Phase
=
PhaseCollect
ing
e
.
Status
.
Phase
=
PhaseCollect
for
i
:=
1
;
i
<=
e
.
Participants
.
Voters
;
i
++
{
ballots
:=
CreateBallots
(
3
,
createXS
(
1
),
big
.
NewInt
(
1
))
...
...
@@ -322,7 +322,7 @@ func TestTallyAfterClose(t *testing.T) {
func
TestOnlyVoteOnce
(
t
*
testing
.
T
)
{
e
:=
setupElection
()
e
.
Status
.
Phase
=
PhaseCollect
ing
e
.
Status
.
Phase
=
PhaseCollect
ballots
:=
CreateBallots
(
3
,
createXS
(
1
),
big
.
NewInt
(
1
))
...
...
@@ -348,7 +348,7 @@ func TestHandleTally(t *testing.T) {
}
_
=
testElection
(
t
,
PhaseResult
,
func
(
e
*
Election
)
<-
chan
time
.
Time
{
e
.
Status
.
Phase
=
PhaseCollect
ing
e
.
Status
.
Phase
=
PhaseCollect
e
.
handleBallot
(
"4"
,
ballots
[
"1"
])
e
.
handleTally
(
"2"
,
tally1
)
...
...
@@ -372,7 +372,7 @@ func TestHandleTally(t *testing.T) {
func
TestCloseTimeout
(
t
*
testing
.
T
)
{
_
=
testElection
(
t
,
PhaseClosed
,
func
(
e
*
Election
)
<-
chan
time
.
Time
{
e
.
Status
.
Phase
=
PhaseCollect
ing
e
.
Status
.
Phase
=
PhaseCollect
e
.
Config
.
CloseSleep
=
1
*
time
.
Second
// This should start a go-routine waiting for late ballots
...
...
@@ -388,7 +388,7 @@ func TestCloseTimeout(t *testing.T) {
func
TestCloseTimeoutTally
(
t
*
testing
.
T
)
{
_
=
testElection
(
t
,
PhaseTallying
,
func
(
e
*
Election
)
<-
chan
time
.
Time
{
e
.
Status
.
Phase
=
PhaseCollect
ing
e
.
Status
.
Phase
=
PhaseCollect
e
.
Config
.
CloseSleep
=
1
*
time
.
Second
ballots
:=
CreateBallots
(
3
,
createXS
(
1
),
big
.
NewInt
(
1
))
...
...
@@ -409,7 +409,7 @@ func TestCloseTimeoutTally(t *testing.T) {
func
TestCloseTimeoutSkipTally
(
t
*
testing
.
T
)
{
_
=
testElection
(
t
,
PhaseResult
,
func
(
e
*
Election
)
<-
chan
time
.
Time
{
e
.
Status
.
Phase
=
PhaseCollect
ing
e
.
Status
.
Phase
=
PhaseCollect
e
.
Config
.
CloseSleep
=
1
*
time
.
Second
e
.
handleClosing
(
"3"
)
...
...
@@ -429,7 +429,7 @@ func TestCloseTimeoutSkipTally(t *testing.T) {
func
TestCloseTimeoutLateBallot
(
t
*
testing
.
T
)
{
_
=
testElection
(
t
,
PhaseClosed
,
func
(
e
*
Election
)
<-
chan
time
.
Time
{
log
.
SetOutput
(
os
.
Stdout
)
e
.
Status
.
Phase
=
PhaseCollect
ing
e
.
Status
.
Phase
=
PhaseCollect
e
.
Config
.
CloseSleep
=
500
*
time
.
Millisecond
for
i
:=
1
;
i
<
e
.
Participants
.
Voters
;
i
++
{
...
...
election/handler.go
View file @
50f621eb
...
...
@@ -16,6 +16,8 @@ const (
MsgClosing
=
"election_closing"
// Server -> Server: Sent when server have tallied
MsgTally
=
"election_tally"
// Server -> Server: Sent when servers agree on ballots to tally
MsgBallotList
=
"ballot_list"
)
func
(
e
*
Election
)
OnBallot
(
conn
*
network
.
Conn
,
data
network
.
MessageData
)
{
...
...
@@ -142,3 +144,25 @@ func (election *Election) handleTally(serverID UniqueID, tally *Tally) {
election
.
nextPhase
(
ReasonTally
)
}
}
func
(
e
*
Election
)
OnBallotList
(
conn
*
network
.
Conn
,
data
network
.
MessageData
)
{
var
ballotList
BallotList
err
:=
data
.
Unmarshal
(
&
ballotList
)
if
err
!=
nil
{
log
.
Println
(
"Election: error:"
,
err
)
return
}
e
.
Lock
()
defer
e
.
Unlock
()
err
=
e
.
Agreement
.
AddList
(
UniqueID
(
conn
.
SerialNumber
()),
ballotList
)
if
err
!=
nil
{
log
.
Println
(
"Election: error:"
,
err
)
}
if
len
(
e
.
Agreement
.
ballotLists
)
>=
e
.
Participants
.
Servers
&&
e
.
Status
.
Phase
==
PhasePreTally
{
e
.
Status
.
PhaseChannel
<-
true
}
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment