diff options
-rw-r--r-- | core/authenticator.go | 8 | ||||
-rw-r--r-- | core/configuration-chain.go | 14 | ||||
-rw-r--r-- | core/configuration-chain_test.go | 16 | ||||
-rw-r--r-- | core/consensus.go | 9 | ||||
-rw-r--r-- | core/crypto.go | 24 | ||||
-rw-r--r-- | core/dkg-tsig-protocol.go | 10 | ||||
-rw-r--r-- | core/dkg-tsig-protocol_test.go | 19 | ||||
-rw-r--r-- | core/interfaces.go | 6 | ||||
-rw-r--r-- | core/nodeset-cache_test.go | 2 | ||||
-rw-r--r-- | core/test/governance.go | 25 | ||||
-rw-r--r-- | core/types/dkg.go | 9 | ||||
-rw-r--r-- | simulation/governance.go | 25 | ||||
-rw-r--r-- | simulation/marshaller.go | 8 | ||||
-rw-r--r-- | simulation/node.go | 2 |
14 files changed, 175 insertions, 2 deletions
diff --git a/core/authenticator.go b/core/authenticator.go index de22c1f..97b62d6 100644 --- a/core/authenticator.go +++ b/core/authenticator.go @@ -101,6 +101,14 @@ func (au *Authenticator) SignDKGPartialSignature( return } +// SignDKGFinalize signs a DKG finalize message. +func (au *Authenticator) SignDKGFinalize( + final *types.DKGFinalize) (err error) { + final.ProposerID = au.proposerID + final.Signature, err = au.prvKey.Sign(hashDKGFinalize(final)) + return +} + // VerifyBlock verifies the signature of types.Block. func (au *Authenticator) VerifyBlock(b *types.Block) (err error) { hash, err := hashBlock(b) diff --git a/core/configuration-chain.go b/core/configuration-chain.go index c9dbab9..5cedcf4 100644 --- a/core/configuration-chain.go +++ b/core/configuration-chain.go @@ -21,6 +21,7 @@ import ( "fmt" "log" "sync" + "time" "github.com/dexon-foundation/dexon-consensus-core/common" "github.com/dexon-foundation/dexon-consensus-core/core/crypto" @@ -123,10 +124,21 @@ func (cc *configurationChain) runDKG(round uint64) error { // Phase 7(T = 4λ): Enforce complaints and nack complaints. cc.dkg.enforceNackComplaints(cc.gov.DKGComplaints(round)) // Enforce complaint is done in `processPrivateShare`. - // Phase 8(T = 5λ): DKG is ready. + // Phase 8(T = 5λ): DKG finalize. cc.dkgLock.Unlock() <-ticker.Tick() cc.dkgLock.Lock() + cc.dkg.proposeFinalize() + // Phase 9(T = 6λ): DKG is ready. + cc.dkgLock.Unlock() + <-ticker.Tick() + cc.dkgLock.Lock() + // Normally, IsDKGFinal would return true here. Use this for in case of + // unexpected network fluctuation and ensure the robustness of DKG protocol. + for !cc.gov.IsDKGFinal(round) { + log.Printf("[%s] DKG is not ready yet. Try again later...\n", cc.ID) + time.Sleep(500 * time.Millisecond) + } gpk, err := NewDKGGroupPublicKey(round, cc.gov.DKGMasterPublicKeys(round), cc.gov.DKGComplaints(round), diff --git a/core/configuration-chain_test.go b/core/configuration-chain_test.go index 45120b4..856d46e 100644 --- a/core/configuration-chain_test.go +++ b/core/configuration-chain_test.go @@ -125,6 +125,22 @@ func (r *testCCReceiver) ProposeDKGAntiNackComplaint( }() } +func (r *testCCReceiver) ProposeDKGFinalize(final *types.DKGFinalize) { + prvKey, exist := r.s.prvKeys[final.ProposerID] + r.s.Require().True(exist) + var err error + final.Signature, err = prvKey.Sign(hashDKGFinalize(final)) + r.s.Require().NoError(err) + for _, gov := range r.govs { + // Use Marshal/Unmarshal to do deep copy. + data, err := json.Marshal(final) + r.s.Require().NoError(err) + finalCopy := &types.DKGFinalize{} + r.s.Require().NoError(json.Unmarshal(data, finalCopy)) + gov.AddDKGFinalize(finalCopy) + } +} + func (s *ConfigurationChainTestSuite) setupNodes(n int) { s.nIDs = make(types.NodeIDs, 0, n) s.prvKeys = make(map[types.NodeID]crypto.PrivateKey, n) diff --git a/core/consensus.go b/core/consensus.go index dc1e3d4..47e075b 100644 --- a/core/consensus.go +++ b/core/consensus.go @@ -176,6 +176,15 @@ func (recv *consensusDKGReceiver) ProposeDKGAntiNackComplaint( recv.network.BroadcastDKGPrivateShare(prv) } +// ProposeDKGFinalize propose a DKGFinalize message. +func (recv *consensusDKGReceiver) ProposeDKGFinalize(final *types.DKGFinalize) { + if err := recv.authModule.SignDKGFinalize(final); err != nil { + log.Println(err) + return + } + recv.gov.AddDKGFinalize(final) +} + // Consensus implements DEXON Consensus algorithm. type Consensus struct { // Node Info. diff --git a/core/crypto.go b/core/crypto.go index ab484a0..e286d2b 100644 --- a/core/crypto.go +++ b/core/crypto.go @@ -243,3 +243,27 @@ func verifyDKGPartialSignatureSignature( } return true, nil } + +func hashDKGFinalize(final *types.DKGFinalize) common.Hash { + binaryRound := make([]byte, 8) + binary.LittleEndian.PutUint64(binaryRound, final.Round) + + return crypto.Keccak256Hash( + final.ProposerID.Hash[:], + binaryRound, + ) +} + +// VerifyDKGFinalizeSignature verifies DKGFinalize signature. +func VerifyDKGFinalizeSignature( + final *types.DKGPartialSignature) (bool, error) { + hash := hashDKGPartialSignature(final) + pubKey, err := crypto.SigToPub(hash, final.Signature) + if err != nil { + return false, err + } + if final.ProposerID != types.NewNodeID(pubKey) { + return false, nil + } + return true, nil +} diff --git a/core/dkg-tsig-protocol.go b/core/dkg-tsig-protocol.go index 3853468..3be717f 100644 --- a/core/dkg-tsig-protocol.go +++ b/core/dkg-tsig-protocol.go @@ -60,6 +60,9 @@ type dkgReceiver interface { // ProposeDKGAntiNackComplaint propose a DKGPrivateShare as an anti complaint. ProposeDKGAntiNackComplaint(prv *types.DKGPrivateShare) + + // ProposeDKGFinalize propose a DKGFinalize message. + ProposeDKGFinalize(final *types.DKGFinalize) } type dkgProtocol struct { @@ -299,6 +302,13 @@ func (d *dkgProtocol) processPrivateShare( return nil } +func (d *dkgProtocol) proposeFinalize() { + d.recv.ProposeDKGFinalize(&types.DKGFinalize{ + ProposerID: d.ID, + Round: d.round, + }) +} + 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 a3a0acc..9d66222 100644 --- a/core/dkg-tsig-protocol_test.go +++ b/core/dkg-tsig-protocol_test.go @@ -46,6 +46,7 @@ type testDKGReceiver struct { mpk *types.DKGMasterPublicKey prvShare map[types.NodeID]*types.DKGPrivateShare antiComplaints map[types.NodeID]*types.DKGPrivateShare + final []*types.DKGFinalize } func newTestDKGReceiver( @@ -90,6 +91,10 @@ func (r *testDKGReceiver) ProposeDKGAntiNackComplaint( r.antiComplaints[prv.ReceiverID] = prv } +func (r *testDKGReceiver) ProposeDKGFinalize(final *types.DKGFinalize) { + r.final = append(r.final, final) +} + func (s *DKGTSIGProtocolTestSuite) setupDKGParticipants(n int) { s.nIDs = make(types.NodeIDs, 0, n) s.prvKeys = make(map[types.NodeID]crypto.PrivateKey, n) @@ -616,6 +621,20 @@ func (s *DKGTSIGProtocolTestSuite) TestPartialSignature() { s.True(gpk.VerifySignature(msgHash, sig)) } +func (s *DKGTSIGProtocolTestSuite) TestProposeFinalize() { + prvKey, err := ecdsa.NewPrivateKey() + s.Require().NoError(err) + recv := newTestDKGReceiver(s, prvKey) + nID := types.NewNodeID(prvKey.PublicKey()) + protocol := newDKGProtocol(nID, recv, 1, 2) + protocol.proposeFinalize() + s.Require().Len(recv.final, 1) + final := recv.final[0] + s.Equal(&types.DKGFinalize{ + ProposerID: nID, + Round: 1, + }, final) +} func TestDKGTSIGProtocol(t *testing.T) { suite.Run(t, new(DKGTSIGProtocolTestSuite)) } diff --git a/core/interfaces.go b/core/interfaces.go index 3497535..9232251 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -116,6 +116,12 @@ type Governance interface { // DKGMasterPublicKeys gets all the DKGMasterPublicKey of round. DKGMasterPublicKeys(round uint64) []*types.DKGMasterPublicKey + + // AddDKGFinalize adds a DKG finalize message. + AddDKGFinalize(final *types.DKGFinalize) + + // IsDKGFinal checks if DKG is final. + IsDKGFinal(round uint64) bool } // Ticker define the capability to tick by interval. diff --git a/core/nodeset-cache_test.go b/core/nodeset-cache_test.go index a9a31c2..a1dded6 100644 --- a/core/nodeset-cache_test.go +++ b/core/nodeset-cache_test.go @@ -64,6 +64,8 @@ func (g *testGov) DKGMasterPublicKeys( round uint64) (keys []*types.DKGMasterPublicKey) { return } +func (g *testGov) AddDKGFinalize(final *types.DKGFinalize) {} +func (g *testGov) IsDKGFinal(round uint64) bool { return true } type NodeSetCacheTestSuite struct { suite.Suite diff --git a/core/test/governance.go b/core/test/governance.go index 24fd2de..de3f34b 100644 --- a/core/test/governance.go +++ b/core/test/governance.go @@ -44,6 +44,7 @@ type Governance struct { tsig map[uint64]crypto.Signature DKGComplaint map[uint64][]*types.DKGComplaint DKGMasterPublicKey map[uint64][]*types.DKGMasterPublicKey + DKGFinal map[uint64]map[types.NodeID]struct{} RoundInterval time.Duration MinBlockInterval time.Duration MaxBlockInterval time.Duration @@ -62,6 +63,7 @@ func NewGovernance(nodeCount int, lambda time.Duration) ( tsig: make(map[uint64]crypto.Signature), DKGComplaint: make(map[uint64][]*types.DKGComplaint), DKGMasterPublicKey: make(map[uint64][]*types.DKGMasterPublicKey), + DKGFinal: make(map[uint64]map[types.NodeID]struct{}), RoundInterval: 365 * 86400 * time.Second, MinBlockInterval: 1 * time.Millisecond, MaxBlockInterval: lambda * 8, @@ -135,8 +137,14 @@ func (g *Governance) PrivateKeys() (keys []crypto.PrivateKey) { // AddDKGComplaint add a DKGComplaint. func (g *Governance) AddDKGComplaint(complaint *types.DKGComplaint) { + if g.IsDKGFinal(complaint.Round) { + return + } g.lock.Lock() defer g.lock.Unlock() + if _, exist := g.DKGFinal[complaint.Round][complaint.ProposerID]; exist { + return + } for _, comp := range g.DKGComplaint[complaint.Round] { if comp == complaint { return @@ -184,3 +192,20 @@ func (g *Governance) DKGMasterPublicKeys( } return mpks } + +// AddDKGFinalize adds a DKG finalize message. +func (g *Governance) AddDKGFinalize(final *types.DKGFinalize) { + g.lock.Lock() + defer g.lock.Unlock() + if _, exist := g.DKGFinal[final.Round]; !exist { + g.DKGFinal[final.Round] = make(map[types.NodeID]struct{}) + } + g.DKGFinal[final.Round][final.ProposerID] = struct{}{} +} + +// IsDKGFinal checks if DKG is final. +func (g *Governance) IsDKGFinal(round uint64) bool { + g.lock.RLock() + defer g.lock.RUnlock() + return len(g.DKGFinal[round]) > int(g.Configuration(round).DKGSetSize)/3*2 +} diff --git a/core/types/dkg.go b/core/types/dkg.go index a4c6bf1..c761504 100644 --- a/core/types/dkg.go +++ b/core/types/dkg.go @@ -69,13 +69,20 @@ type DKGComplaint struct { // DKGPartialSignature describe a partial signature in DKG protocol. type DKGPartialSignature struct { - ProposerID NodeID `json:"proposerID"` + ProposerID NodeID `json:"proposer_id"` Round uint64 `json:"round"` Hash common.Hash `json:"hash"` PartialSignature dkg.PartialSignature `json:"partial_signature"` Signature crypto.Signature `json:"signature"` } +// DKGFinalize describe a dig finalize message in DKG protocol. +type DKGFinalize struct { + ProposerID NodeID `json:"proposer_id"` + Round uint64 `json:"round"` + Signature crypto.Signature `json:"signature"` +} + // IsNack returns true if it's a nack complaint in DKG protocol. func (c *DKGComplaint) IsNack() bool { return len(c.PrivateShare.Signature.Signature) == 0 diff --git a/simulation/governance.go b/simulation/governance.go index 23e1884..f22abce 100644 --- a/simulation/governance.go +++ b/simulation/governance.go @@ -42,6 +42,7 @@ type simGovernance struct { tsig map[uint64]crypto.Signature dkgComplaint map[uint64][]*types.DKGComplaint dkgMasterPublicKey map[uint64][]*types.DKGMasterPublicKey + dkgFinal map[uint64]map[types.NodeID]struct{} lambdaBA time.Duration lambdaDKG time.Duration roundInterval time.Duration @@ -64,6 +65,7 @@ func newSimGovernance( tsig: make(map[uint64]crypto.Signature), dkgComplaint: make(map[uint64][]*types.DKGComplaint), dkgMasterPublicKey: make(map[uint64][]*types.DKGMasterPublicKey), + dkgFinal: make(map[uint64]map[types.NodeID]struct{}), lambdaBA: time.Duration(consensusConfig.LambdaBA) * time.Millisecond, lambdaDKG: time.Duration(consensusConfig.LambdaDKG) * @@ -139,6 +141,12 @@ func (g *simGovernance) addNode(pubKey crypto.PublicKey) { // AddDKGComplaint adds a DKGComplaint. func (g *simGovernance) AddDKGComplaint(complaint *types.DKGComplaint) { + if g.IsDKGFinal(complaint.Round) { + return + } + if _, exist := g.dkgFinal[complaint.Round][complaint.ProposerID]; exist { + return + } // TODO(jimmy-dexon): check if the input is valid. g.dkgComplaint[complaint.Round] = append( g.dkgComplaint[complaint.Round], complaint) @@ -176,3 +184,20 @@ func (g *simGovernance) DKGMasterPublicKeys( } return masterPublicKeys } + +// AddDKGFinalize adds a DKG finalize message. +func (g *simGovernance) AddDKGFinalize(final *types.DKGFinalize) { + // TODO(jimmy-dexon): check if the input is valid. + if _, exist := g.dkgFinal[final.Round]; !exist { + g.dkgFinal[final.Round] = make(map[types.NodeID]struct{}) + } + g.dkgFinal[final.Round][final.ProposerID] = struct{}{} + if final.ProposerID == g.id { + g.network.broadcast(final) + } +} + +// IsDKGFinal checks if DKG is final. +func (g *simGovernance) IsDKGFinal(round uint64) bool { + return len(g.dkgFinal[round]) > int(g.Configuration(round).DKGSetSize)/3*2 +} diff --git a/simulation/marshaller.go b/simulation/marshaller.go index d677c1f..bb60432 100644 --- a/simulation/marshaller.go +++ b/simulation/marshaller.go @@ -99,6 +99,12 @@ func (m *jsonMarshaller) Unmarshal( break } msg = psig + case "dkg-finalize": + final := &types.DKGFinalize{} + if err = json.Unmarshal(payload, final); err != nil { + break + } + msg = final default: err = fmt.Errorf("unrecognized message type: %v", msgType) } @@ -135,6 +141,8 @@ func (m *jsonMarshaller) Marshal(msg interface{}) ( msgType = "dkg-complaint" case *types.DKGPartialSignature: msgType = "dkg-partial-signature" + case *types.DKGFinalize: + msgType = "dkg-finalize" default: err = fmt.Errorf("unknwon message type: %v", msg) } diff --git a/simulation/node.go b/simulation/node.go index f2ddd5a..b90c1f8 100644 --- a/simulation/node.go +++ b/simulation/node.go @@ -114,6 +114,8 @@ MainLoop: n.gov.AddDKGComplaint(val) case *types.DKGMasterPublicKey: n.gov.AddDKGMasterPublicKey(val) + case *types.DKGFinalize: + n.gov.AddDKGFinalize(val) default: panic(fmt.Errorf("unexpected message from server: %v", val)) } |