diff --git a/README.md b/README.md index 6aea9217..38dadb6b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Permissively MIT Licensed. Note! This is a library for developers. You may find a TSS tool that you can use with the Binance Chain CLI [here](https://docs.binance.org/tss.html). ## Introduction -This is an implementation of multi-party {t,n}-threshold ECDSA (elliptic curve digital signatures) based on Gennaro and Goldfeder CCS 2018 protocol [\[1\]](#references) +This is an implementation of multi-party {t,n}-threshold ECDSA (elliptic curve digital signatures) based on Gennaro and Goldfeder CCS 2018 [\[1\]](#references) This library includes three protocols: @@ -25,7 +25,7 @@ This library includes three protocols: ## Rationale ECDSA is used extensively for crypto-currencies such as Bitcoin, Ethereum (secp256k1 curve), NEO (NIST P-256 curve) and many more. -For such currencies this technique may be used to create crypto wallets where multiple participants must collaborate to sign transactions. See [MultiSig Use Cases](https://en.bitcoin.it/wiki/Multisignature#Multisignature_Applications) +For such currencies this technique may be used to create crypto wallets where multiple parties must collaborate to sign transactions. See [MultiSig Use Cases](https://en.bitcoin.it/wiki/Multisignature#Multisignature_Applications) One secret share per key/address is stored locally by each participant and these are kept safe by the protocol – they are never revealed to others at any time. Moreover, there is no trusted dealer of the shares. @@ -99,15 +99,17 @@ go func() { }() ``` +⚠️ During re-sharing the key data may be modified during the rounds. Do not ever overwrite any data saved on disk until the final struct has been received through the `end` channel. + ## Messaging In these examples the `outCh` will collect outgoing messages from the party and the `endCh` will receive save data or a signature when the protocol is complete. During the protocol you should provide the party with updates received from other participating parties on the network. -A `Party` has two thread-safe methods on it for receiving updates: +A `Party` has two thread-safe methods on it for receiving updates. Note that the last two booleans are only used in re-sharing. ```go // The main entry point when updating a party's state from the wire -UpdateFromBytes(wireBytes []byte, from *tss.PartyID, isBroadcast, isToOldCommittee bool) (ok bool, err *tss.Error) +UpdateFromBytes(wireBytes []byte, from *tss.PartyID, isBroadcast bool) (ok bool, err *tss.Error) // You may use this entry point to update a party's state when running locally or in tests Update(msg tss.ParsedMessage) (ok bool, err *tss.Error) ``` diff --git a/ecdsa/keygen/local_party.go b/ecdsa/keygen/local_party.go index 4a056e90..dfd905d1 100644 --- a/ecdsa/keygen/local_party.go +++ b/ecdsa/keygen/local_party.go @@ -117,12 +117,13 @@ func NewLocalParty( end: end, } // msgs init - p.temp.KGCs = make([]cmt.HashCommitment, partyCount) p.temp.kgRound1Messages = make([]tss.ParsedMessage, partyCount) p.temp.kgRound2Message1s = make([]tss.ParsedMessage, partyCount) p.temp.kgRound2Message2s = make([]tss.ParsedMessage, partyCount) p.temp.kgRound3Messages = make([]tss.ParsedMessage, partyCount) - // data init + // temp data init + p.temp.KGCs = make([]cmt.HashCommitment, partyCount) + // save data init p.data.BigXj = make([]*crypto.ECPoint, partyCount) p.data.PaillierPKs = make([]*paillier.PublicKey, partyCount) p.data.NTildej = make([]*big.Int, partyCount) @@ -142,8 +143,8 @@ func (p *LocalParty) Update(msg tss.ParsedMessage) (ok bool, err *tss.Error) { return tss.BaseUpdate(p, msg, "keygen") } -func (p *LocalParty) UpdateFromBytes(wireBytes []byte, from *tss.PartyID, isBroadcast, isToOldCommittee bool) (bool, *tss.Error) { - msg, err := tss.ParseWireMessage(wireBytes, from, isBroadcast, isToOldCommittee) +func (p *LocalParty) UpdateFromBytes(wireBytes []byte, from *tss.PartyID, isBroadcast bool) (bool, *tss.Error) { + msg, err := tss.ParseWireMessage(wireBytes, from, isBroadcast) if err != nil { return false, p.WrapError(err) } diff --git a/ecdsa/resharing/ecdsa-resharing.pb.go b/ecdsa/resharing/ecdsa-resharing.pb.go index da6c175c..141056a3 100644 --- a/ecdsa/resharing/ecdsa-resharing.pb.go +++ b/ecdsa/resharing/ecdsa-resharing.pb.go @@ -265,34 +265,68 @@ func (m *DGRound3Message2) GetVDecommitment() [][]byte { return nil } +// +// The Round 4 "ACK" is broadcast to peers of the Old and New Committees from the New Committee in this message. +type DGRound4Message struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *DGRound4Message) Reset() { *m = DGRound4Message{} } +func (m *DGRound4Message) String() string { return proto.CompactTextString(m) } +func (*DGRound4Message) ProtoMessage() {} +func (*DGRound4Message) Descriptor() ([]byte, []int) { + return fileDescriptor_f7d3ae1dc68dc295, []int{5} +} + +func (m *DGRound4Message) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_DGRound4Message.Unmarshal(m, b) +} +func (m *DGRound4Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_DGRound4Message.Marshal(b, m, deterministic) +} +func (m *DGRound4Message) XXX_Merge(src proto.Message) { + xxx_messageInfo_DGRound4Message.Merge(m, src) +} +func (m *DGRound4Message) XXX_Size() int { + return xxx_messageInfo_DGRound4Message.Size(m) +} +func (m *DGRound4Message) XXX_DiscardUnknown() { + xxx_messageInfo_DGRound4Message.DiscardUnknown(m) +} + +var xxx_messageInfo_DGRound4Message proto.InternalMessageInfo + func init() { proto.RegisterType((*DGRound1Message)(nil), "DGRound1Message") proto.RegisterType((*DGRound2Message1)(nil), "DGRound2Message1") proto.RegisterType((*DGRound2Message2)(nil), "DGRound2Message2") proto.RegisterType((*DGRound3Message1)(nil), "DGRound3Message1") proto.RegisterType((*DGRound3Message2)(nil), "DGRound3Message2") + proto.RegisterType((*DGRound4Message)(nil), "DGRound4Message") } func init() { proto.RegisterFile("protob/ecdsa-resharing.proto", fileDescriptor_f7d3ae1dc68dc295) } var fileDescriptor_f7d3ae1dc68dc295 = []byte{ - // 277 bytes of a gzipped FileDescriptorProto + // 285 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x91, 0xc1, 0x4b, 0xc3, 0x30, - 0x18, 0xc5, 0x69, 0xe7, 0x26, 0x7e, 0xab, 0x9b, 0x06, 0xc1, 0x1c, 0x54, 0x66, 0x41, 0xe8, 0x45, - 0x47, 0xbb, 0x93, 0x57, 0x1d, 0x78, 0x52, 0x46, 0xf1, 0xa0, 0x5e, 0x42, 0xbb, 0xc6, 0x35, 0xd0, - 0x26, 0x25, 0x4d, 0x8b, 0xfe, 0x19, 0xfe, 0xc7, 0xd2, 0x2c, 0x0d, 0x1b, 0x3b, 0xbe, 0xdf, 0xf7, - 0xe0, 0x3d, 0xde, 0x07, 0x57, 0x95, 0x14, 0x4a, 0xa4, 0x73, 0xba, 0xce, 0xea, 0xe4, 0x5e, 0xd2, - 0x3a, 0x4f, 0x24, 0xe3, 0x9b, 0x07, 0x8d, 0x7d, 0x05, 0xd3, 0xe5, 0x4b, 0x2c, 0x1a, 0x9e, 0x85, - 0xaf, 0xb4, 0xae, 0x93, 0x0d, 0x45, 0x37, 0x30, 0xd6, 0x5e, 0x52, 0x35, 0x29, 0xf9, 0xc1, 0xce, - 0xcc, 0x09, 0xbc, 0xf8, 0x44, 0xa3, 0x55, 0x93, 0x7e, 0xec, 0xdf, 0x7f, 0xb1, 0xbb, 0x7f, 0xff, - 0x44, 0xb7, 0xe0, 0xb5, 0x64, 0x2d, 0xca, 0x92, 0xa9, 0x92, 0x72, 0x85, 0x07, 0xda, 0x30, 0x6e, - 0x9f, 0x2d, 0xf2, 0xff, 0x1c, 0x38, 0x33, 0xb1, 0x91, 0x89, 0x0d, 0xd1, 0x35, 0x40, 0x95, 0xb0, - 0xa2, 0x60, 0x54, 0x12, 0xde, 0xc7, 0xf6, 0xe4, 0x0d, 0xdd, 0xc1, 0xc4, 0x9e, 0x2b, 0x29, 0xc4, - 0x37, 0x76, 0x67, 0x83, 0xc0, 0x8b, 0x4f, 0x7b, 0xba, 0xea, 0x20, 0xba, 0x84, 0x63, 0x4e, 0x14, - 0x2b, 0x32, 0x6a, 0x82, 0x47, 0xfc, 0xbd, 0x53, 0x68, 0x02, 0x6e, 0x1e, 0xe2, 0x23, 0xcd, 0xdc, - 0x3c, 0xd4, 0x3a, 0xc2, 0x43, 0xa3, 0x23, 0x1f, 0x1d, 0x54, 0x8a, 0xfc, 0xc0, 0xb2, 0x85, 0xad, - 0x79, 0x01, 0xc3, 0x6e, 0x42, 0x6a, 0x1a, 0x6e, 0x85, 0xff, 0x78, 0xe0, 0x8c, 0xba, 0xc6, 0x2d, - 0xc9, 0xe8, 0xce, 0x14, 0xce, 0xb6, 0x71, 0xbb, 0xdc, 0x81, 0x4f, 0xe7, 0x5f, 0x53, 0x3d, 0xde, - 0xdc, 0xfe, 0x26, 0x1d, 0xe9, 0xe7, 0x2c, 0xfe, 0x03, 0x00, 0x00, 0xff, 0xff, 0x15, 0x9e, 0x5b, - 0xb0, 0xbc, 0x01, 0x00, 0x00, + 0x14, 0xc6, 0x69, 0xe7, 0x26, 0xbe, 0xcd, 0xcd, 0x05, 0xc1, 0x1c, 0x54, 0x66, 0x40, 0xe8, 0x45, + 0x47, 0x3b, 0x2f, 0x5e, 0x75, 0xe0, 0x49, 0x19, 0xc5, 0x83, 0x7a, 0x09, 0xed, 0x1a, 0xd7, 0x42, + 0x9b, 0x94, 0x34, 0x2d, 0xfa, 0x67, 0xf8, 0x1f, 0x4b, 0xb3, 0x34, 0x6c, 0xec, 0xf8, 0xfd, 0xde, + 0x7b, 0x7c, 0x1f, 0xef, 0x83, 0xcb, 0x52, 0x0a, 0x25, 0xe2, 0x39, 0x5b, 0x27, 0x55, 0x74, 0x27, + 0x59, 0x95, 0x46, 0x32, 0xe3, 0x9b, 0x7b, 0x8d, 0x89, 0x82, 0xc9, 0xf2, 0x25, 0x14, 0x35, 0x4f, + 0xfc, 0x57, 0x56, 0x55, 0xd1, 0x86, 0xa1, 0x6b, 0x18, 0xea, 0x5d, 0x5a, 0xd6, 0x31, 0xfd, 0xc1, + 0xce, 0xcc, 0xf1, 0x46, 0xe1, 0x89, 0x46, 0xab, 0x3a, 0xfe, 0xd8, 0x9f, 0xff, 0x62, 0x77, 0x7f, + 0xfe, 0x89, 0x6e, 0x60, 0xd4, 0xd0, 0xb5, 0x28, 0x8a, 0x4c, 0x15, 0x8c, 0x2b, 0xdc, 0xd3, 0x0b, + 0xc3, 0xe6, 0xd9, 0x22, 0xf2, 0xe7, 0xc0, 0x99, 0xb1, 0x0d, 0x8c, 0xad, 0x8f, 0xae, 0x00, 0xca, + 0x28, 0xcb, 0xf3, 0x8c, 0x49, 0xca, 0x3b, 0xdb, 0x8e, 0xbc, 0xa1, 0x5b, 0x18, 0xdb, 0x71, 0x29, + 0x85, 0xf8, 0xc6, 0xee, 0xac, 0xe7, 0x8d, 0xc2, 0xd3, 0x8e, 0xae, 0x5a, 0x88, 0x2e, 0xe0, 0x98, + 0x53, 0x95, 0xe5, 0x09, 0x33, 0xc6, 0x03, 0xfe, 0xde, 0x2a, 0x34, 0x06, 0x37, 0xf5, 0xf1, 0x91, + 0x66, 0x6e, 0xea, 0x6b, 0x1d, 0xe0, 0xbe, 0xd1, 0x01, 0x41, 0x07, 0x91, 0x02, 0xe2, 0x59, 0xb6, + 0xb0, 0x31, 0xcf, 0xa1, 0xdf, 0xbe, 0x90, 0x99, 0x84, 0x5b, 0x41, 0x1e, 0x0f, 0x36, 0x83, 0x36, + 0x71, 0x43, 0x13, 0xb6, 0xf3, 0x0a, 0x67, 0x9b, 0xb8, 0x59, 0xee, 0x40, 0x32, 0xb5, 0x15, 0x3c, + 0x98, 0xd3, 0xa7, 0xe9, 0xd7, 0x44, 0xff, 0x73, 0x6e, 0xeb, 0x8a, 0x07, 0xba, 0xaf, 0xc5, 0x7f, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x59, 0x4b, 0x8f, 0x39, 0xcf, 0x01, 0x00, 0x00, } diff --git a/ecdsa/resharing/local_party.go b/ecdsa/resharing/local_party.go index 5a4dc405..f3b83748 100644 --- a/ecdsa/resharing/local_party.go +++ b/ecdsa/resharing/local_party.go @@ -8,8 +8,10 @@ package resharing import ( "fmt" + "math/big" "github.com/binance-chain/tss-lib/common" + "github.com/binance-chain/tss-lib/crypto" cmt "github.com/binance-chain/tss-lib/crypto/commitments" "github.com/binance-chain/tss-lib/crypto/vss" "github.com/binance-chain/tss-lib/ecdsa/keygen" @@ -39,7 +41,8 @@ type ( dgRound2Message1s, dgRound2Message2s, dgRound3Message1s, - dgRound3Message2s []tss.ParsedMessage + dgRound3Message2s, + dgRound4Messages []tss.ParsedMessage } localTempData struct { @@ -49,6 +52,11 @@ type ( NewVs vss.Vs NewShares vss.Shares VD cmt.HashDeCommitment + + // temporary storage of data that is persisted by the new party in round 5 if all "ACK" messages are received + newXi *big.Int + newKs []*big.Int + newBigXjs []*crypto.ECPoint // Xj to save in round 5 } ) @@ -76,6 +84,7 @@ func NewLocalParty( p.temp.dgRound2Message2s = make([]tss.ParsedMessage, params.NewPartyCount()) // " p.temp.dgRound3Message1s = make([]tss.ParsedMessage, params.Threshold()+1) // from t+1 of Old Committee p.temp.dgRound3Message2s = make([]tss.ParsedMessage, params.Threshold()+1) // " + p.temp.dgRound4Messages = make([]tss.ParsedMessage, params.NewPartyCount()) // from n of New Committee return p } @@ -91,8 +100,8 @@ func (p *LocalParty) Update(msg tss.ParsedMessage) (ok bool, err *tss.Error) { return tss.BaseUpdate(p, msg, "resharing") } -func (p *LocalParty) UpdateFromBytes(wireBytes []byte, from *tss.PartyID, isBroadcast, isToOldCommittee bool) (bool, *tss.Error) { - msg, err := tss.ParseWireMessage(wireBytes, from, isBroadcast, isToOldCommittee) +func (p *LocalParty) UpdateFromBytes(wireBytes []byte, from *tss.PartyID, isBroadcast bool) (bool, *tss.Error) { + msg, err := tss.ParseWireMessage(wireBytes, from, isBroadcast) if err != nil { return false, p.WrapError(err) } @@ -124,6 +133,9 @@ func (p *LocalParty) StoreMessage(msg tss.ParsedMessage) (bool, *tss.Error) { case *DGRound3Message2: p.temp.dgRound3Message2s[fromPIdx] = msg + case *DGRound4Message: + p.temp.dgRound4Messages[fromPIdx] = msg + default: // unrecognised message, just ignore! common.Logger.Warningf("unrecognised message ignored: %v", msg) return false, nil diff --git a/ecdsa/resharing/local_party_test.go b/ecdsa/resharing/local_party_test.go index 8b11f6c4..9dea2190 100644 --- a/ecdsa/resharing/local_party_test.go +++ b/ecdsa/resharing/local_party_test.go @@ -43,32 +43,32 @@ func TestE2EConcurrent(t *testing.T) { // tss.SetCurve(elliptic.P256()) threshold, newThreshold := testThreshold, testThreshold - pIDs := tss.GenerateTestPartyIDs(testParticipants) + oldPIDs := tss.GenerateTestPartyIDs(testParticipants) // PHASE: load keygen fixtures keys, err := keygen.LoadKeygenTestFixtures(testParticipants) assert.NoError(t, err, "should load keygen fixtures") // PHASE: resharing - pIDs = pIDs[:threshold+1] // always resharing with old_t+1 - p2pCtx := tss.NewPeerContext(pIDs) - newPIDs := tss.GenerateTestPartyIDs(testParticipants) // new group (start from new index) + oldPIDs = oldPIDs[:threshold+1] // always resharing with old_t+1 + oldP2PCtx := tss.NewPeerContext(oldPIDs) + newPIDs := tss.GenerateTestPartyIDs(testParticipants) newP2PCtx := tss.NewPeerContext(newPIDs) newPCount := len(newPIDs) - oldCommittee := make([]*LocalParty, 0, len(pIDs)) + oldCommittee := make([]*LocalParty, 0, len(oldPIDs)) newCommittee := make([]*LocalParty, 0, newPCount) bothCommitteesPax := len(oldCommittee) + len(newCommittee) errCh := make(chan *tss.Error, bothCommitteesPax) outCh := make(chan tss.Message, bothCommitteesPax) - endCh := make(chan keygen.LocalPartySaveData, len(newCommittee)) + endCh := make(chan keygen.LocalPartySaveData, bothCommitteesPax) updater := test.SharedPartyUpdater // init the old parties first - for i, pID := range pIDs { - params := tss.NewReSharingParameters(p2pCtx, newP2PCtx, pID, testParticipants, threshold, newPCount, newThreshold) + for i, pID := range oldPIDs { + params := tss.NewReSharingParameters(oldP2PCtx, newP2PCtx, pID, testParticipants, threshold, newPCount, newThreshold) keyI := keygen.LocalPartySaveData{ LocalPreParams: keygen.LocalPreParams{ PaillierSK: keys[i].PaillierSK, @@ -88,7 +88,7 @@ func TestE2EConcurrent(t *testing.T) { Ks: keys[i].Ks[:testThreshold+1], ECDSAPub: keys[i].ECDSAPub, } - P := NewLocalParty(params, keyI, outCh, nil).(*LocalParty) // discard old key data + P := NewLocalParty(params, keyI, outCh, endCh).(*LocalParty) // discard old key data oldCommittee = append(oldCommittee, P) } // init the new parties; re-use the fixture pre-params for speed @@ -97,7 +97,7 @@ func TestE2EConcurrent(t *testing.T) { common.Logger.Info("No test fixtures were found, so the safe primes will be generated from scratch. This may take a while...") } for i, pID := range newPIDs { - params := tss.NewReSharingParameters(p2pCtx, newP2PCtx, pID, testParticipants, threshold, newPCount, newThreshold) + params := tss.NewReSharingParameters(oldP2PCtx, newP2PCtx, pID, testParticipants, threshold, newPCount, newThreshold) save := keygen.LocalPartySaveData{ BigXj: make([]*crypto.ECPoint, newPCount), PaillierPKs: make([]*paillier.PublicKey, newPCount), @@ -140,23 +140,29 @@ func TestE2EConcurrent(t *testing.T) { case msg := <-outCh: dest := msg.GetTo() - destParties := newCommittee - if msg.IsToOldCommittee() { - destParties = oldCommittee - } if dest == nil { t.Fatal("did not expect a msg to have a nil destination during resharing") } - for _, destP := range dest { - go updater(destParties[destP.Index], msg, errCh) + if msg.IsToOldCommittee() || msg.IsToOldAndNewCommittees() { + for _, destP := range dest[:len(oldCommittee)] { + go updater(oldCommittee[destP.Index], msg, errCh) + } + } + if !msg.IsToOldCommittee() || msg.IsToOldAndNewCommittees() { + for _, destP := range dest { + go updater(newCommittee[destP.Index], msg, errCh) + } } case save := <-endCh: index, err := save.OriginalIndex() assert.NoErrorf(t, err, "should not be an error getting a party's index from save data") - keys[index] = save + // old committee members that aren't receiving a share have their Xi zeroed + if save.Xi.Cmp(big.NewInt(0)) != 0 { + keys[index] = save + } atomic.AddInt32(&reSharingEnded, 1) - if atomic.LoadInt32(&reSharingEnded) == int32(len(newCommittee)) { + if atomic.LoadInt32(&reSharingEnded) == int32(len(oldCommittee)+len(newCommittee)) { t.Logf("Resharing done. Reshared %d participants", reSharingEnded) // xj tests: BigXj == xj*G diff --git a/ecdsa/resharing/messages.go b/ecdsa/resharing/messages.go index 02bdfe6e..bfd96e95 100644 --- a/ecdsa/resharing/messages.go +++ b/ecdsa/resharing/messages.go @@ -208,3 +208,24 @@ func (m *DGRound3Message2) UnmarshalVDeCommitment() cmt.HashDeCommitment { deComBzs := m.GetVDecommitment() return cmt.NewHashDeCommitmentFromBytes(deComBzs) } + +// ----- // + +func NewDGRound4Message( + to []*tss.PartyID, + from *tss.PartyID, +) tss.ParsedMessage { + meta := tss.MessageRouting{ + From: from, + To: to, + IsBroadcast: true, + IsToOldAndNewCommittees: true, + } + content := &DGRound4Message{} + msg := tss.NewMessageWrapper(meta, content) + return tss.NewMessage(meta, content, msg) +} + +func (m *DGRound4Message) ValidateBasic() bool { + return true +} diff --git a/ecdsa/resharing/round_2_new_step_1.go b/ecdsa/resharing/round_2_new_step_1.go index ac430578..cb665ec4 100644 --- a/ecdsa/resharing/round_2_new_step_1.go +++ b/ecdsa/resharing/round_2_new_step_1.go @@ -78,7 +78,7 @@ func (round *round2) CanAccept(msg tss.ParsedMessage) bool { } if round.ReSharingParams().IsOldCommittee() { if _, ok := msg.Content().(*DGRound2Message2); ok { - return msg.IsBroadcast() && msg.IsToOldCommittee() + return msg.IsBroadcast() } } return false diff --git a/ecdsa/resharing/round_4_new_step_2.go b/ecdsa/resharing/round_4_new_step_2.go index 7e995847..e94290c3 100644 --- a/ecdsa/resharing/round_4_new_step_2.go +++ b/ecdsa/resharing/round_4_new_step_2.go @@ -28,11 +28,10 @@ func (round *round4) Start() *tss.Error { round.resetOK() // resets both round.oldOK and round.newOK round.allOldOK() - round.allNewOK() if !round.ReSharingParams().IsNewCommittee() { - round.end <- *round.save - return nil // old committee finished! + // both committees proceed to round 5 after receiving "ACK" messages from the new committee + return nil } Pi := round.PartyID() @@ -127,7 +126,7 @@ func (round *round4) Start() *tss.Error { // 15-19. newKs := make([]*big.Int, 0, round.NewPartyCount()) - NewBigXj := make([]*crypto.ECPoint, round.NewPartyCount()) + newBigXjs := make([]*crypto.ECPoint, round.NewPartyCount()) culprits = make([]*tss.PartyID, 0, round.NewPartyCount()) // who caused the error(s) for j := 0; j < round.NewPartyCount(); j++ { Pj := round.NewParties().IDs()[j] @@ -142,41 +141,46 @@ func (round *round4) Start() *tss.Error { culprits = append(culprits, Pj) } } - NewBigXj[j] = newBigXj + newBigXjs[j] = newBigXj } if len(culprits) > 0 { return round.WrapError(errors2.Wrapf(err, "newBigXj.Add(Vc[c].ScalarMult(z))"), culprits...) } - round.save.BigXj = NewBigXj - // 21. - // for this P: SAVE other data - round.save.ShareID = round.PartyID().KeyInt() - round.save.Xi = newXi - round.save.Ks = newKs + round.temp.newXi = newXi + round.temp.newKs = newKs + round.temp.newBigXjs = newBigXjs - // misc: build list of paillier public keys to save - for j, msg := range round.temp.dgRound2Message1s { - if j == i { - continue - } - r2msg1 := msg.Content().(*DGRound2Message1) - round.save.PaillierPKs[j] = r2msg1.UnmarshalPaillierPK() - } - - round.end <- *round.save + // Send an "ACK" message to both committees to signal that we're ready to save our data + r4msg := NewDGRound4Message(round.OldAndNewParties(), Pi) + round.temp.dgRound4Messages[i] = r4msg + round.out <- r4msg return nil } func (round *round4) CanAccept(msg tss.ParsedMessage) bool { + if _, ok := msg.Content().(*DGRound4Message); ok { + return msg.IsBroadcast() + } return false } func (round *round4) Update() (bool, *tss.Error) { - return false, nil + // accept messages from new -> old&new committees + for j, msg := range round.temp.dgRound4Messages { + if round.newOK[j] { + continue + } + if msg == nil || !round.CanAccept(msg) { + return false, nil + } + round.newOK[j] = true + } + return true, nil } func (round *round4) NextRound() tss.Round { - return nil // finished! + round.started = false + return &round5{round} } diff --git a/ecdsa/resharing/round_5_new_step_3.go b/ecdsa/resharing/round_5_new_step_3.go new file mode 100644 index 00000000..cc6e6edf --- /dev/null +++ b/ecdsa/resharing/round_5_new_step_3.go @@ -0,0 +1,62 @@ +// Copyright © 2019 Binance +// +// This file is part of Binance. The full Binance copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +package resharing + +import ( + "errors" + + "github.com/binance-chain/tss-lib/tss" +) + +func (round *round5) Start() *tss.Error { + if round.started { + return round.WrapError(errors.New("round already started")) + } + round.number = 5 + round.started = true + + round.allOldOK() + round.allNewOK() + + Pi := round.PartyID() + i := Pi.Index + + if round.IsNewCommittee() { + // 21. + // for this P: SAVE data + round.save.BigXj = round.temp.newBigXjs + round.save.ShareID = round.PartyID().KeyInt() + round.save.Xi = round.temp.newXi + round.save.Ks = round.temp.newKs + + // misc: build list of paillier public keys to save + for j, msg := range round.temp.dgRound2Message1s { + if j == i { + continue + } + r2msg1 := msg.Content().(*DGRound2Message1) + round.save.PaillierPKs[j] = r2msg1.UnmarshalPaillierPK() + } + } else if round.IsOldCommittee() { + round.save.Xi.SetInt64(0) + } + + round.end <- *round.save + return nil +} + +func (round *round5) CanAccept(msg tss.ParsedMessage) bool { + return false +} + +func (round *round5) Update() (bool, *tss.Error) { + return false, nil +} + +func (round *round5) NextRound() tss.Round { + return nil // both committees are finished! +} diff --git a/ecdsa/resharing/rounds.go b/ecdsa/resharing/rounds.go index 30998fed..b9e1ae4d 100644 --- a/ecdsa/resharing/rounds.go +++ b/ecdsa/resharing/rounds.go @@ -39,6 +39,9 @@ type ( round4 struct { *round3 } + round5 struct { + *round4 + } ) var ( @@ -46,6 +49,7 @@ var ( _ tss.Round = (*round2)(nil) _ tss.Round = (*round3)(nil) _ tss.Round = (*round4)(nil) + _ tss.Round = (*round5)(nil) ) // ----- // diff --git a/ecdsa/signing/local_party.go b/ecdsa/signing/local_party.go index 24b55447..0d18efc8 100644 --- a/ecdsa/signing/local_party.go +++ b/ecdsa/signing/local_party.go @@ -121,7 +121,7 @@ func NewLocalParty( p.temp.signRound7Messages = make([]tss.ParsedMessage, partyCount) p.temp.signRound8Messages = make([]tss.ParsedMessage, partyCount) p.temp.signRound9Messages = make([]tss.ParsedMessage, partyCount) - // data init + // temp data init p.temp.m = msg p.temp.cis = make([]*big.Int, partyCount) p.temp.bigWs = make([]*crypto.ECPoint, partyCount) @@ -155,8 +155,8 @@ func (p *LocalParty) Update(msg tss.ParsedMessage) (ok bool, err *tss.Error) { return tss.BaseUpdate(p, msg, "signing") } -func (p *LocalParty) UpdateFromBytes(wireBytes []byte, from *tss.PartyID, isBroadcast, isToOldCommittee bool) (bool, *tss.Error) { - msg, err := tss.ParseWireMessage(wireBytes, from, isBroadcast, isToOldCommittee) +func (p *LocalParty) UpdateFromBytes(wireBytes []byte, from *tss.PartyID, isBroadcast bool) (bool, *tss.Error) { + msg, err := tss.ParseWireMessage(wireBytes, from, isBroadcast) if err != nil { return false, p.WrapError(err) } diff --git a/protob/ecdsa-resharing.proto b/protob/ecdsa-resharing.proto index 9de56f13..70138429 100644 --- a/protob/ecdsa-resharing.proto +++ b/protob/ecdsa-resharing.proto @@ -47,3 +47,9 @@ message DGRound3Message1 { message DGRound3Message2 { repeated bytes v_decommitment = 1; } + +/* + * The Round 4 "ACK" is broadcast to peers of the Old and New Committees from the New Committee in this message. + */ +message DGRound4Message { +} diff --git a/protob/message.proto b/protob/message.proto index 7eae1801..b1f3ce71 100644 --- a/protob/message.proto +++ b/protob/message.proto @@ -27,6 +27,8 @@ message MessageWrapper { bool is_broadcast = 1; // Metadata optionally un-marshalled and used by the transport to route this message. bool is_to_old_committee = 2; // used only in certain resharing messages + // Metadata optionally un-marshalled and used by the transport to route this message. + bool is_to_old_and_new_committees = 5; // used only in certain resharing messages // Metadata optionally un-marshalled and used by the transport to route this message. PartyID from = 3; diff --git a/test/utils.go b/test/utils.go index 635df3ad..d47b719b 100644 --- a/test/utils.go +++ b/test/utils.go @@ -10,18 +10,22 @@ import ( "github.com/binance-chain/tss-lib/tss" ) -func SharedPartyUpdater(P tss.Party, msg tss.Message, errCh chan<- *tss.Error) { +func SharedPartyUpdater(party tss.Party, msg tss.Message, errCh chan<- *tss.Error) { + // do not send a message from this party back to itself + if party.PartyID() == msg.GetFrom() { + return + } bz, _, err := msg.WireBytes() if err != nil { - errCh <- P.WrapError(err) + errCh <- party.WrapError(err) return } - pMsg, err := tss.ParseWireMessage(bz, msg.GetFrom(), msg.IsBroadcast(), msg.IsToOldCommittee()) + pMsg, err := tss.ParseWireMessage(bz, msg.GetFrom(), msg.IsBroadcast()) if err != nil { - errCh <- P.WrapError(err) + errCh <- party.WrapError(err) return } - if _, err := P.Update(pMsg); err != nil { + if _, err := party.Update(pMsg); err != nil { errCh <- err } } diff --git a/tss/message.go b/tss/message.go index 4ce8a0d2..222e6ad3 100644 --- a/tss/message.go +++ b/tss/message.go @@ -18,10 +18,16 @@ type ( Message interface { // Type is encoded in the protobuf Any structure Type() string + // The set of parties that this message should be sent to GetTo() []*PartyID + // The party that this message is from GetFrom() *PartyID + // Indicates whether the message should be broadcast to other participants IsBroadcast() bool + // Indicates whether the message is to the old committee during re-sharing; used mainly in tests IsToOldCommittee() bool + // Indicates whether the message is to both committees during re-sharing; used mainly in tests + IsToOldAndNewCommittees() bool // Returns the encoded inner message bytes to send over the wire along with metadata about how the message should be delivered WireBytes() ([]byte, *MessageRouting, error) // Returns the protobuf message wrapper struct @@ -53,6 +59,8 @@ type ( IsBroadcast bool // whether the message should be sent to old committee participants rather than the new committee IsToOldCommittee bool + // whether the message should be sent to both old and new committee participants + IsToOldAndNewCommittees bool } // Implements ParsedMessage; this is a concrete implementation of what messages produced by a LocalParty look like @@ -83,11 +91,12 @@ func NewMessageWrapper(routing MessageRouting, content MessageContent) *MessageW } } return &MessageWrapper{ - IsBroadcast: routing.IsBroadcast, - IsToOldCommittee: routing.IsToOldCommittee, - From: routing.From.MessageWrapper_PartyID, - To: to, - Message: any, + IsBroadcast: routing.IsBroadcast, + IsToOldCommittee: routing.IsToOldCommittee, + IsToOldAndNewCommittees: routing.IsToOldAndNewCommittees, + From: routing.From.MessageWrapper_PartyID, + To: to, + Message: any, } } @@ -117,11 +126,16 @@ func (mm *MessageImpl) IsBroadcast() bool { return mm.wire.IsBroadcast } -// only `true` in DGRound2NewCommitteeACKMessage (resharing) +// only `true` in DGRound2Message (resharing) func (mm *MessageImpl) IsToOldCommittee() bool { return mm.wire.IsToOldCommittee } +// only `true` in DGRound4Message (resharing) +func (mm *MessageImpl) IsToOldAndNewCommittees() bool { + return mm.wire.IsToOldAndNewCommittees +} + func (mm *MessageImpl) WireBytes() ([]byte, *MessageRouting, error) { bz, err := proto.Marshal(mm.wire.Message) if err != nil { diff --git a/tss/message.pb.go b/tss/message.pb.go index 50d9c499..8bc51645 100644 --- a/tss/message.pb.go +++ b/tss/message.pb.go @@ -29,6 +29,8 @@ type MessageWrapper struct { // Metadata optionally un-marshalled and used by the transport to route this message. IsToOldCommittee bool `protobuf:"varint,2,opt,name=is_to_old_committee,json=isToOldCommittee,proto3" json:"is_to_old_committee,omitempty"` // Metadata optionally un-marshalled and used by the transport to route this message. + IsToOldAndNewCommittees bool `protobuf:"varint,5,opt,name=is_to_old_and_new_committees,json=isToOldAndNewCommittees,proto3" json:"is_to_old_and_new_committees,omitempty"` + // Metadata optionally un-marshalled and used by the transport to route this message. From *MessageWrapper_PartyID `protobuf:"bytes,3,opt,name=from,proto3" json:"from,omitempty"` // Metadata optionally un-marshalled and used by the transport to route this message. To []*MessageWrapper_PartyID `protobuf:"bytes,4,rep,name=to,proto3" json:"to,omitempty"` @@ -80,6 +82,13 @@ func (m *MessageWrapper) GetIsToOldCommittee() bool { return false } +func (m *MessageWrapper) GetIsToOldAndNewCommittees() bool { + if m != nil { + return m.IsToOldAndNewCommittees + } + return false +} + func (m *MessageWrapper) GetFrom() *MessageWrapper_PartyID { if m != nil { return m.From @@ -167,22 +176,24 @@ func init() { func init() { proto.RegisterFile("protob/message.proto", fileDescriptor_5be430ad0e7f3d12) } var fileDescriptor_5be430ad0e7f3d12 = []byte{ - // 267 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x8f, 0x41, 0x4b, 0xc3, 0x30, - 0x14, 0xc7, 0x59, 0x3a, 0xad, 0x7b, 0x1b, 0x63, 0xc4, 0x81, 0x71, 0xa7, 0xea, 0xc5, 0x81, 0x98, - 0x82, 0x7e, 0x02, 0xa7, 0x1e, 0x3c, 0x88, 0x12, 0x04, 0xc1, 0x4b, 0x49, 0xd7, 0xac, 0x84, 0x35, - 0x7d, 0x25, 0x89, 0x87, 0x7e, 0x2a, 0xbf, 0xa2, 0x98, 0x2e, 0x07, 0x2f, 0xde, 0xf2, 0xde, 0xff, - 0x97, 0xfc, 0xf3, 0x83, 0x65, 0x67, 0xd1, 0x63, 0x99, 0x1b, 0xe5, 0x9c, 0xac, 0x15, 0x0f, 0xe3, - 0xea, 0xbc, 0x46, 0xac, 0x1b, 0x95, 0x0f, 0xe1, 0xd7, 0x2e, 0x97, 0x6d, 0x3f, 0x44, 0x97, 0xdf, - 0x04, 0xe6, 0x2f, 0x03, 0xfc, 0x61, 0x65, 0xd7, 0x29, 0x4b, 0x2f, 0x60, 0xa6, 0x5d, 0x51, 0x5a, - 0x94, 0xd5, 0x56, 0x3a, 0xcf, 0x46, 0xd9, 0x68, 0x7d, 0x22, 0xa6, 0xda, 0x6d, 0xe2, 0x8a, 0xde, - 0xc0, 0xa9, 0x76, 0x85, 0xc7, 0x02, 0x9b, 0xaa, 0xd8, 0xa2, 0x31, 0xda, 0x7b, 0xa5, 0x18, 0x09, - 0xe4, 0x42, 0xbb, 0x77, 0x7c, 0x6d, 0xaa, 0x87, 0xb8, 0xa7, 0xd7, 0x30, 0xde, 0x59, 0x34, 0x2c, - 0xc9, 0x46, 0xeb, 0xe9, 0xed, 0x19, 0xff, 0x5b, 0xc8, 0xdf, 0xa4, 0xf5, 0xfd, 0xf3, 0xa3, 0x08, - 0x10, 0xbd, 0x02, 0xe2, 0x91, 0x8d, 0xb3, 0xe4, 0x3f, 0x94, 0x78, 0xa4, 0x1c, 0xd2, 0x83, 0x26, - 0x83, 0xf0, 0xf0, 0x92, 0x0f, 0x9e, 0x3c, 0x7a, 0xf2, 0xfb, 0xb6, 0x17, 0x11, 0x5a, 0x3d, 0x41, - 0x7a, 0xb8, 0x4e, 0xe7, 0x40, 0x74, 0x15, 0xc4, 0x26, 0x82, 0xe8, 0x8a, 0x32, 0x48, 0x0d, 0xb6, - 0x7a, 0xaf, 0x6c, 0x70, 0x98, 0x88, 0x38, 0xd2, 0x05, 0x24, 0x7b, 0xd5, 0x87, 0x9f, 0xcf, 0xc4, - 0xef, 0x71, 0x93, 0x7e, 0x1e, 0xf1, 0xdc, 0x3b, 0x57, 0x1e, 0x87, 0x9a, 0xbb, 0x9f, 0x00, 0x00, - 0x00, 0xff, 0xff, 0x18, 0xbf, 0xf1, 0xd7, 0x74, 0x01, 0x00, 0x00, + // 297 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0x4f, 0x4b, 0x33, 0x31, + 0x10, 0x87, 0x69, 0xda, 0xbe, 0xfb, 0x76, 0x5a, 0x4a, 0x89, 0x85, 0xc6, 0xe2, 0xa1, 0x7a, 0xb1, + 0x20, 0x66, 0x41, 0xcf, 0x1e, 0x5a, 0xf5, 0xe0, 0xc1, 0x3f, 0x04, 0x41, 0xf0, 0xb2, 0xa4, 0x4d, + 0x5a, 0x42, 0xbb, 0x99, 0x92, 0x44, 0xca, 0x7e, 0x69, 0x3f, 0x83, 0x98, 0xdd, 0xb5, 0x78, 0xf1, + 0x96, 0x99, 0x79, 0x7e, 0x99, 0xe1, 0x81, 0xe1, 0xce, 0x61, 0xc0, 0x45, 0x9a, 0x6b, 0xef, 0xe5, + 0x5a, 0xf3, 0x58, 0x8e, 0x8f, 0xd7, 0x88, 0xeb, 0xad, 0x4e, 0xcb, 0xe1, 0xc7, 0x2a, 0x95, 0xb6, + 0x28, 0x47, 0x67, 0x9f, 0x04, 0xfa, 0x8f, 0x25, 0xfc, 0xe6, 0xe4, 0x6e, 0xa7, 0x1d, 0x3d, 0x85, + 0x9e, 0xf1, 0xd9, 0xc2, 0xa1, 0x54, 0x4b, 0xe9, 0x03, 0x6b, 0x4c, 0x1a, 0xd3, 0xff, 0xa2, 0x6b, + 0xfc, 0xbc, 0x6e, 0xd1, 0x4b, 0x38, 0x32, 0x3e, 0x0b, 0x98, 0xe1, 0x56, 0x65, 0x4b, 0xcc, 0x73, + 0x13, 0x82, 0xd6, 0x8c, 0x44, 0x72, 0x60, 0xfc, 0x2b, 0x3e, 0x6f, 0xd5, 0x6d, 0xdd, 0xa7, 0x37, + 0x70, 0x72, 0xc0, 0xa5, 0x55, 0x99, 0xd5, 0xfb, 0x43, 0xcc, 0xb3, 0x76, 0xcc, 0x8d, 0xaa, 0xdc, + 0xcc, 0xaa, 0x27, 0xbd, 0xff, 0x49, 0x7b, 0x7a, 0x01, 0xad, 0x95, 0xc3, 0x9c, 0x35, 0x27, 0x8d, + 0x69, 0xf7, 0x6a, 0xc4, 0x7f, 0xdf, 0xcb, 0x5f, 0xa4, 0x0b, 0xc5, 0xc3, 0x9d, 0x88, 0x10, 0x3d, + 0x07, 0x12, 0x90, 0xb5, 0x26, 0xcd, 0xbf, 0x50, 0x12, 0x90, 0x72, 0x48, 0x2a, 0x4b, 0x0c, 0xe2, + 0xc7, 0x43, 0x5e, 0x6a, 0xe2, 0xb5, 0x26, 0x3e, 0xb3, 0x85, 0xa8, 0xa1, 0xf1, 0x3d, 0x24, 0x55, + 0x9c, 0xf6, 0x81, 0x18, 0x15, 0xbd, 0x74, 0x04, 0x31, 0x8a, 0x32, 0x48, 0x72, 0xb4, 0x66, 0xa3, + 0x5d, 0x54, 0xd0, 0x11, 0x75, 0x49, 0x07, 0xd0, 0xdc, 0xe8, 0x22, 0x5e, 0xde, 0x13, 0xdf, 0xcf, + 0x79, 0xf2, 0xde, 0xe6, 0x69, 0xf0, 0x7e, 0xf1, 0x2f, 0xae, 0xb9, 0xfe, 0x0a, 0x00, 0x00, 0xff, + 0xff, 0xac, 0xdd, 0x4e, 0x90, 0xb3, 0x01, 0x00, 0x00, } diff --git a/tss/params.go b/tss/params.go index 40d36ad5..2289f687 100644 --- a/tss/params.go +++ b/tss/params.go @@ -89,6 +89,10 @@ func (rgParams *ReSharingParameters) OldParties() *PeerContext { return rgParams.Parties() // wr use the original method for old parties } +func (rgParams *ReSharingParameters) OldPartyCount() int { + return rgParams.partyCount +} + func (rgParams *ReSharingParameters) NewParties() *PeerContext { return rgParams.newParties } @@ -101,6 +105,14 @@ func (rgParams *ReSharingParameters) NewThreshold() int { return rgParams.newThreshold } +func (rgParams *ReSharingParameters) OldAndNewParties() []*PartyID { + return append(rgParams.OldParties().IDs(), rgParams.NewParties().IDs()...) +} + +func (rgParams *ReSharingParameters) OldAndNewPartyCount() int { + return rgParams.OldPartyCount() + rgParams.NewPartyCount() +} + func (rgParams *ReSharingParameters) IsOldCommittee() bool { partyID := rgParams.partyID for _, Pj := range rgParams.parties.IDs() { diff --git a/tss/party.go b/tss/party.go index 269266c2..4b340ce9 100644 --- a/tss/party.go +++ b/tss/party.go @@ -17,8 +17,8 @@ import ( type Party interface { Start() *Error // The main entry point when updating a party's state from the wire. - // isBroadcast should represent whether the message was received via broadcast, and isToOldCommittee should only be true during re-sharing when a message was sent to the old committee. - UpdateFromBytes(wireBytes []byte, from *PartyID, isBroadcast, isToOldCommittee bool) (ok bool, err *Error) + // isBroadcast should represent whether the message was received via a reliable broadcast + UpdateFromBytes(wireBytes []byte, from *PartyID, isBroadcast bool) (ok bool, err *Error) // You may use this entry point to update a party's state when running locally or in tests Update(msg ParsedMessage) (ok bool, err *Error) WaitingFor() []*PartyID @@ -58,11 +58,8 @@ func (p *BaseParty) ValidateMessage(msg ParsedMessage) (bool, *Error) { if msg == nil || msg.Content() == nil { return false, p.WrapError(fmt.Errorf("received nil msg: %s", msg)) } - if msg.GetFrom() == nil { - return false, p.WrapError(fmt.Errorf("received msg with nil sender: %s", msg)) - } - if !msg.GetFrom().ValidateBasic() { - return false, p.WrapError(fmt.Errorf("received msg with an invalid sender: %+v", msg.GetFrom())) + if msg.GetFrom() == nil || !msg.GetFrom().ValidateBasic() { + return false, p.WrapError(fmt.Errorf("received msg with an invalid sender: %s", msg)) } if !msg.ValidateBasic() { return false, p.WrapError(fmt.Errorf("message failed ValidateBasic: %s", msg), msg.GetFrom()) @@ -162,13 +159,14 @@ func BaseUpdate(p Party, msg ParsedMessage, task string) (ok bool, err *Error) { } rndNum := p.round().RoundNumber() common.Logger.Infof("party %s: %s round %d started", p.round().Params().PartyID(), task, rndNum) + } else { + // finished! the round implementation will have sent the data through the `end` channel. + common.Logger.Infof("party %s: %s finished!", p.PartyID(), task) } p.unlock() // recursive so can't defer after return return BaseUpdate(p, msg, task) // re-run round update or finish) } return r(true, nil) } - // finished! the round implementation will have sent the data through the `end` channel. - common.Logger.Infof("party %s: %s finished!", p.PartyID(), task) return r(true, nil) } diff --git a/tss/wire.go b/tss/wire.go index 236b1c8d..2cba46fe 100644 --- a/tss/wire.go +++ b/tss/wire.go @@ -19,12 +19,11 @@ const ( ) // Used externally to update a LocalParty with a valid ParsedMessage -func ParseWireMessage(wireBytes []byte, from *PartyID, isBroadcast, isToOldCommittee bool) (ParsedMessage, error) { +func ParseWireMessage(wireBytes []byte, from *PartyID, isBroadcast bool) (ParsedMessage, error) { wire := new(MessageWrapper) wire.Message = new(any.Any) wire.From = from.MessageWrapper_PartyID wire.IsBroadcast = isBroadcast - wire.IsToOldCommittee = isToOldCommittee if err := proto.Unmarshal(wireBytes, wire.Message); err != nil { return nil, err } @@ -34,7 +33,8 @@ func ParseWireMessage(wireBytes []byte, from *PartyID, isBroadcast, isToOldCommi func parseWrappedMessage(wire *MessageWrapper, from *PartyID) (ParsedMessage, error) { var any ptypes.DynamicAny meta := MessageRouting{ - From: from, + From: from, + IsBroadcast: wire.IsBroadcast, } if err := ptypes.UnmarshalAny(wire.Message, &any); err != nil { return nil, err