// Copyright 2018 The dexon-consensus Authors
// This file is part of the dexon-consensus library.
//
// The dexon-consensus 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 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 library. If not, see
// <http://www.gnu.org/licenses/>.
package core
import (
"testing"
"github.com/stretchr/testify/suite"
"github.com/dexon-foundation/dexon-consensus/common"
"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/ecdsa"
"github.com/dexon-foundation/dexon-consensus/core/test"
"github.com/dexon-foundation/dexon-consensus/core/types"
typesDKG "github.com/dexon-foundation/dexon-consensus/core/types/dkg"
"github.com/dexon-foundation/dexon-consensus/core/utils"
)
type DKGTSIGProtocolTestSuite struct {
suite.Suite
nIDs types.NodeIDs
dkgIDs map[types.NodeID]dkg.ID
signers map[types.NodeID]*utils.Signer
}
type testDKGReceiver struct {
s *DKGTSIGProtocolTestSuite
signer *utils.Signer
complaints map[types.NodeID]*typesDKG.Complaint
mpk *typesDKG.MasterPublicKey
prvShare map[types.NodeID]*typesDKG.PrivateShare
antiComplaints map[types.NodeID]*typesDKG.PrivateShare
ready []*typesDKG.MPKReady
final []*typesDKG.Finalize
}
func newTestDKGReceiver(s *DKGTSIGProtocolTestSuite,
signer *utils.Signer) *testDKGReceiver {
return &testDKGReceiver{
s: s,
signer: signer,
complaints: make(map[types.NodeID]*typesDKG.Complaint),
prvShare: make(map[types.NodeID]*typesDKG.PrivateShare),
antiComplaints: make(map[types.NodeID]*typesDKG.PrivateShare),
}
}
func (r *testDKGReceiver) ProposeDKGComplaint(complaint *typesDKG.Complaint) {
complaint = test.CloneDKGComplaint(complaint)
err := r.signer.SignDKGComplaint(complaint)
r.s.Require().NoError(err)
r.complaints[complaint.PrivateShare.ProposerID] = complaint
}
func (r *testDKGReceiver) ProposeDKGMasterPublicKey(
mpk *typesDKG.MasterPublicKey) {
mpk = test.CloneDKGMasterPublicKey(mpk)
err := r.signer.SignDKGMasterPublicKey(mpk)
r.s.Require().NoError(err)
r.mpk = mpk
}
func (r *testDKGReceiver) ProposeDKGPrivateShare(
prv *typesDKG.PrivateShare) {
prv = test.CloneDKGPrivateShare(prv)
err := r.signer.SignDKGPrivateShare(prv)
r.s.Require().NoError(err)
r.prvShare[prv.ReceiverID] = prv
}
func (r *testDKGReceiver) ProposeDKGAntiNackComplaint(
prv *typesDKG.PrivateShare) {
prv = test.CloneDKGPrivateShare(prv)
err := r.signer.SignDKGPrivateShare(prv)
r.s.Require().NoError(err)
r.antiComplaints[prv.ReceiverID] = prv
}
func (r *testDKGReceiver) ProposeDKGMPKReady(ready *typesDKG.MPKReady) {
r.ready = append(r.ready, ready)
}
func (r *testDKGReceiver) ProposeDKGFinalize(final *typesDKG.Finalize) {
r.final = append(r.final, final)
}
func (s *DKGTSIGProtocolTestSuite) setupDKGParticipants(n int) {
s.nIDs = make(types.NodeIDs, 0, n)
s.signers = make(map[types.NodeID]*utils.Signer, n)
s.dkgIDs = make(map[types.NodeID]dkg.ID)
ids := make(dkg.IDs, 0, n)
for i := 0; i < n; i++ {
prvKey, err := ecdsa.NewPrivateKey()
s.Require().NoError(err)
nID := types.NewNodeID(prvKey.PublicKey())
s.nIDs = append(s.nIDs, nID)
s.signers[nID] = utils.NewSigner(prvKey)
id := dkg.NewID(nID.Hash[:])
ids = append(ids, id)
s.dkgIDs[nID] = id
}
}
func (s *DKGTSIGProtocolTestSuite) newGov(
pubKeys []crypto.PublicKey,
round, reset uint64) *test.Governance {
// NOTE: this method doesn't make the tip round in governance to the input
// one.
gov, err := test.NewGovernance(test.NewState(DKGDelayRound,
pubKeys, 100, &common.NullLogger{}, true), ConfigRoundShift)
s.Require().NoError(err)
for i := uint64(0); i < reset; i++ {
s.Require().NoError(gov.State().RequestChange(test.StateResetDKG,
common.NewRandomHash()))
}
s.Require().Equal(gov.DKGResetCount(round), reset)
return gov
}
func (s *DKGTSIGProtocolTestSuite) newProtocols(k, n int, round, reset uint64) (
map[types.NodeID]*testDKGReceiver, map[types.NodeID]*dkgProtocol) {
s.setupDKGParticipants(n)
receivers := make(map[types.NodeID]*testDKGReceiver, n)
protocols := make(map[types.NodeID]*dkgProtocol, n)
for _, nID := range s.nIDs {
receivers[nID] = newTestDKGReceiver(s, s.signers[nID])
protocols[nID] = newDKGProtocol(
nID,
receivers[nID],
round,
reset,
k,
)
s.Require().NotNil(receivers[nID].mpk)
}
return receivers, protocols
}
// 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 := 2
n := 10
round := uint64(1)
reset := uint64(3)
_, pubKeys, err := test.NewKeys(5)
s.Require().NoError(err)
gov := s.newGov(pubKeys, round, reset)
receivers, protocols := s.newProtocols(k, n, round, reset)
for _, receiver := range receivers {
gov.AddDKGMasterPublicKey(round, 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 nID, prvShare := range receiver.prvShare {
s.Require().NoError(protocols[nID].processPrivateShare(prvShare))
}
}
for _, protocol := range protocols {
protocol.proposeNackComplaints()
}
for _, recv := range receivers {
s.Require().Len(recv.complaints, 0)
}
for _, receiver := range receivers {
for _, complaint := range receiver.complaints {
gov.AddDKGComplaint(round, complaint)
}
}
for _, protocol := range protocols {
s.Require().NoError(protocol.processNackComplaints(
gov.DKGComplaints(round)))
}
for _, recv := range receivers {
s.Require().Len(recv.antiComplaints, 0)
}
for _, protocol := range protocols {
protocol.enforceNackComplaints(gov.DKGComplaints(round))
}
for _, recv := range receivers {
s.Require().Len(recv.complaints, 0)
}
// DKG is fininished.
gpk, err := typesDKG.NewGroupPublicKey(round,
gov.DKGMasterPublicKeys(round), gov.DKGComplaints(round),
k)
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{}{}
}
for nID := range gpk.QualifyNodeIDs {
id, exist := gpk.IDMap[nID]
s.Require().True(exist)
_, exist = qualifyIDs[id]
s.Require().True(exist)
}
shareSecrets := make(
map[types.NodeID]*dkgShareSecret, len(qualifyIDs))
for nID, protocol := range protocols {
_, exist := qualifyIDs[s.dkgIDs[nID]]
s.Require().True(exist)
var err error
shareSecrets[nID], err = protocol.recoverShareSecret(gpk.QualifyIDs)
s.Require().NoError(err)
}
npks, err := typesDKG.NewNodePublicKeys(round,
gov.DKGMasterPublicKeys(round), gov.DKGComplaints(round), k)
s.Require().NoError(err)
msgHash := crypto.Keccak256Hash([]byte("🏖🍹"))
tsig := newTSigProtocol(npks, msgHash)
for nID, shareSecret := range shareSecrets {
psig := &typesDKG.PartialSignature{
ProposerID: nID,
Round: round,
Hash: msgHash,
PartialSignature: shareSecret.sign(msgHash),
}
err := s.signers[nID].SignDKGPartialSignature(psig)
s.Require().NoError(err)
s.Require().NoError(tsig.processPartialSignature(psig))
if len(tsig.sigs) >= k {
break
}
}
sig, err := tsig.signature()
s.Require().NoError(err)
s.True(gpk.VerifySignature(msgHash, sig))
}
func (s *DKGTSIGProtocolTestSuite) TestErrMPKRegistered() {
k := 2
n := 10
round := uint64(1)
reset := uint64(2)
_, pubKeys, err := test.NewKeys(5)
s.Require().NoError(err)
gov := s.newGov(pubKeys, round, reset)
receivers, protocols := s.newProtocols(k, n, round, reset)
notRegisterID := s.nIDs[0]
errRegisterID := s.nIDs[1]
for ID, receiver := range receivers {
if ID == notRegisterID {
continue
}
if ID == errRegisterID {
_, mpk := dkg.NewPrivateKeyShares(k)
receiver.ProposeDKGMasterPublicKey(&typesDKG.MasterPublicKey{
Round: round,
Reset: reset,
DKGID: typesDKG.NewID(ID),
PublicKeyShares: *mpk,
})
}
gov.AddDKGMasterPublicKey(round, receiver.mpk)
}
for ID, protocol := range protocols {
err := protocol.processMasterPublicKeys(gov.DKGMasterPublicKeys(round))
if ID == notRegisterID {
s.Require().Equal(ErrSelfMPKNotRegister, err)
} else if ID == errRegisterID {
s.Require().Equal(ErrSelfPrvShareMismatch, err)
} else {
s.Require().NoError(err)
}
}
for ID, receiver := range receivers {
if ID == notRegisterID || ID == errRegisterID {
continue
}
s.Require().Len(receiver.prvShare, n-1)
for nID, prvShare := range receiver.prvShare {
s.Require().NoError(protocols[nID].processPrivateShare(prvShare))
}
}
for ID, protocol := range protocols {
if ID == notRegisterID {
continue
}
protocol.proposeNackComplaints()
}
for ID, recv := range receivers {
if ID == notRegisterID || ID == errRegisterID {
continue
}
s.Require().Len(recv.complaints, 1)
for _, complaint := range recv.complaints {
s.Require().True(complaint.IsNack())
s.Require().Equal(errRegisterID, complaint.PrivateShare.ProposerID)
}
}
for _, receiver := range receivers {
for _, complaint := range receiver.complaints {
gov.AddDKGComplaint(round, complaint)
}
}
s.Require().Len(gov.DKGComplaints(round), n-1)
for ID, protocol := range protocols {
err := protocol.processNackComplaints(gov.DKGComplaints(round))
if ID == notRegisterID {
s.Require().Equal(ErrSelfMPKNotRegister, err)
} else if ID == errRegisterID {
s.Require().Equal(ErrSelfPrvShareMismatch, err)
} else {
s.Require().NoError(err)
}
}
for _, recv := range receivers {
s.Require().Len(recv.antiComplaints, 0)
}
for _, protocol := range protocols {
protocol.enforceNackComplaints(gov.DKGComplaints(round))
}
for ID, recv := range receivers {
if ID == notRegisterID || ID == errRegisterID {
continue
}
s.Require().Len(recv.complaints, 1)
for _, complaint := range recv.complaints {
s.Require().True(complaint.IsNack())
s.Require().Equal(errRegisterID, complaint.PrivateShare.ProposerID)
}
}
// DKG is fininished.
gpk, err := typesDKG.NewGroupPublicKey(round,
gov.DKGMasterPublicKeys(round), gov.DKGComplaints(round),
k,
)
s.Require().NoError(err)
s.Require().Len(gpk.QualifyIDs, n-2)
qualifyIDs := make(map[dkg.ID]struct{}, len(gpk.QualifyIDs))
for _, id := range gpk.QualifyIDs {
qualifyIDs[id] = struct{}{}
}
for nID := range gpk.QualifyNodeIDs {
if nID == notRegisterID || nID == errRegisterID {
continue
}
id, exist := gpk.IDMap[nID]
s.Require().True(exist)
_, exist = qualifyIDs[id]
s.Require().True(exist)
}
}
func (s *DKGTSIGProtocolTestSuite) TestNackComplaint() {
k := 3
n := 10
round := uint64(1)
reset := uint64(3)
_, pubKeys, err := test.NewKeys(5)
s.Require().NoError(err)
gov := s.newGov(pubKeys, round, reset)
receivers, protocols := s.newProtocols(k, n, round, reset)
byzantineID := s.nIDs[0]
for _, receiver := range receivers {
gov.AddDKGMasterPublicKey(round, receiver.mpk)
}
for _, protocol := range protocols {
s.Require().NoError(
protocol.processMasterPublicKeys(gov.DKGMasterPublicKeys(round)))
}
for senderID, receiver := range receivers {
s.Require().Len(receiver.prvShare, n)
if senderID == byzantineID {
continue
}
for nID, prvShare := range receiver.prvShare {
s.Require().NoError(protocols[nID].processPrivateShare(prvShare))
}
}
for _, protocol := range protocols {
protocol.proposeNackComplaints()
}
for _, recv := range receivers {
complaint, exist := recv.complaints[byzantineID]
s.True(complaint.IsNack())
s.Require().True(exist)
s.True(utils.VerifyDKGComplaintSignature(complaint))
}
}
// TestComplaint tests if the received private share is not valid, a complaint
// should be proposed.
func (s *DKGTSIGProtocolTestSuite) TestComplaint() {
k := 3
n := 10
round := uint64(1)
reset := uint64(3)
_, pubKeys, err := test.NewKeys(5)
s.Require().NoError(err)
gov := s.newGov(pubKeys, round, reset)
receivers, protocols := s.newProtocols(k, n, round, reset)
byzantineID := s.nIDs[0]
targetID := s.nIDs[1]
receiver := receivers[targetID]
protocol := protocols[targetID]
for _, receiver := range receivers {
gov.AddDKGMasterPublicKey(round, receiver.mpk)
}
for _, protocol := range protocols {
s.Require().NoError(
protocol.processMasterPublicKeys(gov.DKGMasterPublicKeys(round)))
}
// These messages are not valid.
err = protocol.processPrivateShare(&typesDKG.PrivateShare{
ProposerID: types.NodeID{Hash: common.NewRandomHash()},
ReceiverID: targetID,
Round: round,
Reset: reset,
})
s.Equal(ErrNotDKGParticipant, err)
receivers[byzantineID].ProposeDKGPrivateShare(&typesDKG.PrivateShare{
ProposerID: byzantineID,
ReceiverID: targetID,
Round: round,
Reset: reset,
})
invalidShare := receivers[byzantineID].prvShare[targetID]
invalidShare.ReceiverID = s.nIDs[2]
err = protocol.processPrivateShare(invalidShare)
s.Equal(ErrIncorrectPrivateShareSignature, err)
delete(receivers[byzantineID].prvShare, targetID)
// Byzantine node is sending incorrect private share.
receivers[byzantineID].ProposeDKGPrivateShare(&typesDKG.PrivateShare{
ProposerID: byzantineID,
ReceiverID: targetID,
Round: round,
Reset: reset,
PrivateShare: *dkg.NewPrivateKey(),
})
invalidShare = receivers[byzantineID].prvShare[targetID]
s.Require().NoError(protocol.processPrivateShare(invalidShare))
s.Require().Len(receiver.complaints, 1)
complaint, exist := receiver.complaints[byzantineID]
s.True(exist)
s.Equal(byzantineID, complaint.PrivateShare.ProposerID)
// Sending the incorrect private share again should not complain.
delete(receiver.complaints, byzantineID)
s.Require().NoError(protocol.processPrivateShare(invalidShare))
s.Len(receiver.complaints, 0)
}
// TestDuplicateComplaint tests if the duplicated complaint is process properly.
func (s *DKGTSIGProtocolTestSuite) TestDuplicateComplaint() {
k := 3
n := 10
round := uint64(1)
reset := uint64(3)
_, pubKeys, err := test.NewKeys(5)
s.Require().NoError(err)
gov := s.newGov(pubKeys, round, reset)
receivers, _ := s.newProtocols(k, n, round, reset)
byzantineID := s.nIDs[0]
victomID := s.nIDs[1]
for _, receiver := range receivers {
gov.AddDKGMasterPublicKey(round, receiver.mpk)
}
// Test for nack complaints.
complaints := make([]*typesDKG.Complaint, k+1)
for i := range complaints {
complaints[i] = &typesDKG.Complaint{
ProposerID: byzantineID,
Round: round,
Reset: reset,
PrivateShare: typesDKG.PrivateShare{
ProposerID: victomID,
Round: round,
Reset: reset,
},
}
s.Require().True(complaints[i].IsNack())
}
gpk, err := typesDKG.NewGroupPublicKey(round,
gov.DKGMasterPublicKeys(round), complaints,
k,
)
s.Require().NoError(err)
s.Require().Len(gpk.QualifyIDs, n)
}
// TestAntiComplaint tests if a nack complaint is received,
// create an anti complaint.
func (s *DKGTSIGProtocolTestSuite) TestAntiComplaint() {
k := 3
n := 10
round := uint64(1)
reset := uint64(3)
_, pubKeys, err := test.NewKeys(5)
s.Require().NoError(err)
gov := s.newGov(pubKeys, round, reset)
receivers, protocols := s.newProtocols(k, n, round, reset)
byzantineID := s.nIDs[0]
targetID := s.nIDs[1]
thirdPerson := s.nIDs[2]
for _, receiver := range receivers {
gov.AddDKGMasterPublicKey(round, receiver.mpk)
}
for _, protocol := range protocols {
s.Require().NoError(
protocol.processMasterPublicKeys(gov.DKGMasterPublicKeys(round)))
}
// Creating Nack complaint.
protocols[targetID].proposeNackComplaints()
protocols[thirdPerson].proposeNackComplaints()
complaint, exist := receivers[targetID].complaints[byzantineID]
s.Require().True(exist)
s.Require().True(complaint.IsNack())
s.Require().Equal(byzantineID, complaint.PrivateShare.ProposerID)
complaint2, exist := receivers[thirdPerson].complaints[byzantineID]
s.Require().True(exist)
s.Require().True(complaint2.IsNack())
s.Require().Equal(byzantineID, complaint2.PrivateShare.ProposerID)
// Creating an anti-nack complaint.
err = protocols[byzantineID].processNackComplaints(
[]*typesDKG.Complaint{complaint})
s.Require().NoError(err)
s.Require().Len(receivers[byzantineID].antiComplaints, 1)
antiComplaint, exist := receivers[byzantineID].antiComplaints[targetID]
s.Require().True(exist)
s.Require().Equal(targetID, antiComplaint.ReceiverID)
// The anti-complaint should be successfully verified by all others.
receivers[targetID].complaints = make(map[types.NodeID]*typesDKG.Complaint)
s.Require().NoError(protocols[targetID].processPrivateShare(antiComplaint))
s.Len(receivers[targetID].complaints, 0)
receivers[thirdPerson].complaints = make(map[types.NodeID]*typesDKG.Complaint)
s.Require().NoError(protocols[thirdPerson].processPrivateShare(antiComplaint))
s.Len(receivers[thirdPerson].complaints, 0)
}
// TestEncorceNackComplaint tests if the nack complaint is enforced properly.
func (s *DKGTSIGProtocolTestSuite) TestEncorceNackComplaint() {
k := 3
n := 10
round := uint64(1)
reset := uint64(3)
_, pubKeys, err := test.NewKeys(5)
s.Require().NoError(err)
gov := s.newGov(pubKeys, round, reset)
receivers, protocols := s.newProtocols(k, n, round, reset)
byzantineID := s.nIDs[0]
targetID := s.nIDs[1]
thirdPerson := s.nIDs[2]
for _, receiver := range receivers {
gov.AddDKGMasterPublicKey(round, receiver.mpk)
}
for _, protocol := range protocols {
s.Require().NoError(
protocol.processMasterPublicKeys(gov.DKGMasterPublicKeys(round)))
}
// Creating nack complaint.
protocols[targetID].proposeNackComplaints()
complaint, exist := receivers[targetID].complaints[byzantineID]
s.Require().True(exist)
s.Require().True(complaint.IsNack())
s.Require().Equal(byzantineID, complaint.PrivateShare.ProposerID)
// Encorce nack complaint.
protocols[thirdPerson].enforceNackComplaints([]*typesDKG.Complaint{complaint})
complaint2, exist := receivers[thirdPerson].complaints[byzantineID]
s.Require().True(exist)
s.Require().True(complaint2.IsNack())
s.Require().Equal(byzantineID, complaint2.PrivateShare.ProposerID)
// Received valid private share, do not enforce nack complaint.
delete(receivers[thirdPerson].complaints, byzantineID)
err = protocols[byzantineID].processNackComplaints(
[]*typesDKG.Complaint{complaint})
s.Require().NoError(err)
antiComplaint, exist := receivers[byzantineID].antiComplaints[targetID]
s.Require().True(exist)
s.Require().Equal(targetID, antiComplaint.ReceiverID)
s.Require().NoError(protocols[thirdPerson].processPrivateShare(antiComplaint))
protocols[thirdPerson].enforceNackComplaints([]*typesDKG.Complaint{complaint})
_, exist = receivers[thirdPerson].complaints[byzantineID]
s.Require().False(exist)
}
// TestQualifyIDs tests if there is a id with t+1 nack complaints
// or a complaint, it should not be in the qualifyIDs.
func (s *DKGTSIGProtocolTestSuite) TestQualifyIDs() {
k := 3
n := 10
round := uint64(1)
reset := uint64(3)
_, pubKeys, err := test.NewKeys(5)
s.Require().NoError(err)
gov := s.newGov(pubKeys, round, reset)
receivers, _ := s.newProtocols(k, n, round, reset)
byzantineID := s.nIDs[0]
for _, receiver := range receivers {
gov.AddDKGMasterPublicKey(round, receiver.mpk)
}
// Test for nack complaints.
complaints := make([]*typesDKG.Complaint, k+1)
for i := range complaints {
nID := s.nIDs[i]
complaints[i] = &typesDKG.Complaint{
ProposerID: nID,
Round: round,
Reset: reset,
PrivateShare: typesDKG.PrivateShare{
ProposerID: byzantineID,
Round: round,
Reset: reset,
},
}
s.Require().True(complaints[i].IsNack())
}
gpk, err := typesDKG.NewGroupPublicKey(round,
gov.DKGMasterPublicKeys(round), complaints,
k,
)
s.Require().NoError(err)
s.Require().Len(gpk.QualifyIDs, n-1)
for _, id := range gpk.QualifyIDs {
s.NotEqual(id, byzantineID)
}
gpk2, err := typesDKG.NewGroupPublicKey(round,
gov.DKGMasterPublicKeys(round), complaints[:k],
k,
)
s.Require().NoError(err)
s.Require().Len(gpk2.QualifyIDs, n)
// Test for complaint.
complaints[0].PrivateShare.Signature = crypto.Signature{Signature: []byte{0}}
s.Require().False(complaints[0].IsNack())
gpk3, err := typesDKG.NewGroupPublicKey(round,
gov.DKGMasterPublicKeys(round), complaints[:1],
k,
)
s.Require().NoError(err)
s.Require().Len(gpk3.QualifyIDs, n-1)
for _, id := range gpk3.QualifyIDs {
s.NotEqual(id, byzantineID)
}
}
// TestPartialSignature tests if tsigProtocol can handle incorrect partial
// signature and report error.
func (s *DKGTSIGProtocolTestSuite) TestPartialSignature() {
k := 3
n := 10
round := uint64(1)
reset := uint64(3)
_, pubKeys, err := test.NewKeys(5)
s.Require().NoError(err)
gov := s.newGov(pubKeys, round, reset)
receivers, protocols := s.newProtocols(k, n, round, reset)
byzantineID := s.nIDs[0]
for _, receiver := range receivers {
gov.AddDKGMasterPublicKey(round, receiver.mpk)
}
for _, protocol := range protocols {
s.Require().NoError(
protocol.processMasterPublicKeys(gov.DKGMasterPublicKeys(round)))
}
for senderID, receiver := range receivers {
s.Require().Len(receiver.prvShare, n)
if senderID == byzantineID {
continue
}
for nID, prvShare := range receiver.prvShare {
s.Require().NoError(protocols[nID].processPrivateShare(prvShare))
}
}
for _, protocol := range protocols {
protocol.proposeNackComplaints()
}
for _, recv := range receivers {
s.Require().Len(recv.complaints, 1)
complaint, exist := recv.complaints[byzantineID]
s.Require().True(exist)
gov.AddDKGComplaint(round, complaint)
}
// DKG is fininished.
gpk, err := typesDKG.NewGroupPublicKey(round,
gov.DKGMasterPublicKeys(round), gov.DKGComplaints(round),
k,
)
s.Require().NoError(err)
s.Require().Len(gpk.QualifyIDs, n-1)
qualifyIDs := make(map[dkg.ID]struct{}, len(gpk.QualifyIDs))
for _, id := range gpk.QualifyIDs {
qualifyIDs[id] = struct{}{}
}
shareSecrets := make(
map[types.NodeID]*dkgShareSecret, len(qualifyIDs))
for nID, protocol := range protocols {
_, exist := qualifyIDs[s.dkgIDs[nID]]
if nID == byzantineID {
exist = !exist
}
s.Require().True(exist)
var err error
shareSecrets[nID], err = protocol.recoverShareSecret(gpk.QualifyIDs)
s.Require().NoError(err)
}
msgHash := crypto.Keccak256Hash([]byte("🏖🍹"))
npks, err := typesDKG.NewNodePublicKeys(round,
gov.DKGMasterPublicKeys(round), gov.DKGComplaints(round), k)
s.Require().NoError(err)
tsig := newTSigProtocol(npks, msgHash)
byzantineID2 := s.nIDs[1]
byzantineID3 := s.nIDs[2]
for nID, shareSecret := range shareSecrets {
psig := &typesDKG.PartialSignature{
ProposerID: nID,
Round: round,
Hash: msgHash,
PartialSignature: shareSecret.sign(msgHash),
}
switch nID {
case byzantineID2:
psig.PartialSignature = shareSecret.sign(
crypto.Keccak256Hash([]byte("💣")))
case byzantineID3:
psig.Hash = common.NewRandomHash()
}
err := s.signers[nID].SignDKGPartialSignature(psig)
s.Require().NoError(err)
err = tsig.processPartialSignature(psig)
switch nID {
case byzantineID:
s.Require().Equal(ErrNotQualifyDKGParticipant, err)
case byzantineID2:
s.Require().Equal(ErrIncorrectPartialSignature, err)
case byzantineID3:
s.Require().Equal(ErrMismatchPartialSignatureHash, err)
default:
s.Require().NoError(err)
}
}
sig, err := tsig.signature()
s.Require().NoError(err)
s.True(gpk.VerifySignature(msgHash, sig))
}
func (s *DKGTSIGProtocolTestSuite) TestProposeReady() {
prvKey, err := ecdsa.NewPrivateKey()
s.Require().NoError(err)
recv := newTestDKGReceiver(s, utils.NewSigner(prvKey))
nID := types.NewNodeID(prvKey.PublicKey())
protocol := newDKGProtocol(nID, recv, 1, 3, 2)
protocol.proposeMPKReady()
s.Require().Len(recv.ready, 1)
ready := recv.ready[0]
s.Equal(&typesDKG.MPKReady{
ProposerID: nID,
Round: 1,
Reset: 3,
}, ready)
}
func (s *DKGTSIGProtocolTestSuite) TestProposeFinalize() {
prvKey, err := ecdsa.NewPrivateKey()
s.Require().NoError(err)
recv := newTestDKGReceiver(s, utils.NewSigner(prvKey))
nID := types.NewNodeID(prvKey.PublicKey())
protocol := newDKGProtocol(nID, recv, 1, 3, 2)
protocol.proposeFinalize()
s.Require().Len(recv.final, 1)
final := recv.final[0]
s.Equal(&typesDKG.Finalize{
ProposerID: nID,
Round: 1,
Reset: 3,
}, final)
}
func (s *DKGTSIGProtocolTestSuite) TestTSigVerifierCache() {
k := 3
n := 10
round := uint64(10)
reset := uint64(0)
_, pubKeys, err := test.NewKeys(n)
s.Require().NoError(err)
gov := s.newGov(pubKeys, round, reset)
gov.CatchUpWithRound(round)
for i := 0; i < 10; i++ {
round := uint64(i + 1)
receivers, protocols := s.newProtocols(k, n, round, reset)
for _, receiver := range receivers {
gov.AddDKGMasterPublicKey(round, receiver.mpk)
}
for _, protocol := range protocols {
protocol.proposeMPKReady()
}
for _, recv := range receivers {
s.Require().Len(recv.ready, 1)
gov.AddDKGMPKReady(recv.ready[0].Round, recv.ready[0])
}
s.Require().True(gov.IsDKGMPKReady(round))
for _, protocol := range protocols {
protocol.proposeFinalize()
}
for _, recv := range receivers {
s.Require().Len(recv.final, 1)
gov.AddDKGFinalize(recv.final[0].Round, recv.final[0])
}
s.Require().True(gov.IsDKGFinal(round))
}
cache := NewTSigVerifierCache(gov, 3)
for i := 0; i < 5; i++ {
round := uint64(i + 1)
ok, err := cache.Update(round)
s.Require().NoError(err)
s.True(ok)
}
s.Len(cache.verifier, 3)
for i := 0; i < 2; i++ {
round := uint64(i + 1)
_, exist := cache.Get(round)
s.False(exist)
}
for i := 3; i < 5; i++ {
round := uint64(i + 1)
_, exist := cache.Get(round)
s.True(exist)
}
ok, err := cache.Update(uint64(1))
s.Require().Equal(ErrRoundAlreadyPurged, err)
cache.Delete(uint64(5))
s.Len(cache.verifier, 2)
_, exist := cache.Get(uint64(5))
s.False(exist)
cache = NewTSigVerifierCache(gov, 1)
ok, err = cache.Update(uint64(3))
s.Require().NoError(err)
s.Require().True(ok)
s.Equal(uint64(3), cache.minRound)
ok, err = cache.Update(uint64(5))
s.Require().NoError(err)
s.Require().True(ok)
s.Equal(uint64(5), cache.minRound)
}
func (s *DKGTSIGProtocolTestSuite) TestUnexpectedDKGResetCount() {
// MPKs and private shares from unexpected reset count should be ignored.
k := 2
n := 10
round := uint64(1)
reset := uint64(3)
receivers, protocols := s.newProtocols(k, n, round, reset)
var sourceID, targetID types.NodeID
for sourceID = range receivers {
break
}
for targetID = range receivers {
break
}
// Test private share
s.Require().NoError(protocols[targetID].processMasterPublicKeys(
[]*typesDKG.MasterPublicKey{
receivers[targetID].mpk,
receivers[sourceID].mpk}))
receivers[sourceID].ProposeDKGPrivateShare(&typesDKG.PrivateShare{
ProposerID: sourceID,
ReceiverID: targetID,
Round: round,
Reset: reset + 1,
PrivateShare: *dkg.NewPrivateKey(),
})
err := protocols[targetID].processPrivateShare(
receivers[sourceID].prvShare[targetID])
s.Require().IsType(ErrUnexpectedDKGResetCount{}, err)
// Test MPK.
_, mpk := dkg.NewPrivateKeyShares(k)
receivers[sourceID].ProposeDKGMasterPublicKey(&typesDKG.MasterPublicKey{
Round: round,
Reset: reset + 1,
DKGID: typesDKG.NewID(sourceID),
PublicKeyShares: *mpk,
})
err = protocols[sourceID].processMasterPublicKeys(
[]*typesDKG.MasterPublicKey{receivers[sourceID].mpk})
s.Require().IsType(ErrUnexpectedDKGResetCount{}, err)
}
func TestDKGTSIGProtocol(t *testing.T) {
suite.Run(t, new(DKGTSIGProtocolTestSuite))
}
func BenchmarkGPK4_7(b *testing.B) { benchmarkDKGGroupPubliKey(4, 7, b) }
func BenchmarkGPK9_13(b *testing.B) { benchmarkDKGGroupPubliKey(9, 13, b) }
func BenchmarkGPK17_24(b *testing.B) { benchmarkDKGGroupPubliKey(17, 24, b) }
func BenchmarkGPK81_121(b *testing.B) { benchmarkDKGGroupPubliKey(81, 121, b) }
func benchmarkDKGGroupPubliKey(k, n int, b *testing.B) {
round := uint64(1)
reset := uint64(0)
_, pubKeys, err := test.NewKeys(n)
if err != nil {
panic(err)
}
gov, err := test.NewGovernance(test.NewState(DKGDelayRound,
pubKeys, 100, &common.NullLogger{}, true), ConfigRoundShift)
if err != nil {
panic(err)
}
for _, pk := range pubKeys {
_, pubShare := dkg.NewPrivateKeyShares(k)
gov.AddDKGMasterPublicKey(round, &typesDKG.MasterPublicKey{
ProposerID: types.NewNodeID(pk),
Round: round,
Reset: reset,
DKGID: typesDKG.NewID(types.NewNodeID(pk)),
PublicKeyShares: *pubShare,
})
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
// DKG is fininished.
gpk, err := typesDKG.NewGroupPublicKey(round,
gov.DKGMasterPublicKeys(round), gov.DKGComplaints(round),
k,
)
if err != nil {
panic(err)
}
if len(gpk.QualifyIDs) != n {
panic("not enough of qualify id")
}
}
}
func BenchmarkNPKs4_7(b *testing.B) { benchmarkDKGNodePubliKeys(4, 7, b) }
func BenchmarkNPKs9_13(b *testing.B) { benchmarkDKGNodePubliKeys(9, 13, b) }
func BenchmarkNPKs17_24(b *testing.B) { benchmarkDKGNodePubliKeys(17, 24, b) }
func BenchmarkNPKs81_121(b *testing.B) { benchmarkDKGNodePubliKeys(81, 121, b) }
func benchmarkDKGNodePubliKeys(k, n int, b *testing.B) {
round := uint64(1)
reset := uint64(0)
_, pubKeys, err := test.NewKeys(n)
if err != nil {
panic(err)
}
gov, err := test.NewGovernance(test.NewState(DKGDelayRound,
pubKeys, 100, &common.NullLogger{}, true), ConfigRoundShift)
if err != nil {
panic(err)
}
for _, pk := range pubKeys {
_, pubShare := dkg.NewPrivateKeyShares(k)
gov.AddDKGMasterPublicKey(round, &typesDKG.MasterPublicKey{
ProposerID: types.NewNodeID(pk),
Round: round,
Reset: reset,
DKGID: typesDKG.NewID(types.NewNodeID(pk)),
PublicKeyShares: *pubShare,
})
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
// DKG is fininished.
npks, err := typesDKG.NewNodePublicKeys(round,
gov.DKGMasterPublicKeys(round), gov.DKGComplaints(round),
k,
)
if err != nil {
panic(err)
}
if len(npks.QualifyIDs) != n {
panic("not enough of qualify id")
}
}
}