From 79be89a6b0b1d24b889e7c9fe0244026af4d49d0 Mon Sep 17 00:00:00 2001 From: Jimmy Hu Date: Mon, 15 Apr 2019 11:44:04 +0800 Subject: core: Add DKGSuccess (#569) * core: Add DKGSuccess * core: reset if not enough of dkg success --- core/configuration-chain.go | 1 + core/configuration-chain_test.go | 13 ++++++++ core/consensus.go | 16 ++++++++++ core/dkg-tsig-protocol.go | 11 +++++++ core/dkg-tsig-protocol_test.go | 21 +++++++++++++ core/interfaces.go | 6 ++++ core/test/governance.go | 32 +++++++++++++++++-- core/test/state-change-request.go | 1 + core/test/state.go | 65 +++++++++++++++++++++++++++++++++++++-- core/test/state_test.go | 24 +++++++-------- core/test/utils.go | 14 +++++++++ core/types/dkg/dkg.go | 34 +++++++++++++++++--- core/types/dkg/dkg_test.go | 36 ++++++++++++++++++++++ core/utils/crypto.go | 27 ++++++++++++++++ core/utils/crypto_test.go | 23 ++++++++++++++ core/utils/round-event.go | 2 +- core/utils/signer.go | 7 +++++ 17 files changed, 311 insertions(+), 22 deletions(-) diff --git a/core/configuration-chain.go b/core/configuration-chain.go index e9e04a2..4e70ff0 100644 --- a/core/configuration-chain.go +++ b/core/configuration-chain.go @@ -400,6 +400,7 @@ func (cc *configurationChain) runDKGPhaseNine(round uint64, reset uint64) error cc.db.PutDKGPrivateKey(round, reset, *signer.privateKey); err != nil { return err } + cc.dkg.proposeSuccess() cc.dkgResult.Lock() defer cc.dkgResult.Unlock() cc.dkgSigner[round] = signer diff --git a/core/configuration-chain_test.go b/core/configuration-chain_test.go index 61f0906..edaacf6 100644 --- a/core/configuration-chain_test.go +++ b/core/configuration-chain_test.go @@ -114,6 +114,12 @@ func (r *testCCGlobalReceiver) ProposeDKGFinalize(final *typesDKG.Finalize) { } } +func (r *testCCGlobalReceiver) ProposeDKGSuccess(success *typesDKG.Success) { + for _, gov := range r.govs { + gov.AddDKGSuccess(test.CloneDKGSuccess(success)) + } +} + type testCCReceiver struct { signer *utils.Signer recv *testCCGlobalReceiver @@ -176,6 +182,13 @@ func (r *testCCReceiver) ProposeDKGFinalize(final *typesDKG.Finalize) { r.recv.ProposeDKGFinalize(final) } +func (r *testCCReceiver) ProposeDKGSuccess(success *typesDKG.Success) { + if err := r.signer.SignDKGSuccess(success); err != nil { + panic(err) + } + r.recv.ProposeDKGSuccess(success) +} + func (s *ConfigurationChainTestSuite) setupNodes(n int) { s.nIDs = make(types.NodeIDs, 0, n) s.signers = make(map[types.NodeID]*utils.Signer, n) diff --git a/core/consensus.go b/core/consensus.go index 968b90e..7c44bdf 100644 --- a/core/consensus.go +++ b/core/consensus.go @@ -496,6 +496,16 @@ func (recv *consensusDKGReceiver) ProposeDKGFinalize(final *typesDKG.Finalize) { recv.gov.AddDKGFinalize(final) } +// ProposeDKGSuccess propose a DKGSuccess message. +func (recv *consensusDKGReceiver) ProposeDKGSuccess(success *typesDKG.Success) { + if err := recv.signer.SignDKGSuccess(success); err != nil { + recv.logger.Error("Failed to sign DKG successize", "error", err) + return + } + recv.logger.Debug("Calling Governance.AddDKGSuccess", "success", success) + recv.gov.AddDKGSuccess(success) +} + // Consensus implements DEXON Consensus algorithm. type Consensus struct { // Node Info. @@ -818,6 +828,12 @@ func (con *Consensus) prepare(initBlock *types.Block) (err error) { "reset", e.Reset) return false } + if !con.gov.IsDKGSuccess(nextRound) { + con.logger.Error("Next DKG is not success, reset it", + "round", e.Round, + "reset", e.Reset) + return false + } gpk, err := typesDKG.NewGroupPublicKey( nextRound, con.gov.DKGMasterPublicKeys(nextRound), diff --git a/core/dkg-tsig-protocol.go b/core/dkg-tsig-protocol.go index d81d485..8383ad1 100644 --- a/core/dkg-tsig-protocol.go +++ b/core/dkg-tsig-protocol.go @@ -102,6 +102,9 @@ type dkgReceiver interface { // ProposeDKGFinalize propose a DKGFinalize message. ProposeDKGFinalize(final *typesDKG.Finalize) + + // ProposeDKGSuccess propose a DKGSuccess message. + ProposeDKGSuccess(final *typesDKG.Success) } type dkgProtocol struct { @@ -514,6 +517,14 @@ func (d *dkgProtocol) proposeFinalize() { }) } +func (d *dkgProtocol) proposeSuccess() { + d.recv.ProposeDKGSuccess(&typesDKG.Success{ + ProposerID: d.ID, + Round: d.round, + Reset: d.reset, + }) +} + func (d *dkgProtocol) recoverShareSecret(qualifyIDs dkg.IDs) ( *dkgShareSecret, error) { if len(qualifyIDs) < d.threshold { diff --git a/core/dkg-tsig-protocol_test.go b/core/dkg-tsig-protocol_test.go index 89fd105..184460b 100644 --- a/core/dkg-tsig-protocol_test.go +++ b/core/dkg-tsig-protocol_test.go @@ -50,6 +50,7 @@ type testDKGReceiver struct { antiComplaints map[types.NodeID]*typesDKG.PrivateShare ready []*typesDKG.MPKReady final []*typesDKG.Finalize + success []*typesDKG.Success } func newTestDKGReceiver(s *DKGTSIGProtocolTestSuite, @@ -102,6 +103,10 @@ func (r *testDKGReceiver) ProposeDKGFinalize(final *typesDKG.Finalize) { r.final = append(r.final, final) } +func (r *testDKGReceiver) ProposeDKGSuccess(success *typesDKG.Success) { + r.success = append(r.success, success) +} + func (s *DKGTSIGProtocolTestSuite) setupDKGParticipants(n int) { s.nIDs = make(types.NodeIDs, 0, n) s.signers = make(map[types.NodeID]*utils.Signer, n) @@ -878,6 +883,22 @@ func (s *DKGTSIGProtocolTestSuite) TestProposeFinalize() { }, final) } +func (s *DKGTSIGProtocolTestSuite) TestProposeSuccess() { + 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.proposeSuccess() + s.Require().Len(recv.success, 1) + success := recv.success[0] + s.Equal(&typesDKG.Success{ + ProposerID: nID, + Round: 1, + Reset: 3, + }, success) +} + func (s *DKGTSIGProtocolTestSuite) TestTSigVerifierCache() { k := 3 n := 10 diff --git a/core/interfaces.go b/core/interfaces.go index c16c624..c88b3dc 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -141,6 +141,12 @@ type Governance interface { // IsDKGFinal checks if DKG is final. IsDKGFinal(round uint64) bool + // AddDKGSuccess adds a DKG success message. + AddDKGSuccess(success *typesDKG.Success) + + // IsDKGSuccess checks if DKG is success. + IsDKGSuccess(round uint64) bool + // ReportForkVote reports a node for forking votes. ReportForkVote(vote1, vote2 *types.Vote) diff --git a/core/test/governance.go b/core/test/governance.go index 14b7838..204e68b 100644 --- a/core/test/governance.go +++ b/core/test/governance.go @@ -236,7 +236,7 @@ func (g *Governance) IsDKGMPKReady(round uint64) bool { if round >= uint64(len(g.configs)) { return false } - return g.stateModule.IsDKGMPKReady(round, int(g.configs[round].NotarySetSize)/3*2) + return g.stateModule.IsDKGMPKReady(round, int(g.configs[round].NotarySetSize)*2/3+1) } // AddDKGFinalize adds a DKG finalize message. @@ -264,7 +264,35 @@ func (g *Governance) IsDKGFinal(round uint64) bool { if round >= uint64(len(g.configs)) { return false } - return g.stateModule.IsDKGFinal(round, int(g.configs[round].NotarySetSize)/3*2) + return g.stateModule.IsDKGFinal(round, int(g.configs[round].NotarySetSize)*2/3+1) +} + +// AddDKGSuccess adds a DKG success message. +func (g *Governance) AddDKGSuccess(success *typesDKG.Success) { + if g.isProhibited(StateAddDKGSuccess) { + return + } + if err := g.stateModule.RequestChange(StateAddDKGSuccess, success); err != nil { + if err != ErrChangeWontApply { + panic(err) + } + } + g.broadcastPendingStateChanges() +} + +// IsDKGSuccess checks if DKG is success. +func (g *Governance) IsDKGSuccess(round uint64) bool { + if round == 0 || round == 1 { + // Round 0, 1 are genesis round, their configs should be created + // by default. + g.CatchUpWithRound(round) + } + g.lock.RLock() + defer g.lock.RUnlock() + if round >= uint64(len(g.configs)) { + return false + } + return g.stateModule.IsDKGFinal(round, int(g.configs[round].NotarySetSize)*5/6) } // ReportForkVote reports a node for forking votes. diff --git a/core/test/state-change-request.go b/core/test/state-change-request.go index fe55d6e..4ddd40f 100644 --- a/core/test/state-change-request.go +++ b/core/test/state-change-request.go @@ -40,6 +40,7 @@ const ( StateAddDKGMasterPublicKey StateAddDKGMPKReady StateAddDKGFinal + StateAddDKGSuccess StateResetDKG // Configuration related. StateChangeLambdaBA diff --git a/core/test/state.go b/core/test/state.go index 41c6b38..7597377 100644 --- a/core/test/state.go +++ b/core/test/state.go @@ -66,6 +66,9 @@ var ( // ErrStateDKGFinalsNotEqual means DKG finalizations of two states are not // equal. ErrStateDKGFinalsNotEqual = errors.New("dkg finalizations not equal") + // ErrStateDKGSuccessesNotEqual means DKG successes of two states are not + // equal. + ErrStateDKGSuccessesNotEqual = errors.New("dkg successes not equal") // ErrStateCRSsNotEqual means CRSs of two states are not equal. ErrStateCRSsNotEqual = errors.New("crs not equal") // ErrStateDKGResetCountNotEqual means dkgResetCount of two states are not @@ -103,6 +106,7 @@ type State struct { dkgMasterPublicKeys map[uint64]map[types.NodeID]*typesDKG.MasterPublicKey dkgReadys map[uint64]map[types.NodeID]*typesDKG.MPKReady dkgFinals map[uint64]map[types.NodeID]*typesDKG.Finalize + dkgSuccesses map[uint64]map[types.NodeID]*typesDKG.Success crs []common.Hash dkgResetCount map[uint64]uint64 // Other stuffs @@ -150,6 +154,8 @@ func NewState( map[uint64]map[types.NodeID]*typesDKG.MPKReady), dkgFinals: make( map[uint64]map[types.NodeID]*typesDKG.Finalize), + dkgSuccesses: make( + map[uint64]map[types.NodeID]*typesDKG.Success), dkgComplaints: make( map[uint64]map[types.NodeID][]*typesDKG.Complaint), dkgMasterPublicKeys: make( @@ -211,6 +217,9 @@ func (s *State) unpackPayload( case StateAddDKGFinal: v = &typesDKG.Finalize{} err = rlp.DecodeBytes(raw.Payload, v) + case StateAddDKGSuccess: + v = &typesDKG.Success{} + err = rlp.DecodeBytes(raw.Payload, v) case StateResetDKG: var tmp common.Hash err = rlp.DecodeBytes(raw.Payload, &tmp) @@ -393,6 +402,28 @@ func (s *State) Equal(other *State) error { } } } + // Check DKG successes. + if len(s.dkgSuccesses) != len(other.dkgSuccesses) { + return ErrStateDKGSuccessesNotEqual + } + for round, successesForRound := range s.dkgSuccesses { + otherSuccessesForRound, exists := other.dkgSuccesses[round] + if !exists { + return ErrStateDKGSuccessesNotEqual + } + if len(successesForRound) != len(otherSuccessesForRound) { + return ErrStateDKGSuccessesNotEqual + } + for nID, success := range successesForRound { + otherSuccesse, exists := otherSuccessesForRound[nID] + if !exists { + return ErrStateDKGSuccessesNotEqual + } + if !success.Equal(otherSuccesse) { + return ErrStateDKGSuccessesNotEqual + } + } + } // Check CRS part. if len(s.crs) != len(other.crs) { return ErrStateCRSsNotEqual @@ -455,6 +486,7 @@ func (s *State) Clone() (copied *State) { map[uint64]map[types.NodeID]*typesDKG.MasterPublicKey), dkgReadys: make(map[uint64]map[types.NodeID]*typesDKG.MPKReady), dkgFinals: make(map[uint64]map[types.NodeID]*typesDKG.Finalize), + dkgSuccesses: make(map[uint64]map[types.NodeID]*typesDKG.Success), appliedRequests: make(map[common.Hash]struct{}), } // Nodes @@ -493,6 +525,12 @@ func (s *State) Clone() (copied *State) { copied.dkgFinals[round][nID] = CloneDKGFinalize(final) } } + for round, successesForRound := range s.dkgSuccesses { + copied.dkgSuccesses[round] = make(map[types.NodeID]*typesDKG.Success) + for nID, success := range successesForRound { + copied.dkgSuccesses[round][nID] = CloneDKGSuccess(success) + } + } for _, crs := range s.crs { copied.crs = append(copied.crs, crs) } @@ -636,6 +674,11 @@ func (s *State) isValidRequest(req *StateChangeRequest) error { if final.Reset != s.dkgResetCount[final.Round] { return ErrChangeWontApply } + case StateAddDKGSuccess: + success := req.Payload.(*typesDKG.Success) + if success.Reset != s.dkgResetCount[success.Round] { + return ErrChangeWontApply + } case StateAddDKGMasterPublicKey: mpk := req.Payload.(*typesDKG.MasterPublicKey) if mpk.Reset != s.dkgResetCount[mpk.Round] { @@ -747,6 +790,13 @@ func (s *State) applyRequest(req *StateChangeRequest) error { s.dkgFinals[final.Round] = make(map[types.NodeID]*typesDKG.Finalize) } s.dkgFinals[final.Round][final.ProposerID] = final + case StateAddDKGSuccess: + success := req.Payload.(*typesDKG.Success) + if _, exists := s.dkgSuccesses[success.Round]; !exists { + s.dkgSuccesses[success.Round] = + make(map[types.NodeID]*typesDKG.Success) + } + s.dkgSuccesses[success.Round][success.ProposerID] = success case StateResetDKG: round := uint64(len(s.crs) - 1) s.crs[round] = req.Payload.(common.Hash) @@ -755,6 +805,7 @@ func (s *State) applyRequest(req *StateChangeRequest) error { delete(s.dkgReadys, round) delete(s.dkgComplaints, round) delete(s.dkgFinals, round) + delete(s.dkgSuccesses, round) case StateChangeLambdaBA: s.lambdaBA = time.Duration(req.Payload.(uint64)) case StateChangeLambdaDKG: @@ -799,6 +850,8 @@ func (s *State) RequestChange( payload = payload.(*typesDKG.MPKReady) case StateAddDKGFinal: payload = payload.(*typesDKG.Finalize) + case StateAddDKGSuccess: + payload = payload.(*typesDKG.Success) case StateAddDKGMasterPublicKey: payload = payload.(*typesDKG.MasterPublicKey) case StateAddDKGComplaint: @@ -873,7 +926,7 @@ func (s *State) DKGMasterPublicKeys(round uint64) []*typesDKG.MasterPublicKey { func (s *State) IsDKGMPKReady(round uint64, threshold int) bool { s.lock.RLock() defer s.lock.RUnlock() - return len(s.dkgReadys[round]) > threshold + return len(s.dkgReadys[round]) >= threshold } // IsDKGFinal checks if current received dkg finals exceeds threshold. @@ -881,7 +934,15 @@ func (s *State) IsDKGMPKReady(round uint64, threshold int) bool { func (s *State) IsDKGFinal(round uint64, threshold int) bool { s.lock.RLock() defer s.lock.RUnlock() - return len(s.dkgFinals[round]) > threshold + return len(s.dkgFinals[round]) >= threshold +} + +// IsDKGSuccess checks if current received dkg successes exceeds threshold. +// This information won't be snapshot, thus can't be cached in test.Governance. +func (s *State) IsDKGSuccess(round uint64, threshold int) bool { + s.lock.RLock() + defer s.lock.RUnlock() + return len(s.dkgSuccesses[round]) >= threshold } // DKGResetCount returns the reset count for DKG of given round. diff --git a/core/test/state_test.go b/core/test/state_test.go index e3ed9bb..63f9d27 100644 --- a/core/test/state_test.go +++ b/core/test/state_test.go @@ -282,9 +282,9 @@ func (s *StateTestSuite) TestLocalMode() { // Test adding node set, DKG complaints, final, master public key. // Make sure everything is empty before changed. req.Empty(st.DKGMasterPublicKeys(2)) - req.False(st.IsDKGMPKReady(2, 0)) + req.False(st.IsDKGMPKReady(2, 1)) req.Empty(st.DKGComplaints(2)) - req.False(st.IsDKGFinal(2, 0)) + req.False(st.IsDKGFinal(2, 1)) // Add DKG stuffs. masterPubKey := s.newDKGMasterPublicKey(2, 0) ready := s.newDKGMPKReady(2, 0) @@ -296,22 +296,22 @@ func (s *StateTestSuite) TestLocalMode() { req.Len(masterKeyForRound, 1) req.True(masterKeyForRound[0].Equal(masterPubKey)) // Check IsDKGMPKReady. - req.True(st.IsDKGMPKReady(2, 0)) + req.True(st.IsDKGMPKReady(2, 1)) // Check DKGComplaints. compForRound := st.DKGComplaints(2) req.Len(compForRound, 1) req.True(compForRound[0].Equal(comp)) // Check IsDKGFinal. - req.True(st.IsDKGFinal(2, 0)) + req.True(st.IsDKGFinal(2, 1)) // Test ResetDKG. crs = common.NewRandomHash() req.NoError(st.RequestChange(StateResetDKG, crs)) req.Equal(st.CRS(2), crs) // Make sure all DKG fields are cleared. req.Empty(st.DKGMasterPublicKeys(2)) - req.False(st.IsDKGMPKReady(2, 0)) + req.False(st.IsDKGMPKReady(2, 1)) req.Empty(st.DKGComplaints(2)) - req.False(st.IsDKGFinal(2, 0)) + req.False(st.IsDKGFinal(2, 1)) } func (s *StateTestSuite) TestPacking() { @@ -353,9 +353,9 @@ func (s *StateTestSuite) TestPacking() { s.makeDKGChanges(st, masterPubKey, ready, comp, final) // Make sure everything is empty before changed. req.Empty(st.DKGMasterPublicKeys(2)) - req.False(st.IsDKGMPKReady(2, 0)) + req.False(st.IsDKGMPKReady(2, 1)) req.Empty(st.DKGComplaints(2)) - req.False(st.IsDKGFinal(2, 0)) + req.False(st.IsDKGFinal(2, 1)) packAndApply(st) // Check if configs are changed. config, nodes := st.Snapshot() @@ -373,9 +373,9 @@ func (s *StateTestSuite) TestPacking() { req.Len(compForRound, 1) req.True(compForRound[0].Equal(comp)) // Check IsDKGMPKReady. - req.True(st.IsDKGMPKReady(2, 0)) + req.True(st.IsDKGMPKReady(2, 1)) // Check IsDKGFinal. - req.True(st.IsDKGFinal(2, 0)) + req.True(st.IsDKGFinal(2, 1)) // Test ResetDKG. crs = common.NewRandomHash() @@ -384,9 +384,9 @@ func (s *StateTestSuite) TestPacking() { req.Equal(st.CRS(2), crs) // Make sure all DKG fields are cleared. req.Empty(st.DKGMasterPublicKeys(2)) - req.False(st.IsDKGMPKReady(2, 0)) + req.False(st.IsDKGMPKReady(2, 1)) req.Empty(st.DKGComplaints(2)) - req.False(st.IsDKGFinal(2, 0)) + req.False(st.IsDKGFinal(2, 1)) } func (s *StateTestSuite) TestRequestBroadcastAndPack() { diff --git a/core/test/utils.go b/core/test/utils.go index 74cde45..e02e194 100644 --- a/core/test/utils.go +++ b/core/test/utils.go @@ -170,6 +170,20 @@ func CloneDKGFinalize(final *typesDKG.Finalize) ( return } +// CloneDKGSuccess clones a typesDKG.Success instance. +func CloneDKGSuccess(success *typesDKG.Success) ( + copied *typesDKG.Success) { + b, err := rlp.EncodeToBytes(success) + if err != nil { + panic(err) + } + copied = &typesDKG.Success{} + if err = rlp.DecodeBytes(b, copied); err != nil { + panic(err) + } + return +} + // CloneDKGPrivateShare clones a typesDKG.PrivateShare instance. func CloneDKGPrivateShare(prvShare *typesDKG.PrivateShare) ( copied *typesDKG.PrivateShare) { diff --git a/core/types/dkg/dkg.go b/core/types/dkg/dkg.go index 868f0da..cb921e5 100644 --- a/core/types/dkg/dkg.go +++ b/core/types/dkg/dkg.go @@ -242,6 +242,11 @@ func (c *Complaint) DecodeRLP(s *rlp.Stream) error { return nil } +// IsNack returns true if it's a nack complaint in DKG protocol. +func (c *Complaint) IsNack() bool { + return len(c.PrivateShare.Signature.Signature) == 0 +} + // PartialSignature describe a partial signature in DKG protocol. type PartialSignature struct { ProposerID types.NodeID `json:"proposer_id"` @@ -251,7 +256,7 @@ type PartialSignature struct { Signature crypto.Signature `json:"signature"` } -// MPKReady describe a dig ready message in DKG protocol. +// MPKReady describe a dkg ready message in DKG protocol. type MPKReady struct { ProposerID types.NodeID `json:"proposer_id"` Round uint64 `json:"round"` @@ -275,7 +280,7 @@ func (ready *MPKReady) Equal(other *MPKReady) bool { bytes.Compare(ready.Signature.Signature, other.Signature.Signature) == 0 } -// Finalize describe a dig finalize message in DKG protocol. +// Finalize describe a dkg finalize message in DKG protocol. type Finalize struct { ProposerID types.NodeID `json:"proposer_id"` Round uint64 `json:"round"` @@ -299,9 +304,28 @@ func (final *Finalize) Equal(other *Finalize) bool { bytes.Compare(final.Signature.Signature, other.Signature.Signature) == 0 } -// IsNack returns true if it's a nack complaint in DKG protocol. -func (c *Complaint) IsNack() bool { - return len(c.PrivateShare.Signature.Signature) == 0 +// Success describe a dkg success message in DKG protocol. +type Success struct { + ProposerID types.NodeID `json:"proposer_id"` + Round uint64 `json:"round"` + Reset uint64 `json:"reset"` + Signature crypto.Signature `json:"signature"` +} + +func (s *Success) String() string { + return fmt.Sprintf("DKGSuccess{SP:%s Round:%d Reset:%d}", + s.ProposerID.String()[:6], + s.Round, + s.Reset) +} + +// Equal check equality of two Success instances. +func (s *Success) Equal(other *Success) bool { + return s.ProposerID.Equal(other.ProposerID) && + s.Round == other.Round && + s.Reset == other.Reset && + s.Signature.Type == other.Signature.Type && + bytes.Compare(s.Signature.Signature, other.Signature.Signature) == 0 } // GroupPublicKey is the result of DKG protocol. diff --git a/core/types/dkg/dkg_test.go b/core/types/dkg/dkg_test.go index 9f50feb..ea6a565 100644 --- a/core/types/dkg/dkg_test.go +++ b/core/types/dkg/dkg_test.go @@ -394,6 +394,42 @@ func (s *DKGTestSuite) TestFinalizeEquality() { req.True(final1.Equal(final2)) } +func (s *DKGTestSuite) TestSuccessEquality() { + var req = s.Require() + success1 := &Success{ + ProposerID: types.NodeID{Hash: common.NewRandomHash()}, + Round: 1, + Reset: 2, + Signature: crypto.Signature{ + Signature: s.genRandomBytes(), + }, + } + // Make a copy + success2 := &Success{} + s.clone(success1, success2) + req.True(success1.Equal(success2)) + // Change proposer ID. + success2.ProposerID = types.NodeID{Hash: common.NewRandomHash()} + req.False(success1.Equal(success2)) + success2.ProposerID = success1.ProposerID + // Change round. + success2.Round = success1.Round + 1 + req.False(success1.Equal(success2)) + success2.Round = success1.Round + // Change reset. + success2.Reset = success1.Reset + 1 + req.False(success1.Equal(success2)) + success2.Reset = success1.Reset + // Change signature. + success2.Signature = crypto.Signature{ + Signature: s.genRandomBytes(), + } + req.False(success1.Equal(success2)) + success2.Signature = success1.Signature + // After changing every field back, they should be equal. + req.True(success1.Equal(success2)) +} + func TestDKG(t *testing.T) { suite.Run(t, new(DKGTestSuite)) } diff --git a/core/utils/crypto.go b/core/utils/crypto.go index 496944d..42ee612 100644 --- a/core/utils/crypto.go +++ b/core/utils/crypto.go @@ -325,6 +325,19 @@ func hashDKGFinalize(final *typesDKG.Finalize) common.Hash { ) } +func hashDKGSuccess(success *typesDKG.Success) common.Hash { + binaryRound := make([]byte, 8) + binary.LittleEndian.PutUint64(binaryRound, success.Round) + binaryReset := make([]byte, 8) + binary.LittleEndian.PutUint64(binaryReset, success.Reset) + + return crypto.Keccak256Hash( + success.ProposerID.Hash[:], + binaryRound, + binaryReset, + ) +} + // VerifyDKGFinalizeSignature verifies DKGFinalize signature. func VerifyDKGFinalizeSignature( final *typesDKG.Finalize) (bool, error) { @@ -339,6 +352,20 @@ func VerifyDKGFinalizeSignature( return true, nil } +// VerifyDKGSuccessSignature verifies DKGSuccess signature. +func VerifyDKGSuccessSignature( + success *typesDKG.Success) (bool, error) { + hash := hashDKGSuccess(success) + pubKey, err := crypto.SigToPub(hash, success.Signature) + if err != nil { + return false, err + } + if success.ProposerID != types.NewNodeID(pubKey) { + return false, nil + } + return true, nil +} + // Rehash hashes the hash again and again and again... func Rehash(hash common.Hash, count uint) common.Hash { result := hash diff --git a/core/utils/crypto_test.go b/core/utils/crypto_test.go index 24ea68e..29396c5 100644 --- a/core/utils/crypto_test.go +++ b/core/utils/crypto_test.go @@ -302,6 +302,29 @@ func (s *CryptoTestSuite) TestDKGSignature() { s.Require().NoError(err) s.False(ok) final.Reset-- + + success := &typesDKG.Success{ + ProposerID: nID, + Round: 5, + Reset: 6, + } + success.Signature, err = prv.Sign(hashDKGSuccess(success)) + s.Require().NoError(err) + ok, err = VerifyDKGSuccessSignature(success) + s.Require().NoError(err) + s.True(ok) + // Test incorrect round. + success.Round++ + ok, err = VerifyDKGSuccessSignature(success) + s.Require().NoError(err) + s.False(ok) + success.Round-- + // Test incorrect reset. + success.Reset++ + ok, err = VerifyDKGSuccessSignature(success) + s.Require().NoError(err) + s.False(ok) + success.Reset-- } func TestCrypto(t *testing.T) { diff --git a/core/utils/round-event.go b/core/utils/round-event.go index 602d2da..b1d4d23 100644 --- a/core/utils/round-event.go +++ b/core/utils/round-event.go @@ -84,7 +84,7 @@ func (e RoundEventParam) NextTouchNodeSetCacheHeight() uint64 { // NextDKGResetHeight returns the height to reset DKG for next period. func (e RoundEventParam) NextDKGResetHeight() uint64 { - return e.BeginHeight + e.Config.RoundLength*8/10 + return e.BeginHeight + e.Config.RoundLength*85/100 } // NextDKGRegisterHeight returns the height to register DKG. diff --git a/core/utils/signer.go b/core/utils/signer.go index 9904410..ff76743 100644 --- a/core/utils/signer.go +++ b/core/utils/signer.go @@ -145,3 +145,10 @@ func (s *Signer) SignDKGFinalize(final *typesDKG.Finalize) (err error) { final.Signature, err = s.prvKey.Sign(hashDKGFinalize(final)) return } + +// SignDKGSuccess signs a DKG success message. +func (s *Signer) SignDKGSuccess(success *typesDKG.Success) (err error) { + success.ProposerID = s.proposerID + success.Signature, err = s.prvKey.Sign(hashDKGSuccess(success)) + return +} -- cgit v1.2.3