aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/crypto.go99
-rw-r--r--core/crypto_test.go53
-rw-r--r--core/dkg-tsig-protocol.go341
-rw-r--r--core/dkg-tsig-protocol_test.go191
-rw-r--r--core/types/dkg.go19
-rw-r--r--crypto/dkg/dkg.go52
-rw-r--r--crypto/dkg/dkg_test.go31
-rw-r--r--simulation/governance.go17
-rw-r--r--simulation/network.go10
-rw-r--r--simulation/validator.go7
10 files changed, 791 insertions, 29 deletions
diff --git a/core/crypto.go b/core/crypto.go
index 402fd2e..8fcb118 100644
--- a/core/crypto.go
+++ b/core/crypto.go
@@ -143,3 +143,102 @@ func hashPosition(position types.Position) common.Hash {
binaryHeight,
)
}
+
+func hashDKGPrivateShare(prvShare *types.DKGPrivateShare) common.Hash {
+ binaryRound := make([]byte, 8)
+ binary.LittleEndian.PutUint64(binaryRound, prvShare.Round)
+
+ return crypto.Keccak256Hash(
+ prvShare.ProposerID.Hash[:],
+ binaryRound,
+ prvShare.PrivateShare.Bytes(),
+ )
+}
+
+func verifyDKGPrivateShareSignature(
+ prvShare *types.DKGPrivateShare, sigToPub SigToPubFn) (bool, error) {
+ hash := hashDKGPrivateShare(prvShare)
+ pubKey, err := sigToPub(hash, prvShare.Signature)
+ if err != nil {
+ return false, err
+ }
+ if prvShare.ProposerID != types.NewValidatorID(pubKey) {
+ return false, nil
+ }
+ return true, nil
+}
+
+func hashDKGMasterPublicKey(mpk *types.DKGMasterPublicKey) common.Hash {
+ binaryRound := make([]byte, 8)
+ binary.LittleEndian.PutUint64(binaryRound, mpk.Round)
+
+ return crypto.Keccak256Hash(
+ mpk.ProposerID.Hash[:],
+ mpk.DKGID.GetLittleEndian(),
+ mpk.PublicKeyShares.MasterKeyBytes(),
+ binaryRound,
+ )
+}
+
+func verifyDKGMasterPublicKeySignature(
+ mpk *types.DKGMasterPublicKey, sigToPub SigToPubFn) (bool, error) {
+ hash := hashDKGMasterPublicKey(mpk)
+ pubKey, err := sigToPub(hash, mpk.Signature)
+ if err != nil {
+ return false, err
+ }
+ if mpk.ProposerID != types.NewValidatorID(pubKey) {
+ return false, nil
+ }
+ return true, nil
+}
+
+func hashDKGComplaint(complaint *types.DKGComplaint) common.Hash {
+ binaryRound := make([]byte, 8)
+ binary.LittleEndian.PutUint64(binaryRound, complaint.Round)
+
+ hashPrvShare := hashDKGPrivateShare(&complaint.PrivateShare)
+
+ return crypto.Keccak256Hash(
+ complaint.ProposerID.Hash[:],
+ binaryRound,
+ hashPrvShare[:],
+ )
+}
+
+func verifyDKGComplaintSignature(
+ complaint *types.DKGComplaint, sigToPub SigToPubFn) (bool, error) {
+ hash := hashDKGComplaint(complaint)
+ pubKey, err := sigToPub(hash, complaint.Signature)
+ if err != nil {
+ return false, err
+ }
+ if complaint.ProposerID != types.NewValidatorID(pubKey) {
+ return false, nil
+ }
+ return true, nil
+}
+
+func hashDKGPartialSignature(psig *types.DKGPartialSignature) common.Hash {
+ binaryRound := make([]byte, 8)
+ binary.LittleEndian.PutUint64(binaryRound, psig.Round)
+
+ return crypto.Keccak256Hash(
+ psig.ProposerID.Hash[:],
+ binaryRound,
+ psig.PartialSignature[:],
+ )
+}
+
+func verifyDKGPartialSignatureSignature(
+ psig *types.DKGPartialSignature, sigToPub SigToPubFn) (bool, error) {
+ hash := hashDKGPartialSignature(psig)
+ pubKey, err := sigToPub(hash, psig.Signature)
+ if err != nil {
+ return false, err
+ }
+ if psig.ProposerID != types.NewValidatorID(pubKey) {
+ return false, nil
+ }
+ return true, nil
+}
diff --git a/core/crypto_test.go b/core/crypto_test.go
index b96b0bd..7f3c3f3 100644
--- a/core/crypto_test.go
+++ b/core/crypto_test.go
@@ -24,6 +24,7 @@ import (
"github.com/dexon-foundation/dexon-consensus-core/common"
"github.com/dexon-foundation/dexon-consensus-core/core/types"
"github.com/dexon-foundation/dexon-consensus-core/crypto"
+ "github.com/dexon-foundation/dexon-consensus-core/crypto/dkg"
"github.com/dexon-foundation/dexon-consensus-core/crypto/eth"
"github.com/stretchr/testify/suite"
)
@@ -210,6 +211,58 @@ func (s *CryptoTestSuite) TestCRSSignature() {
s.False(verifyCRSSignature(block, crs, eth.SigToPub))
}
+func (s *CryptoTestSuite) TestDKGSignature() {
+ prv, err := eth.NewPrivateKey()
+ s.Require().Nil(err)
+ vID := types.NewValidatorID(prv.PublicKey())
+ prvShare := &types.DKGPrivateShare{
+ ProposerID: vID,
+ Round: 5,
+ PrivateShare: *dkg.NewPrivateKey(),
+ }
+ prvShare.Signature, err = prv.Sign(hashDKGPrivateShare(prvShare))
+ s.Require().Nil(err)
+ s.True(verifyDKGPrivateShareSignature(prvShare, eth.SigToPub))
+ prvShare.Round++
+ s.False(verifyDKGPrivateShareSignature(prvShare, eth.SigToPub))
+
+ id := dkg.NewID([]byte{13})
+ _, pkShare := dkg.NewPrivateKeyShares(1)
+ mpk := &types.DKGMasterPublicKey{
+ ProposerID: vID,
+ Round: 5,
+ DKGID: id,
+ PublicKeyShares: *pkShare,
+ }
+ mpk.Signature, err = prv.Sign(hashDKGMasterPublicKey(mpk))
+ s.Require().Nil(err)
+ s.True(verifyDKGMasterPublicKeySignature(mpk, eth.SigToPub))
+ mpk.Round++
+ s.False(verifyDKGMasterPublicKeySignature(mpk, eth.SigToPub))
+
+ complaint := &types.DKGComplaint{
+ ProposerID: vID,
+ Round: 5,
+ PrivateShare: *prvShare,
+ }
+ complaint.Signature, err = prv.Sign(hashDKGComplaint(complaint))
+ s.Require().Nil(err)
+ s.True(verifyDKGComplaintSignature(complaint, eth.SigToPub))
+ complaint.Round++
+ s.False(verifyDKGComplaintSignature(complaint, eth.SigToPub))
+
+ sig := &types.DKGPartialSignature{
+ ProposerID: vID,
+ Round: 5,
+ PartialSignature: dkg.PartialSignature{},
+ }
+ sig.Signature, err = prv.Sign(hashDKGPartialSignature(sig))
+ s.Require().Nil(err)
+ s.True(verifyDKGPartialSignatureSignature(sig, eth.SigToPub))
+ sig.Round++
+ s.False(verifyDKGPartialSignatureSignature(sig, eth.SigToPub))
+}
+
func TestCrypto(t *testing.T) {
suite.Run(t, new(CryptoTestSuite))
}
diff --git a/core/dkg-tsig-protocol.go b/core/dkg-tsig-protocol.go
new file mode 100644
index 0000000..c549ecb
--- /dev/null
+++ b/core/dkg-tsig-protocol.go
@@ -0,0 +1,341 @@
+// Copyright 2018 The dexon-consensus-core Authors
+// This file is part of the dexon-consensus-core library.
+//
+// The dexon-consensus-core library is free software: you can redistribute it
+// and/or modify it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// The dexon-consensus-core library is distributed in the hope that it will be
+// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+// General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the dexon-consensus-core library. If not, see
+// <http://www.gnu.org/licenses/>.
+
+package core
+
+import (
+ "fmt"
+
+ "github.com/dexon-foundation/dexon-consensus-core/common"
+ "github.com/dexon-foundation/dexon-consensus-core/core/types"
+ "github.com/dexon-foundation/dexon-consensus-core/crypto"
+ "github.com/dexon-foundation/dexon-consensus-core/crypto/dkg"
+)
+
+// Errors for dkg module.
+var (
+ ErrNotDKGParticipant = fmt.Errorf(
+ "not a DKG participant")
+ ErrNotQualifyDKGParticipant = fmt.Errorf(
+ "not a qualified DKG participant")
+ ErrIDShareNotFound = fmt.Errorf(
+ "private share not found for specific ID")
+ ErrNotReachThreshold = fmt.Errorf(
+ "threshold not reach")
+ ErrIncorrectPrivateShareSignature = fmt.Errorf(
+ "incorrect private share signature")
+ ErrIncorrectPartialSignatureSignature = fmt.Errorf(
+ "incorrect partialSignature signature")
+ ErrIncorrectPartialSignature = fmt.Errorf(
+ "incorrect partialSignature")
+ ErrNotEnoughtPartialSignatures = fmt.Errorf(
+ "not enough of partial signatures")
+)
+
+type dkgComplaintReceiver interface {
+ // ProposeDKGComplaint proposes a DKGComplaint.
+ ProposeDKGComplaint(complaint *types.DKGComplaint)
+
+ // ProposeDKGMasterPublicKey propose a DKGMasterPublicKey.
+ ProposeDKGMasterPublicKey(mpk *types.DKGMasterPublicKey)
+
+ // ProposeDKGPrivateShare propose a DKGPrivateShare.
+ ProposeDKGPrivateShare(to types.ValidatorID, prv *types.DKGPrivateShare)
+}
+
+type dkgProtocol struct {
+ ID types.ValidatorID
+ recv dkgComplaintReceiver
+ round uint64
+ threshold int
+ sigToPub SigToPubFn
+ idMap map[types.ValidatorID]dkg.ID
+ mpkMap map[types.ValidatorID]*dkg.PublicKeyShares
+ masterPrivateShare *dkg.PrivateKeyShares
+ prvShares *dkg.PrivateKeyShares
+}
+
+type dkgShareSecret struct {
+ privateKey *dkg.PrivateKey
+}
+
+type dkgGroupPublicKey struct {
+ round uint64
+ qualifyIDs dkg.IDs
+ idMap map[types.ValidatorID]dkg.ID
+ publicKeys map[types.ValidatorID]*dkg.PublicKey
+ groupPublicKey *dkg.PublicKey
+ threshold int
+ sigToPub SigToPubFn
+}
+
+type tsigProtocol struct {
+ groupPublicKey *dkgGroupPublicKey
+ sigs map[dkg.ID]dkg.PartialSignature
+ threshold int
+}
+
+func newDKGID(ID types.ValidatorID) dkg.ID {
+ return dkg.NewID(ID.Hash[:])
+}
+
+func newDKGProtocol(
+ ID types.ValidatorID,
+ recv dkgComplaintReceiver,
+ round uint64,
+ threshold int,
+ sigToPub SigToPubFn) *dkgProtocol {
+
+ prvShare, pubShare := dkg.NewPrivateKeyShares(threshold)
+
+ recv.ProposeDKGMasterPublicKey(&types.DKGMasterPublicKey{
+ ProposerID: ID,
+ Round: round,
+ DKGID: newDKGID(ID),
+ PublicKeyShares: *pubShare,
+ })
+
+ return &dkgProtocol{
+ ID: ID,
+ recv: recv,
+ round: round,
+ threshold: threshold,
+ sigToPub: sigToPub,
+ idMap: make(map[types.ValidatorID]dkg.ID),
+ mpkMap: make(map[types.ValidatorID]*dkg.PublicKeyShares),
+ masterPrivateShare: prvShare,
+ prvShares: dkg.NewEmptyPrivateKeyShares(),
+ }
+}
+
+func (d *dkgProtocol) processMasterPublicKeys(
+ mpks []*types.DKGMasterPublicKey) error {
+ d.idMap = make(map[types.ValidatorID]dkg.ID, len(mpks))
+ d.mpkMap = make(map[types.ValidatorID]*dkg.PublicKeyShares, len(mpks))
+ ids := make(dkg.IDs, len(mpks))
+ for i := range mpks {
+ vID := mpks[i].ProposerID
+ d.idMap[vID] = mpks[i].DKGID
+ d.mpkMap[vID] = &mpks[i].PublicKeyShares
+ ids[i] = mpks[i].DKGID
+ }
+ d.masterPrivateShare.SetParticipants(ids)
+ for _, mpk := range mpks {
+ share, ok := d.masterPrivateShare.Share(mpk.DKGID)
+ if !ok {
+ return ErrIDShareNotFound
+ }
+ d.recv.ProposeDKGPrivateShare(mpk.ProposerID, &types.DKGPrivateShare{
+ ProposerID: d.ID,
+ Round: d.round,
+ PrivateShare: *share,
+ })
+ }
+ return nil
+}
+
+func (d *dkgProtocol) sanityCheck(prvShare *types.DKGPrivateShare) error {
+ if _, exist := d.idMap[prvShare.ProposerID]; !exist {
+ return ErrNotDKGParticipant
+ }
+ ok, err := verifyDKGPrivateShareSignature(prvShare, d.sigToPub)
+ if err != nil {
+ return err
+ }
+ if !ok {
+ return ErrIncorrectPrivateShareSignature
+ }
+ return nil
+}
+
+func (d *dkgProtocol) processPrivateShare(
+ prvShare *types.DKGPrivateShare) error {
+ if d.round != prvShare.Round {
+ return nil
+ }
+ self, exist := d.idMap[d.ID]
+ // This validator is not a DKG participant, ignore the private share.
+ if !exist {
+ return nil
+ }
+ if err := d.sanityCheck(prvShare); err != nil {
+ return err
+ }
+ mpk := d.mpkMap[prvShare.ProposerID]
+ ok, err := mpk.VerifyPrvShare(self, &prvShare.PrivateShare)
+ if err != nil {
+ return err
+ }
+ if !ok {
+ complaint := &types.DKGComplaint{
+ ProposerID: d.ID,
+ Round: d.round,
+ PrivateShare: *prvShare,
+ }
+ d.recv.ProposeDKGComplaint(complaint)
+ } else {
+ sender := d.idMap[prvShare.ProposerID]
+ if err := d.prvShares.AddShare(sender, &prvShare.PrivateShare); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (d *dkgProtocol) recoverShareSecret(qualifyIDs dkg.IDs) (
+ *dkgShareSecret, error) {
+ if len(qualifyIDs) <= d.threshold {
+ return nil, ErrNotReachThreshold
+ }
+ prvKey, err := d.prvShares.RecoverPrivateKey(qualifyIDs)
+ if err != nil {
+ return nil, err
+ }
+ return &dkgShareSecret{
+ privateKey: prvKey,
+ }, nil
+}
+
+func (ss *dkgShareSecret) sign(hash common.Hash) dkg.PartialSignature {
+ // DKG sign will always success.
+ sig, _ := ss.privateKey.Sign(hash)
+ return dkg.PartialSignature(sig)
+}
+
+func newDKGGroupPublicKey(
+ round uint64,
+ mpks []*types.DKGMasterPublicKey, complaints []*types.DKGComplaint,
+ threshold int, sigToPub SigToPubFn) (
+ *dkgGroupPublicKey, error) {
+ // Calculate qualify members.
+ complaintsByID := map[types.ValidatorID]int{}
+ for _, complaint := range complaints {
+ complaintsByID[complaint.PrivateShare.ProposerID]++
+ }
+ disqualifyIDs := map[types.ValidatorID]struct{}{}
+ for vID, num := range complaintsByID {
+ if num > threshold {
+ disqualifyIDs[vID] = struct{}{}
+ }
+ }
+ qualifyIDs := make(dkg.IDs, 0, len(mpks)-len(disqualifyIDs))
+ mpkMap := make(map[dkg.ID]*types.DKGMasterPublicKey, cap(qualifyIDs))
+ idMap := make(map[types.ValidatorID]dkg.ID)
+ for _, mpk := range mpks {
+ if _, exist := disqualifyIDs[mpk.ProposerID]; exist {
+ continue
+ }
+ mpkMap[mpk.DKGID] = mpk
+ idMap[mpk.ProposerID] = mpk.DKGID
+ qualifyIDs = append(qualifyIDs, mpk.DKGID)
+ }
+ // Recover qualify members' public key.
+ pubKeys := make(map[types.ValidatorID]*dkg.PublicKey, len(qualifyIDs))
+ for _, recvID := range qualifyIDs {
+ pubShares := dkg.NewEmptyPublicKeyShares()
+ for _, id := range qualifyIDs {
+ pubShare, err := mpkMap[id].PublicKeyShares.Share(recvID)
+ if err != nil {
+ return nil, err
+ }
+ if err := pubShares.AddShare(id, pubShare); err != nil {
+ return nil, err
+ }
+ }
+ pubKey, err := pubShares.RecoverPublicKey(qualifyIDs)
+ if err != nil {
+ return nil, err
+ }
+ pubKeys[mpkMap[recvID].ProposerID] = pubKey
+ }
+ // Recover Group Public Key.
+ pubShares := make([]*dkg.PublicKeyShares, 0, len(qualifyIDs))
+ for _, id := range qualifyIDs {
+ pubShares = append(pubShares, &mpkMap[id].PublicKeyShares)
+ }
+ groupPK := dkg.RecoverGroupPublicKey(pubShares)
+ return &dkgGroupPublicKey{
+ round: round,
+ qualifyIDs: qualifyIDs,
+ idMap: idMap,
+ publicKeys: pubKeys,
+ threshold: threshold,
+ groupPublicKey: groupPK,
+ sigToPub: sigToPub,
+ }, nil
+}
+
+func (gpk *dkgGroupPublicKey) verifySignature(
+ hash common.Hash, sig crypto.Signature) bool {
+ return gpk.groupPublicKey.VerifySignature(hash, sig)
+}
+
+func newTSigProtocol(gpk *dkgGroupPublicKey) *tsigProtocol {
+ return &tsigProtocol{
+ groupPublicKey: gpk,
+ sigs: make(map[dkg.ID]dkg.PartialSignature, gpk.threshold+1),
+ }
+}
+
+func (tsig *tsigProtocol) sanityCheck(psig *types.DKGPartialSignature) error {
+ _, exist := tsig.groupPublicKey.publicKeys[psig.ProposerID]
+ if !exist {
+ return ErrNotQualifyDKGParticipant
+ }
+ ok, err := verifyDKGPartialSignatureSignature(
+ psig, tsig.groupPublicKey.sigToPub)
+ if err != nil {
+ return err
+ }
+ if !ok {
+ return ErrIncorrectPartialSignatureSignature
+ }
+ return nil
+}
+
+func (tsig *tsigProtocol) processPartialSignature(
+ hash common.Hash, psig *types.DKGPartialSignature) error {
+ if psig.Round != tsig.groupPublicKey.round {
+ return nil
+ }
+ id, exist := tsig.groupPublicKey.idMap[psig.ProposerID]
+ if !exist {
+ return ErrNotQualifyDKGParticipant
+ }
+ if err := tsig.sanityCheck(psig); err != nil {
+ return err
+ }
+ pubKey := tsig.groupPublicKey.publicKeys[psig.ProposerID]
+ if !pubKey.VerifySignature(hash, crypto.Signature(psig.PartialSignature)) {
+ return ErrIncorrectPartialSignature
+ }
+ tsig.sigs[id] = psig.PartialSignature
+ return nil
+}
+
+func (tsig *tsigProtocol) signature() (crypto.Signature, error) {
+ if len(tsig.sigs) <= tsig.groupPublicKey.threshold {
+ return nil, ErrNotEnoughtPartialSignatures
+ }
+ ids := make(dkg.IDs, 0, len(tsig.sigs))
+ psigs := make([]dkg.PartialSignature, 0, len(tsig.sigs))
+ for id, psig := range tsig.sigs {
+ ids = append(ids, id)
+ psigs = append(psigs, psig)
+ }
+ return dkg.RecoverSignature(psigs, ids)
+}
diff --git a/core/dkg-tsig-protocol_test.go b/core/dkg-tsig-protocol_test.go
new file mode 100644
index 0000000..f1c0017
--- /dev/null
+++ b/core/dkg-tsig-protocol_test.go
@@ -0,0 +1,191 @@
+// Copyright 2018 The dexon-consensus-core Authors
+// This file is part of the dexon-consensus-core library.
+//
+// The dexon-consensus-core library is free software: you can redistribute it
+// and/or modify it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// The dexon-consensus-core library is distributed in the hope that it will be
+// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+// General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the dexon-consensus-core library. If not, see
+// <http://www.gnu.org/licenses/>.
+
+package core
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/suite"
+
+ "github.com/dexon-foundation/dexon-consensus-core/core/test"
+ "github.com/dexon-foundation/dexon-consensus-core/core/types"
+ "github.com/dexon-foundation/dexon-consensus-core/crypto"
+ "github.com/dexon-foundation/dexon-consensus-core/crypto/dkg"
+ "github.com/dexon-foundation/dexon-consensus-core/crypto/eth"
+)
+
+type DKGTSIGProtocolTestSuite struct {
+ suite.Suite
+
+ vIDs types.ValidatorIDs
+ dkgIDs map[types.ValidatorID]dkg.ID
+ prvKeys map[types.ValidatorID]crypto.PrivateKey
+}
+
+type testDKGReceiver struct {
+ s *DKGTSIGProtocolTestSuite
+
+ prvKey crypto.PrivateKey
+ complaints map[types.ValidatorID]*types.DKGComplaint
+ mpk *types.DKGMasterPublicKey
+ prvShare map[types.ValidatorID]*types.DKGPrivateShare
+}
+
+func newTestDKGReceiver(
+ s *DKGTSIGProtocolTestSuite, prvKey crypto.PrivateKey) *testDKGReceiver {
+ return &testDKGReceiver{
+ s: s,
+ prvKey: prvKey,
+ complaints: make(map[types.ValidatorID]*types.DKGComplaint),
+ prvShare: make(map[types.ValidatorID]*types.DKGPrivateShare),
+ }
+}
+
+func (r *testDKGReceiver) ProposeDKGComplaint(complaint *types.DKGComplaint) {
+ var err error
+ complaint.Signature, err = r.prvKey.Sign(hashDKGComplaint(complaint))
+ r.s.Require().NoError(err)
+ r.complaints[complaint.ProposerID] = complaint
+}
+
+func (r *testDKGReceiver) ProposeDKGMasterPublicKey(
+ mpk *types.DKGMasterPublicKey) {
+ var err error
+ mpk.Signature, err = r.prvKey.Sign(hashDKGMasterPublicKey(mpk))
+ r.s.Require().NoError(err)
+ r.mpk = mpk
+}
+func (r *testDKGReceiver) ProposeDKGPrivateShare(
+ to types.ValidatorID, prv *types.DKGPrivateShare) {
+ var err error
+ prv.Signature, err = r.prvKey.Sign(hashDKGPrivateShare(prv))
+ r.s.Require().NoError(err)
+ r.prvShare[to] = prv
+}
+
+func (s *DKGTSIGProtocolTestSuite) setupDKGParticipants(n int) {
+ s.vIDs = make(types.ValidatorIDs, 0, n)
+ s.prvKeys = make(map[types.ValidatorID]crypto.PrivateKey, n)
+ s.dkgIDs = make(map[types.ValidatorID]dkg.ID)
+ ids := make(dkg.IDs, 0, n)
+ for i := 0; i < n; i++ {
+ prvKey, err := eth.NewPrivateKey()
+ s.Require().NoError(err)
+ vID := types.NewValidatorID(prvKey.PublicKey())
+ s.vIDs = append(s.vIDs, vID)
+ s.prvKeys[vID] = prvKey
+ id := dkg.NewID(vID.Hash[:])
+ ids = append(ids, id)
+ s.dkgIDs[vID] = id
+ }
+}
+
+// TestDKGTSIGProtocol will test the entire DKG+TISG protocol including
+// exchanging private shares, recovering share secret, creating partial sign and
+// recovering threshold signature.
+// All participants are good people in this test.
+func (s *DKGTSIGProtocolTestSuite) TestDKGTSIGProtocol() {
+ k := 3
+ n := 10
+ round := uint64(1)
+ gov, err := test.NewGovernance(5, 100)
+ s.Require().NoError(err)
+ s.setupDKGParticipants(n)
+
+ receivers := make(map[types.ValidatorID]*testDKGReceiver, n)
+ protocols := make(map[types.ValidatorID]*dkgProtocol, n)
+ for _, vID := range s.vIDs {
+ receivers[vID] = newTestDKGReceiver(s, s.prvKeys[vID])
+ protocols[vID] = newDKGProtocol(
+ vID,
+ receivers[vID],
+ round,
+ k,
+ eth.SigToPub,
+ )
+ s.Require().NotNil(receivers[vID].mpk)
+ }
+
+ for _, receiver := range receivers {
+ gov.AddDKGMasterPublicKey(receiver.mpk)
+ }
+
+ for _, protocol := range protocols {
+ s.Require().NoError(
+ protocol.processMasterPublicKeys(gov.DKGMasterPublicKeys(round)))
+ }
+
+ for _, receiver := range receivers {
+ s.Require().Len(receiver.prvShare, n)
+ for vID, prvShare := range receiver.prvShare {
+ s.Require().NoError(protocols[vID].processPrivateShare(prvShare))
+ }
+ }
+
+ for _, recv := range receivers {
+ s.Require().Len(recv.complaints, 0)
+ }
+
+ // DKG is fininished.
+ gpk, err := newDKGGroupPublicKey(round,
+ gov.DKGMasterPublicKeys(round), gov.DKGComplaints(round),
+ k, eth.SigToPub,
+ )
+ s.Require().NoError(err)
+ s.Require().Len(gpk.qualifyIDs, n)
+ qualifyIDs := make(map[dkg.ID]struct{}, len(gpk.qualifyIDs))
+ for _, id := range gpk.qualifyIDs {
+ qualifyIDs[id] = struct{}{}
+ }
+
+ shareSecrets := make(
+ map[types.ValidatorID]*dkgShareSecret, len(qualifyIDs))
+
+ for vID, protocol := range protocols {
+ _, exist := qualifyIDs[s.dkgIDs[vID]]
+ s.Require().True(exist)
+ var err error
+ shareSecrets[vID], err = protocol.recoverShareSecret(gpk.qualifyIDs)
+ s.Require().NoError(err)
+ }
+
+ tsig := newTSigProtocol(gpk)
+ msgHash := crypto.Keccak256Hash([]byte("🏖🍹"))
+ for vID, shareSecret := range shareSecrets {
+ psig := &types.DKGPartialSignature{
+ ProposerID: vID,
+ Round: round,
+ PartialSignature: shareSecret.sign(msgHash),
+ }
+ var err error
+ psig.Signature, err = s.prvKeys[vID].Sign(hashDKGPartialSignature(psig))
+ s.Require().NoError(err)
+ s.Require().NoError(tsig.processPartialSignature(msgHash, psig))
+ if len(tsig.sigs) > k {
+ break
+ }
+ }
+
+ sig, err := tsig.signature()
+ s.Require().NoError(err)
+ s.True(gpk.verifySignature(msgHash, sig))
+}
+
+func TestDKGTSIGProtocol(t *testing.T) {
+ suite.Run(t, new(DKGTSIGProtocolTestSuite))
+}
diff --git a/core/types/dkg.go b/core/types/dkg.go
index 7fee404..7fb686c 100644
--- a/core/types/dkg.go
+++ b/core/types/dkg.go
@@ -24,17 +24,18 @@ import (
// DKGPrivateShare describe a secret share in DKG protocol.
type DKGPrivateShare struct {
- ProposerID ValidatorID `json:"proposer_id"`
- Round uint64 `json:"round"`
- PrivateKeyShare dkg.PrivateKey `json:"private_key_share"`
- Signature crypto.Signature `json:"signature"`
+ ProposerID ValidatorID `json:"proposer_id"`
+ Round uint64 `json:"round"`
+ PrivateShare dkg.PrivateKey `json:"private_share"`
+ Signature crypto.Signature `json:"signature"`
}
// DKGMasterPublicKey decrtibe a master public key in DKG protocol.
type DKGMasterPublicKey struct {
ProposerID ValidatorID `json:"proposer_id"`
Round uint64 `json:"round"`
- PublicKeyShares dkg.PublicKeyShares `json:"private_key_share"`
+ DKGID dkg.ID `json:"dkg_id"`
+ PublicKeyShares dkg.PublicKeyShares `json:"public_key_shares"`
Signature crypto.Signature `json:"signature"`
}
@@ -45,3 +46,11 @@ type DKGComplaint struct {
PrivateShare DKGPrivateShare `json:"private_share"`
Signature crypto.Signature `json:"signature"`
}
+
+// DKGPartialSignature describe a partial signature in DKG protocol.
+type DKGPartialSignature struct {
+ ProposerID ValidatorID `json:"proposerID"`
+ Round uint64 `json:"round"`
+ PartialSignature dkg.PartialSignature `json:"partial_signature"`
+ Signature crypto.Signature `json:"signature"`
+}
diff --git a/crypto/dkg/dkg.go b/crypto/dkg/dkg.go
index d3596e6..4e21d45 100644
--- a/crypto/dkg/dkg.go
+++ b/crypto/dkg/dkg.go
@@ -37,8 +37,13 @@ var (
ErrShareNotFound = fmt.Errorf("share not found")
)
+var publicKeyLength int
+
func init() {
bls.Init(curve)
+
+ pubKey := &bls.PublicKey{}
+ publicKeyLength = len(pubKey.Serialize())
}
// PrivateKey represents a private key structure implments
@@ -63,8 +68,9 @@ type PublicKey struct {
// PrivateKeyShares represents a private key shares for DKG protocol.
type PrivateKeyShares struct {
- shares []PrivateKey
- shareIndex map[ID]int
+ shares []PrivateKey
+ shareIndex map[ID]int
+ masterPrivateKey []bls.SecretKey
}
// PublicKeyShares represents a public key shares for DKG protocol.
@@ -91,21 +97,15 @@ func NewPrivateKey() *PrivateKey {
}
}
-// NewPrivateKeyShares creates a private key shares for k-of-n TSIG.
-func NewPrivateKeyShares(k int, IDs IDs) (*PrivateKeyShares, *PublicKeyShares) {
+// NewPrivateKeyShares creates a DKG private key shares of threshold t.
+func NewPrivateKeyShares(t int) (*PrivateKeyShares, *PublicKeyShares) {
var prv bls.SecretKey
prv.SetByCSPRNG()
- msk := prv.GetMasterSecretKey(k)
+ msk := prv.GetMasterSecretKey(t)
mpk := bls.GetMasterPublicKey(msk)
- shares := make([]PrivateKey, len(IDs))
- shareMap := make(map[ID]int)
- for idx, ID := range IDs {
- shares[idx].privateKey.Set(msk, &ID)
- shareMap[ID] = idx
- }
return &PrivateKeyShares{
- shares: shares,
- shareIndex: shareMap,
+ masterPrivateKey: msk,
+ shareIndex: make(map[ID]int),
}, &PublicKeyShares{
shareIndex: make(map[ID]int),
masterPublicKey: mpk,
@@ -119,6 +119,16 @@ func NewEmptyPrivateKeyShares() *PrivateKeyShares {
}
}
+// SetParticipants sets the DKG participants.
+func (prvs *PrivateKeyShares) SetParticipants(IDs IDs) {
+ prvs.shares = make([]PrivateKey, len(IDs))
+ prvs.shareIndex = make(map[ID]int, len(IDs))
+ for idx, ID := range IDs {
+ prvs.shares[idx].privateKey.Set(prvs.masterPrivateKey, &ID)
+ prvs.shareIndex[ID] = idx
+ }
+}
+
// AddShare adds a share.
func (prvs *PrivateKeyShares) AddShare(ID ID, share *PrivateKey) error {
if idx, exist := prvs.shareIndex[ID]; exist {
@@ -258,6 +268,15 @@ func (pubs *PublicKeyShares) RecoverPublicKey(qualifyIDs IDs) (
return &pub, nil
}
+// MasterKeyBytes returns []byte representation of master public key.
+func (pubs *PublicKeyShares) MasterKeyBytes() []byte {
+ bytes := make([]byte, 0, len(pubs.masterPublicKey)*publicKeyLength)
+ for _, pk := range pubs.masterPublicKey {
+ bytes = append(bytes, pk.Serialize()...)
+ }
+ return bytes
+}
+
// newPublicKey creates a new PublicKey structure.
func newPublicKey(prvKey *bls.SecretKey) *PublicKey {
return &PublicKey{
@@ -277,6 +296,11 @@ func (prv *PrivateKey) Sign(hash common.Hash) (crypto.Signature, error) {
return crypto.Signature(sign.Serialize()), nil
}
+// Bytes returns []byte representation of private key.
+func (prv *PrivateKey) Bytes() []byte {
+ return prv.privateKey.GetLittleEndian()
+}
+
// VerifySignature checks that the given public key created signature over hash.
func (pub PublicKey) VerifySignature(
hash common.Hash, signature crypto.Signature) bool {
@@ -289,7 +313,7 @@ func (pub PublicKey) VerifySignature(
return sig.Verify(&pub.publicKey, msg)
}
-// Bytes returns the []byte representation of public key.
+// Bytes returns []byte representation of public key.
func (pub PublicKey) Bytes() []byte {
var bytes []byte
pub.publicKey.Deserialize(bytes)
diff --git a/crypto/dkg/dkg_test.go b/crypto/dkg/dkg_test.go
index 4c2c344..8e8ce79 100644
--- a/crypto/dkg/dkg_test.go
+++ b/crypto/dkg/dkg_test.go
@@ -53,6 +53,7 @@ func (s *DKGTestSuite) genID(k int) IDs {
}
func (s *DKGTestSuite) sendKey(senders []member, receivers []member) {
+ receiveFrom := make(map[ID][]member)
for _, sender := range senders {
for _, receiver := range receivers {
// Here's the demonstration of DKG protocol. `pubShares` is broadcasted
@@ -71,7 +72,19 @@ func (s *DKGTestSuite) sendKey(senders []member, receivers []member) {
VerifyPubShare(receiver.id, pubShare)
s.Require().NoError(err)
s.Require().True(valid)
- err = receiver.receivedPrvShares.AddShare(sender.id, prvShare)
+ receiveFrom[receiver.id] = append(receiveFrom[receiver.id], sender)
+ }
+ }
+ // The received order do not need to be the same.
+ for _, receiver := range receivers {
+ rand.Shuffle(len(senders), func(i, j int) {
+ receiveFrom[receiver.id][i], receiveFrom[receiver.id][j] =
+ receiveFrom[receiver.id][j], receiveFrom[receiver.id][i]
+ })
+ for _, sender := range receiveFrom[receiver.id] {
+ prvShare, ok := sender.prvShares.Share(receiver.id)
+ s.Require().True(ok)
+ err := receiver.receivedPrvShares.AddShare(sender.id, prvShare)
s.Require().NoError(err)
}
}
@@ -119,7 +132,8 @@ func (s *DKGTestSuite) TestVerifyKeyShares() {
})
}
- prvShares, pubShares := NewPrivateKeyShares(2, ids)
+ prvShares, pubShares := NewPrivateKeyShares(2)
+ prvShares.SetParticipants(ids)
_, ok := prvShares.Share(invalidID)
s.False(ok)
@@ -150,7 +164,8 @@ func (s *DKGTestSuite) TestVerifyKeyShares() {
// Test of faulty signature.
for idx := range members {
- members[idx].prvShares, members[idx].pubShares = NewPrivateKeyShares(2, ids)
+ members[idx].prvShares, members[idx].pubShares = NewPrivateKeyShares(2)
+ members[idx].prvShares.SetParticipants(ids)
members[idx].receivedPrvShares = NewEmptyPrivateKeyShares()
}
s.sendKey(members, members)
@@ -190,7 +205,8 @@ func (s *DKGTestSuite) TestDKGProtocol() {
}
for idx := range members {
- members[idx].prvShares, members[idx].pubShares = NewPrivateKeyShares(k, ids)
+ members[idx].prvShares, members[idx].pubShares = NewPrivateKeyShares(k)
+ members[idx].prvShares.SetParticipants(ids)
members[idx].receivedPrvShares = NewEmptyPrivateKeyShares()
}
// Randomly select non-disqualified members.
@@ -292,8 +308,8 @@ func BenchmarkDKGProtocol(b *testing.B) {
ids[n-1] = self.id
prvShares := make(map[ID]*PrivateKey, n)
for idx := range members {
- members[idx].prvShares, members[idx].pubShares = NewPrivateKeyShares(
- t, ids)
+ members[idx].prvShares, members[idx].pubShares = NewPrivateKeyShares(t)
+ members[idx].prvShares.SetParticipants(ids)
prvShare, ok := members[idx].prvShares.Share(self.id)
if !ok {
b.FailNow()
@@ -302,7 +318,8 @@ func BenchmarkDKGProtocol(b *testing.B) {
}
b.StartTimer()
- self.prvShares, self.pubShares = NewPrivateKeyShares(t, ids)
+ self.prvShares, self.pubShares = NewPrivateKeyShares(t)
+ self.prvShares.SetParticipants(ids)
self.receivedPrvShares = NewEmptyPrivateKeyShares()
for _, member := range members {
self.receivedPubShares[member.id] = member.pubShares
diff --git a/simulation/governance.go b/simulation/governance.go
index ebfd32b..159d536 100644
--- a/simulation/governance.go
+++ b/simulation/governance.go
@@ -29,6 +29,7 @@ import (
// simGovernance is a simulated governance contract implementing the
// core.Governance interface.
type simGovernance struct {
+ id types.ValidatorID
lock sync.RWMutex
notarySet map[types.ValidatorID]struct{}
expectedNumValidators int
@@ -39,12 +40,15 @@ type simGovernance struct {
dkgComplaint map[uint64][]*types.DKGComplaint
dkgMasterPublicKey map[uint64][]*types.DKGMasterPublicKey
lambda time.Duration
+ network *network
}
// newSimGovernance returns a new simGovernance instance.
func newSimGovernance(
+ id types.ValidatorID,
numValidators int, consensusConfig config.Consensus) *simGovernance {
return &simGovernance{
+ id: id,
notarySet: make(map[types.ValidatorID]struct{}),
expectedNumValidators: numValidators,
k: consensusConfig.K,
@@ -57,9 +61,12 @@ func newSimGovernance(
}
}
+func (g *simGovernance) setNetwork(network *network) {
+ g.network = network
+}
+
// GetNotarySet returns the current notary set.
func (g *simGovernance) GetNotarySet() map[types.ValidatorID]struct{} {
-
g.lock.RLock()
defer g.lock.RUnlock()
@@ -99,8 +106,12 @@ func (g *simGovernance) addValidator(vID types.ValidatorID) {
// AddDKGComplaint adds a DKGComplaint.
func (g *simGovernance) AddDKGComplaint(complaint *types.DKGComplaint) {
+ // TODO(jimmy-dexon): check if the input is valid.
g.dkgComplaint[complaint.Round] = append(
g.dkgComplaint[complaint.Round], complaint)
+ if complaint.ProposerID == g.id {
+ g.network.broadcast(complaint)
+ }
}
// DKGComplaints returns the DKGComplaints of round.
@@ -115,8 +126,12 @@ func (g *simGovernance) DKGComplaints(round uint64) []*types.DKGComplaint {
// AddDKGMasterPublicKey adds a DKGMasterPublicKey.
func (g *simGovernance) AddDKGMasterPublicKey(
masterPublicKey *types.DKGMasterPublicKey) {
+ // TODO(jimmy-dexon): check if the input is valid.
g.dkgMasterPublicKey[masterPublicKey.Round] = append(
g.dkgMasterPublicKey[masterPublicKey.Round], masterPublicKey)
+ if masterPublicKey.ProposerID == g.id {
+ g.network.broadcast(masterPublicKey)
+ }
}
// DKGMasterPublicKeys returns the DKGMasterPublicKeys of round.
diff --git a/simulation/network.go b/simulation/network.go
index 89b4f59..83c4daf 100644
--- a/simulation/network.go
+++ b/simulation/network.go
@@ -138,6 +138,13 @@ func (n *network) BroadcastNotaryAck(notaryAck *types.NotaryAck) {
}
}
+// broadcast message to all other validators in the network.
+func (n *network) broadcast(message interface{}) {
+ if err := n.trans.Broadcast(message); err != nil {
+ panic(err)
+ }
+}
+
// SendDKGPrivateShare implements core.Network interface.
func (n *network) SendDKGPrivateShare(
recv types.ValidatorID, prvShare *types.DKGPrivateShare) {
@@ -181,7 +188,8 @@ func (n *network) run() {
// to consensus or validator, that's the question.
disp := func(e *test.TransportEnvelope) {
switch e.Msg.(type) {
- case *types.Block, *types.Vote, *types.NotaryAck:
+ case *types.Block, *types.Vote, *types.NotaryAck,
+ *types.DKGPrivateShare, *types.DKGPartialSignature:
n.toConsensus <- e.Msg
default:
n.toValidator <- e.Msg
diff --git a/simulation/validator.go b/simulation/validator.go
index 12ff308..1662dc0 100644
--- a/simulation/validator.go
+++ b/simulation/validator.go
@@ -58,7 +58,7 @@ func newValidator(
if err != nil {
panic(err)
}
- gov := newSimGovernance(config.Validator.Num, config.Validator.Consensus)
+ gov := newSimGovernance(id, config.Validator.Num, config.Validator.Consensus)
return &validator{
ID: id,
prvKey: prvKey,
@@ -85,6 +85,7 @@ func (v *validator) run(serverEndpoint interface{}, legacy bool) {
msgChannel := v.netModule.receiveChanForValidator()
peers := v.netModule.peers()
go v.netModule.run()
+ v.gov.setNetwork(v.netModule)
// Run consensus.
hashes := make(common.Hashes, 0, len(peers))
for vID := range peers {
@@ -115,6 +116,10 @@ MainLoop:
if val == statusShutdown {
break MainLoop
}
+ case *types.DKGComplaint:
+ v.gov.AddDKGComplaint(val)
+ case *types.DKGMasterPublicKey:
+ v.gov.AddDKGMasterPublicKey(val)
default:
panic(fmt.Errorf("unexpected message from server: %v", val))
}