diff options
Diffstat (limited to 'vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go')
-rw-r--r-- | vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go new file mode 100644 index 000000000..6645ecbae --- /dev/null +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go @@ -0,0 +1,578 @@ +// 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 ( + "fmt" + "sync" + + "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/types" + typesDKG "github.com/dexon-foundation/dexon-consensus/core/types/dkg" +) + +// Errors for dkg module. +var ( + ErrNotDKGParticipant = fmt.Errorf( + "not a DKG participant") + ErrNotQualifyDKGParticipant = fmt.Errorf( + "not a qualified DKG participant") + ErrIDShareNotFound = fmt.Errorf( + "private share not found for specific ID") + ErrNotReachThreshold = fmt.Errorf( + "threshold not reach") + ErrInvalidThreshold = fmt.Errorf( + "invalid threshold") + ErrIncorrectPrivateShareSignature = fmt.Errorf( + "incorrect private share signature") + ErrMismatchPartialSignatureHash = fmt.Errorf( + "mismatch partialSignature hash") + ErrIncorrectPartialSignatureSignature = fmt.Errorf( + "incorrect partialSignature signature") + ErrIncorrectPartialSignature = fmt.Errorf( + "incorrect partialSignature") + ErrNotEnoughtPartialSignatures = fmt.Errorf( + "not enough of partial signatures") + ErrRoundAlreadyPurged = fmt.Errorf( + "cache of round already been purged") +) + +type dkgReceiver interface { + // ProposeDKGComplaint proposes a DKGComplaint. + ProposeDKGComplaint(complaint *typesDKG.Complaint) + + // ProposeDKGMasterPublicKey propose a DKGMasterPublicKey. + ProposeDKGMasterPublicKey(mpk *typesDKG.MasterPublicKey) + + // ProposeDKGPrivateShare propose a DKGPrivateShare. + ProposeDKGPrivateShare(prv *typesDKG.PrivateShare) + + // ProposeDKGAntiNackComplaint propose a DKGPrivateShare as an anti complaint. + ProposeDKGAntiNackComplaint(prv *typesDKG.PrivateShare) + + // ProposeDKGFinalize propose a DKGFinalize message. + ProposeDKGFinalize(final *typesDKG.Finalize) +} + +type dkgProtocol struct { + ID types.NodeID + recv dkgReceiver + round uint64 + threshold int + idMap map[types.NodeID]dkg.ID + mpkMap map[types.NodeID]*dkg.PublicKeyShares + masterPrivateShare *dkg.PrivateKeyShares + prvShares *dkg.PrivateKeyShares + prvSharesReceived map[types.NodeID]struct{} + nodeComplained map[types.NodeID]struct{} + // Complaint[from][to]'s anti is saved to antiComplaint[from][to]. + antiComplaintReceived map[types.NodeID]map[types.NodeID]struct{} +} + +type dkgShareSecret struct { + privateKey *dkg.PrivateKey +} + +// DKGGroupPublicKey is the result of DKG protocol. +type DKGGroupPublicKey struct { + round uint64 + qualifyIDs dkg.IDs + qualifyNodeIDs map[types.NodeID]struct{} + idMap map[types.NodeID]dkg.ID + publicKeys map[types.NodeID]*dkg.PublicKey + groupPublicKey *dkg.PublicKey + threshold int +} + +// TSigVerifier is the interface verifying threshold signature. +type TSigVerifier interface { + VerifySignature(hash common.Hash, sig crypto.Signature) bool +} + +// TSigVerifierCacheInterface specifies interface used by TSigVerifierCache. +type TSigVerifierCacheInterface interface { + // Configuration returns the configuration at a given round. + // Return the genesis configuration if round == 0. + Configuration(round uint64) *types.Config + + // DKGComplaints gets all the DKGComplaints of round. + DKGComplaints(round uint64) []*typesDKG.Complaint + + // DKGMasterPublicKeys gets all the DKGMasterPublicKey of round. + DKGMasterPublicKeys(round uint64) []*typesDKG.MasterPublicKey + + // IsDKGFinal checks if DKG is final. + IsDKGFinal(round uint64) bool +} + +// TSigVerifierCache is the cache for TSigVerifier. +type TSigVerifierCache struct { + intf TSigVerifierCacheInterface + verifier map[uint64]TSigVerifier + minRound uint64 + cacheSize int + lock sync.RWMutex +} + +type tsigProtocol struct { + groupPublicKey *DKGGroupPublicKey + hash common.Hash + sigs map[dkg.ID]dkg.PartialSignature + threshold int +} + +func newDKGID(ID types.NodeID) dkg.ID { + return dkg.NewID(ID.Hash[:]) +} + +func newDKGProtocol( + ID types.NodeID, + recv dkgReceiver, + round uint64, + threshold int) *dkgProtocol { + + prvShare, pubShare := dkg.NewPrivateKeyShares(threshold) + + recv.ProposeDKGMasterPublicKey(&typesDKG.MasterPublicKey{ + ProposerID: ID, + Round: round, + DKGID: newDKGID(ID), + PublicKeyShares: *pubShare, + }) + + return &dkgProtocol{ + ID: ID, + recv: recv, + round: round, + threshold: threshold, + idMap: make(map[types.NodeID]dkg.ID), + mpkMap: make(map[types.NodeID]*dkg.PublicKeyShares), + masterPrivateShare: prvShare, + prvShares: dkg.NewEmptyPrivateKeyShares(), + prvSharesReceived: make(map[types.NodeID]struct{}), + nodeComplained: make(map[types.NodeID]struct{}), + antiComplaintReceived: make(map[types.NodeID]map[types.NodeID]struct{}), + } +} + +func (d *dkgProtocol) processMasterPublicKeys( + mpks []*typesDKG.MasterPublicKey) error { + d.idMap = make(map[types.NodeID]dkg.ID, len(mpks)) + d.mpkMap = make(map[types.NodeID]*dkg.PublicKeyShares, len(mpks)) + d.prvSharesReceived = make(map[types.NodeID]struct{}, len(mpks)) + ids := make(dkg.IDs, len(mpks)) + for i := range mpks { + nID := mpks[i].ProposerID + d.idMap[nID] = mpks[i].DKGID + d.mpkMap[nID] = &mpks[i].PublicKeyShares + ids[i] = mpks[i].DKGID + } + d.masterPrivateShare.SetParticipants(ids) + for _, mpk := range mpks { + share, ok := d.masterPrivateShare.Share(mpk.DKGID) + if !ok { + return ErrIDShareNotFound + } + d.recv.ProposeDKGPrivateShare(&typesDKG.PrivateShare{ + ProposerID: d.ID, + ReceiverID: mpk.ProposerID, + Round: d.round, + PrivateShare: *share, + }) + } + return nil +} + +func (d *dkgProtocol) proposeNackComplaints() { + for nID := range d.mpkMap { + if _, exist := d.prvSharesReceived[nID]; exist { + continue + } + d.recv.ProposeDKGComplaint(&typesDKG.Complaint{ + ProposerID: d.ID, + Round: d.round, + PrivateShare: typesDKG.PrivateShare{ + ProposerID: nID, + Round: d.round, + }, + }) + } +} + +func (d *dkgProtocol) processNackComplaints(complaints []*typesDKG.Complaint) ( + err error) { + for _, complaint := range complaints { + if !complaint.IsNack() { + continue + } + if complaint.PrivateShare.ProposerID != d.ID { + continue + } + id, exist := d.idMap[complaint.ProposerID] + if !exist { + err = ErrNotDKGParticipant + continue + } + share, ok := d.masterPrivateShare.Share(id) + if !ok { + err = ErrIDShareNotFound + continue + } + d.recv.ProposeDKGAntiNackComplaint(&typesDKG.PrivateShare{ + ProposerID: d.ID, + ReceiverID: complaint.ProposerID, + Round: d.round, + PrivateShare: *share, + }) + } + return +} + +func (d *dkgProtocol) enforceNackComplaints(complaints []*typesDKG.Complaint) { + for _, complaint := range complaints { + if !complaint.IsNack() { + continue + } + to := complaint.PrivateShare.ProposerID + // Do not propose nack complaint to itself. + if to == d.ID { + continue + } + from := complaint.ProposerID + // Nack complaint is already proposed. + if from == d.ID { + continue + } + if _, exist := + d.antiComplaintReceived[from][to]; !exist { + d.recv.ProposeDKGComplaint(&typesDKG.Complaint{ + ProposerID: d.ID, + Round: d.round, + PrivateShare: typesDKG.PrivateShare{ + ProposerID: to, + Round: d.round, + }, + }) + } + } +} + +func (d *dkgProtocol) sanityCheck(prvShare *typesDKG.PrivateShare) error { + if _, exist := d.idMap[prvShare.ProposerID]; !exist { + return ErrNotDKGParticipant + } + ok, err := verifyDKGPrivateShareSignature(prvShare) + if err != nil { + return err + } + if !ok { + return ErrIncorrectPrivateShareSignature + } + return nil +} + +func (d *dkgProtocol) processPrivateShare( + prvShare *typesDKG.PrivateShare) error { + if d.round != prvShare.Round { + return nil + } + receiverID, exist := d.idMap[prvShare.ReceiverID] + // This node is not a DKG participant, ignore the private share. + if !exist { + return nil + } + if err := d.sanityCheck(prvShare); err != nil { + return err + } + mpk := d.mpkMap[prvShare.ProposerID] + ok, err := mpk.VerifyPrvShare(receiverID, &prvShare.PrivateShare) + if err != nil { + return err + } + if prvShare.ReceiverID == d.ID { + d.prvSharesReceived[prvShare.ProposerID] = struct{}{} + } + if !ok { + if _, exist := d.nodeComplained[prvShare.ProposerID]; exist { + return nil + } + complaint := &typesDKG.Complaint{ + ProposerID: d.ID, + Round: d.round, + PrivateShare: *prvShare, + } + d.nodeComplained[prvShare.ProposerID] = struct{}{} + d.recv.ProposeDKGComplaint(complaint) + } else if prvShare.ReceiverID == d.ID { + sender := d.idMap[prvShare.ProposerID] + if err := d.prvShares.AddShare(sender, &prvShare.PrivateShare); err != nil { + return err + } + } else { + // The prvShare is an anti complaint. + if _, exist := d.antiComplaintReceived[prvShare.ReceiverID]; !exist { + d.antiComplaintReceived[prvShare.ReceiverID] = + make(map[types.NodeID]struct{}) + d.recv.ProposeDKGAntiNackComplaint(prvShare) + } + d.antiComplaintReceived[prvShare.ReceiverID][prvShare.ProposerID] = + struct{}{} + } + return nil +} + +func (d *dkgProtocol) proposeFinalize() { + d.recv.ProposeDKGFinalize(&typesDKG.Finalize{ + ProposerID: d.ID, + Round: d.round, + }) +} + +func (d *dkgProtocol) recoverShareSecret(qualifyIDs dkg.IDs) ( + *dkgShareSecret, error) { + if len(qualifyIDs) < d.threshold { + return nil, ErrNotReachThreshold + } + prvKey, err := d.prvShares.RecoverPrivateKey(qualifyIDs) + if err != nil { + return nil, err + } + return &dkgShareSecret{ + privateKey: prvKey, + }, nil +} + +func (ss *dkgShareSecret) sign(hash common.Hash) dkg.PartialSignature { + // DKG sign will always success. + sig, _ := ss.privateKey.Sign(hash) + return dkg.PartialSignature(sig) +} + +// NewDKGGroupPublicKey creats a DKGGroupPublicKey instance. +func NewDKGGroupPublicKey( + round uint64, + mpks []*typesDKG.MasterPublicKey, complaints []*typesDKG.Complaint, + threshold int) ( + *DKGGroupPublicKey, error) { + + if len(mpks) < threshold { + return nil, ErrInvalidThreshold + } + + // Calculate qualify members. + disqualifyIDs := map[types.NodeID]struct{}{} + complaintsByID := map[types.NodeID]int{} + for _, complaint := range complaints { + if complaint.IsNack() { + complaintsByID[complaint.PrivateShare.ProposerID]++ + } else { + disqualifyIDs[complaint.PrivateShare.ProposerID] = struct{}{} + } + } + for nID, num := range complaintsByID { + if num > threshold { + disqualifyIDs[nID] = struct{}{} + } + } + qualifyIDs := make(dkg.IDs, 0, len(mpks)-len(disqualifyIDs)) + qualifyNodeIDs := make(map[types.NodeID]struct{}) + mpkMap := make(map[dkg.ID]*typesDKG.MasterPublicKey, cap(qualifyIDs)) + idMap := make(map[types.NodeID]dkg.ID) + for _, mpk := range mpks { + if _, exist := disqualifyIDs[mpk.ProposerID]; exist { + continue + } + mpkMap[mpk.DKGID] = mpk + idMap[mpk.ProposerID] = mpk.DKGID + qualifyIDs = append(qualifyIDs, mpk.DKGID) + qualifyNodeIDs[mpk.ProposerID] = struct{}{} + } + // Recover qualify members' public key. + pubKeys := make(map[types.NodeID]*dkg.PublicKey, len(qualifyIDs)) + for _, recvID := range qualifyIDs { + pubShares := dkg.NewEmptyPublicKeyShares() + for _, id := range qualifyIDs { + pubShare, err := mpkMap[id].PublicKeyShares.Share(recvID) + if err != nil { + return nil, err + } + if err := pubShares.AddShare(id, pubShare); err != nil { + return nil, err + } + } + pubKey, err := pubShares.RecoverPublicKey(qualifyIDs) + if err != nil { + return nil, err + } + pubKeys[mpkMap[recvID].ProposerID] = pubKey + } + // Recover Group Public Key. + pubShares := make([]*dkg.PublicKeyShares, 0, len(qualifyIDs)) + for _, id := range qualifyIDs { + pubShares = append(pubShares, &mpkMap[id].PublicKeyShares) + } + groupPK := dkg.RecoverGroupPublicKey(pubShares) + return &DKGGroupPublicKey{ + round: round, + qualifyIDs: qualifyIDs, + qualifyNodeIDs: qualifyNodeIDs, + idMap: idMap, + publicKeys: pubKeys, + threshold: threshold, + groupPublicKey: groupPK, + }, nil +} + +// VerifySignature verifies if the signature is correct. +func (gpk *DKGGroupPublicKey) VerifySignature( + hash common.Hash, sig crypto.Signature) bool { + return gpk.groupPublicKey.VerifySignature(hash, sig) +} + +// NewTSigVerifierCache creats a DKGGroupPublicKey instance. +func NewTSigVerifierCache( + intf TSigVerifierCacheInterface, cacheSize int) *TSigVerifierCache { + return &TSigVerifierCache{ + intf: intf, + verifier: make(map[uint64]TSigVerifier), + cacheSize: cacheSize, + } +} + +// UpdateAndGet calls Update and then Get. +func (tc *TSigVerifierCache) UpdateAndGet(round uint64) ( + TSigVerifier, bool, error) { + ok, err := tc.Update(round) + if err != nil { + return nil, false, err + } + if !ok { + return nil, false, nil + } + v, ok := tc.Get(round) + return v, ok, nil +} + +// Update the cache and returns if success. +func (tc *TSigVerifierCache) Update(round uint64) (bool, error) { + tc.lock.Lock() + defer tc.lock.Unlock() + if round < tc.minRound { + return false, ErrRoundAlreadyPurged + } + if _, exist := tc.verifier[round]; exist { + return true, nil + } + if !tc.intf.IsDKGFinal(round) { + return false, nil + } + gpk, err := NewDKGGroupPublicKey(round, + tc.intf.DKGMasterPublicKeys(round), + tc.intf.DKGComplaints(round), + int(tc.intf.Configuration(round).DKGSetSize/3)+1) + if err != nil { + return false, err + } + if len(tc.verifier) == 0 { + tc.minRound = round + } + tc.verifier[round] = gpk + if len(tc.verifier) > tc.cacheSize { + delete(tc.verifier, tc.minRound) + } + for { + if _, exist := tc.verifier[tc.minRound]; !exist { + tc.minRound++ + } else { + break + } + } + return true, nil +} + +// Get the TSigVerifier of round and returns if it exists. +func (tc *TSigVerifierCache) Get(round uint64) (TSigVerifier, bool) { + tc.lock.RLock() + defer tc.lock.RUnlock() + verifier, exist := tc.verifier[round] + return verifier, exist +} + +func newTSigProtocol( + gpk *DKGGroupPublicKey, + hash common.Hash) *tsigProtocol { + return &tsigProtocol{ + groupPublicKey: gpk, + hash: hash, + sigs: make(map[dkg.ID]dkg.PartialSignature, gpk.threshold+1), + } +} + +func (tsig *tsigProtocol) sanityCheck(psig *typesDKG.PartialSignature) error { + _, exist := tsig.groupPublicKey.publicKeys[psig.ProposerID] + if !exist { + return ErrNotQualifyDKGParticipant + } + ok, err := verifyDKGPartialSignatureSignature(psig) + if err != nil { + return err + } + if !ok { + return ErrIncorrectPartialSignatureSignature + } + if psig.Hash != tsig.hash { + return ErrMismatchPartialSignatureHash + } + return nil +} + +func (tsig *tsigProtocol) processPartialSignature( + psig *typesDKG.PartialSignature) error { + if psig.Round != tsig.groupPublicKey.round { + return nil + } + id, exist := tsig.groupPublicKey.idMap[psig.ProposerID] + if !exist { + return ErrNotQualifyDKGParticipant + } + if err := tsig.sanityCheck(psig); err != nil { + return err + } + pubKey := tsig.groupPublicKey.publicKeys[psig.ProposerID] + if !pubKey.VerifySignature( + tsig.hash, crypto.Signature(psig.PartialSignature)) { + return ErrIncorrectPartialSignature + } + tsig.sigs[id] = psig.PartialSignature + return nil +} + +func (tsig *tsigProtocol) signature() (crypto.Signature, error) { + if len(tsig.sigs) < tsig.groupPublicKey.threshold { + return crypto.Signature{}, ErrNotEnoughtPartialSignatures + } + ids := make(dkg.IDs, 0, len(tsig.sigs)) + psigs := make([]dkg.PartialSignature, 0, len(tsig.sigs)) + for id, psig := range tsig.sigs { + ids = append(ids, id) + psigs = append(psigs, psig) + } + return dkg.RecoverSignature(psigs, ids) +} |