aboutsummaryrefslogblamecommitdiffstats
path: root/core/crypto/dkg/dkg_test.go
blob: 20dd90815e6a1fa6b011a56cfe2051ebb485d03e (plain) (tree)
1
2
3
4
5
6
7
8
9

                                                    
  
                                                                        



                                                                               
                                                                         




                                                                           
                                                      






                                  
                 
              
              

                 
                                                    
                                               

                                           

                                                                 
























                                                                      
                                            

















                                                                                               












                                                                                           














































                                                                             

                                                      





























                                                                       

                                                                                       






































                                                                                      

                                                                                       





































































                                                                                               






                                                    
                          




                                                  











                                                        




                                                             

                                                             













                                                                               















                                                              

 



























































                                                                                      


                                       



























                                                                                         

                                                                                                       







                                                                                     

                                                                               





























































































































                                                                                                       
// 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 dkg

import (
    "encoding/binary"
    "math/rand"
    "reflect"
    "sort"
    "sync"
    "testing"

    "github.com/dexon-foundation/bls/ffi/go/bls"
    "github.com/dexon-foundation/dexon/rlp"
    "github.com/stretchr/testify/suite"

    "github.com/dexon-foundation/dexon-consensus/common"
    "github.com/dexon-foundation/dexon-consensus/core/crypto"
)

type DKGTestSuite struct {
    suite.Suite
}

type member struct {
    id                ID
    prvShares         *PrivateKeyShares
    pubShares         *PublicKeyShares
    receivedPrvShares *PrivateKeyShares
    receivedPubShares map[ID]*PublicKeyShares
}

func (s *DKGTestSuite) genID(k int) IDs {
    IDs := make(IDs, 0, k)
    for i := 0; i < k; i++ {
        id := make([]byte, 8)
        binary.LittleEndian.PutUint64(id, rand.Uint64())
        IDs = append(IDs, NewID(id))
    }
    return IDs
}

func (s *DKGTestSuite) sendKey(senders []member, receivers []member) {
    receiveFrom := make(map[ID][]member)
    for _, sender := range senders {
        for _, receiver := range receivers {
            // Here's the demonstration of DKG protocol. `pubShares` is broadcasted
            // and all the receiver would save it to the `receivedPubShares`.
            // Do not optimize the memory usage of this part.
            receiver.receivedPubShares[sender.id] = sender.pubShares
            prvShare, ok := sender.prvShares.Share(receiver.id)
            s.Require().True(ok)
            pubShare, err := sender.pubShares.Share(receiver.id)
            s.Require().NoError(err)
            valid, err := receiver.receivedPubShares[sender.id].
                VerifyPrvShare(receiver.id, prvShare)
            s.Require().NoError(err)
            s.Require().True(valid)
            valid, err = receiver.receivedPubShares[sender.id].
                VerifyPubShare(receiver.id, pubShare)
            s.Require().NoError(err)
            s.Require().True(valid)
            receiveFrom[receiver.id] = append(receiveFrom[receiver.id], sender)
        }
    }
    // The received order do not need to be the same.
    for _, receiver := range receivers {
        rand.Shuffle(len(senders), func(i, j int) {
            receiveFrom[receiver.id][i], receiveFrom[receiver.id][j] =
                receiveFrom[receiver.id][j], receiveFrom[receiver.id][i]
        })
        for _, sender := range receiveFrom[receiver.id] {
            prvShare, ok := sender.prvShares.Share(receiver.id)
            s.Require().True(ok)
            err := receiver.receivedPrvShares.AddShare(sender.id, prvShare)
            s.Require().NoError(err)
        }
    }
}

func (s *DKGTestSuite) signWithQualifyIDs(
    member member, qualifyIDs IDs, hash common.Hash) PartialSignature {
    prvKey, err := member.receivedPrvShares.RecoverPrivateKey(qualifyIDs)
    s.Require().NoError(err)
    sig, err := prvKey.Sign(hash)
    s.Require().NoError(err)
    return PartialSignature(sig)
}

