From 7c9ad0cbd819cabcdf825968709a17213f2b136a Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 21 Feb 2018 15:42:54 -0500 Subject: [PATCH 01/44] Fix docker build, but really this time though We re-separate the dep ensure step so that it doesn't run when a simple Go file changes. We also fix the directory paths so that imports that reference the full keep-core GitHub path will resolve correctly (currently no files with these references are compiled, which is how we missed it, but they're coming). --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e098ec9f77..2821b04c27 100644 --- a/Dockerfile +++ b/Dockerfile @@ -76,4 +76,4 @@ COPY --from=cbuild $INCLUDE_DIR $INCLUDE_DIR ENTRYPOINT ["keep-client", "-config", "/keepclient/config.toml"] # docker caches more when using CMD [] resulting in a faster build. -CMD [] +CMD [] \ No newline at end of file From 419ec5004f73d7dce9fc8b36224c70b2bb60f5f3 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 8 Feb 2018 19:12:56 -0500 Subject: [PATCH 02/44] Add stub interface for broadcast channel --- go/beacon/broadcast/broadcast.go | 57 ++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 go/beacon/broadcast/broadcast.go diff --git a/go/beacon/broadcast/broadcast.go b/go/beacon/broadcast/broadcast.go new file mode 100644 index 0000000000..ce1c14866f --- /dev/null +++ b/go/beacon/broadcast/broadcast.go @@ -0,0 +1,57 @@ +package broadcast + +import "github.com/dfinity/go-dfinity-crypto/bls" + +// Message represents a message to communicate over a broadcast channel. +// TODO Combine with Raghav's work on protobuf messages. +type Message struct { + sender bls.ID + receiver bls.ID + encrypted bool + data interface{} +} + +// Channel represents a named broadcast channel. It allows consumers to send +// messages to the channel (via Send) and to access a low-level receive chan +// that furnishes messages sent onto the broadcast channel. +type Channel interface { + Name() string + + Send(message Message) bool + + RecvChan() <-chan Message +} + +type localChannel struct { + name string + recvChans []chan Message +} + +func (channel *localChannel) Name() string { + return channel.name +} + +func (channel *localChannel) Send(message Message) bool { + for _, recvChan := range channel.recvChans { + recvChan <- message + } + + return true +} + +func (channel *localChannel) RecvChan() <-chan Message { + newChan := make(chan Message) + + channel.recvChans = append(channel.recvChans, newChan) + + return newChan +} + +// LocalChannel returns a Channel designed to mediate between local +// participants. It delivers all messages sent to the channel through its +// receive channels. RecvChan on a LocalChannel creates a new receive channel +// that is returned to the caller, so that all receive channels can receive +// the message. +func LocalChannel(name string) Channel { + return &localChannel{name, make([]chan Message, 0)} +} From 143c1eff4fdc5297d4f88d68ef50f9b98225b846 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 8 Feb 2018 19:13:23 -0500 Subject: [PATCH 03/44] Add initial threshold group Member struct Includes a healthy dose of documentation, as well as a NewMember constructor function. There are still some open questions around how to handle the id, but this should be a good starting point. --- go/thresholdgroup/member.go | 73 +++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 go/thresholdgroup/member.go diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go new file mode 100644 index 0000000000..e1a87113ba --- /dev/null +++ b/go/thresholdgroup/member.go @@ -0,0 +1,73 @@ +package thresholdgroup + +import "github.com/dfinity/go-dfinity-crypto/bls" + +// Member represents one member in a threshold key sharing group. +// Publicly, it exposes only an ID. +type Member struct { + // ID of this group member. + ID string + // The BLS ID of this group member, computed from the ID. + blsID bls.ID + // The threshold of group members who must be honest in order for the + // generated key to be uncompromised. Corresponds to the number of secret + // shares and public commitments of this group member. + threshold int + // The public commitments received from each other group member. For each + // other group member, we track their list of public commitments to their + // private secrets. This allows us to verify the share of their private + // secret that they send us. + commitments map[bls.ID][]bls.PublicKey + // Created locally, these are the t secret components that, combined, + // represent this group member's share of the group secret key. They are + // publicly committed to via their public keys, which are broadcast to all + // other members. They are used to generate shares of this member's group + // secret key share for other members, which can be verified against the + // public commitments from this member. + secretShares []bls.SecretKey + // Shares of this group member's secret, one per group member. The group + // member generates a share of its own secret as well! Note that a share for + // a given member m is shared privately with that member in the secret + // sharing phase and only shared publicly in the justification phase if this + // member receives an accusation from m in the accusation phase. + shares map[bls.ID]bls.SecretKey + // For each other group member, the share of that member's secret that the + // member sent this member. + receivedShares map[bls.ID]bls.SecretKey + // Public key for the group; nil if not yet computed. + groupPublicKey *bls.PublicKey + // This group member's share of the group secret key; nil if not yet + // computed. + groupSecretKeyShare *bls.SecretKey + // A list of ids of other group members who have accused this group member + // of sending them an invalid share. + accuserIDs []bls.ID + // Received via broadcast, tracks IDs of all members who accused this + // member so their shares can be broadcast publicly during justification. + accusedIDs map[bls.ID]bool + // A (spares) map from the ID of other group members to a boolean indicating + // if they were disqualified. Players are disqualified when they are accused + // and then broadcast a justification that fails to verify against their + // public commitments. + disqualifiedPlayers map[bls.ID]bool // all players disqualified during justification + // The final list of qualified group members; empty if not yet computed. + qualifiedPlayers []bls.ID +} + +// NewMember creates a new member with the given id. The id should be a base-10 +// string and is encoded into a bls.ID for use with the built-in secret sharing. +// The id should be unique per group member. +func NewMember(id string) Member { + blsID := bls.ID{} + blsID.SetDecString(id) + + return Member{ + ID: id, + blsID: blsID, + commitments: map[bls.ID][]bls.PublicKey{}, + shares: map[bls.ID]bls.SecretKey{}, + receivedShares: map[bls.ID]bls.SecretKey{}, + accusedIDs: map[bls.ID]bool{}, + disqualifiedPlayers: map[bls.ID]bool{}, + } +} From b5dbf55ac6ddb5dd3273f5617e25028945c88dac Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Fri, 9 Feb 2018 13:26:41 -0500 Subject: [PATCH 04/44] Break member struct into structs for various phases The one Member struct becomes a LocalMember, SharingMember, JustifyingMember, and Member struct. Each one of these represents a phase in DKG, except SharingMember which represents both the commitment and private share receiving/validation phase. These can occur at the same time without any issues, so we combine the struct. Each struct has an associated method to transition it to the next phase, in which we do any final work (e.g., when we go from LocalMember to SharingMember we compute the private member shares that are to be sent to each member of the threshold group). Each struct also exposes the various methods that make sense for its phase. The Member struct now represents a fully-fleshed-out member, ready to participate in threshold signatures. Note that there are still a few todos around properly tracking accusation/justification. There is also some validation that needs to happen so that we don't generate a final Member if we've failed to meet our threshold requirements for qualified members. --- go/Gopkg.lock | 4 +- go/thresholdgroup/member.go | 364 +++++++++++++++++++++++++++++++----- 2 files changed, 324 insertions(+), 44 deletions(-) diff --git a/go/Gopkg.lock b/go/Gopkg.lock index 674c52ec96..f30cdf8046 100644 --- a/go/Gopkg.lock +++ b/go/Gopkg.lock @@ -3,7 +3,7 @@ [[projects]] name = "github.com/dfinity/go-dfinity-crypto" - packages = ["rand"] + packages = ["bls","rand"] revision = "1db358303499a9879dd640c71f831897e06e9ddf" source = "https://github.com/keep-network/go-dfinity-crypto.git" @@ -16,6 +16,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "70bb7494b440b113186ecf479de4af67c971a97b57bd73deb937262bc9926216" + inputs-digest = "78ac2431c67ca4ede5e17b9c7c510b63506d59edb08d471e8a3a279618205469" solver-name = "gps-cdcl" solver-version = 1 diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go index e1a87113ba..5f59d99292 100644 --- a/go/thresholdgroup/member.go +++ b/go/thresholdgroup/member.go @@ -1,10 +1,18 @@ package thresholdgroup -import "github.com/dfinity/go-dfinity-crypto/bls" +import ( + "github.com/dfinity/go-dfinity-crypto/bls" +) -// Member represents one member in a threshold key sharing group. -// Publicly, it exposes only an ID. -type Member struct { +// [GJKR 99]: Gennaro R., Jarecki S., Krawczyk H., Rabin T. (1999) Secure +// Distributed Key Generation for Discrete-Log Based Cryptosystems. In: +// Stern J. (eds) Advances in Cryptology — EUROCRYPT ’99. EUROCRYPT 1999. +// Lecture Notes in Computer Science, vol 1592. Springer, Berlin, Heidelberg +// http://groups.csail.mit.edu/cis/pubs/stasio/vss.ps.gz + +// LocalMember represents one member in a threshold key sharing group, prior to +// any sharing or key generation process. +type LocalMember struct { // ID of this group member. ID string // The BLS ID of this group member, computed from the ID. @@ -13,61 +21,333 @@ type Member struct { // generated key to be uncompromised. Corresponds to the number of secret // shares and public commitments of this group member. threshold int + // Created locally, these are the `threshold` secret components that, + // combined, represent this group member's share of the group secret key. + // They are used to generate shares of this member's group secret key share + // for other members, which can be verified against the public commitments + // from this member. + secretShares []bls.SecretKey + // Created locally from secretShares, these are the `threshold` public + // commitments to this group member's secret shares, which are broadcast to + // all other members. + shareCommitments []bls.PublicKey +} + +// SharingMember represents one member in a threshold key sharing group, after +// it has a full list of `memberIDs` that belong to its threshold group. A +// member in this state has a set of `memberShares`, one for each member of the +// group, which can be accessed per member using `SecretShareForID()`. A member +// in this state also has a set of public commitments, accessible via +// `Commitments()`. +// +// As public commitments come in from other members, they can be added using +// `AddCommitmentsFromID`. Similarly, as private shares come in from other +// members, they can be added using `AddShareFromID`. +// +// Once all commitments and shares have been received, `Accusations()` will +// return a full list of members who sent invalid private shares. These can then +// be broadcast to the group, and the member can be transitioned to +// the justification phase using `InitializeJustification()`. +type SharingMember struct { + LocalMember + + // A list of the ids of all members in the threshold group, including this + // one. + memberIDs []bls.ID + + // Shares of this group member's secret, one per member of the overall + // group. The group member generates a share of its own secret as well! Note + // that a share for a given member m is shared privately with that member in + // the secret sharing phase. It is only shared publicly this member receives + // an accusation from m in the accusation phase; this public sharing takes + // place in the justification phase. + memberShares map[bls.ID]bls.SecretKey + // The public commitments received from each other group member. For each // other group member, we track their list of public commitments to their // private secrets. This allows us to verify the share of their private // secret that they send us. commitments map[bls.ID][]bls.PublicKey - // Created locally, these are the t secret components that, combined, - // represent this group member's share of the group secret key. They are - // publicly committed to via their public keys, which are broadcast to all - // other members. They are used to generate shares of this member's group - // secret key share for other members, which can be verified against the - // public commitments from this member. - secretShares []bls.SecretKey - // Shares of this group member's secret, one per group member. The group - // member generates a share of its own secret as well! Note that a share for - // a given member m is shared privately with that member in the secret - // sharing phase and only shared publicly in the justification phase if this - // member receives an accusation from m in the accusation phase. - shares map[bls.ID]bls.SecretKey - // For each other group member, the share of that member's secret that the - // member sent this member. + // For each other group member m, the share of that member's secret that m + // sent this group member. A share is only added if it is valid; a member + // with no entry for their received share has either not sent their share + // or has sent an invalid share; they are therefore subject to an accusation + // requiring them to reveal their share to all group members. receivedShares map[bls.ID]bls.SecretKey +} + +// JustifyingMember represents a threshold group member that has entered the +// justification phase. In this phase, the member will receive a set of +// accusations broadcast to the group from other members via +// `AddAccusationFromID`. Once all accuations have been received, the member +// provides access to a set of justifications for those accusers via +// `Justifications()`, which should be broadcast to all members. Finally, as +// justifications are received they can be recorded using +// `RecordJustificationFromID`. Once all justifications have been received and +// recorded, call `FinalizeMember()` to get the final `Member`. See [GJKR 99], +// Fig. 2 (c). +type JustifyingMember struct { + SharingMember + + // A list of ids of other group members who have accused this group member + // of sending them an invalid share. + accuserIDs []bls.ID + // A list of ids we are expecting justifications from. + // TODO This needs to track each accusation pair (accuser, accused) so that + // TODO we can make sure at the end we've gone through all of them. + pendingJustificationIDs map[bls.ID]bool +} + +// Member represents a fully initialized threshold group member that is ready to +// participate in group threshold signatures and signature validation. +type Member struct { + JustifyingMember + // Public key for the group; nil if not yet computed. groupPublicKey *bls.PublicKey // This group member's share of the group secret key; nil if not yet // computed. groupSecretKeyShare *bls.SecretKey - // A list of ids of other group members who have accused this group member - // of sending them an invalid share. - accuserIDs []bls.ID - // Received via broadcast, tracks IDs of all members who accused this - // member so their shares can be broadcast publicly during justification. - accusedIDs map[bls.ID]bool - // A (spares) map from the ID of other group members to a boolean indicating - // if they were disqualified. Players are disqualified when they are accused - // and then broadcast a justification that fails to verify against their - // public commitments. - disqualifiedPlayers map[bls.ID]bool // all players disqualified during justification // The final list of qualified group members; empty if not yet computed. - qualifiedPlayers []bls.ID + qualifiedMembers []bls.ID } -// NewMember creates a new member with the given id. The id should be a base-10 -// string and is encoded into a bls.ID for use with the built-in secret sharing. -// The id should be unique per group member. -func NewMember(id string) Member { +// NewMember creates a new member with the given id for a threshold group with +// the given threshold. The id should be a base-10 string and is encoded into a +// bls.ID for use with the built-in secret sharing. The id should be unique per +// group member. +// +// Note that the returned member is not initialized; you will need to call +// `Initialize` on it once the full list of member IDs for the group is available, +// at which time it will be promoted to an `InitializedMember`. +func NewMember(id string, threshold int) LocalMember { blsID := bls.ID{} blsID.SetDecString(id) + // Note: bls.SecretKey, before we call some sort of `Set` on it, can be + // considered a zeroed *container* for a secret key. + // + // - `SetByCSPRNG` initializes the zeroed secret key from a + // cryptographically secure pseudo-random number generator. + // - `Set` instead initializes a key from an existing set of shares and a + // group member bls.ID. + secretShares := make([]bls.SecretKey, threshold) + shareCommitments := make([]bls.PublicKey, threshold) + + // Commitmnent to s is E_0 = E(s, t) = g^s·h^t. + // E_i = E(F_i, G_i) + // F_i = coefficient i in F(x) = s + F_1·x + F_2·x^2 + ... + F_{k-1}·x^{k-1} + // s_i = F(i) + // G_i = coefficient i in G(x) = t + G_1·x + G_2·x^2 + ... + G_{k-1}·x^{k-1} + // t_i = G(i) + // Broadcast commitment is E_i = E(F_i, G_i) for i = 1, ..., k - 1 + // + // [GJKR 99], Fig 2, 1(a). + // For this dealer, i, we generate t secret keys, which are equivalent to t + // coefficients a_ik and b_ik, k in [0,t], in two polynomials A and B, + // and store them in secretShares. We also generate the equivalent public + // keys, C_ik = g^{a_ik}·h^{b_ik} mod p, which are stored as the commitments + // to those shares. + for i := 0; i < threshold; i++ { + secretShares[i].SetByCSPRNG() + + // The public keys for each share of this group member's secret key + // represent a public commitment to the underlying secret key shares. + // Another member cannot get the secret key or secret key shares from + // the public keys, but they can use them to verify that the shares of + // the group secret key sent from this member were validly generated + // from the same secret data. + shareCommitments[i] = *secretShares[i].GetPublicKey() + } + + return LocalMember{ + ID: id, + blsID: blsID, + secretShares: secretShares, + shareCommitments: shareCommitments, + } + // receivedShares: map[bls.ID]bls.SecretKey{}, + // accusedIDs: map[bls.ID]bool{}, + // disqualifiedPlayers: map[bls.ID]bool{}, + // } +} + +// InitializeSharing initializes a LocalMember with a list of the memberIDs of +// all members in the threshold group it is operating in, producing a +// SharingMember ready to participate in secret sharing. +func (member *LocalMember) InitializeSharing(otherMemberIDs []bls.ID) SharingMember { + memberIDs := append(otherMemberIDs, member.blsID) + + // [GJKR 99], Fig 2, 1(a). + // For each member (including the caller!), we create a share from our set + // of secret shares (that is, our polynomials). Equivalent to (s_ij, s'_ij), + // but carried in the envelope of a bls.SecretKey (similar to (a_ik, b_ik)). + shares := make(map[bls.ID]bls.SecretKey) + for _, memberID := range memberIDs { + memberShare := bls.SecretKey{} + memberShare.Set(member.secretShares, &memberID) + shares[memberID] = memberShare + } + + return SharingMember{ + LocalMember: *member, + memberIDs: memberIDs, + memberShares: shares, + commitments: make(map[bls.ID][]bls.PublicKey), + receivedShares: make(map[bls.ID]bls.SecretKey), + } +} + +// Commitments returns the `threshold` public commitments this group member has +// generated corresponding to the `threshold` shares of its secret key. +func (member LocalMember) Commitments() []bls.PublicKey { + return member.shareCommitments +} + +// SecretShareForID returns the secret share this member has generated for the +// given `memberID`. +func (member *SharingMember) SecretShareForID(memberID bls.ID) bls.SecretKey { + return member.memberShares[memberID] +} + +// AddCommitmentsFromID associates the given commitments with the given +// memberID. These will later be used to verify the validity of the member +// shares sent by the member with that id. +func (member *SharingMember) AddCommitmentsFromID(memberID bls.ID, commitments []bls.PublicKey) { + member.commitments[memberID] = commitments +} + +// AddShareFromID associates the given secret share with the given `senderID`, +// if and only if the share is valid with respect to the public commitments the +// sharing member gave. +func (member *SharingMember) AddShareFromID(senderID bls.ID, share bls.SecretKey) { + if member.isValidShare(senderID, share) { + member.receivedShares[senderID] = share + } +} + +// Check whether the given share is valid with respect to the sender's public +// commitvments as seen by this member. +func (member SharingMember) isValidShare(shareSenderID bls.ID, share bls.SecretKey) bool { + commitments := member.commitments[shareSenderID] + + combinedCommitment := bls.PublicKey{} + combinedCommitment.Set(commitments, &member.blsID) + + comparisonShare := share.GetPublicKey() + + return combinedCommitment.IsEqual(comparisonShare) +} + +// AccusedIDs returns the list of member IDs that this member will accuse. These +// are the members who have either not sent their shares to this group member, +// or who sent their shares but the shares were invalid with respect to their +// public commitments. +func (member SharingMember) AccusedIDs() []bls.ID { + accusedIDs := make([]bls.ID, 0, len(member.memberIDs)-len(member.receivedShares)) + for _, memberID := range member.memberIDs { + if _, found := member.receivedShares[memberID]; !found { + accusedIDs = append(accusedIDs, memberID) + } + } + + return accusedIDs +} + +// InitializeJustification switches a member from sharing mode to justifying +// mode. +func (member SharingMember) InitializeJustification() JustifyingMember { + return JustifyingMember{ + member, + make([]bls.ID, 0), + make(map[bls.ID]bool), + } +} + +// AddAccusationFromID registers an accusation sent by the member with the given +// `senderID` against the member with id `accusedID`, claiming the accused sent +// an invalid share to the sender. +func (member *JustifyingMember) AddAccusationFromID(senderID bls.ID, accusedID bls.ID) { + if accusedID.IsEqual(&member.blsID) { + member.accuserIDs = append(member.accuserIDs, senderID) + } else { + member.pendingJustificationIDs[senderID] = true + } +} + +// Justifications returns a map from accuser ID to their secret share that is +// to be broadcast to justify against an accusation. A given accuser will have +// accused this member of providing an invalid secret share with respect to this +// member's public commitments, and this justification publishes that share for +// all other members to verify against the same public commitments. +func (member JustifyingMember) Justifications() map[bls.ID]bls.SecretKey { + justifications := make(map[bls.ID]bls.SecretKey, len(member.accuserIDs)) + for _, accuserID := range member.accuserIDs { + justifications[accuserID] = member.memberShares[accuserID] + } + return justifications +} + +// RecordJustificationFromID records, from this member's perspective, a +// justification from accusedID regarding an accusation from accuserID, in the +// form of the secretShare that was privately exchanged between accusedID and +// accuserID. +func (member *JustifyingMember) RecordJustificationFromID(accusedID bls.ID, accuserID bls.ID, secretShare bls.SecretKey) { + if !member.isValidShare(accusedID, secretShare) { + // If the member broadcast an invalid justification, we immediately + // remove them from our shares as they have proven dishonest. + delete(member.receivedShares, accusedID) + } else { + delete(member.pendingJustificationIDs, accusedID) + + if accuserID.IsEqual(&member.blsID) { + // If we originally accused, and the justification is valid, then we can + // add the valid entry to our received shares. + member.receivedShares[accuserID] = secretShare + } + } +} + +// FinalizeMember initializes a member that has finished the justification phase +// into a fully functioning Member that knows the group public key and can sign +// with a share of the private key. +func (member JustifyingMember) FinalizeMember() Member { + // [GJKR 99], Fig 2, 3 + initialShare := member.receivedShares[member.blsID] + groupSecretKeyShare := &initialShare + for id, share := range member.receivedShares { + if !id.IsEqual(&member.blsID) { + groupSecretKeyShare.Add(&share) + } + } + + // [GJKR 99], Fig 2, 4(c)? There is an accusation flow around public key + // computation as well... + combinedCommitments := make([]bls.PublicKey, len(member.commitments[member.blsID])) + for i, commitment := range member.commitments[member.blsID] { + combinedCommitments[i] = commitment + } + for id, commitmentSet := range member.commitments { + if !id.IsEqual(&member.blsID) { // we handled this above + for i, commitment := range commitmentSet { + combinedCommitments[i].Add(&commitment) + } + } + } + + // Qualified players are the players who ended up with entries in + // receivedShares; other players were removed. + // TODO Take into account players who failed to justify against an + // TODO observed accusation. + qualifiedMembers := make([]bls.ID, 0, len(member.receivedShares)) + for memberID := range member.receivedShares { + qualifiedMembers = append(qualifiedMembers, memberID) + } + return Member{ - ID: id, - blsID: blsID, - commitments: map[bls.ID][]bls.PublicKey{}, - shares: map[bls.ID]bls.SecretKey{}, - receivedShares: map[bls.ID]bls.SecretKey{}, - accusedIDs: map[bls.ID]bool{}, - disqualifiedPlayers: map[bls.ID]bool{}, + JustifyingMember: member, + groupSecretKeyShare: groupSecretKeyShare, + groupPublicKey: &combinedCommitments[0], + qualifiedMembers: qualifiedMembers, } } From 2e4736a1364d7ba2d7f748a3be8ac0d8e477e3fa Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 17:46:53 -0500 Subject: [PATCH 05/44] Add constructor functions for broadcast and private messages This starts the formalization of distinctions between these two. Eventually private messages will need to encrypt, for example. --- go/beacon/broadcast/broadcast.go | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/go/beacon/broadcast/broadcast.go b/go/beacon/broadcast/broadcast.go index ce1c14866f..b2b402a16e 100644 --- a/go/beacon/broadcast/broadcast.go +++ b/go/beacon/broadcast/broadcast.go @@ -1,14 +1,32 @@ package broadcast -import "github.com/dfinity/go-dfinity-crypto/bls" +import ( + "github.com/dfinity/go-dfinity-crypto/bls" +) // Message represents a message to communicate over a broadcast channel. // TODO Combine with Raghav's work on protobuf messages. type Message struct { - sender bls.ID - receiver bls.ID - encrypted bool - data interface{} + Sender bls.ID + Receiver *bls.ID // pointer so it can be nil for broadcast messages + Data interface{} +} + +// NewBroadcastMessage creates a new message from the given sender, carrying the +// given data payload, meant for broacast into a channel watched by others. The +// message is signed, but not encrypted. +func NewBroadcastMessage(sender bls.ID, data interface{}) Message { + // FIXME Sign, will require private key... + return Message{sender, nil, data} +} + +// NewPrivateMessage creates a new private message from the given sender to the +// given receiver, carrying the given data payload, meant for broacast into a +// channel watched by others. The message is signed and encrypted. +func NewPrivateMessage(sender bls.ID, receiver bls.ID, data interface{}) Message { + // FIXME Actually encrypt here... Will require a key, best taken from + // FIXME chain... + return Message{sender, &receiver, data} } // Channel represents a named broadcast channel. It allows consumers to send From 7d2c9288b61af45682e8a3af16082db66054bb61 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 17:47:41 -0500 Subject: [PATCH 06/44] Local channel sends messages in goroutines This allows message sends to not block the caller, so that the caller can move on to receiving from other participants. --- go/beacon/broadcast/broadcast.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/beacon/broadcast/broadcast.go b/go/beacon/broadcast/broadcast.go index b2b402a16e..008aa1d1f6 100644 --- a/go/beacon/broadcast/broadcast.go +++ b/go/beacon/broadcast/broadcast.go @@ -51,7 +51,7 @@ func (channel *localChannel) Name() string { func (channel *localChannel) Send(message Message) bool { for _, recvChan := range channel.recvChans { - recvChan <- message + go func(c chan Message) { c <- message }(recvChan) } return true From 843c7f0e89b9f40474a81ec26156b8af2ed51daf Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 17:49:22 -0500 Subject: [PATCH 07/44] Make member BLS ID publicly accessible. There are a few places where we need it for filtering, though it's very likely these will be unnecessary if our underlying channels give us a way to not receive our own messages. --- go/thresholdgroup/member.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go index 5f59d99292..ddc1fb0cf4 100644 --- a/go/thresholdgroup/member.go +++ b/go/thresholdgroup/member.go @@ -16,7 +16,7 @@ type LocalMember struct { // ID of this group member. ID string // The BLS ID of this group member, computed from the ID. - blsID bls.ID + BlsID bls.ID // The threshold of group members who must be honest in order for the // generated key to be uncompromised. Corresponds to the number of secret // shares and public commitments of this group member. @@ -162,7 +162,7 @@ func NewMember(id string, threshold int) LocalMember { return LocalMember{ ID: id, - blsID: blsID, + BlsID: blsID, secretShares: secretShares, shareCommitments: shareCommitments, } @@ -176,7 +176,7 @@ func NewMember(id string, threshold int) LocalMember { // all members in the threshold group it is operating in, producing a // SharingMember ready to participate in secret sharing. func (member *LocalMember) InitializeSharing(otherMemberIDs []bls.ID) SharingMember { - memberIDs := append(otherMemberIDs, member.blsID) + memberIDs := append(otherMemberIDs, member.BlsID) // [GJKR 99], Fig 2, 1(a). // For each member (including the caller!), we create a share from our set @@ -232,7 +232,7 @@ func (member SharingMember) isValidShare(shareSenderID bls.ID, share bls.SecretK commitments := member.commitments[shareSenderID] combinedCommitment := bls.PublicKey{} - combinedCommitment.Set(commitments, &member.blsID) + combinedCommitment.Set(commitments, &member.BlsID) comparisonShare := share.GetPublicKey() @@ -268,7 +268,7 @@ func (member SharingMember) InitializeJustification() JustifyingMember { // `senderID` against the member with id `accusedID`, claiming the accused sent // an invalid share to the sender. func (member *JustifyingMember) AddAccusationFromID(senderID bls.ID, accusedID bls.ID) { - if accusedID.IsEqual(&member.blsID) { + if accusedID.IsEqual(&member.BlsID) { member.accuserIDs = append(member.accuserIDs, senderID) } else { member.pendingJustificationIDs[senderID] = true @@ -300,7 +300,7 @@ func (member *JustifyingMember) RecordJustificationFromID(accusedID bls.ID, accu } else { delete(member.pendingJustificationIDs, accusedID) - if accuserID.IsEqual(&member.blsID) { + if accuserID.IsEqual(&member.BlsID) { // If we originally accused, and the justification is valid, then we can // add the valid entry to our received shares. member.receivedShares[accuserID] = secretShare @@ -313,10 +313,10 @@ func (member *JustifyingMember) RecordJustificationFromID(accusedID bls.ID, accu // with a share of the private key. func (member JustifyingMember) FinalizeMember() Member { // [GJKR 99], Fig 2, 3 - initialShare := member.receivedShares[member.blsID] + initialShare := member.receivedShares[member.BlsID] groupSecretKeyShare := &initialShare for id, share := range member.receivedShares { - if !id.IsEqual(&member.blsID) { + if !id.IsEqual(&member.BlsID) { groupSecretKeyShare.Add(&share) } } From b94058d4b46b02dedf9def1f5ffcc81395cc4d57 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 17:50:06 -0500 Subject: [PATCH 08/44] Set BLS id from a hex string in member instantiation --- go/thresholdgroup/member.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go index ddc1fb0cf4..fdd7d7c136 100644 --- a/go/thresholdgroup/member.go +++ b/go/thresholdgroup/member.go @@ -122,7 +122,7 @@ type Member struct { // at which time it will be promoted to an `InitializedMember`. func NewMember(id string, threshold int) LocalMember { blsID := bls.ID{} - blsID.SetDecString(id) + blsID.SetHexString(id) // Note: bls.SecretKey, before we call some sort of `Set` on it, can be // considered a zeroed *container* for a secret key. From 516e738733bb7fe6306a6bcfe46429bab9d5ca5f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 17:50:24 -0500 Subject: [PATCH 09/44] Properly capture threshold in member constructor We were forgetting to set it, so even though we took it into the function we never propagated it to the member. We'll be using this for better public key creation once the DKG process is done. --- go/thresholdgroup/member.go | 1 + 1 file changed, 1 insertion(+) diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go index fdd7d7c136..e3f0ff8882 100644 --- a/go/thresholdgroup/member.go +++ b/go/thresholdgroup/member.go @@ -163,6 +163,7 @@ func NewMember(id string, threshold int) LocalMember { return LocalMember{ ID: id, BlsID: blsID, + threshold: threshold, secretShares: secretShares, shareCommitments: shareCommitments, } From 140a5374736682194f60a3b36dbe31f91cc6cba5 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 17:51:16 -0500 Subject: [PATCH 10/44] Provide access to member IDs excluding the current member OtherMemberIDs is just a convenient way to get the list of group member IDs other than a member's own ID, which we use when computing and shipping out private shares. --- go/thresholdgroup/member.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go index e3f0ff8882..6de552ab05 100644 --- a/go/thresholdgroup/member.go +++ b/go/thresholdgroup/member.go @@ -205,6 +205,17 @@ func (member LocalMember) Commitments() []bls.PublicKey { return member.shareCommitments } +func (member SharingMember) OtherMemberIDs() []bls.ID { + otherIDs := make([]bls.ID, 0, len(member.memberIDs)-1) + for _, memberID := range member.memberIDs { + if !memberID.IsEqual(&member.BlsID) { + otherIDs = append(otherIDs, memberID) + } + } + + return otherIDs +} + // SecretShareForID returns the secret share this member has generated for the // given `memberID`. func (member *SharingMember) SecretShareForID(memberID bls.ID) bls.SecretKey { From cb13084f7ce1cf535a494f55332603d52a94f79f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 17:52:16 -0500 Subject: [PATCH 11/44] Add methods to know when we have all expected bits of a given type Without these methods we don't have a way externally of knowing that the member no longer needs to receive additional shares or commitments. --- go/thresholdgroup/member.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go index 6de552ab05..e84ec2d7bf 100644 --- a/go/thresholdgroup/member.go +++ b/go/thresholdgroup/member.go @@ -229,6 +229,12 @@ func (member *SharingMember) AddCommitmentsFromID(memberID bls.ID, commitments [ member.commitments[memberID] = commitments } +// CommitmentsComplete returns true if all commitments expected by this member +// have been seen, false otherwise. +func (member SharingMember) CommitmentsComplete() bool { + return len(member.commitments) == len(member.memberIDs)-1 +} + // AddShareFromID associates the given secret share with the given `senderID`, // if and only if the share is valid with respect to the public commitments the // sharing member gave. @@ -238,6 +244,13 @@ func (member *SharingMember) AddShareFromID(senderID bls.ID, share bls.SecretKey } } +// SharesComplete returns true if all shares expected by this member have been +// seen, false otherwise. +func (member SharingMember) SharesComplete() bool { + // FIXME If a member sent an invalid share, we'll never hit the right len. + return len(member.receivedShares) == len(member.memberIDs)-1 +} + // Check whether the given share is valid with respect to the sender's public // commitvments as seen by this member. func (member SharingMember) isValidShare(shareSenderID bls.ID, share bls.SecretKey) bool { From dd1f7083fced00f7ea7c1781388c95e78076916c Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 17:54:14 -0500 Subject: [PATCH 12/44] Don't accidentally accuse ourselves We were accidentally checking ourselves for received shares while deciding who to accuse. Of course we didn't receive shares for ourselves -- we simply have them available. --- go/thresholdgroup/member.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go index e84ec2d7bf..b264228eee 100644 --- a/go/thresholdgroup/member.go +++ b/go/thresholdgroup/member.go @@ -270,7 +270,7 @@ func (member SharingMember) isValidShare(shareSenderID bls.ID, share bls.SecretK // public commitments. func (member SharingMember) AccusedIDs() []bls.ID { accusedIDs := make([]bls.ID, 0, len(member.memberIDs)-len(member.receivedShares)) - for _, memberID := range member.memberIDs { + for _, memberID := range member.OtherMemberIDs() { if _, found := member.receivedShares[memberID]; !found { accusedIDs = append(accusedIDs, memberID) } From 33b1378a6bbcd9df560d343055b6842bbaa04982 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 17:58:23 -0500 Subject: [PATCH 13/44] Fix combined commitment computation We were expecting to find this member's commitments in the commitments map, but they are tracked separately. We also change the combined commitments allocation to reference the threshold, since this is most directly correct. --- go/thresholdgroup/member.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go index b264228eee..287c82610f 100644 --- a/go/thresholdgroup/member.go +++ b/go/thresholdgroup/member.go @@ -348,15 +348,13 @@ func (member JustifyingMember) FinalizeMember() Member { // [GJKR 99], Fig 2, 4(c)? There is an accusation flow around public key // computation as well... - combinedCommitments := make([]bls.PublicKey, len(member.commitments[member.blsID])) - for i, commitment := range member.commitments[member.blsID] { + combinedCommitments := make([]bls.PublicKey, member.threshold) + for i, commitment := range member.shareCommitments { combinedCommitments[i] = commitment } - for id, commitmentSet := range member.commitments { - if !id.IsEqual(&member.blsID) { // we handled this above - for i, commitment := range commitmentSet { - combinedCommitments[i].Add(&commitment) - } + for _, commitmentSet := range member.commitments { + for i, commitment := range commitmentSet { + combinedCommitments[i].Add(&commitment) } } From 6a8fe198ce737f0f49957d0d388194c9c90e9574 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 18:00:31 -0500 Subject: [PATCH 14/44] Add two chain stubs, BeaconConfig and BlockCounter BeaconConfig is a stub for getting the current configuration of the relay beacon from the chain. BlockCounter is a stub for setting up notifications of when a certain number of blocks has been observed. --- go/beacon/chain/chain.go | 83 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 go/beacon/chain/chain.go diff --git a/go/beacon/chain/chain.go b/go/beacon/chain/chain.go new file mode 100644 index 0000000000..f6bf38bc48 --- /dev/null +++ b/go/beacon/chain/chain.go @@ -0,0 +1,83 @@ +package chain + +import ( + "sync" + "time" +) + +type BlockCounter interface { + // WaitForBlocks blocks at the caller until numBlocks new blocks have been + // seen. + WaitForBlocks(numBlocks int) + // BlockWaiter returns a channel that will emit the current block height + // after the given number of blocks has elapsed and then immediately close. + BlockWaiter(numBlocks int) <-chan int +} + +type localBlockCounter struct { + blockHeight int + heightMutex sync.Mutex + waiters map[int][]chan int +} + +func (counter *localBlockCounter) WaitForBlocks(numBlocks int) { + waiter := counter.BlockWaiter(numBlocks) + <-waiter + return +} + +func (counter *localBlockCounter) BlockWaiter(numBlocks int) <-chan int { + newWaiter := make(chan int) + + counter.heightMutex.Lock() + defer counter.heightMutex.Unlock() + notifyBlockHeight := counter.blockHeight + numBlocks + + if notifyBlockHeight == counter.blockHeight { + newWaiter <- notifyBlockHeight + } else { + waiterList, exists := counter.waiters[notifyBlockHeight] + if !exists { + waiterList = make([]chan int, 0) + counter.waiters[notifyBlockHeight] = waiterList + } + + counter.waiters[notifyBlockHeight] = append(waiterList, newWaiter) + } + + return newWaiter +} + +func (counter *localBlockCounter) count() { + ticker := time.NewTicker(time.Duration(time.Second / 2)) + + for _ = range ticker.C { + counter.heightMutex.Lock() + counter.blockHeight++ + waiters, exists := counter.waiters[counter.blockHeight] + if exists { + for _, waiter := range waiters { + waiter <- counter.blockHeight + } + delete(counter.waiters, counter.blockHeight) + } + counter.heightMutex.Unlock() + } +} + +func LocalBlockCounter() BlockCounter { + counter := localBlockCounter{blockHeight: 0, waiters: make(map[int][]chan int)} + + go counter.count() + + return &counter +} + +type BeaconConfig struct { + GroupSize int + Threshold int +} + +func GetBeaconConfig() BeaconConfig { + return BeaconConfig{10, 4} +} From 8522f27fd45f89983538fae3ad55ded782315282 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 18:01:26 -0500 Subject: [PATCH 15/44] Add DKG package and procedure DKG.Execute takes a block counter, broadcast channel, group size, and threshold, and attempts to initialize a single member in a threshold group. Multiple DKG.Execute calls executed in parallel, combined with a local broadcast channel, will result in a local DKG process occurring. This gives us the infrastructure to do a local non-networked test of DKG, with interface stubs to interact with a networked version as well. Note that currently the DKG relies entirely on block timeouts for phase synchronization. We'll need to consider whether that's necessary throughout. --- go/beacon/dkg/dkg.go | 283 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 go/beacon/dkg/dkg.go diff --git a/go/beacon/dkg/dkg.go b/go/beacon/dkg/dkg.go new file mode 100644 index 0000000000..3613b1f554 --- /dev/null +++ b/go/beacon/dkg/dkg.go @@ -0,0 +1,283 @@ +package dkg + +import ( + "fmt" + + "github.com/dfinity/go-dfinity-crypto/bls" + "github.com/dfinity/go-dfinity-crypto/rand" + "github.com/keep-network/keep-core/go/beacon/broadcast" + "github.com/keep-network/keep-core/go/beacon/chain" + "github.com/keep-network/keep-core/go/thresholdgroup" +) + +// JoinMessage is an empty message payload indicating a member has joined. The +// sender is the joining member. It is expected to be broadcast. +type JoinMessage struct{} + +// MemberCommitmentsMessage is a message payload that carries the sender's +// public commitments during distributed key generation. It is expected to be +// broadcast. +type MemberCommitmentsMessage struct { + Commitments []bls.PublicKey +} + +// MemberShareMessage is a message payload that carries the sender's private +// share for the recipient during distributed key generation. It is expected to +// be communicated in encrypted fashion to the recipient over a broadcast +// channel. +type MemberShareMessage struct { + Share bls.SecretKey +} + +// AccusationsMessage is a message payload that carries all of the sender's +// accusations against other members of the threshold group. If all other +// members behaved honestly from the sender's point of view, this message should +// be broadcast but with an empty slice of `accusedIDs`. It is expected to be +// broadcast. +type AccusationsMessage struct { + accusedIDs []bls.ID +} + +// JustificationsMessage is a message payload that carries all of the sender's +// justifications in response to other threshold group members' accusations. If +// no other member accused the sender, this message should be broadcast but with +// an empty map of `justifications`. It is expected to be broadcast. +type JustificationsMessage struct { + justifications map[bls.ID]bls.SecretKey +} + +// Execute runs the full distributed key generation lifecycle, given a broadcast +// channel to mediate it and a group size and threshold. It returns a threshold +// group member who is participating in the group if the generation was +// successful, and an error representing what went wrong if not. +func Execute(blockCounter chain.BlockCounter, channel broadcast.Channel, groupSize int, threshold int) (*thresholdgroup.Member, error) { + // FIXME Probably pass in a way to ask for a receiver's public key? + // FIXME Need a way to time out in a given stage, especially the waiting + // ones. + + memberID := rand.NewRand().String() + fmt.Printf("[member:%v] Initializing member.\n", memberID) + localMember := thresholdgroup.NewMember(memberID, threshold) + + recvChan := channel.RecvChan() + + fmt.Printf("[member:%v] Waiting for join timeout...\n", memberID) + blockCounter.WaitForBlocks(5) + + fmt.Printf("[member:%v] Broadcasting join.\n", memberID) + channel.Send(broadcast.NewBroadcastMessage(localMember.BlsID, JoinMessage{})) + + // Wait for all members. + waiter := blockCounter.BlockWaiter(3) + fmt.Printf("[member:%v] Waiting for other members...\n", memberID) + memberIDs, err := waitForMemberIDs(&localMember.BlsID, recvChan, groupSize) + if err != nil { + return nil, fmt.Errorf("failed to receive all member ids: [%v]", err) + } + + fmt.Printf("[member:%v] Waiting for member join timeout...\n", memberID) + <-waiter + + waiter = blockCounter.BlockWaiter(3) + fmt.Printf("[member:%v] Initiating commitment broadcast phase.\n", memberID) + sharingMember := localMember.InitializeSharing(memberIDs) + + fmt.Printf("[member:%v] Broadcasting public commitment.\n", memberID) + err = sendCommitments(channel, &sharingMember) + if err != nil { + return nil, fmt.Errorf("failed to broadcast commitments: [%v]", err) + } + + fmt.Printf("[member:%v] Waiting for other commitments...\n", memberID) + err = waitForCommitments(&localMember.BlsID, recvChan, &sharingMember) + if err != nil { + return nil, fmt.Errorf("failed to receive all commitments: [%v]", err) + } + + fmt.Printf("[member:%v] Waiting for commitment timeout...\n", memberID) + <-waiter + + waiter = blockCounter.BlockWaiter(5) + fmt.Printf("[member:%v] Sending private shares.\n", memberID) + err = sendShares(channel, &sharingMember) + if err != nil { + return nil, fmt.Errorf("failed to send all private shares: [%v]", err) + } + + fmt.Printf("[member:%v] Waiting for other shares...\n", memberID) + err = waitForShares(&sharingMember.BlsID, recvChan, &sharingMember) + if err != nil { + return nil, fmt.Errorf("failed to receive all private shares: [%v]", err) + } + + fmt.Printf("[member:%v] Waiting for share exchange timeout...\n", memberID) + <-waiter + + waiter = blockCounter.BlockWaiter(3) + fmt.Printf("[member:%v] Initiating accusation/justification phase.\n", memberID) + justifyingMember := sharingMember.InitializeJustification() + fmt.Printf("[member:%v] Broadcasting accusations.\n", memberID) + err = sendAccusations(channel, &justifyingMember) + if err != nil { + return nil, fmt.Errorf("failed to broadcast accusations: [%v]", err) + } + + fmt.Printf("[member:%v] Waiting for other accusations...\n", memberID) + err = waitForAccusations(recvChan, &justifyingMember) + if err != nil { + return nil, fmt.Errorf("failed to receive all accusations: [%v]", err) + } + + fmt.Printf("[member:%v] Waiting for accusation timeout...\n", memberID) + <-waiter + + fmt.Printf("[member:%v] Broadcasting justifications.\n", memberID) + err = sendJustifications(channel, &justifyingMember) + if err != nil { + return nil, fmt.Errorf("failed to broadcast justifications: [%v]", err) + } + + fmt.Printf("[member:%v] Waiting for other justifications...\n", memberID) + err = waitForJustifications(recvChan, &justifyingMember) + if err != nil { + return nil, fmt.Errorf("failed to receive all justifications: [%v]", err) + } + + fmt.Printf("[member:%v] Finalizing member.\n", memberID) + member := justifyingMember.FinalizeMember() + return &member, nil +} + +func waitForMemberIDs(myID *bls.ID, recvChan <-chan broadcast.Message, groupSize int) ([]bls.ID, error) { + memberIDs := make([]bls.ID, 0, groupSize) + +done: + for msg := range recvChan { + switch msg.Data.(type) { + case JoinMessage: + if msg.Sender.IsEqual(myID) { + continue + } + + memberIDs = append(memberIDs, msg.Sender) + + if len(memberIDs) == groupSize-1 { + break done + } + } + } + + return memberIDs, nil +} + +func sendCommitments(channel broadcast.Channel, member *thresholdgroup.SharingMember) error { + channel.Send(broadcast.NewBroadcastMessage(member.BlsID, MemberCommitmentsMessage{member.Commitments()})) + + return nil +} + +func waitForCommitments(myID *bls.ID, recvChan <-chan broadcast.Message, sharingMember *thresholdgroup.SharingMember) error { +done: + for msg := range recvChan { + switch commitmentMsg := msg.Data.(type) { + case MemberCommitmentsMessage: + if msg.Sender.IsEqual(myID) { + continue + } + + sharingMember.AddCommitmentsFromID(msg.Sender, commitmentMsg.Commitments) + + if sharingMember.CommitmentsComplete() { + break done + } + } + } + + return nil +} + +func sendShares(channel broadcast.Channel, member *thresholdgroup.SharingMember) error { + for _, receiverID := range member.OtherMemberIDs() { + share := member.SecretShareForID(receiverID) + fmt.Printf("[member:%v] Despatching a share!\n", member.ID) + channel.Send(broadcast.NewPrivateMessage(member.BlsID, receiverID, MemberShareMessage{share})) + } + + return nil +} + +func waitForShares(myID *bls.ID, recvChan <-chan broadcast.Message, sharingMember *thresholdgroup.SharingMember) error { +done: + for msg := range recvChan { + switch shareMsg := msg.Data.(type) { + case MemberShareMessage: + if msg.Receiver.IsEqual(myID) { + fmt.Printf("[member:%v] Received one id from [%v].\n", myID.GetHexString(), msg.Sender.GetHexString()) + sharingMember.AddShareFromID(msg.Sender, shareMsg.Share) + + if sharingMember.SharesComplete() { + break done + } + } + } + } + + return nil +} + +func sendAccusations(channel broadcast.Channel, member *thresholdgroup.JustifyingMember) error { + channel.Send(broadcast.NewBroadcastMessage(member.BlsID, AccusationsMessage{member.AccusedIDs()})) + + return nil +} + +func waitForAccusations(recvChan <-chan broadcast.Message, justifyingMember *thresholdgroup.JustifyingMember) error { + memberIDs := justifyingMember.OtherMemberIDs() + seenAccusations := make(map[bls.ID]bool, len(memberIDs)) +done: + for msg := range recvChan { + switch accusationMsg := msg.Data.(type) { + case AccusationsMessage: + for _, accusedID := range accusationMsg.accusedIDs { + justifyingMember.AddAccusationFromID(msg.Sender, accusedID) + } + + seenAccusations[msg.Sender] = true + if len(seenAccusations) == len(memberIDs) { + break done + } + } + } + + return nil +} + +func sendJustifications(channel broadcast.Channel, justifyingMember *thresholdgroup.JustifyingMember) error { + channel.Send( + broadcast.NewBroadcastMessage( + justifyingMember.BlsID, + JustificationsMessage{justifyingMember.Justifications()})) + + return nil +} + +func waitForJustifications(recvChan <-chan broadcast.Message, justifyingMember *thresholdgroup.JustifyingMember) error { + memberIDs := justifyingMember.OtherMemberIDs() + seenJustifications := make(map[bls.ID]bool, len(memberIDs)) +done: + for msg := range recvChan { + switch justificationsMsg := msg.Data.(type) { + case JustificationsMessage: + for accuserID, justification := range justificationsMsg.justifications { + justifyingMember.RecordJustificationFromID(msg.Sender, accuserID, justification) + } + + seenJustifications[msg.Sender] = true + if len(seenJustifications) == len(memberIDs) { + break done + } + } + } + + return nil +} From cd2601702ac2768234aac7e3a0c8c8ee66b2d705 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 18:03:34 -0500 Subject: [PATCH 16/44] Set up main to call DKG with the configured group size and threshold We use a local broadcast channel and a channel for reporting the complete members as they wrap up. No group signing yet though! --- go/main.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/go/main.go b/go/main.go index d4d2afc265..4d6cf4f159 100644 --- a/go/main.go +++ b/go/main.go @@ -2,15 +2,56 @@ package main import ( "fmt" + "os" "github.com/dfinity/go-dfinity-crypto/bls" - "github.com/dfinity/go-dfinity-crypto/rand" + "github.com/keep-network/keep-core/go/beacon/broadcast" + "github.com/keep-network/keep-core/go/beacon/chain" + "github.com/keep-network/keep-core/go/beacon/dkg" + "github.com/keep-network/keep-core/go/thresholdgroup" ) func main() { - bls.Init(bls.CurveFp254BNb) - r := rand.NewRand() - id := bls.ID{} - id.SetHexString(r.String()) - fmt.Printf("%s %v\n", r, id) + bls.Init(bls.CurveFp382_1) + + beaconConfig := chain.GetBeaconConfig() + + channel := broadcast.LocalChannel("test") + chainCounter := chain.LocalBlockCounter() + + members := make([]*thresholdgroup.Member, 0, beaconConfig.GroupSize) + memberChannel := make(chan *thresholdgroup.Member) + for i := 0; i < beaconConfig.GroupSize; i++ { + go func() { + member, err := dkg.Execute(chainCounter, channel, beaconConfig.GroupSize, beaconConfig.Threshold) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to run DKG for member %v: [%s].", i, err) + memberChannel <- nil + return + } + + memberChannel <- member + }() + } + + seenMembers := 0 + for member := range memberChannel { + seenMembers++ + if member != nil { + members = append(members, member) + if len(members) == beaconConfig.GroupSize { + break + } + } + + if seenMembers == beaconConfig.GroupSize { + break + } + } + + if len(members) < beaconConfig.GroupSize { + panic("Failed to reach group size during DKG, aborting.") + } + + fmt.Printf("Members! %v\n", members) } From b8668f775d11bd69baeba65d0acd8f73e50efd71 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 18:32:47 -0500 Subject: [PATCH 17/44] Move DKG into relay instead of its own package Not a huge deal, but don't want a million packages. --- go/beacon/{dkg => relay}/dkg.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename go/beacon/{dkg => relay}/dkg.go (98%) diff --git a/go/beacon/dkg/dkg.go b/go/beacon/relay/dkg.go similarity index 98% rename from go/beacon/dkg/dkg.go rename to go/beacon/relay/dkg.go index 3613b1f554..66b3c7a898 100644 --- a/go/beacon/dkg/dkg.go +++ b/go/beacon/relay/dkg.go @@ -1,4 +1,4 @@ -package dkg +package relay import ( "fmt" @@ -50,7 +50,7 @@ type JustificationsMessage struct { // channel to mediate it and a group size and threshold. It returns a threshold // group member who is participating in the group if the generation was // successful, and an error representing what went wrong if not. -func Execute(blockCounter chain.BlockCounter, channel broadcast.Channel, groupSize int, threshold int) (*thresholdgroup.Member, error) { +func ExecuteDKG(blockCounter chain.BlockCounter, channel broadcast.Channel, groupSize int, threshold int) (*thresholdgroup.Member, error) { // FIXME Probably pass in a way to ask for a receiver's public key? // FIXME Need a way to time out in a given stage, especially the waiting // ones. From c535ee4a5a3d0a83d669bd95a32eb0f52ecb934f Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 19:21:26 -0500 Subject: [PATCH 18/44] Fix group secret key share creation We were expecting the group member's own share to be in the receivedShares map, but it was not. Instead, we have to query the member's secret shares to see the share it generated for itself. Then we add all the received shares. --- go/thresholdgroup/member.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go index 287c82610f..f4226f9a0c 100644 --- a/go/thresholdgroup/member.go +++ b/go/thresholdgroup/member.go @@ -338,12 +338,10 @@ func (member *JustifyingMember) RecordJustificationFromID(accusedID bls.ID, accu // with a share of the private key. func (member JustifyingMember) FinalizeMember() Member { // [GJKR 99], Fig 2, 3 - initialShare := member.receivedShares[member.BlsID] + initialShare := member.SecretShareForID(member.BlsID) groupSecretKeyShare := &initialShare - for id, share := range member.receivedShares { - if !id.IsEqual(&member.BlsID) { - groupSecretKeyShare.Add(&share) - } + for _, share := range member.receivedShares { + groupSecretKeyShare.Add(&share) } // [GJKR 99], Fig 2, 4(c)? There is an accusation flow around public key From 045ce6e37166bf226f1efdac7e7dab499ea64ff3 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 19:22:36 -0500 Subject: [PATCH 19/44] Clarify group publicy key extraction No substantive change, but the explicit assignment makes it clear where the public key is coming from. --- go/thresholdgroup/member.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go index f4226f9a0c..9c95067051 100644 --- a/go/thresholdgroup/member.go +++ b/go/thresholdgroup/member.go @@ -355,6 +355,7 @@ func (member JustifyingMember) FinalizeMember() Member { combinedCommitments[i].Add(&commitment) } } + groupPublicKey := combinedCommitments[0] // Qualified players are the players who ended up with entries in // receivedShares; other players were removed. @@ -368,7 +369,7 @@ func (member JustifyingMember) FinalizeMember() Member { return Member{ JustifyingMember: member, groupSecretKeyShare: groupSecretKeyShare, - groupPublicKey: &combinedCommitments[0], + groupPublicKey: &groupPublicKey, qualifiedMembers: qualifiedMembers, } } From 0aee2d2a814a8d997e831cce99bbd98aa02191c6 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 19:24:08 -0500 Subject: [PATCH 20/44] Add signature share generation and verification thresholdgroup.Member now has SignatureShare(message) to compute its share of a threshold signature, and VerifySignature(shares, message) to take a set of shares and verify that they are a valid signature of the message for this group. The shares are all passed around as byte arrays, so that the details of BLS are tucked away behind everything with the exception of BLS IDs (which we may hide later). --- go/thresholdgroup/member.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go index 9c95067051..b64b3b5e81 100644 --- a/go/thresholdgroup/member.go +++ b/go/thresholdgroup/member.go @@ -333,6 +333,37 @@ func (member *JustifyingMember) RecordJustificationFromID(accusedID bls.ID, accu } } +// SignatureShare returns this member's serialized share of the threshold +// signature for the given message. It can be combined with `threshold` other +// signatures to produce a valid group signature (that is the same no matter +// which other members participate). +func (member Member) SignatureShare(message string) []byte { + return member.groupSecretKeyShare.Sign(message).Serialize() +} + +// VerifySignature takes a message and a set of serialized signature shares by +// member ID, and verifies that the signature shares combine to a group +// signature that is valid for the given message. Returns true if so, false if +// not. +func (member Member) VerifySignature(signatureShares map[bls.ID][]byte, message string) bool { + availableIDs := make([]bls.ID, 0, len(signatureShares)) + deserializedShares := make([]bls.Sign, 0, len(signatureShares)) + for _, memberID := range member.memberIDs { + if serializedShare, found := signatureShares[memberID]; found { + share := bls.Sign{} + share.Deserialize(serializedShare) + + availableIDs = append(availableIDs, memberID) + deserializedShares = append(deserializedShares, share) + } + } + + fullSignature := bls.Sign{} + fullSignature.Recover(deserializedShares, availableIDs) + + return fullSignature.Verify(member.groupPublicKey, message) +} + // FinalizeMember initializes a member that has finished the justification phase // into a fully functioning Member that knows the group public key and can sign // with a share of the private key. From 1ee6a47478b315e0cde96195ca116ecb361a6ee5 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 19:30:22 -0500 Subject: [PATCH 21/44] Whoops, fix reference to ExecuteDKG from dkg to relay package --- go/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/main.go b/go/main.go index 4d6cf4f159..fe145673f6 100644 --- a/go/main.go +++ b/go/main.go @@ -7,7 +7,7 @@ import ( "github.com/dfinity/go-dfinity-crypto/bls" "github.com/keep-network/keep-core/go/beacon/broadcast" "github.com/keep-network/keep-core/go/beacon/chain" - "github.com/keep-network/keep-core/go/beacon/dkg" + "github.com/keep-network/keep-core/go/beacon/relay" "github.com/keep-network/keep-core/go/thresholdgroup" ) @@ -23,7 +23,7 @@ func main() { memberChannel := make(chan *thresholdgroup.Member) for i := 0; i < beaconConfig.GroupSize; i++ { go func() { - member, err := dkg.Execute(chainCounter, channel, beaconConfig.GroupSize, beaconConfig.Threshold) + member, err := relay.ExecuteDKG(chainCounter, channel, beaconConfig.GroupSize, beaconConfig.Threshold) if err != nil { fmt.Fprintf(os.Stderr, "Failed to run DKG for member %v: [%s].", i, err) memberChannel <- nil From e65c985e944124079672a0f6069252b9474144b9 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 19:30:42 -0500 Subject: [PATCH 22/44] Add group signature check to main Notably, this doesn't do anything about thresholds, it does the whole thing. --- go/main.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/go/main.go b/go/main.go index fe145673f6..9ae144c41c 100644 --- a/go/main.go +++ b/go/main.go @@ -53,5 +53,16 @@ func main() { panic("Failed to reach group size during DKG, aborting.") } - fmt.Printf("Members! %v\n", members) + message := "This is a message!" + shares := make(map[bls.ID][]byte, 0) + for _, member := range members { + shares[member.BlsID] = member.SignatureShare(message) + } + + for _, member := range members { + fmt.Printf( + "[member:%v] Did we get it? %v\n", + member.BlsID.GetHexString(), + member.VerifySignature(shares, message)) + } } From cc4102de81e535589e40eb0937423fefbcf447bf Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 21:43:47 -0500 Subject: [PATCH 23/44] Deal with unjustified accused members in a more complete way When we receive an accusation for a member A from another member B, we need to track this fact so that we only consider member A to be a valid member if we receive their justification for member B. We were tracking these at a lower granularity, namely by simply tracking the member as being accused, and accepting any justification from them as validting all accusations against them. We now track the accusations between all member pairs, and record the justifications between member pairs. When we go to finalize the member, we do a final disqualification of all members who were accused without a corresponding valid justification. This yields a correct final list of qualified members. --- go/thresholdgroup/member.go | 43 +++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go index b64b3b5e81..20317f2a36 100644 --- a/go/thresholdgroup/member.go +++ b/go/thresholdgroup/member.go @@ -92,10 +92,8 @@ type JustifyingMember struct { // A list of ids of other group members who have accused this group member // of sending them an invalid share. accuserIDs []bls.ID - // A list of ids we are expecting justifications from. - // TODO This needs to track each accusation pair (accuser, accused) so that - // TODO we can make sure at the end we've gone through all of them. - pendingJustificationIDs map[bls.ID]bool + // A map of accuser IDs to a "set" of the IDs they accused. + pendingJustificationIDs map[bls.ID]map[bls.ID]bool } // Member represents a fully initialized threshold group member that is ready to @@ -285,7 +283,7 @@ func (member SharingMember) InitializeJustification() JustifyingMember { return JustifyingMember{ member, make([]bls.ID, 0), - make(map[bls.ID]bool), + make(map[bls.ID]map[bls.ID]bool), } } @@ -296,7 +294,12 @@ func (member *JustifyingMember) AddAccusationFromID(senderID bls.ID, accusedID b if accusedID.IsEqual(&member.BlsID) { member.accuserIDs = append(member.accuserIDs, senderID) } else { - member.pendingJustificationIDs[senderID] = true + existingAccusedIDs, found := member.pendingJustificationIDs[senderID] + if !found { + existingAccusedIDs = make(map[bls.ID]bool) + member.pendingJustificationIDs[senderID] = existingAccusedIDs + } + existingAccusedIDs[accusedID] = true } } @@ -323,16 +326,34 @@ func (member *JustifyingMember) RecordJustificationFromID(accusedID bls.ID, accu // remove them from our shares as they have proven dishonest. delete(member.receivedShares, accusedID) } else { - delete(member.pendingJustificationIDs, accusedID) + if pendingAccusedIDs, found := member.pendingJustificationIDs[accuserID]; found { + delete(pendingAccusedIDs, accusedID) + if len(pendingAccusedIDs) == 0 { + delete(member.pendingJustificationIDs, accuserID) + } + } if accuserID.IsEqual(&member.BlsID) { - // If we originally accused, and the justification is valid, then we can - // add the valid entry to our received shares. + // If we originally accused, and the justification is valid, then we + // can add the valid entry to our received shares. member.receivedShares[accuserID] = secretShare } } } +func (member *JustifyingMember) deleteUnjustifiedShares() { + // At this point any entry in pendingJustificationIDs is a member who was + // accused but whose justification we did not see. Those members are invalid + // from our perspective. For each accuser that remains, go through the IDs + // they accused. For each of those IDs, clear out their received shares, as + // their failure to justify means they are not eligible players. + for _, accusedIDs := range member.pendingJustificationIDs { + for accusedID := range accusedIDs { + delete(member.receivedShares, accusedID) + } + } +} + // SignatureShare returns this member's serialized share of the threshold // signature for the given message. It can be combined with `threshold` other // signatures to produce a valid group signature (that is the same no matter @@ -368,6 +389,8 @@ func (member Member) VerifySignature(signatureShares map[bls.ID][]byte, message // into a fully functioning Member that knows the group public key and can sign // with a share of the private key. func (member JustifyingMember) FinalizeMember() Member { + member.deleteUnjustifiedShares() + // [GJKR 99], Fig 2, 3 initialShare := member.SecretShareForID(member.BlsID) groupSecretKeyShare := &initialShare @@ -390,8 +413,6 @@ func (member JustifyingMember) FinalizeMember() Member { // Qualified players are the players who ended up with entries in // receivedShares; other players were removed. - // TODO Take into account players who failed to justify against an - // TODO observed accusation. qualifiedMembers := make([]bls.ID, 0, len(member.receivedShares)) for memberID := range member.receivedShares { qualifiedMembers = append(qualifiedMembers, memberID) From 9465aafea61d0221a54a1eeb14f2634cdfbf647c Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Mon, 12 Feb 2018 21:46:37 -0500 Subject: [PATCH 24/44] Move methods for Member to the end of member.go The methods are defined in order from first used to last used during the DKG/relay process. --- go/thresholdgroup/member.go | 62 ++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go index 20317f2a36..b3438f7e76 100644 --- a/go/thresholdgroup/member.go +++ b/go/thresholdgroup/member.go @@ -354,37 +354,6 @@ func (member *JustifyingMember) deleteUnjustifiedShares() { } } -// SignatureShare returns this member's serialized share of the threshold -// signature for the given message. It can be combined with `threshold` other -// signatures to produce a valid group signature (that is the same no matter -// which other members participate). -func (member Member) SignatureShare(message string) []byte { - return member.groupSecretKeyShare.Sign(message).Serialize() -} - -// VerifySignature takes a message and a set of serialized signature shares by -// member ID, and verifies that the signature shares combine to a group -// signature that is valid for the given message. Returns true if so, false if -// not. -func (member Member) VerifySignature(signatureShares map[bls.ID][]byte, message string) bool { - availableIDs := make([]bls.ID, 0, len(signatureShares)) - deserializedShares := make([]bls.Sign, 0, len(signatureShares)) - for _, memberID := range member.memberIDs { - if serializedShare, found := signatureShares[memberID]; found { - share := bls.Sign{} - share.Deserialize(serializedShare) - - availableIDs = append(availableIDs, memberID) - deserializedShares = append(deserializedShares, share) - } - } - - fullSignature := bls.Sign{} - fullSignature.Recover(deserializedShares, availableIDs) - - return fullSignature.Verify(member.groupPublicKey, message) -} - // FinalizeMember initializes a member that has finished the justification phase // into a fully functioning Member that knows the group public key and can sign // with a share of the private key. @@ -425,3 +394,34 @@ func (member JustifyingMember) FinalizeMember() Member { qualifiedMembers: qualifiedMembers, } } + +// SignatureShare returns this member's serialized share of the threshold +// signature for the given message. It can be combined with `threshold` other +// signatures to produce a valid group signature (that is the same no matter +// which other members participate). +func (member Member) SignatureShare(message string) []byte { + return member.groupSecretKeyShare.Sign(message).Serialize() +} + +// VerifySignature takes a message and a set of serialized signature shares by +// member ID, and verifies that the signature shares combine to a group +// signature that is valid for the given message. Returns true if so, false if +// not. +func (member Member) VerifySignature(signatureShares map[bls.ID][]byte, message string) bool { + availableIDs := make([]bls.ID, 0, len(signatureShares)) + deserializedShares := make([]bls.Sign, 0, len(signatureShares)) + for _, memberID := range member.memberIDs { + if serializedShare, found := signatureShares[memberID]; found { + share := bls.Sign{} + share.Deserialize(serializedShare) + + availableIDs = append(availableIDs, memberID) + deserializedShares = append(deserializedShares, share) + } + } + + fullSignature := bls.Sign{} + fullSignature.Recover(deserializedShares, availableIDs) + + return fullSignature.Verify(member.groupPublicKey, message) +} From 85696afa4234076fc81cb0f4eb43144031c39bfc Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 13 Feb 2018 09:22:23 -0500 Subject: [PATCH 25/44] Switch the BLS config we're using to CurveFp254BNb This is what we'll need to work with on-chain implementations, from our current understanding. --- go/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/main.go b/go/main.go index 9ae144c41c..fd2ea43018 100644 --- a/go/main.go +++ b/go/main.go @@ -12,7 +12,7 @@ import ( ) func main() { - bls.Init(bls.CurveFp382_1) + bls.Init(bls.CurveFp254BNb) beaconConfig := chain.GetBeaconConfig() From 15a5f0c6ff276685a406f8011e9c7ba28f1b4b02 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 13 Feb 2018 15:52:19 -0500 Subject: [PATCH 26/44] Add some documentation to chain.go --- go/beacon/chain/chain.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/go/beacon/chain/chain.go b/go/beacon/chain/chain.go index f6bf38bc48..f43eeefe0a 100644 --- a/go/beacon/chain/chain.go +++ b/go/beacon/chain/chain.go @@ -5,6 +5,11 @@ import ( "time" ) +// BlockCounter is an interface that provides the ability to wait for a certain +// number of abstract blocks. It provides for two ways to wait, one blocking and +// one chan-based. Block height is expected to increase monotonically, though +// the time between blocks will depend on the underlying implementation. See +// LocalBlockCounter() for a local implementation. type BlockCounter interface { // WaitForBlocks blocks at the caller until numBlocks new blocks have been // seen. @@ -65,6 +70,9 @@ func (counter *localBlockCounter) count() { } } +// LocalBlockCounter creates a BlockCounter that runs completely locally. It is +// designed to simply increase block height at a set time interval in the +// background. func LocalBlockCounter() BlockCounter { counter := localBlockCounter{blockHeight: 0, waiters: make(map[int][]chan int)} @@ -73,11 +81,15 @@ func LocalBlockCounter() BlockCounter { return &counter } +// BeaconConfig contains configuration for the threshold relay beacon, typically +// from the underlying blockchain. type BeaconConfig struct { GroupSize int Threshold int } +// GetBeaconConfig Get the latest threshold relay beacon configuration. +// TODO Make this actually look up/update from chain information. func GetBeaconConfig() BeaconConfig { return BeaconConfig{10, 4} } From e843d856ce2c240fa2bdf1893fcaf75ba6bbda56 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 13 Feb 2018 15:54:14 -0500 Subject: [PATCH 27/44] Rename localBlockCounter.heightMutex to structMutex heightMutex implies the mutex locks only access to block height information, but in fact the mutex protects access to all struct members. --- go/beacon/chain/chain.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/go/beacon/chain/chain.go b/go/beacon/chain/chain.go index f43eeefe0a..6146d772c5 100644 --- a/go/beacon/chain/chain.go +++ b/go/beacon/chain/chain.go @@ -20,8 +20,8 @@ type BlockCounter interface { } type localBlockCounter struct { + structMutex sync.Mutex blockHeight int - heightMutex sync.Mutex waiters map[int][]chan int } @@ -34,8 +34,8 @@ func (counter *localBlockCounter) WaitForBlocks(numBlocks int) { func (counter *localBlockCounter) BlockWaiter(numBlocks int) <-chan int { newWaiter := make(chan int) - counter.heightMutex.Lock() - defer counter.heightMutex.Unlock() + counter.structMutex.Lock() + defer counter.structMutex.Unlock() notifyBlockHeight := counter.blockHeight + numBlocks if notifyBlockHeight == counter.blockHeight { @@ -57,7 +57,7 @@ func (counter *localBlockCounter) count() { ticker := time.NewTicker(time.Duration(time.Second / 2)) for _ = range ticker.C { - counter.heightMutex.Lock() + counter.structMutex.Lock() counter.blockHeight++ waiters, exists := counter.waiters[counter.blockHeight] if exists { @@ -66,7 +66,7 @@ func (counter *localBlockCounter) count() { } delete(counter.waiters, counter.blockHeight) } - counter.heightMutex.Unlock() + counter.structMutex.Unlock() } } From 68c8f865b9445eb64187938433c243bbd547b916 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 13 Feb 2018 15:55:04 -0500 Subject: [PATCH 28/44] Cover edge cases more clearly when registring a new block waiter --- go/beacon/chain/chain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/beacon/chain/chain.go b/go/beacon/chain/chain.go index 6146d772c5..d5aebaa027 100644 --- a/go/beacon/chain/chain.go +++ b/go/beacon/chain/chain.go @@ -38,7 +38,7 @@ func (counter *localBlockCounter) BlockWaiter(numBlocks int) <-chan int { defer counter.structMutex.Unlock() notifyBlockHeight := counter.blockHeight + numBlocks - if notifyBlockHeight == counter.blockHeight { + if notifyBlockHeight <= counter.blockHeight { newWaiter <- notifyBlockHeight } else { waiterList, exists := counter.waiters[notifyBlockHeight] From b6c1c4c7e82febd3879c66ff8fec8b4c403fa948 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 13 Feb 2018 15:55:33 -0500 Subject: [PATCH 29/44] Drop a spurious assignment We append + assign further down in the function anyway. --- go/beacon/chain/chain.go | 1 - 1 file changed, 1 deletion(-) diff --git a/go/beacon/chain/chain.go b/go/beacon/chain/chain.go index d5aebaa027..90638e5d8f 100644 --- a/go/beacon/chain/chain.go +++ b/go/beacon/chain/chain.go @@ -44,7 +44,6 @@ func (counter *localBlockCounter) BlockWaiter(numBlocks int) <-chan int { waiterList, exists := counter.waiters[notifyBlockHeight] if !exists { waiterList = make([]chan int, 0) - counter.waiters[notifyBlockHeight] = waiterList } counter.waiters[notifyBlockHeight] = append(waiterList, newWaiter) From 37bce336f03d948c188dd6560a94026838d16344 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 13 Feb 2018 15:56:12 -0500 Subject: [PATCH 30/44] Reduce scope of localBlockCounter mutex when incrementing block height We were wrapping the whole tick in the mutex, including waiter notification, but really we can read all the state we need quickly and notify waiters separately. --- go/beacon/chain/chain.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/go/beacon/chain/chain.go b/go/beacon/chain/chain.go index 90638e5d8f..192a981f5c 100644 --- a/go/beacon/chain/chain.go +++ b/go/beacon/chain/chain.go @@ -58,14 +58,16 @@ func (counter *localBlockCounter) count() { for _ = range ticker.C { counter.structMutex.Lock() counter.blockHeight++ - waiters, exists := counter.waiters[counter.blockHeight] + height := counter.blockHeight + waiters, exists := counter.waiters[height] + delete(counter.waiters, height) + counter.structMutex.Unlock() + if exists { for _, waiter := range waiters { waiter <- counter.blockHeight } - delete(counter.waiters, counter.blockHeight) } - counter.structMutex.Unlock() } } From 172c705b21364cfc264d667f293eefa2ee7d0697 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 13 Feb 2018 15:57:18 -0500 Subject: [PATCH 31/44] Notify waiters of a local block height increment in a goroutine This most closely mimics waiting on individual clients, since each one will get its signal when it is ready for it. Without the goroutines, we find ourselves waiting for a slow waiter before notifying other waiters. --- go/beacon/chain/chain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/beacon/chain/chain.go b/go/beacon/chain/chain.go index 192a981f5c..3e573060da 100644 --- a/go/beacon/chain/chain.go +++ b/go/beacon/chain/chain.go @@ -65,7 +65,7 @@ func (counter *localBlockCounter) count() { if exists { for _, waiter := range waiters { - waiter <- counter.blockHeight + go func(w chan int) { w <- height }(waiter) } } } From af952f5d709cfb6344e4cae7b8d758674fdb1599 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Tue, 13 Feb 2018 16:11:23 -0500 Subject: [PATCH 32/44] Burn some commented out code --- go/beacon/broadcast/broadcast.go | 8 +++++--- go/thresholdgroup/member.go | 4 ---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/go/beacon/broadcast/broadcast.go b/go/beacon/broadcast/broadcast.go index 008aa1d1f6..7aa9db01f1 100644 --- a/go/beacon/broadcast/broadcast.go +++ b/go/beacon/broadcast/broadcast.go @@ -50,9 +50,11 @@ func (channel *localChannel) Name() string { } func (channel *localChannel) Send(message Message) bool { - for _, recvChan := range channel.recvChans { - go func(c chan Message) { c <- message }(recvChan) - } + go func(recvChans []chan Message) { + for _, recvChan := range recvChans { + recvChan <- message + } + }(channel.recvChans) return true } diff --git a/go/thresholdgroup/member.go b/go/thresholdgroup/member.go index b3438f7e76..5b1ed9b4cd 100644 --- a/go/thresholdgroup/member.go +++ b/go/thresholdgroup/member.go @@ -165,10 +165,6 @@ func NewMember(id string, threshold int) LocalMember { secretShares: secretShares, shareCommitments: shareCommitments, } - // receivedShares: map[bls.ID]bls.SecretKey{}, - // accusedIDs: map[bls.ID]bool{}, - // disqualifiedPlayers: map[bls.ID]bool{}, - // } } // InitializeSharing initializes a LocalMember with a list of the memberIDs of From 5a7f8d30cfca248adc989e318f0c0cc814ee5ebc Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 21 Feb 2018 09:27:53 -0500 Subject: [PATCH 33/44] Switch to integer multiple of milliseconds for timer We were dividing seconds, but multiplication > division. --- go/beacon/chain/chain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/beacon/chain/chain.go b/go/beacon/chain/chain.go index 3e573060da..f4c5ee545f 100644 --- a/go/beacon/chain/chain.go +++ b/go/beacon/chain/chain.go @@ -53,7 +53,7 @@ func (counter *localBlockCounter) BlockWaiter(numBlocks int) <-chan int { } func (counter *localBlockCounter) count() { - ticker := time.NewTicker(time.Duration(time.Second / 2)) + ticker := time.NewTicker(time.Duration(500 * time.Millisecond)) for _ = range ticker.C { counter.structMutex.Lock() From a1ab3163663e75d1572d62b5aacae0656b10f1dc Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 21 Feb 2018 10:51:22 -0500 Subject: [PATCH 34/44] Avoid 0 ids It's unclear how these interact with the rest of the BLS stuff, so let's just avoid them altogether. --- go/beacon/relay/dkg.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/go/beacon/relay/dkg.go b/go/beacon/relay/dkg.go index 66b3c7a898..9aa74ebd38 100644 --- a/go/beacon/relay/dkg.go +++ b/go/beacon/relay/dkg.go @@ -55,7 +55,9 @@ func ExecuteDKG(blockCounter chain.BlockCounter, channel broadcast.Channel, grou // FIXME Need a way to time out in a given stage, especially the waiting // ones. - memberID := rand.NewRand().String() + memberID := "0" + for memberID = rand.NewRand().String(); memberID == "0"; { + } fmt.Printf("[member:%v] Initializing member.\n", memberID) localMember := thresholdgroup.NewMember(memberID, threshold) From e929c6bd9ed39f8cbbc29cef3c82639501cac188 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 21 Feb 2018 11:20:47 -0500 Subject: [PATCH 35/44] Mutex-protect recvChans in LocalChannel We were failing to do this, which resulted in all sorts of weird runtime behavior when every member's channel didn't end up in the channel list. --- go/beacon/broadcast/broadcast.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/go/beacon/broadcast/broadcast.go b/go/beacon/broadcast/broadcast.go index 7aa9db01f1..f8b847c036 100644 --- a/go/beacon/broadcast/broadcast.go +++ b/go/beacon/broadcast/broadcast.go @@ -1,6 +1,8 @@ package broadcast import ( + "sync" + "github.com/dfinity/go-dfinity-crypto/bls" ) @@ -41,8 +43,9 @@ type Channel interface { } type localChannel struct { - name string - recvChans []chan Message + name string + recvChansMutex sync.Mutex + recvChans []chan Message } func (channel *localChannel) Name() string { @@ -50,11 +53,13 @@ func (channel *localChannel) Name() string { } func (channel *localChannel) Send(message Message) bool { + channel.recvChansMutex.Lock() go func(recvChans []chan Message) { for _, recvChan := range recvChans { recvChan <- message } }(channel.recvChans) + channel.recvChansMutex.Unlock() return true } @@ -62,7 +67,9 @@ func (channel *localChannel) Send(message Message) bool { func (channel *localChannel) RecvChan() <-chan Message { newChan := make(chan Message) + channel.recvChansMutex.Lock() channel.recvChans = append(channel.recvChans, newChan) + channel.recvChansMutex.Unlock() return newChan } @@ -73,5 +80,5 @@ func (channel *localChannel) RecvChan() <-chan Message { // that is returned to the caller, so that all receive channels can receive // the message. func LocalChannel(name string) Channel { - return &localChannel{name, make([]chan Message, 0)} + return &localChannel{name, sync.Mutex{}, make([]chan Message, 0)} } From 9d39e7d22721fd54b375d3322aad787d22ed7654 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 21 Feb 2018 11:22:10 -0500 Subject: [PATCH 36/44] Give LocalChannel recvChans a buffer The buffer is big enough to hold all private shares in a share exchange for a 250-member group. We need these buffers so that a message broadcast doesn't block on a member who is behind, which is likely to happen if number of cores < number of members. --- go/beacon/broadcast/broadcast.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/beacon/broadcast/broadcast.go b/go/beacon/broadcast/broadcast.go index f8b847c036..e0d4d7f18c 100644 --- a/go/beacon/broadcast/broadcast.go +++ b/go/beacon/broadcast/broadcast.go @@ -65,7 +65,7 @@ func (channel *localChannel) Send(message Message) bool { } func (channel *localChannel) RecvChan() <-chan Message { - newChan := make(chan Message) + newChan := make(chan Message, 62500) channel.recvChansMutex.Lock() channel.recvChans = append(channel.recvChans, newChan) From cfbc53b3707b9a98b1e65599dca3c48b54a85aef Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 21 Feb 2018 11:23:48 -0500 Subject: [PATCH 37/44] Ignore justification/accusation messages from self We were letting these contribute to our overall count, which meant we could stop looking for justifications or accusations before we had actually received all of them. Specifically, it meant some members could miss exactly one such message. --- go/beacon/relay/dkg.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/go/beacon/relay/dkg.go b/go/beacon/relay/dkg.go index 9aa74ebd38..4190f1880c 100644 --- a/go/beacon/relay/dkg.go +++ b/go/beacon/relay/dkg.go @@ -125,7 +125,7 @@ func ExecuteDKG(blockCounter chain.BlockCounter, channel broadcast.Channel, grou } fmt.Printf("[member:%v] Waiting for other accusations...\n", memberID) - err = waitForAccusations(recvChan, &justifyingMember) + err = waitForAccusations(&justifyingMember.BlsID, recvChan, &justifyingMember) if err != nil { return nil, fmt.Errorf("failed to receive all accusations: [%v]", err) } @@ -140,7 +140,7 @@ func ExecuteDKG(blockCounter chain.BlockCounter, channel broadcast.Channel, grou } fmt.Printf("[member:%v] Waiting for other justifications...\n", memberID) - err = waitForJustifications(recvChan, &justifyingMember) + err = waitForJustifications(&justifyingMember.BlsID, recvChan, &justifyingMember) if err != nil { return nil, fmt.Errorf("failed to receive all justifications: [%v]", err) } @@ -233,13 +233,17 @@ func sendAccusations(channel broadcast.Channel, member *thresholdgroup.Justifyin return nil } -func waitForAccusations(recvChan <-chan broadcast.Message, justifyingMember *thresholdgroup.JustifyingMember) error { +func waitForAccusations(myID *bls.ID, recvChan <-chan broadcast.Message, justifyingMember *thresholdgroup.JustifyingMember) error { memberIDs := justifyingMember.OtherMemberIDs() seenAccusations := make(map[bls.ID]bool, len(memberIDs)) done: for msg := range recvChan { switch accusationMsg := msg.Data.(type) { case AccusationsMessage: + if msg.Sender.IsEqual(myID) { + continue + } + for _, accusedID := range accusationMsg.accusedIDs { justifyingMember.AddAccusationFromID(msg.Sender, accusedID) } @@ -263,13 +267,17 @@ func sendJustifications(channel broadcast.Channel, justifyingMember *thresholdgr return nil } -func waitForJustifications(recvChan <-chan broadcast.Message, justifyingMember *thresholdgroup.JustifyingMember) error { +func waitForJustifications(myID *bls.ID, recvChan <-chan broadcast.Message, justifyingMember *thresholdgroup.JustifyingMember) error { memberIDs := justifyingMember.OtherMemberIDs() seenJustifications := make(map[bls.ID]bool, len(memberIDs)) done: for msg := range recvChan { switch justificationsMsg := msg.Data.(type) { case JustificationsMessage: + if msg.Sender.IsEqual(myID) { + continue + } + for accuserID, justification := range justificationsMsg.justifications { justifyingMember.RecordJustificationFromID(msg.Sender, accuserID, justification) } From 9856a92927b3012593be4760d591c85419a6f38a Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 21 Feb 2018 11:24:52 -0500 Subject: [PATCH 38/44] Increase timeouts to accommodate larger group sizes These timeouts were tested for 250 members on a 2017 MacBook Pro. They are meant to allow all members to complete one phase before any member proceeds to the next one, in a fully local simulation. --- go/beacon/relay/dkg.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/go/beacon/relay/dkg.go b/go/beacon/relay/dkg.go index 4190f1880c..aaa3ddff19 100644 --- a/go/beacon/relay/dkg.go +++ b/go/beacon/relay/dkg.go @@ -64,13 +64,13 @@ func ExecuteDKG(blockCounter chain.BlockCounter, channel broadcast.Channel, grou recvChan := channel.RecvChan() fmt.Printf("[member:%v] Waiting for join timeout...\n", memberID) - blockCounter.WaitForBlocks(5) + blockCounter.WaitForBlocks(15) fmt.Printf("[member:%v] Broadcasting join.\n", memberID) channel.Send(broadcast.NewBroadcastMessage(localMember.BlsID, JoinMessage{})) // Wait for all members. - waiter := blockCounter.BlockWaiter(3) + waiter := blockCounter.BlockWaiter(10) fmt.Printf("[member:%v] Waiting for other members...\n", memberID) memberIDs, err := waitForMemberIDs(&localMember.BlsID, recvChan, groupSize) if err != nil { @@ -80,7 +80,7 @@ func ExecuteDKG(blockCounter chain.BlockCounter, channel broadcast.Channel, grou fmt.Printf("[member:%v] Waiting for member join timeout...\n", memberID) <-waiter - waiter = blockCounter.BlockWaiter(3) + waiter = blockCounter.BlockWaiter(15) fmt.Printf("[member:%v] Initiating commitment broadcast phase.\n", memberID) sharingMember := localMember.InitializeSharing(memberIDs) @@ -99,7 +99,7 @@ func ExecuteDKG(blockCounter chain.BlockCounter, channel broadcast.Channel, grou fmt.Printf("[member:%v] Waiting for commitment timeout...\n", memberID) <-waiter - waiter = blockCounter.BlockWaiter(5) + waiter = blockCounter.BlockWaiter(20) fmt.Printf("[member:%v] Sending private shares.\n", memberID) err = sendShares(channel, &sharingMember) if err != nil { @@ -115,7 +115,7 @@ func ExecuteDKG(blockCounter chain.BlockCounter, channel broadcast.Channel, grou fmt.Printf("[member:%v] Waiting for share exchange timeout...\n", memberID) <-waiter - waiter = blockCounter.BlockWaiter(3) + waiter = blockCounter.BlockWaiter(15) fmt.Printf("[member:%v] Initiating accusation/justification phase.\n", memberID) justifyingMember := sharingMember.InitializeJustification() fmt.Printf("[member:%v] Broadcasting accusations.\n", memberID) From a2e14f4bca552c6350b290cceadcd0ccec2eccb8 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 21 Feb 2018 11:32:20 -0500 Subject: [PATCH 39/44] Tweak some of the output We now report how many ids we saw joining the broadcast channel (helps to make sure everyone made it if something goes wrong), and we stop printing output for each received share and each sent share (this adds up fast). Instead, we print when we start and finish shipping out all private shares. Then we rely on other output to note when all shares have been received. --- go/beacon/relay/dkg.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/go/beacon/relay/dkg.go b/go/beacon/relay/dkg.go index aaa3ddff19..ad464089a8 100644 --- a/go/beacon/relay/dkg.go +++ b/go/beacon/relay/dkg.go @@ -80,6 +80,8 @@ func ExecuteDKG(blockCounter chain.BlockCounter, channel broadcast.Channel, grou fmt.Printf("[member:%v] Waiting for member join timeout...\n", memberID) <-waiter + fmt.Printf("[member:%v] Saw IDs: %v\n", memberID, len(memberIDs)) + waiter = blockCounter.BlockWaiter(15) fmt.Printf("[member:%v] Initiating commitment broadcast phase.\n", memberID) sharingMember := localMember.InitializeSharing(memberIDs) @@ -199,11 +201,12 @@ done: } func sendShares(channel broadcast.Channel, member *thresholdgroup.SharingMember) error { + fmt.Printf("[member:%v] Despatching shares!\n", member.ID) for _, receiverID := range member.OtherMemberIDs() { share := member.SecretShareForID(receiverID) - fmt.Printf("[member:%v] Despatching a share!\n", member.ID) channel.Send(broadcast.NewPrivateMessage(member.BlsID, receiverID, MemberShareMessage{share})) } + fmt.Printf("[member:%v] Shares despatched!\n", member.ID) return nil } @@ -214,7 +217,6 @@ done: switch shareMsg := msg.Data.(type) { case MemberShareMessage: if msg.Receiver.IsEqual(myID) { - fmt.Printf("[member:%v] Received one id from [%v].\n", myID.GetHexString(), msg.Sender.GetHexString()) sharingMember.AddShareFromID(msg.Sender, shareMsg.Share) if sharingMember.SharesComplete() { From 5b11a83f7aedba372c9bb2b84adb90c48a930beb Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 21 Feb 2018 11:34:34 -0500 Subject: [PATCH 40/44] Go back to CurveFp382_1 because it works The Bn254 curve doesn't actually produce valid combined signatures... We'll look into that separately in our research on using the curve implemented by Ethereum. --- go/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/main.go b/go/main.go index fd2ea43018..9ae144c41c 100644 --- a/go/main.go +++ b/go/main.go @@ -12,7 +12,7 @@ import ( ) func main() { - bls.Init(bls.CurveFp254BNb) + bls.Init(bls.CurveFp382_1) beaconConfig := chain.GetBeaconConfig() From 657281fa1348faf1faf670b6a30c278b458cf5f1 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Wed, 21 Feb 2018 17:43:50 -0500 Subject: [PATCH 41/44] Fix comment for ExecuteDKG We weren't starting with the function name like we're supposed to ;) --- go/beacon/relay/dkg.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/go/beacon/relay/dkg.go b/go/beacon/relay/dkg.go index ad464089a8..7b79e4b2ef 100644 --- a/go/beacon/relay/dkg.go +++ b/go/beacon/relay/dkg.go @@ -46,10 +46,10 @@ type JustificationsMessage struct { justifications map[bls.ID]bls.SecretKey } -// Execute runs the full distributed key generation lifecycle, given a broadcast -// channel to mediate it and a group size and threshold. It returns a threshold -// group member who is participating in the group if the generation was -// successful, and an error representing what went wrong if not. +// ExecuteDKG runs the full distributed key generation lifecycle, given a +// broadcast channel to mediate it and a group size and threshold. It returns a +// threshold group member who is participating in the group if the generation +// was successful, and an error representing what went wrong if not. func ExecuteDKG(blockCounter chain.BlockCounter, channel broadcast.Channel, groupSize int, threshold int) (*thresholdgroup.Member, error) { // FIXME Probably pass in a way to ask for a receiver's public key? // FIXME Need a way to time out in a given stage, especially the waiting From 1b15f8bd2ac4ac9cb40897f205cf6d5458f1fa06 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 22 Feb 2018 13:46:31 -0500 Subject: [PATCH 42/44] Add a comment clarifying the avoid-zero-ID loop --- go/beacon/relay/dkg.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go/beacon/relay/dkg.go b/go/beacon/relay/dkg.go index 7b79e4b2ef..4255455f38 100644 --- a/go/beacon/relay/dkg.go +++ b/go/beacon/relay/dkg.go @@ -55,6 +55,8 @@ func ExecuteDKG(blockCounter chain.BlockCounter, channel broadcast.Channel, grou // FIXME Need a way to time out in a given stage, especially the waiting // ones. + // Generate a nonzero memberID; loop until rand.NewRand returns something + // other than 0, hopefully no more than once :) memberID := "0" for memberID = rand.NewRand().String(); memberID == "0"; { } From ccff34df7ec380c5823471791bbb5c74b3578233 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 22 Feb 2018 13:46:46 -0500 Subject: [PATCH 43/44] Fix mutex around recvChans in localChannel.Send The mutex wasn't guarding access to recvChans, since recvChans was passed to a goroutine that escaped the mutex's lock. We now lock the mutex and create a snapshot of recvChans, then use that snapshot in the goroutine. --- go/beacon/broadcast/broadcast.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/go/beacon/broadcast/broadcast.go b/go/beacon/broadcast/broadcast.go index e0d4d7f18c..d9ed6c5e43 100644 --- a/go/beacon/broadcast/broadcast.go +++ b/go/beacon/broadcast/broadcast.go @@ -54,12 +54,14 @@ func (channel *localChannel) Name() string { func (channel *localChannel) Send(message Message) bool { channel.recvChansMutex.Lock() - go func(recvChans []chan Message) { - for _, recvChan := range recvChans { + snapshot := make([]chan Message, len(channel.recvChans)) + copy(snapshot, channel.recvChans) + channel.recvChansMutex.Unlock() + go func() { + for _, recvChan := range snapshot { recvChan <- message } - }(channel.recvChans) - channel.recvChansMutex.Unlock() + }() return true } From a0843852a93cd9a0f5a0f892e97c5af642ee6640 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 22 Feb 2018 18:00:03 -0500 Subject: [PATCH 44/44] Properly capture loop index, report BLS id on DKG error We include the index because the calling code has no way to know that the BLS id is definitely initialized on an error return; however, we also include the BLS id because it should be. --- go/main.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/go/main.go b/go/main.go index 9ae144c41c..5ee2de1a71 100644 --- a/go/main.go +++ b/go/main.go @@ -22,16 +22,21 @@ func main() { members := make([]*thresholdgroup.Member, 0, beaconConfig.GroupSize) memberChannel := make(chan *thresholdgroup.Member) for i := 0; i < beaconConfig.GroupSize; i++ { - go func() { + go func(i int) { member, err := relay.ExecuteDKG(chainCounter, channel, beaconConfig.GroupSize, beaconConfig.Threshold) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to run DKG for member %v: [%s].", i, err) + fmt.Fprintf( + os.Stderr, + "[member:%v] Failed to run DKG: [%s] (index %d).", + member.BlsID.GetHexString(), + err, + i) memberChannel <- nil return } memberChannel <- member - }() + }(i) } seenMembers := 0