// 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(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(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(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(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(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(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(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(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(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(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-1],
		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(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(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(receiver.mpk)
		}

		for _, protocol := range protocols {
			protocol.proposeMPKReady()
		}
		for _, recv := range receivers {
			s.Require().Len(recv.ready, 1)
			gov.AddDKGMPKReady(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])
		}
		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)

	cache.Purge(5)
	s.Require().Len(cache.verifier, 0)
	s.Require().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(&typesDKG.MasterPublicKey{
			ProposerID:      types.NewNodeID(pk),
			Round:           round,
			Reset:           reset,
			DKGID:           typesDKG.NewID(types.NewNodeID(pk)),
			PublicKeyShares: *pubShare,
		})
	}

	mpk := gov.DKGMasterPublicKeys(round)
	comp := gov.DKGComplaints(round)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		// DKG is fininished.
		gpk, err := typesDKG.NewGroupPublicKey(round, mpk, comp, 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(&typesDKG.MasterPublicKey{
			ProposerID:      types.NewNodeID(pk),
			Round:           round,
			Reset:           reset,
			DKGID:           typesDKG.NewID(types.NewNodeID(pk)),
			PublicKeyShares: *pubShare,
		})
	}

	mpk := gov.DKGMasterPublicKeys(round)
	comp := gov.DKGComplaints(round)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		// DKG is fininished.
		npks, err := typesDKG.NewNodePublicKeys(round, mpk, comp, k)
		if err != nil {
			panic(err)
		}
		if len(npks.QualifyIDs) != n {
			panic("not enough of qualify id")
		}
	}
}

func BenchmarkCalcQ4_7(b *testing.B)    { benchmarkCalcQualified(4, 7, b) }
func BenchmarkCalcQ9_13(b *testing.B)   { benchmarkCalcQualified(9, 13, b) }
func BenchmarkCalcQ17_24(b *testing.B)  { benchmarkCalcQualified(17, 24, b) }
func BenchmarkCalcQ81_121(b *testing.B) { benchmarkCalcQualified(81, 121, b) }

func benchmarkCalcQualified(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(&typesDKG.MasterPublicKey{
			ProposerID:      types.NewNodeID(pk),
			Round:           round,
			Reset:           reset,
			DKGID:           typesDKG.NewID(types.NewNodeID(pk)),
			PublicKeyShares: *pubShare,
		})
	}

	mpk := gov.DKGMasterPublicKeys(round)
	comp := gov.DKGComplaints(round)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		// DKG is fininished.
		_, q, err := typesDKG.CalcQualifyNodes(mpk, comp, k)
		if err != nil {
			panic(err)
		}
		if len(q) != n {
			panic("not enough of qualify id")
		}
	}
}