diff options
Diffstat (limited to 'core/types/dkg')
-rw-r--r-- | core/types/dkg/dkg.go | 194 | ||||
-rw-r--r-- | core/types/dkg/dkg_test.go | 248 |
2 files changed, 442 insertions, 0 deletions
diff --git a/core/types/dkg/dkg.go b/core/types/dkg/dkg.go new file mode 100644 index 0000000..ee00f14 --- /dev/null +++ b/core/types/dkg/dkg.go @@ -0,0 +1,194 @@ +// Copyright 2018 The dexon-consensus-core Authors +// This file is part of the dexon-consensus-core library. +// +// The dexon-consensus-core library is free software: you can redistribute it +// and/or modify it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The dexon-consensus-core library is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the dexon-consensus-core library. If not, see +// <http://www.gnu.org/licenses/>. + +package dkg + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + + "github.com/dexon-foundation/dexon/rlp" + + "github.com/dexon-foundation/dexon-consensus-core/common" + "github.com/dexon-foundation/dexon-consensus-core/core/crypto" + cryptoDKG "github.com/dexon-foundation/dexon-consensus-core/core/crypto/dkg" + "github.com/dexon-foundation/dexon-consensus-core/core/types" +) + +// PrivateShare describe a secret share in DKG protocol. +type PrivateShare struct { + ProposerID types.NodeID `json:"proposer_id"` + ReceiverID types.NodeID `json:"receiver_id"` + Round uint64 `json:"round"` + PrivateShare cryptoDKG.PrivateKey `json:"private_share"` + Signature crypto.Signature `json:"signature"` +} + +// Equal checks equality between two PrivateShare instances. +func (p *PrivateShare) Equal(other *PrivateShare) bool { + return p.ProposerID.Equal(other.ProposerID) && + p.ReceiverID.Equal(other.ReceiverID) && + p.Round == other.Round && + p.Signature.Type == other.Signature.Type && + bytes.Compare(p.Signature.Signature, other.Signature.Signature) == 0 && + bytes.Compare( + p.PrivateShare.Bytes(), other.PrivateShare.Bytes()) == 0 +} + +// MasterPublicKey decrtibe a master public key in DKG protocol. +type MasterPublicKey struct { + ProposerID types.NodeID `json:"proposer_id"` + Round uint64 `json:"round"` + DKGID cryptoDKG.ID `json:"dkg_id"` + PublicKeyShares cryptoDKG.PublicKeyShares `json:"public_key_shares"` + Signature crypto.Signature `json:"signature"` +} + +func (d *MasterPublicKey) String() string { + return fmt.Sprintf("MasterPublicKey[%s:%d]", + d.ProposerID.String()[:6], + d.Round) +} + +// Equal check equality of two DKG master public keys. +func (d *MasterPublicKey) Equal(other *MasterPublicKey) bool { + return d.ProposerID.Equal(other.ProposerID) && + d.Round == other.Round && + d.DKGID.GetHexString() == other.DKGID.GetHexString() && + d.PublicKeyShares.Equal(&other.PublicKeyShares) && + d.Signature.Type == other.Signature.Type && + bytes.Compare(d.Signature.Signature, other.Signature.Signature) == 0 +} + +type rlpMasterPublicKey struct { + ProposerID types.NodeID + Round uint64 + DKGID []byte + PublicKeyShares *cryptoDKG.PublicKeyShares + Signature crypto.Signature +} + +// EncodeRLP implements rlp.Encoder +func (d *MasterPublicKey) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, rlpMasterPublicKey{ + ProposerID: d.ProposerID, + Round: d.Round, + DKGID: d.DKGID.GetLittleEndian(), + PublicKeyShares: &d.PublicKeyShares, + Signature: d.Signature, + }) +} + +// DecodeRLP implements rlp.Decoder +func (d *MasterPublicKey) DecodeRLP(s *rlp.Stream) error { + var dec rlpMasterPublicKey + if err := s.Decode(&dec); err != nil { + return err + } + + id, err := cryptoDKG.BytesID(dec.DKGID) + if err != nil { + return err + } + + *d = MasterPublicKey{ + ProposerID: dec.ProposerID, + Round: dec.Round, + DKGID: id, + PublicKeyShares: *dec.PublicKeyShares, + Signature: dec.Signature, + } + return err +} + +// NewMasterPublicKey returns a new MasterPublicKey instance. +func NewMasterPublicKey() *MasterPublicKey { + return &MasterPublicKey{ + PublicKeyShares: *cryptoDKG.NewEmptyPublicKeyShares(), + } +} + +// UnmarshalJSON implements json.Unmarshaller. +func (d *MasterPublicKey) UnmarshalJSON(data []byte) error { + type innertMasterPublicKey MasterPublicKey + d.PublicKeyShares = *cryptoDKG.NewEmptyPublicKeyShares() + return json.Unmarshal(data, (*innertMasterPublicKey)(d)) +} + +// Complaint describe a complaint in DKG protocol. +type Complaint struct { + ProposerID types.NodeID `json:"proposer_id"` + Round uint64 `json:"round"` + PrivateShare PrivateShare `json:"private_share"` + Signature crypto.Signature `json:"signature"` +} + +func (c *Complaint) String() string { + if c.IsNack() { + return fmt.Sprintf("DKGNackComplaint[%s:%d]%s", + c.ProposerID.String()[:6], c.Round, + c.PrivateShare.ProposerID.String()[:6]) + } + return fmt.Sprintf("Complaint[%s:%d]%v", + c.ProposerID.String()[:6], c.Round, c.PrivateShare) +} + +// Equal checks equality between two Complaint instances. +func (c *Complaint) Equal(other *Complaint) bool { + return c.ProposerID.Equal(other.ProposerID) && + c.Round == other.Round && + c.PrivateShare.Equal(&other.PrivateShare) && + c.Signature.Type == other.Signature.Type && + bytes.Compare(c.Signature.Signature, other.Signature.Signature) == 0 +} + +// PartialSignature describe a partial signature in DKG protocol. +type PartialSignature struct { + ProposerID types.NodeID `json:"proposer_id"` + Round uint64 `json:"round"` + Hash common.Hash `json:"hash"` + PartialSignature cryptoDKG.PartialSignature `json:"partial_signature"` + Signature crypto.Signature `json:"signature"` +} + +// Finalize describe a dig finalize message in DKG protocol. +type Finalize struct { + ProposerID types.NodeID `json:"proposer_id"` + Round uint64 `json:"round"` + Signature crypto.Signature `json:"signature"` +} + +func (final *Finalize) String() string { + return fmt.Sprintf("DKGFinal[%s:%d]", + final.ProposerID.String()[:6], + final.Round) +} + +// Equal check equality of two Finalize instances. +func (final *Finalize) Equal(other *Finalize) bool { + return final.ProposerID.Equal(other.ProposerID) && + final.Round == other.Round && + final.Signature.Type == other.Signature.Type && + 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 +} diff --git a/core/types/dkg/dkg_test.go b/core/types/dkg/dkg_test.go new file mode 100644 index 0000000..0186c50 --- /dev/null +++ b/core/types/dkg/dkg_test.go @@ -0,0 +1,248 @@ +// Copyright 2018 The dexon-consensus-core Authors +// This file is part of the dexon-consensus-core library. +// +// The dexon-consensus-core library is free software: you can redistribute it +// and/or modify it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The dexon-consensus-core library is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the dexon-consensus-core library. If not, see +// <http://www.gnu.org/licenses/>. + +package dkg + +import ( + "math/rand" + "reflect" + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/dexon-foundation/dexon-consensus-core/common" + "github.com/dexon-foundation/dexon-consensus-core/core/crypto" + cryptoDKG "github.com/dexon-foundation/dexon-consensus-core/core/crypto/dkg" + "github.com/dexon-foundation/dexon-consensus-core/core/types" + "github.com/dexon-foundation/dexon/rlp" +) + +type DKGTestSuite struct { + suite.Suite +} + +func (s *DKGTestSuite) genRandomBytes() []byte { + randomness := make([]byte, 32) + _, err := rand.Read(randomness) + s.Require().NoError(err) + return randomness +} + +func (s *DKGTestSuite) genID() cryptoDKG.ID { + dID, err := cryptoDKG.BytesID(s.genRandomBytes()) + s.Require().NoError(err) + return dID +} + +func (s *DKGTestSuite) clone(src, dst interface{}) { + b, err := rlp.EncodeToBytes(src) + s.Require().NoError(err) + s.Require().NoError(rlp.DecodeBytes(b, dst)) +} + +func (s *DKGTestSuite) TestRLPEncodeDecode() { + dID := s.genID() + // Prepare master public key for testing. + d := MasterPublicKey{ + ProposerID: types.NodeID{Hash: common.Hash{1, 2, 3}}, + Round: 10, + DKGID: dID, + Signature: crypto.Signature{ + Type: "123", + Signature: []byte{4, 5, 6}, + }, + } + + b, err := rlp.EncodeToBytes(&d) + s.Require().NoError(err) + + var dd MasterPublicKey + err = rlp.DecodeBytes(b, &dd) + s.Require().NoError(err) + + bb, err := rlp.EncodeToBytes(&dd) + s.Require().NoError(err) + s.Require().True(reflect.DeepEqual(b, bb)) + s.Require().True(d.ProposerID.Equal(dd.ProposerID)) + s.Require().True(d.Round == dd.Round) + s.Require().True(reflect.DeepEqual(d.Signature, dd.Signature)) + s.Require().Equal(d.DKGID.GetHexString(), dd.DKGID.GetHexString()) +} + +func (s *DKGTestSuite) TestMasterPublicKeyEquality() { + var req = s.Require() + // Prepare source master public key. + master1 := &MasterPublicKey{ + ProposerID: types.NodeID{Hash: common.NewRandomHash()}, + Round: 1234, + DKGID: s.genID(), + Signature: crypto.Signature{ + Signature: s.genRandomBytes(), + }, + } + prvKey := cryptoDKG.NewPrivateKey() + pubKey := prvKey.PublicKey().(cryptoDKG.PublicKey) + _, pubShares := cryptoDKG.NewPrivateKeyShares(2) + req.NoError(pubShares.AddShare(s.genID(), &pubKey)) + master1.PublicKeyShares = *pubShares + // Prepare another master public key by copying every field. + master2 := &MasterPublicKey{} + s.clone(master1, master2) + // They should be equal. + req.True(master1.Equal(master2)) + // Change round. + master2.Round = 2345 + req.False(master1.Equal(master2)) + master2.Round = 1234 + // Change proposerID. + master2.ProposerID = types.NodeID{Hash: common.NewRandomHash()} + req.False(master1.Equal(master2)) + master2.ProposerID = master1.ProposerID + // Change DKGID. + master2.DKGID = cryptoDKG.NewID(s.genRandomBytes()) + req.False(master1.Equal(master2)) + master2.DKGID = master1.DKGID + // Change signature. + master2.Signature = crypto.Signature{ + Signature: s.genRandomBytes(), + } + req.False(master1.Equal(master2)) + master2.Signature = master1.Signature + // Change public key share. + master2.PublicKeyShares = *cryptoDKG.NewEmptyPublicKeyShares() + req.False(master1.Equal(master2)) +} + +func (s *DKGTestSuite) TestPrivateShareEquality() { + var req = s.Require() + share1 := &PrivateShare{ + ProposerID: types.NodeID{Hash: common.NewRandomHash()}, + ReceiverID: types.NodeID{Hash: common.NewRandomHash()}, + Round: 1, + PrivateShare: *cryptoDKG.NewPrivateKey(), + Signature: crypto.Signature{ + Signature: s.genRandomBytes(), + }, + } + // Make a copy. + share2 := &PrivateShare{} + s.clone(share1, share2) + req.True(share1.Equal(share2)) + // Change proposer ID. + share2.ProposerID = types.NodeID{Hash: common.NewRandomHash()} + req.False(share1.Equal(share2)) + share2.ProposerID = share1.ProposerID + // Change receiver ID. + share2.ReceiverID = types.NodeID{Hash: common.NewRandomHash()} + req.False(share1.Equal(share2)) + share2.ReceiverID = share1.ReceiverID + // Change round. + share2.Round = share1.Round + 1 + req.False(share1.Equal(share2)) + share2.Round = share1.Round + // Change signature. + share2.Signature = crypto.Signature{ + Signature: s.genRandomBytes(), + } + req.False(share1.Equal(share2)) + share2.Signature = share1.Signature + // Change private share. + share2.PrivateShare = *cryptoDKG.NewPrivateKey() + req.False(share1.Equal(share2)) + share2.PrivateShare = share1.PrivateShare + // They should be equal after chaning fields back. + req.True(share1.Equal(share2)) +} + +func (s *DKGTestSuite) TestComplaintEquality() { + var req = s.Require() + comp1 := &Complaint{ + ProposerID: types.NodeID{Hash: common.NewRandomHash()}, + Round: 1, + PrivateShare: PrivateShare{ + ProposerID: types.NodeID{Hash: common.NewRandomHash()}, + ReceiverID: types.NodeID{Hash: common.NewRandomHash()}, + Round: 2, + PrivateShare: *cryptoDKG.NewPrivateKey(), + Signature: crypto.Signature{ + Signature: s.genRandomBytes(), + }, + }, + Signature: crypto.Signature{ + Signature: s.genRandomBytes(), + }, + } + // Make a copy. + comp2 := &Complaint{} + s.clone(comp1, comp2) + req.True(comp1.Equal(comp2)) + // Change proposer ID. + comp2.ProposerID = types.NodeID{Hash: common.NewRandomHash()} + req.False(comp1.Equal(comp2)) + comp2.ProposerID = comp1.ProposerID + // Change round. + comp2.Round = comp1.Round + 1 + req.False(comp1.Equal(comp2)) + comp2.Round = comp1.Round + // Change signature. + comp2.Signature = crypto.Signature{ + Signature: s.genRandomBytes(), + } + req.False(comp1.Equal(comp2)) + comp2.Signature = comp1.Signature + // Change share's round. + comp2.PrivateShare.Round = comp1.PrivateShare.Round + 1 + req.False(comp1.Equal(comp2)) + comp2.PrivateShare.Round = comp1.PrivateShare.Round + // After changing every field back, should be equal. + req.True(comp1.Equal(comp2)) +} + +func (s *DKGTestSuite) TestFinalizeEquality() { + var req = s.Require() + final1 := &Finalize{ + ProposerID: types.NodeID{Hash: common.NewRandomHash()}, + Round: 1, + Signature: crypto.Signature{ + Signature: s.genRandomBytes(), + }, + } + // Make a copy + final2 := &Finalize{} + s.clone(final1, final2) + req.True(final1.Equal(final2)) + // Change proposer ID. + final2.ProposerID = types.NodeID{Hash: common.NewRandomHash()} + req.False(final1.Equal(final2)) + final2.ProposerID = final1.ProposerID + // Change round. + final2.Round = final1.Round + 1 + req.False(final1.Equal(final2)) + final2.Round = final1.Round + // Change signature. + final2.Signature = crypto.Signature{ + Signature: s.genRandomBytes(), + } + req.False(final1.Equal(final2)) + final2.Signature = final1.Signature + // After changing every field back, they should be equal. + req.True(final1.Equal(final2)) +} + +func TestDKG(t *testing.T) { + suite.Run(t, new(DKGTestSuite)) +} |