func (s *DKGTestSuite) verifySigWithQualifyIDs(
    members []member, qualifyIDs IDs,
    signer ID, hash common.Hash, sig PartialSignature) bool {
    membersIdx := make(map[ID]int)
    for idx, member := range members {
        membersIdx[member.id] = idx
    }
    pubShares := NewEmptyPublicKeyShares()
    for _, id := range qualifyIDs {
        idx, exist := membersIdx[id]
        s.Require().True(exist)
        member := members[idx]
        pubShare, err := member.pubShares.Share(signer)
        s.Require().NoError(err)
        err = pubShares.AddShare(id, pubShare)
        s.Require().NoError(err)
    }
    pubKey, err := pubShares.RecoverPublicKey(qualifyIDs)
    s.Require().NoError(err)
    return pubKey.VerifySignature(hash, crypto.Signature(sig))
}

func (s *DKGTestSuite) TestVerifyKeyShares() {
    invalidID := NewID([]byte{0})
    ids := []ID{NewID([]byte{1}), NewID([]byte{2}), NewID([]byte{3})}
    members := []member{}
    for _, id := range ids {
        members = append(members, member{
            id:                id,
            receivedPubShares: make(map[ID]*PublicKeyShares),
        })
    }

    prvShares, pubShares := NewPrivateKeyShares(2)
    prvShares.SetParticipants(ids)

    _, ok := prvShares.Share(invalidID)
    s.False(ok)
    for _, id := range ids {
        prvShare, ok := prvShares.Share(id)
        s.Require().True(ok)
        valid, err := pubShares.VerifyPrvShare(id, prvShare)
        s.Require().NoError(err)
        s.True(valid)
        pubShare, err := pubShares.Share(id)
        s.Require().NoError(err)
        valid, err = pubShares.VerifyPubShare(id, pubShare)
        s.Require().NoError(err)
        s.True(valid)
    }

    // Test of faulty private/public key.
    invalidPrvShare := NewPrivateKey()
    valid, err := pubShares.VerifyPrvShare(ids[0], invalidPrvShare)
    s.Require().NoError(err)
    s.False(valid)

    invalidPubShare, ok := invalidPrvShare.PublicKey().(PublicKey)
    s.Require().True(ok)
    valid, err = pubShares.VerifyPubShare(ids[0], &invalidPubShare)
    s.Require().NoError(err)
    s.False(valid)

    // Test of faulty signature.
    for idx := range members {
        members[idx].prvShares, members[idx].pubShares = NewPrivateKeyShares(2)
        members[idx].prvShares.SetParticipants(ids)
        members[idx].receivedPrvShares = NewEmptyPrivateKeyShares()
    }
    s.sendKey(members, members)
    hash := crypto.Keccak256Hash([]byte("👾👾👾👾👾👾"))
    sig, err := invalidPrvShare.Sign(hash)
    s.Require().NoError(err)
    psig := PartialSignature(sig)
    for _, member := range members {
        valid = s.verifySigWithQualifyIDs(members, ids, member.id, hash, psig)
        s.False(valid)
    }

    // Test of faulty group signature.
    groupPubShares := make([]*PublicKeyShares, 0, len(members))
    sigs := make([]PartialSignature, 0, len(members))
    for _, member := range members {
        sigs = append(sigs, s.signWithQualifyIDs(member, ids, hash))
        groupPubShares = append(groupPubShares, member.pubShares)
    }
    sigs[0] = psig
    recoverSig, err := RecoverSignature(sigs, ids)
    s.Require().NoError(err)

    pubKey := RecoverGroupPublicKey(groupPubShares)
    s.False(pubKey.VerifySignature(hash, recoverSig))
}

