diff --git a/go.mod b/go.mod index e44dc1a..678a6f4 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,11 @@ module github.com/dgamingfoundation/dkglib go 1.12 require ( + github.com/VividCortex/gohistogram v1.0.0 // indirect github.com/cosmos/cosmos-sdk v0.28.2-0.20190827131926-5aacf454e1b6 github.com/dgamingfoundation/cosmos-utils/client v0.0.0-20191105143510-3ddb0501dfbd - github.com/dgamingfoundation/tendermint v0.27.3 + github.com/dgamingfoundation/tendermint v0.27.4-0.20191209175603-06d02b9bd67a + github.com/stumble/gorocksdb v0.0.3 // indirect github.com/tendermint/go-amino v0.15.1 github.com/tendermint/tendermint v0.32.6 go.dedis.ch/kyber/v3 v3.0.4 @@ -13,4 +15,6 @@ require ( replace golang.org/x/crypto => github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5 -replace github.com/tendermint/tendermint => github.com/dgamingfoundation/tendermint v0.27.4-0.20191113121517-26e7e4991bbf +replace github.com/tendermint/tendermint => ../tendermint + +replace github.com/dgamingfoundation/tendermint => ../tendermint diff --git a/lib/alias/dkg.go b/lib/alias/dkg.go index 52a3661..287991c 100644 --- a/lib/alias/dkg.go +++ b/lib/alias/dkg.go @@ -58,5 +58,6 @@ func (m *DKGData) GetAddrString() string { } func (m *DKGData) ValidateBasic() error { + // TODO: at least check the signature. return nil } diff --git a/lib/basic/basic.go b/lib/basic/basic.go index df7958c..58a6718 100644 --- a/lib/basic/basic.go +++ b/lib/basic/basic.go @@ -12,13 +12,13 @@ import ( "github.com/dgamingfoundation/cosmos-utils/client/utils" "github.com/dgamingfoundation/dkglib/lib/offChain" "github.com/dgamingfoundation/dkglib/lib/onChain" + "github.com/dgamingfoundation/dkglib/lib/types" dkg "github.com/dgamingfoundation/dkglib/lib/types" "github.com/tendermint/go-amino" - tmtypes "github.com/tendermint/tendermint/alias" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/libs/events" "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/types" + sdk "github.com/tendermint/tendermint/types" ) type DKGBasic struct { @@ -66,6 +66,14 @@ func NewDKGBasic( }, nil } +func (m *DKGBasic) IsReady() bool { + if m == nil { + return false + } + + return true +} + type MockFirer struct{} func (m *MockFirer) FireEvent(event string, data events.EventData) {} @@ -73,7 +81,7 @@ func (m *MockFirer) FireEvent(event string, data events.EventData) {} func (m *DKGBasic) HandleOffChainShare( dkgMsg *dkg.DKGDataMessage, height int64, - validators *types.ValidatorSet, + validators *sdk.ValidatorSet, pubKey crypto.PubKey, ) bool { @@ -109,7 +117,7 @@ func (m *DKGBasic) HandleOffChainShare( return false } -func (m *DKGBasic) runOnChainDKG(validators *types.ValidatorSet, logger log.Logger) bool { +func (m *DKGBasic) runOnChainDKG(validators *sdk.ValidatorSet, logger log.Logger) bool { err := m.onChain.StartRound( validators, m.offChain.GetPrivValidator(), @@ -135,7 +143,7 @@ func (m *DKGBasic) runOnChainDKG(validators *types.ValidatorSet, logger log.Logg } } -func (m *DKGBasic) CheckDKGTime(height int64, validators *types.ValidatorSet) { +func (m *DKGBasic) CheckDKGTime(height int64, validators *sdk.ValidatorSet) { m.offChain.CheckDKGTime(height, validators) } @@ -151,6 +159,23 @@ func (m *DKGBasic) MsgQueue() chan *dkg.DKGDataMessage { return m.offChain.MsgQueue() } -func (m *DKGBasic) GetLosers() []*tmtypes.Validator { - return append(m.offChain.GetLosers(), m.onChain.GetLosers()...) +func (m *DKGBasic) GetLosers() []*dkg.DKGLoser { + // We only report verifiable on-chain losers. + return m.onChain.GetLosers() +} + +func (m *DKGBasic) CheckLoserDuplicateData(loser *types.DKGLoser) bool { + return m.onChain.GetDealer().CheckLoserDuplicateData(loser) +} + +func (m *DKGBasic) CheckLoserMissingData(loser *types.DKGLoser) bool { + return m.onChain.GetDealer().CheckLoserMissingData(loser) +} + +func (m *DKGBasic) CheckLoserCorruptData(loser *types.DKGLoser) bool { + return m.onChain.GetDealer().CheckLoserCorruptData(loser) +} + +func (m *DKGBasic) CheckLoserCorruptJustification(loser *types.DKGLoser) bool { + return m.onChain.GetDealer().CheckLoserCorruptJustification(loser) } diff --git a/lib/dealer/dkg_dealer.go b/lib/dealer/dkg_dealer.go index 13d52e2..b840390 100644 --- a/lib/dealer/dkg_dealer.go +++ b/lib/dealer/dkg_dealer.go @@ -28,8 +28,7 @@ type Dealer interface { GetState() DealerState Transit() error GenerateTransitions() - GetLosers() []*tmtypes.Validator - PopLosers() []*tmtypes.Validator + GetLosers() []*types.DKGLoser HandleDKGPubKey(msg *alias.DKGData) error SetTransitions(t []transition) SendDeals() (err error, ready bool) @@ -56,6 +55,10 @@ type Dealer interface { GetVerifier() (types.Verifier, error) SendMsgCb(*alias.DKGData) error VerifyMessage(msg types.DKGDataMessage) error + CheckLoserDuplicateData(loser *types.DKGLoser) bool + CheckLoserMissingData(loser *types.DKGLoser) bool + CheckLoserCorruptData(loser *types.DKGLoser) bool + CheckLoserCorruptJustification(loser *types.DKGLoser) bool } type DKGDealer struct { @@ -80,7 +83,9 @@ type DKGDealer struct { complaints *messageStore reconstructCommits *messageStore - losers []crypto.Address + losers []*types.DKGLoser + + messagesHistory []*alias.DKGData } type DealerState struct { @@ -191,20 +196,8 @@ func (d *DKGDealer) SetTransitions(t []transition) { d.transitions = t } -func (d *DKGDealer) GetLosers() []*tmtypes.Validator { - var out []*tmtypes.Validator - for _, loser := range d.losers { - _, validator := d.validators.GetByAddress(loser) - out = append(out, validator) - } - - return out -} - -func (d *DKGDealer) PopLosers() []*tmtypes.Validator { - out := d.GetLosers() - d.losers = nil - return out +func (d *DKGDealer) GetLosers() []*types.DKGLoser { + return d.losers } ////////////////////////////////////////////////////////////////////////////// @@ -214,17 +207,27 @@ func (d *DKGDealer) PopLosers() []*tmtypes.Validator { ////////////////////////////////////////////////////////////////////////////// func (d *DKGDealer) HandleDKGPubKey(msg *alias.DKGData) error { + d.messagesHistory = append(d.messagesHistory, msg) var ( - dec = gob.NewDecoder(bytes.NewBuffer(msg.Data)) - pubKey = d.suiteG2.Point() + dec = gob.NewDecoder(bytes.NewBuffer(msg.Data)) + pubKey = d.suiteG2.Point() + _, validator = d.validators.GetByAddress(msg.Addr) ) if err := dec.Decode(pubKey); err != nil { - d.losers = append(d.losers, crypto.Address(msg.Addr)) + d.losers = append(d.losers, &types.DKGLoser{ + Type: types.LoserTypeCorruptData, + Data: types.DKGDataMessage{Data: msg}, + Validator: validator, + }) return fmt.Errorf("dkgState: failed to decode public key from %s: %v", msg.Addr, err) } - // TODO: check if we want to slash validators who send duplicate keys - // (we probably do). - d.pubKeys.Add(&PK2Addr{PK: pubKey, Addr: crypto.Address(msg.Addr)}) + if !d.pubKeys.Add(&PK2Addr{PK: pubKey, Addr: msg.Addr}) { + d.losers = append(d.losers, &types.DKGLoser{ + Type: types.LoserTypeDuplicateData, + Data: types.DKGDataMessage{Data: msg}, + Validator: validator, + }) + } if err := d.Transit(); err != nil { return fmt.Errorf("failed to Transit: %v", err) @@ -239,18 +242,18 @@ func (d *DKGDealer) SendDeals() (error, bool) { } d.eventFirer.FireEvent(types.EventDKGPubKeyReceived, nil) - messages, err := d.GetDeals() + dealMessages, err := d.GetDeals() if err != nil { return fmt.Errorf("failed to get deals: %v", err), true } - for _, msg := range messages { + for _, msg := range dealMessages { if err = d.SendMsgCb(msg); err != nil { return fmt.Errorf("failed to sign message: %v", err), true } } - d.logger.Info("dkgState: sending deals", "deals", len(messages)) + d.logger.Info("dkgState: sending deals", "deals", len(dealMessages)) return err, true } @@ -304,6 +307,7 @@ func (d *DKGDealer) GetDeals() ([]*alias.DKGData, error) { } func (d *DKGDealer) HandleDKGDeal(msg *alias.DKGData) error { + d.messagesHistory = append(d.messagesHistory, msg) var ( dec = gob.NewDecoder(bytes.NewBuffer(msg.Data)) deal = &dkg.Deal{ // We need to initialize everything down to the kyber.Point to avoid nil panics. @@ -313,7 +317,12 @@ func (d *DKGDealer) HandleDKGDeal(msg *alias.DKGData) error { } ) if err := dec.Decode(deal); err != nil { - d.losers = append(d.losers, crypto.Address(msg.Addr)) + _, validator := d.validators.GetByAddress(msg.Addr) + d.losers = append(d.losers, &types.DKGLoser{ + Type: types.LoserTypeCorruptData, + Data: types.DKGDataMessage{Data: msg}, + Validator: validator, + }) return fmt.Errorf("failed to decode deal: %v", err) } @@ -325,6 +334,7 @@ func (d *DKGDealer) HandleDKGDeal(msg *alias.DKGData) error { d.logger.Info("dkgState: deal is intended for us, storing") if _, exists := d.deals[msg.GetAddrString()]; exists { + // TODO: do we want to blame for duplicate data here? return nil } @@ -390,12 +400,18 @@ func (d *DKGDealer) GetResponses() ([]*alias.DKGData, error) { } func (d *DKGDealer) HandleDKGResponse(msg *alias.DKGData) error { + d.messagesHistory = append(d.messagesHistory, msg) var ( dec = gob.NewDecoder(bytes.NewBuffer(msg.Data)) resp = &dkg.Response{} ) if err := dec.Decode(resp); err != nil { - d.losers = append(d.losers, crypto.Address(msg.Addr)) + _, validator := d.validators.GetByAddress(msg.Addr) + d.losers = append(d.losers, &types.DKGLoser{ + Type: types.LoserTypeCorruptData, + Data: types.DKGDataMessage{Data: msg}, + Validator: validator, + }) return fmt.Errorf("failed to decode deal: %v", err) } @@ -498,12 +514,19 @@ func (d *DKGDealer) GetJustifications() ([]*alias.DKGData, error) { } func (d *DKGDealer) HandleDKGJustification(msg *alias.DKGData) error { + d.messagesHistory = append(d.messagesHistory, msg) + var justification *dkg.Justification if msg.Data != nil { dec := gob.NewDecoder(bytes.NewBuffer(msg.Data)) justification = &dkg.Justification{} if err := dec.Decode(justification); err != nil { - d.losers = append(d.losers, crypto.Address(msg.Addr)) + _, validator := d.validators.GetByAddress(msg.Addr) + d.losers = append(d.losers, &types.DKGLoser{ + Type: types.LoserTypeCorruptData, + Data: types.DKGDataMessage{Data: msg}, + Validator: validator, + }) return fmt.Errorf("failed to decode deal: %v", err) } } @@ -558,12 +581,20 @@ func (d *DKGDealer) IsJustificationsReady() bool { } func (d DKGDealer) GetCommits() (*dkg.SecretCommits, error) { - for _, peerJustifications := range d.justifications.data { + for addr, peerJustifications := range d.justifications.data { for _, just := range peerJustifications { justification := just.(*dkg.Justification) if justification != nil { d.logger.Info("dkgState: processing non-empty justification", "from", justification.Index) + _, validator := d.validators.GetByAddress([]byte(addr)) if err := d.instance.ProcessJustification(justification); err != nil { + d.losers = append(d.losers, &types.DKGLoser{ + Type: types.LoserTypeCorruptJustification, + // TODO: we need to provide the signature from original message + // for the evidence to be provable, and this info is lost when + // we add the justification to the messageStore. + Validator: validator, + }) return nil, fmt.Errorf("failed to ProcessJustification: %v", err) } } else { @@ -586,13 +617,11 @@ func (d DKGDealer) GetCommits() (*dkg.SecretCommits, error) { qualSet[idx] = true } - for idx, pk2addr := range d.pubKeys { + for idx, _ := range d.pubKeys { if !qualSet[idx] { - d.losers = append(d.losers, pk2addr.Addr) + return nil, errors.New("some of participants failed to complete phase I") } } - - return nil, errors.New("some of participants failed to complete phase I") } commits, err := d.instance.SecretCommits() @@ -610,13 +639,14 @@ func (d DKGDealer) GetCommits() (*dkg.SecretCommits, error) { ////////////////////////////////////////////////////////////////////////////// func (d *DKGDealer) HandleDKGCommit(msg *alias.DKGData) error { + d.messagesHistory = append(d.messagesHistory, msg) + dec := gob.NewDecoder(bytes.NewBuffer(msg.Data)) commits := &dkg.SecretCommits{} for i := 0; i < msg.NumEntities; i++ { commits.Commitments = append(commits.Commitments, d.suiteG2.Point()) } if err := dec.Decode(commits); err != nil { - d.losers = append(d.losers, crypto.Address(msg.Addr)) return fmt.Errorf("failed to decode commit: %v", err) } d.commits.add(msg.GetAddrString(), commits) @@ -679,6 +709,8 @@ func (d *DKGDealer) ProcessCommits() (error, bool) { } func (d *DKGDealer) HandleDKGComplaint(msg *alias.DKGData) error { + d.messagesHistory = append(d.messagesHistory, msg) + var complaint *dkg.ComplaintCommits if msg.Data != nil { dec := gob.NewDecoder(bytes.NewBuffer(msg.Data)) @@ -689,7 +721,6 @@ func (d *DKGDealer) HandleDKGComplaint(msg *alias.DKGData) error { complaint.Deal.Commitments = append(complaint.Deal.Commitments, d.suiteG2.Point()) } if err := dec.Decode(complaint); err != nil { - d.losers = append(d.losers, crypto.Address(msg.Addr)) return fmt.Errorf("failed to decode complaint: %v", err) } } @@ -745,12 +776,13 @@ func (d *DKGDealer) ProcessComplaints() (error, bool) { } func (d *DKGDealer) HandleDKGReconstructCommit(msg *alias.DKGData) error { + d.messagesHistory = append(d.messagesHistory, msg) + var rc *dkg.ReconstructCommits if msg.Data != nil { dec := gob.NewDecoder(bytes.NewBuffer(msg.Data)) rc = &dkg.ReconstructCommits{} if err := dec.Decode(rc); err != nil { - d.losers = append(d.losers, crypto.Address(msg.Addr)) return fmt.Errorf("failed to decode complaint: %v", err) } } @@ -829,6 +861,85 @@ func (d *DKGDealer) VerifyMessage(msg types.DKGDataMessage) error { return nil } +func (d *DKGDealer) CheckLoserDuplicateData(loser *types.DKGLoser) bool { + var ( + count int + msg = loser.Data.Data + ) + for _, historyMsg := range d.messagesHistory { + if bytes.Equal(historyMsg.Addr, msg.Addr) && historyMsg.Type == msg.Type { + // TODO: check if participants can have zero indices, and if so, + // make ToIndex nullable. + if historyMsg.ToIndex > 0 && historyMsg.ToIndex == msg.ToIndex { + count++ + } else { + count++ + } + } + } + + return count > 1 +} + +func (d *DKGDealer) CheckLoserMissingData(loser *types.DKGLoser) bool { + var msg = loser.Data.Data + for _, historyMsg := range d.messagesHistory { + if bytes.Equal(historyMsg.Addr, msg.Addr) && historyMsg.Type == msg.Type { + // TODO: check if participants can have zero indices, and if so, + // make ToIndex nullable. + if historyMsg.ToIndex > 0 && historyMsg.ToIndex == msg.ToIndex { + return true + } + } + } + + return false +} + +func (d *DKGDealer) CheckLoserCorruptData(loser *types.DKGLoser) bool { + var msg = loser.Data.Data + + if err := msg.ValidateBasic(); err != nil { + // We can not be sure that the message was actually sent by this user. + return false + } + + // TODO: implement other types checks. + var dec = gob.NewDecoder(bytes.NewBuffer(msg.Data)) + switch loser.Data.Data.Type { + case alias.DKGPubKey: + val := d.suiteG2.Point() + if err := dec.Decode(val); err != nil { + return true + } + case alias.DKGDeal: + val := &dkg.Deal{ // We need to initialize everything down to the kyber.Point to avoid nil panics. + Deal: &vss.EncryptedDeal{ + DHKey: d.suiteG2.Point(), + }, + } + if err := dec.Decode(val); err != nil { + return true + } + case alias.DKGResponse: + val := &dkg.Response{} + if err := dec.Decode(val); err != nil { + return true + } + case alias.DKGJustification: + val := &dkg.Justification{} + if err := dec.Decode(val); err != nil { + return true + } + } + return false +} + +func (d *DKGDealer) CheckLoserCorruptJustification(loser *types.DKGLoser) bool { + // TODO: implement. + return false +} + func (d *DKGDealer) SendMsgCb(msg *alias.DKGData) error { return d.sendMsgCb(msg) } diff --git a/lib/offChain/dkg.go b/lib/offChain/dkg.go index 5394f72..16ccb95 100644 --- a/lib/offChain/dkg.go +++ b/lib/offChain/dkg.go @@ -9,9 +9,9 @@ import ( dkgalias "github.com/dgamingfoundation/dkglib/lib/alias" "github.com/dgamingfoundation/dkglib/lib/blsShare" dkglib "github.com/dgamingfoundation/dkglib/lib/dealer" + "github.com/dgamingfoundation/dkglib/lib/types" dkgtypes "github.com/dgamingfoundation/dkglib/lib/types" "github.com/tendermint/tendermint/alias" - tmtypes "github.com/tendermint/tendermint/alias" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/libs/events" "github.com/tendermint/tendermint/libs/log" @@ -259,7 +259,7 @@ func (m *OffChainDKG) GetPrivValidator() alias.PrivValidator { return m.privValidator } -func (m *OffChainDKG) GetLosers() []*tmtypes.Validator { +func (m *OffChainDKG) GetLosers() []*types.DKGLoser { m.mtx.Lock() defer m.mtx.Unlock() @@ -268,7 +268,31 @@ func (m *OffChainDKG) GetLosers() []*tmtypes.Validator { panic(fmt.Sprintf("failed to get dealer for current round ID (%d)", m.dkgRoundID)) } - return dealer.PopLosers() + return dealer.GetLosers() +} + +func (m *OffChainDKG) CheckLoserDuplicateData(loser *types.DKGLoser) bool { + return false +} + +func (m *OffChainDKG) CheckLoserMissingData(loser *types.DKGLoser) bool { + return false +} + +func (m *OffChainDKG) CheckLoserCorruptData(loser *types.DKGLoser) bool { + return false +} + +func (m *OffChainDKG) CheckLoserCorruptJustification(loser *types.DKGLoser) bool { + return false +} + +func (m *OffChainDKG) IsReady() bool { + if m == nil { + return false + } + + return true } type verifierFunc func(s string, i int) dkgtypes.Verifier diff --git a/lib/onChain/dkg.go b/lib/onChain/dkg.go index e25ea87..93da619 100644 --- a/lib/onChain/dkg.go +++ b/lib/onChain/dkg.go @@ -32,6 +32,10 @@ func NewOnChainDKG(cli *context.Context, txBldr *authtxb.TxBuilder) *OnChainDKG } } +func (m *OnChainDKG) GetDealer() dealer.Dealer { + return m.dealer +} + func (m *OnChainDKG) GetVerifier() (types.Verifier, error) { return m.dealer.GetVerifier() } @@ -97,7 +101,7 @@ func (m *OnChainDKG) StartRound( return nil } -func (m *OnChainDKG) GetLosers() []*tmtypes.Validator { +func (m *OnChainDKG) GetLosers() []*types.DKGLoser { return m.dealer.GetLosers() } diff --git a/lib/types/dkg.go b/lib/types/dkg.go index ad52d4a..835e2d0 100644 --- a/lib/types/dkg.go +++ b/lib/types/dkg.go @@ -5,12 +5,22 @@ import ( "fmt" "github.com/dgamingfoundation/dkglib/lib/alias" + tmtypes "github.com/tendermint/tendermint/alias" ) var ( ErrDKGVerifierNotReady = errors.New("verifier not ready yet") ) +type LoserType string + +const ( + LoserTypeCorruptData LoserType = "loser_type_corrupt_data" + LoserTypeMissingData LoserType = "loser_type_missing_data" + LoserTypeDuplicateData LoserType = "loser_type_duplicate_data" + LoserTypeCorruptJustification LoserType = "loser_type_corrupt_justification" +) + type DKGDataMessage struct { Data *alias.DKGData } @@ -22,3 +32,9 @@ func (m *DKGDataMessage) ValidateBasic() error { func (m *DKGDataMessage) String() string { return fmt.Sprintf("[Proposal %+v]", m.Data) } + +type DKGLoser struct { + Type LoserType + Data DKGDataMessage + Validator *tmtypes.Validator +} diff --git a/lib/types/interface.go b/lib/types/interface.go index 25fe3dd..47a4954 100644 --- a/lib/types/interface.go +++ b/lib/types/interface.go @@ -1,16 +1,20 @@ package types import ( - tmtypes "github.com/tendermint/tendermint/alias" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/types" ) type DKG interface { + IsReady() bool HandleOffChainShare(dkgMsg *DKGDataMessage, height int64, validators *types.ValidatorSet, pubKey crypto.PubKey) (switchToOnChain bool) CheckDKGTime(height int64, validators *types.ValidatorSet) SetVerifier(verifier Verifier) Verifier() Verifier MsgQueue() chan *DKGDataMessage - GetLosers() []*tmtypes.Validator + GetLosers() []*DKGLoser + CheckLoserDuplicateData(loser *DKGLoser) bool + CheckLoserMissingData(loser *DKGLoser) bool + CheckLoserCorruptData(loser *DKGLoser) bool + CheckLoserCorruptJustification(loser *DKGLoser) bool }