func (s *DKGTestSuite) TestDKGProtocol() {
    k := 5
    members := []member{}
    ids := s.genID((k + 1) * 2)
    for _, id := range ids {
        members = append(members, member{
            id:                id,
            receivedPubShares: make(map[ID]*PublicKeyShares),
        })
    }

    for idx := range members {
        members[idx].prvShares, members[idx].pubShares = NewPrivateKeyShares(k)
        members[idx].prvShares.SetParticipants(ids)
        members[idx].receivedPrvShares = NewEmptyPrivateKeyShares()
    }
    // Randomly select non-disqualified members.
    nums := make([]int, len(members))
    for i := range nums {
        nums[i] = i
    }
    rand.Shuffle(len(nums), func(i, j int) {
        nums[i], nums[j] = nums[j], nums[i]
    })
    nums = nums[:rand.Intn(len(members))]
    sort.Ints(nums)
    qualify := make([]member, 0, len(nums))
    for _, idx := range nums {
        qualify = append(qualify, members[idx])
    }
    // TODO(jimmy-dexon): Remove below line after finishing test of random select.
    qualify = members
    // Members are partitioned into two groups.
    grp1, grp2 := members[:k+1], members[k+1:]
    collectIDs := func(members []member) IDs {
        IDs := make(IDs, 0, len(members))
        for _, member := range members {
            IDs = append(IDs, member.id)
        }
        return IDs
    }
    signMsg := func(
        members []member, qualify []member, hash common.Hash) []PartialSignature {
        ids := collectIDs(qualify)
        sigs := make([]PartialSignature, 0, len(members))
        for _, member := range members {
            sig := s.signWithQualifyIDs(member, ids, hash)
            sigs = append(sigs, sig)
        }
        return sigs
    }
    verifySig := func(
        members []member,
        signer []ID, sig []PartialSignature, qualify []member, hash common.Hash) bool {
        ids := collectIDs(qualify)
        for i := range sig {
            if !s.verifySigWithQualifyIDs(members, ids, signer[i], hash, sig[i]) {
                return false
            }
        }
        return true
    }
    s.sendKey(qualify, grp1)
    s.sendKey(qualify, grp2)
    hash := crypto.Keccak256Hash([]byte("🛫"))
    sig1 := signMsg(grp1, qualify, hash)
    sig2 := signMsg(grp2, qualify, hash)
    s.True(verifySig(members, collectIDs(grp1), sig1, qualify, hash))
    s.True(verifySig(members, collectIDs(grp2), sig2, qualify, hash))
    recoverSig1, err := RecoverSignature(sig1, collectIDs(grp1))
    s.Require().NoError(err)
    recoverSig2, err := RecoverSignature(sig2, collectIDs(grp2))
    s.Require().NoError(err)
    s.Equal(recoverSig1, recoverSig2)

    pubShares := make([]*PublicKeyShares, 0, len(members))
    for _, member := range members {
        pubShares = append(pubShares, member.pubShares)
    }
    groupPK := RecoverGroupPublicKey(pubShares)
    s.True(groupPK.VerifySignature(hash, recoverSig1))
    s.True(groupPK.VerifySignature(hash, recoverSig2))
}

func (s *DKGTestSuite) TestSignature() {
    prvKey := NewPrivateKey()
    pubKey := prvKey.PublicKey()
    hash := crypto.Keccak256Hash([]byte("🛫"))
    sig, err := prvKey.Sign(hash)
    s.Require().NoError(err)
    s.True(pubKey.VerifySignature(hash, sig))
    sig.Signature[0]++
    s.False(pubKey.VerifySignature(hash, sig))
    sig = crypto.Signature{}
    s.False(pubKey.VerifySignature(hash, sig))
}

func (s *DKGTestSuite) TestPrivateKeyRLPEncodeDecode() {
    k := NewPrivateKey()
    b, err := rlp.EncodeToBytes(k)
    s.Require().NoError(err)

    var kk PrivateKey
    err = rlp.DecodeBytes(b, &kk)
    s.Require().NoError(err)

    s.Require().True(reflect.DeepEqual(*k, kk))
}

func (s *DKGTestSuite) TestPublicKeySharesRLPEncodeDecode() {
    p := NewEmptyPublicKeyShares()
    for i, id := range s.genID(1) {
        privkey := NewPrivateKey()
        pubkey := privkey.PublicKey().(PublicKey)
        p.shareCaches = append(p.shareCaches, pubkey)
        p.shareCacheIndex[id] = i
        p.masterPublicKey = append(p.masterPublicKey, pubkey.publicKey)
    }

    b, err := rlp.EncodeToBytes(p)
    s.Require().NoError(err)

    var pp PublicKeyShares
    err = rlp.DecodeBytes(b, &pp)
    s.Require().NoError(err)

    bb, err := rlp.EncodeToBytes(&pp)
    s.Require().NoError(err)

    s.Require().True(reflect.DeepEqual(b, bb))
}

func (s *DKGTestSuite) TestPrivateKeySharesRLPEncodeDecode() {
    privShares, _ := NewPrivateKeyShares(10)

    b, err := rlp.EncodeToBytes(privShares)
    s.Require().NoError(err)

    var newPrivShares PrivateKeyShares
    err = rlp.DecodeBytes(b, &newPrivShares)
    s.Require().NoError(err)

    bb, err := rlp.EncodeToBytes(&newPrivShares)
    s.Require().NoError(err)

    s.Require().True(reflect.DeepEqual(b, bb))
}

func (s *DKGTestSuite) TestPublicKeySharesEquality() {
    var req = s.Require()
    IDs := s.genID(2)
    _, pubShares1 := NewPrivateKeyShares(4)
    // Make a copy from an empty share.
    pubShares2 := pubShares1.Clone()
    req.True(pubShares1.Equal(pubShares2))
    // Add two shares.
    prvKey1 := NewPrivateKey()
    pubKey1 := prvKey1.PublicKey().(PublicKey)
    req.NoError(pubShares1.AddShare(IDs[0], &pubKey1))
    prvKey2 := NewPrivateKey()
    pubKey2 := prvKey2.PublicKey().(PublicKey)
    req.True(pubShares1.Equal(pubShares2))
    // Clone the shares.
    req.NoError(pubShares2.AddShare(IDs[0], &pubKey1))
    req.NoError(pubShares2.AddShare(IDs[1], &pubKey2))
    // They should be equal now.
    req.True(pubShares1.Equal(pubShares2))
    req.True(pubShares2.Equal(pubShares1))
}

func (s *DKGTestSuite) TestPrivateKeySharesEquality() {
    var req = s.Require()
    IDs := s.genID(2)
    prvShares1, _ := NewPrivateKeyShares(4)
    // Make a copy of empty share.
    prvShares2 := NewEmptyPrivateKeyShares()
    req.False(prvShares1.Equal(prvShares2))
    // Clone the master private key.
    for _, m := range prvShares1.masterPrivateKey {
        var key bls.SecretKey
        req.NoError(key.SetLittleEndian(m.GetLittleEndian()))
        prvShares2.masterPrivateKey = append(prvShares2.masterPrivateKey, key)
    }
    // Add two shares.
    prvKey1 := NewPrivateKey()
    req.NoError(prvShares1.AddShare(IDs[0], prvKey1))
    prvKey2 := NewPrivateKey()
    req.NoError(prvShares1.AddShare(IDs[1], prvKey2))
    // They are not equal now.
    req.False(prvShares1.Equal(prvShares2))
    // Clone the shares.
    req.NoError(prvShares2.AddShare(IDs[0], prvKey1))
    req.NoError(prvShares2.AddShare(IDs[1], prvKey2))
    // They should be equal now.
    req.True(prvShares1.Equal(prvShares2))
    req.True(prvShares2.Equal(prvShares1))
}

func (s *DKGTestSuite) TestPublicKeySharesClone() {
    _, pubShares1 := NewPrivateKeyShares(4)
    IDs := s.genID(2)
    prvKey1 := NewPrivateKey()
    pubKey1 := prvKey1.PublicKey().(PublicKey)
    s.Require().NoError(pubShares1.AddShare(IDs[0], &pubKey1))
    pubShares2 := pubShares1.Clone()
    s.Require().True(pubShares1.Equal(pubShares2))
}

func TestDKG(t *testing.T) {
    suite.Run(t, new(DKGTestSuite))
}

func BenchmarkDKGProtocol(b *testing.B) {
    t := 33
    n := 100
    s := new(DKGTestSuite)

    self := member{}
    members := make([]*member, n-1)
    ids := make(IDs, n)

    b.Run("DKG", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            b.StopTimer()
            self.id = s.genID(1)[0]
            self.receivedPubShares = make(map[ID]*PublicKeyShares, n)
            for idx, id := range s.genID(n - 1) {
                ids[idx] = id
            }
            for idx := range members {
                members[idx] = &member{
                    id:                ids[idx],
                    receivedPubShares: make(map[ID]*PublicKeyShares),
                    receivedPrvShares: NewEmptyPrivateKeyShares(),
                }
            }
            ids[n-1] = self.id
            prvShares := make(map[ID]*PrivateKey, n)
            for idx := range members {
                members[idx].prvShares, members[idx].pubShares = NewPrivateKeyShares(t)
                members[idx].prvShares.SetParticipants(ids)
                prvShare, ok := members[idx].prvShares.Share(self.id)
                if !ok {
                    b.FailNow()
                }
                prvShares[members[idx].id] = prvShare
            }

            b.StartTimer()
            self.prvShares, self.pubShares = NewPrivateKeyShares(t)
            self.prvShares.SetParticipants(ids)
            self.receivedPrvShares = NewEmptyPrivateKeyShares()
            for _, member := range members {
                self.receivedPubShares[member.id] = member.pubShares
            }
            self.receivedPubShares[self.id] = self.pubShares
            prvShare, ok := self.prvShares.Share(self.id)
            if !ok {
                b.FailNow()
            }
            prvShares[self.id] = prvShare
            for id, prvShare := range prvShares {
                ok, err := self.receivedPubShares[id].VerifyPrvShare(self.id, prvShare)
                if err != nil {
                    b.Fatalf("%v", err)
                }
                if !ok {
                    b.FailNow()
                }
                if err := self.receivedPrvShares.AddShare(id, prvShare); err != nil {
                    b.Fatalf("%v", err)
                }
            }
            if _, err := self.receivedPrvShares.RecoverPrivateKey(ids); err != nil {
                b.Fatalf("%v", err)
            }
        }
    })

    hash := crypto.Keccak256Hash([]byte("🏖"))
    b.Run("Share-Sign", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            b.StopTimer()
            prvKey, err := self.receivedPrvShares.RecoverPrivateKey(ids)
            if err != nil {
                b.Fatalf("%v", err)
            }
            b.StartTimer()
            if _, err := prvKey.Sign(hash); err != nil {
                b.Fatalf("%v", err)
            }
        }
    })

    sendKey := func(sender *member, receiver *member, b *testing.B) {
        receiver.receivedPubShares[sender.id] = sender.pubShares
        prvShare, ok := sender.prvShares.Share(receiver.id)
        if !ok {
            b.FailNow()
        }
        ok, err := receiver.receivedPubShares[sender.id].VerifyPrvShare(
            receiver.id, prvShare)
        if err != nil {
            b.Fatalf("%v", err)
        }
        if !ok {
            b.FailNow()
        }
        if err := receiver.receivedPrvShares.AddShare(sender.id, prvShare); err != nil {
            b.Fatalf("%v", err)
        }
    }

    members = append(members, &self)

    for _, sender := range members {
        wg := sync.WaitGroup{}
        for _, receiver := range members {
            if sender == receiver {
                continue
            }
            wg.Add(1)
            go func(receiver *member) {
                sendKey(sender, receiver, b)
                wg.Done()
            }(receiver)
        }
        wg.Wait()
    }
    wg := sync.WaitGroup{}
    for _, m := range members {
        wg.Add(1)
        go func(member *member) {
            sendKey(member, member, b)
            wg.Done()
        }(m)
    }
    wg.Wait()

    sign := func(member *member) PartialSignature {
        prvKey, err := member.receivedPrvShares.RecoverPrivateKey(ids)
        if err != nil {
            b.Fatalf("%v", err)
        }
        sig, err := prvKey.Sign(hash)
        if err != nil {
            b.Fatalf("%v", err)
        }
        return PartialSignature(sig)
    }

    b.Run("Combine-Sign", func(b *testing.B) {
        b.StopTimer()
        sigs := make([]PartialSignature, n)
        for idx, member := range members {
            sigs[idx] = sign(member)
        }
        b.StartTimer()
        for i := 0; i < b.N; i++ {
            if _, err := RecoverSignature(sigs, ids); err != nil {
                b.Fatalf("%v", err)
            }
        }
    })

    b.Run("Recover-GroupPK", func(b *testing.B) {
        b.StopTimer()
        pubShares := make([]*PublicKeyShares, 0, len(members))
        for _, member := range members {
            pubShares = append(pubShares, member.pubShares)
        }
        b.StartTimer()
        for i := 0; i < b.N; i++ {
            RecoverGroupPublicKey(pubShares)
        }
    })
}