From eeb8e908fde4f01d67cbe5e87ac65b8278b6f41c Mon Sep 17 00:00:00 2001 From: Andrej Zavgorodnij Date: Thu, 24 Sep 2020 20:12:38 +0300 Subject: [PATCH] feat: airgapped state --- airgapped/airgapped.go | 119 ++++++++++++++++- airgapped/airgapped_test.go | 253 +++++++++++++++++++++++++++++++++++- airgapped/dkg.go | 5 +- airgapped/types.go | 7 +- dkg/dkg.go | 31 ++++- dkg/types.go | 10 +- go.mod | 6 + go.sum | 9 +- 8 files changed, 419 insertions(+), 21 deletions(-) diff --git a/airgapped/airgapped.go b/airgapped/airgapped.go index 6ae80ac..a54fb43 100644 --- a/airgapped/airgapped.go +++ b/airgapped/airgapped.go @@ -8,6 +8,8 @@ import ( "os" "sync" + bls12381 "github.com/depools/kyber-bls12381" + vss "github.com/corestario/kyber/share/vss/rabin" "github.com/corestario/kyber" @@ -20,7 +22,6 @@ import ( "github.com/depools/dc4bc/fsm/state_machines/signing_proposal_fsm" "github.com/depools/dc4bc/fsm/types/requests" "github.com/depools/dc4bc/qr" - bls12381 "github.com/depools/kyber-bls12381" "github.com/syndtr/goleveldb/leveldb" ) @@ -28,6 +29,7 @@ const ( resultQRFolder = "result_qr_codes" pubKeyDBKey = "public_key" privateKeyDBKey = "private_key" + operationsLogDBKey = "operations_log" participantAddressKey = "participant_address" saltKey = "salt_key" ) @@ -44,6 +46,7 @@ type AirgappedMachine struct { pubKey kyber.Point secKey kyber.Scalar suite vss.Suite + seed []byte db *leveldb.DB } @@ -64,19 +67,120 @@ func NewAirgappedMachine(dbPath string) (*AirgappedMachine, error) { qrProcessor: qr.NewCameraProcessor(), } - am.suite = bls12381.NewBLS12381Suite() - if am.db, err = leveldb.OpenFile(dbPath, nil); err != nil { return nil, fmt.Errorf("failed to open db file %s for keys: %w", dbPath, err) } + seed, err := am.getSeed() + if err != nil { + seed = make([]byte, 32) + _, _ = rand.Read(seed) + if err := am.storeSeed(seed); err != nil { + panic(err) + } + } + am.seed = seed + am.suite = bls12381.NewBLS12381Suite(am.seed) + if err = am.loadAddressFromDB(dbPath); err != nil { return nil, fmt.Errorf("failed to load address from db") } + if _, err = am.db.Get([]byte(operationsLogDBKey), nil); err != nil { + if err == leveldb.ErrNotFound { + operationsLogBz, _ := json.Marshal([]client.Operation{}) + if err := am.db.Put([]byte(operationsLogDBKey), operationsLogBz, nil); err != nil { + return nil, fmt.Errorf("failed to init Operation log: %w", err) + } + } else { + return nil, fmt.Errorf("failed to init Operation log (fatal): %w", err) + } + } + return am, nil } +func (am *AirgappedMachine) storeSeed(seed []byte) error { + if err := am.db.Put([]byte("seedKey"), seed, nil); err != nil { + return fmt.Errorf("failed to put seed: %w", err) + } + + return nil +} + +func (am *AirgappedMachine) getSeed() ([]byte, error) { + seed, err := am.db.Get([]byte("seedKey"), nil) + if err != nil { + return nil, fmt.Errorf("failed to get seed: %w", err) + } + + return seed, nil +} + +func (am *AirgappedMachine) storeOperation(o client.Operation) error { + operationsLogBz, err := am.db.Get([]byte(operationsLogDBKey), nil) + if err != nil { + if err == leveldb.ErrNotFound { + return err + } + return fmt.Errorf("failed to get operationsLogBz from db: %w", err) + } + + var operationsLog []client.Operation + if err := json.Unmarshal(operationsLogBz, &operationsLog); err != nil { + return fmt.Errorf("failed to unmarshal stored operationsLog: %w", err) + } + + operationsLog = append(operationsLog, o) + + operationsLogBz, err = json.Marshal(operationsLog) + if err != nil { + return fmt.Errorf("failed to marshal operationsLog: %w", err) + } + + if err := am.db.Put([]byte(operationsLogDBKey), operationsLogBz, nil); err != nil { + return fmt.Errorf("failed to put updated operationsLog: %w", err) + } + + return nil +} + +func (am *AirgappedMachine) getOperationsLog() ([]client.Operation, error) { + operationsLogBz, err := am.db.Get([]byte(operationsLogDBKey), nil) + if err != nil { + if err == leveldb.ErrNotFound { + return nil, err + } + return nil, fmt.Errorf("failed to get public key from db: %w", err) + } + + var operationsLog []client.Operation + if err := json.Unmarshal(operationsLogBz, &operationsLog); err != nil { + return nil, fmt.Errorf("failed to unmarshal stored operationsLog: %w", err) + } + + return operationsLog, nil +} + +func (am *AirgappedMachine) ReplayOperationsLog() error { + operationsLog, err := am.getOperationsLog() + if err != nil { + return fmt.Errorf("failed to getOperationsLog: %w", err) + } + + for _, operation := range operationsLog { + if _, err := am.HandleOperation(operation); err != nil { + return fmt.Errorf( + "failed to HandleOperation %s (this error is fatal, the state can not be recovered): %w", + operation.ID, err) + } + } + + log.Println("Successfully replayed Operation log") + + return nil +} + func (am *AirgappedMachine) InitKeys() error { err := am.LoadKeysFromDB() if err != nil && err != leveldb.ErrNotFound { @@ -147,6 +251,7 @@ func (am *AirgappedMachine) LoadKeysFromDB() error { if err != nil { return err } + decryptedPrivateKey, err := decrypt(am.encryptionKey, salt, privateKeyBz) if err != nil { return err @@ -263,6 +368,14 @@ func (am *AirgappedMachine) decryptDataFromParticipant(data []byte) ([]byte, err } func (am *AirgappedMachine) HandleOperation(operation client.Operation) (client.Operation, error) { + if err := am.storeOperation(operation); err != nil { + return client.Operation{}, fmt.Errorf("failed to storeOperation: %w", err) + } + + return am.handleOperation(operation) +} + +func (am *AirgappedMachine) handleOperation(operation client.Operation) (client.Operation, error) { var ( err error ) diff --git a/airgapped/airgapped_test.go b/airgapped/airgapped_test.go index 6a5f6c6..7799a68 100644 --- a/airgapped/airgapped_test.go +++ b/airgapped/airgapped_test.go @@ -9,6 +9,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + client "github.com/depools/dc4bc/client/types" "github.com/depools/dc4bc/fsm/fsm" "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm" @@ -311,12 +313,261 @@ func TestAirgappedAllSteps(t *testing.T) { fmt.Println("DKG succeeded, signature recovered") } +func TestAirgappedMachine_Replay(t *testing.T) { + testDir := "/tmp/airgapped_test" + nodesCount := 2 + threshold := 2 + participants := make([]string, nodesCount) + for i := 0; i < nodesCount; i++ { + participants[i] = fmt.Sprintf("Participant#%d", i) + } + + tr := &Transport{} + for i := 0; i < nodesCount; i++ { + am, err := NewAirgappedMachine(fmt.Sprintf("%s/%s-%d", testDir, testDB, i)) + if err != nil { + t.Fatalf("failed to create airgapped machine: %v", err) + } + am.SetAddress(participants[i]) + am.SetEncryptionKey([]byte(fmt.Sprintf(testDB+"%d", i))) + if err = am.InitKeys(); err != nil { + t.Fatalf(err.Error()) + } + node := Node{ + ParticipantID: i, + Participant: participants[i], + Machine: am, + } + tr.nodes = append(tr.nodes, &node) + } + defer os.RemoveAll(testDir) + + var initReq responses.SignatureProposalParticipantInvitationsResponse + for _, n := range tr.nodes { + pubKey, err := n.Machine.pubKey.MarshalBinary() + if err != nil { + t.Fatalf("failed to marshal dkg pubkey: %v", err) + } + entry := &responses.SignatureProposalParticipantInvitationEntry{ + ParticipantId: n.ParticipantID, + Addr: n.Participant, + Threshold: threshold, + DkgPubKey: pubKey, + } + initReq = append(initReq, entry) + } + op := createOperation(t, string(signature_proposal_fsm.StateAwaitParticipantsConfirmations), "", initReq) + runStep(tr, func(n *Node, wg *sync.WaitGroup) { + defer wg.Done() + + _, err := n.Machine.HandleOperation(op) + if err != nil { + t.Fatalf("%s: failed to handle operation %s: %v", n.Participant, op.Type, err) + } + }) + + // get commits + var getCommitsRequest responses.DKGProposalPubKeysParticipantResponse + for _, n := range tr.nodes { + pubKey, err := n.Machine.pubKey.MarshalBinary() + if err != nil { + t.Fatalf("%s: failed to marshal pubkey: %v", n.Participant, err) + } + entry := &responses.DKGProposalPubKeysParticipantEntry{ + ParticipantId: n.ParticipantID, + Addr: n.Participant, + DkgPubKey: pubKey, + } + getCommitsRequest = append(getCommitsRequest, entry) + } + op = createOperation(t, string(dkg_proposal_fsm.StateDkgCommitsAwaitConfirmations), "", getCommitsRequest) + runStep(tr, func(n *Node, wg *sync.WaitGroup) { + defer wg.Done() + + operation, err := n.Machine.HandleOperation(op) + if err != nil { + t.Fatalf("%s: failed to handle operation %s: %v", n.Participant, op.Type, err) + } + for _, msg := range operation.ResultMsgs { + tr.BroadcastMessage(t, msg) + } + }) + + //deals + runStep(tr, func(n *Node, wg *sync.WaitGroup) { + defer wg.Done() + + var payload responses.DKGProposalCommitParticipantResponse + for _, req := range n.commits { + p := responses.DKGProposalCommitParticipantEntry{ + ParticipantId: req.ParticipantId, + Addr: fmt.Sprintf("Participant#%d", req.ParticipantId), + DkgCommit: req.Commit, + } + payload = append(payload, &p) + } + op := createOperation(t, string(dkg_proposal_fsm.StateDkgDealsAwaitConfirmations), "", payload) + + operation, err := n.Machine.HandleOperation(op) + if err != nil { + t.Fatalf("%s: failed to handle operation %s: %v", n.Participant, op.Type, err) + } + for _, msg := range operation.ResultMsgs { + tr.BroadcastMessage(t, msg) + } + }) + + // At this point something goes wrong and we have to restart the machines. + for _, node := range tr.nodes { + _ = node.Machine.db.Close() + } + + participants = make([]string, nodesCount) + for i := 0; i < nodesCount; i++ { + participants[i] = fmt.Sprintf("Participant#%d", i) + } + + newTr := &Transport{} + for i := 0; i < nodesCount; i++ { + am, err := NewAirgappedMachine(fmt.Sprintf("%s/%s-%d", testDir, testDB, i)) + if err != nil { + t.Fatalf("failed to create airgapped machine: %v", err) + } + _ = am.SetAddress(participants[i]) + am.SetEncryptionKey([]byte(fmt.Sprintf(testDB+"%d", i))) + if err = am.InitKeys(); err != nil { + t.Fatalf(err.Error()) + } + node := Node{ + ParticipantID: i, + Participant: participants[i], + Machine: am, + deals: tr.nodes[i].deals, + } + newTr.nodes = append(newTr.nodes, &node) + } + defer os.RemoveAll(testDir) + + for _, node := range newTr.nodes { + err := node.Machine.ReplayOperationsLog() + require.NoError(t, err) + } + + //oldTr := tr + tr = newTr + + //responses + runStep(tr, func(n *Node, wg *sync.WaitGroup) { + defer wg.Done() + + var payload responses.DKGProposalDealParticipantResponse + for _, req := range n.deals { + p := responses.DKGProposalDealParticipantEntry{ + ParticipantId: req.ParticipantId, + Addr: fmt.Sprintf("Participant#%d", req.ParticipantId), + DkgDeal: req.Deal, + } + payload = append(payload, &p) + } + op := createOperation(t, string(dkg_proposal_fsm.StateDkgResponsesAwaitConfirmations), "", payload) + + operation, err := n.Machine.HandleOperation(op) + if err != nil { + t.Fatalf("%s: failed to handle operation %s: %v", n.Participant, op.Type, err) + } + for _, msg := range operation.ResultMsgs { + tr.BroadcastMessage(t, msg) + } + }) + + //master key + runStep(tr, func(n *Node, wg *sync.WaitGroup) { + defer wg.Done() + + var payload responses.DKGProposalResponseParticipantResponse + for _, req := range n.responses { + p := responses.DKGProposalResponseParticipantEntry{ + ParticipantId: req.ParticipantId, + Addr: fmt.Sprintf("Participant#%d", req.ParticipantId), + DkgResponse: req.Response, + } + payload = append(payload, &p) + } + op := createOperation(t, string(dkg_proposal_fsm.StateDkgMasterKeyAwaitConfirmations), "", payload) + + operation, err := n.Machine.HandleOperation(op) + if err != nil { + t.Fatalf("%s: failed to handle operation %s: %v", n.Participant, op.Type, err) + } + for _, msg := range operation.ResultMsgs { + tr.BroadcastMessage(t, msg) + } + }) + + // check that all master keys are equal + for _, n := range tr.nodes { + for i := 0; i < len(n.masterKeys); i++ { + if !bytes.Equal(n.masterKeys[0].MasterKey, n.masterKeys[i].MasterKey) { + t.Fatalf("master keys is not equal!") + } + } + } + + msgToSign := []byte("i am a message") + + //partialSigns + runStep(tr, func(n *Node, wg *sync.WaitGroup) { + defer wg.Done() + + payload := responses.SigningPartialSignsParticipantInvitationsResponse{ + SrcPayload: msgToSign, + } + + op := createOperation(t, string(signing_proposal_fsm.StateSigningAwaitPartialSigns), "", payload) + + operation, err := n.Machine.HandleOperation(op) + if err != nil { + t.Fatalf("%s: failed to handle operation %s: %v", n.Participant, op.Type, err) + } + for _, msg := range operation.ResultMsgs { + tr.BroadcastMessage(t, msg) + } + }) + + //recover full signature + runStep(tr, func(n *Node, wg *sync.WaitGroup) { + defer wg.Done() + + var payload responses.SigningProcessParticipantResponse + for _, req := range n.partialSigns { + p := responses.SigningProcessParticipantEntry{ + ParticipantId: req.ParticipantId, + Addr: fmt.Sprintf("Participant#%d", req.ParticipantId), + PartialSign: req.PartialSign, + } + payload.Participants = append(payload.Participants, &p) + } + payload.SrcPayload = msgToSign + op := createOperation(t, string(signing_proposal_fsm.StateSigningPartialSignsCollected), "", payload) + + operation, err := n.Machine.HandleOperation(op) + if err != nil { + t.Fatalf("%s: failed to handle operation %s: %v", n.Participant, op.Type, err) + } + for _, msg := range operation.ResultMsgs { + tr.BroadcastMessage(t, msg) + } + }) + + fmt.Println("DKG succeeded, signature recovered") +} + func runStep(transport *Transport, cb func(n *Node, wg *sync.WaitGroup)) { var wg = &sync.WaitGroup{} for _, node := range transport.nodes { wg.Add(1) n := node - go cb(n, wg) + cb(n, wg) } wg.Wait() } diff --git a/airgapped/dkg.go b/airgapped/dkg.go index 00bcf3e..c0b41fd 100644 --- a/airgapped/dkg.go +++ b/airgapped/dkg.go @@ -13,7 +13,6 @@ import ( "github.com/depools/dc4bc/fsm/types/requests" "github.com/depools/dc4bc/fsm/types/responses" "github.com/depools/dc4bc/storage" - bls12381 "github.com/depools/kyber-bls12381" ) func createMessage(o client.Operation, data []byte) storage.Message { @@ -97,14 +96,14 @@ func (am *AirgappedMachine) handleStateDkgCommitsAwaitConfirmations(o *client.Op } for _, entry := range payload { - pubKey := bls12381.NewBLS12381Suite().Point() + pubKey := am.suite.Point() if err = pubKey.UnmarshalBinary(entry.DkgPubKey); err != nil { return fmt.Errorf("failed to unmarshal pubkey: %w", err) } dkgInstance.StorePubKey(entry.Addr, entry.ParticipantId, pubKey) } - if err = dkgInstance.InitDKGInstance(); err != nil { + if err = dkgInstance.InitDKGInstance(am.seed); err != nil { return fmt.Errorf("failed to init dkg instance: %w", err) } diff --git a/airgapped/types.go b/airgapped/types.go index 78ab194..638e3a3 100644 --- a/airgapped/types.go +++ b/airgapped/types.go @@ -2,9 +2,10 @@ package airgapped import ( "fmt" + "strings" + "github.com/depools/dc4bc/dkg" "github.com/syndtr/goleveldb/leveldb/util" - "strings" ) const ( @@ -57,7 +58,7 @@ func (am *AirgappedMachine) loadBLSKeyring(dkgID string) (*dkg.BLSKeyring, error return nil, fmt.Errorf("failed to decrypt BLS keyring: %w", err) } - if blsKeyring, err = dkg.LoadBLSKeyringFromBytes(decryptedKeyring); err != nil { + if blsKeyring, err = dkg.LoadBLSKeyringFromBytes(am.suite, decryptedKeyring); err != nil { return nil, fmt.Errorf("failed to decode bls keyring") } return blsKeyring, nil @@ -85,7 +86,7 @@ func (am *AirgappedMachine) GetBLSKeyrings() (map[string]*dkg.BLSKeyring, error) if err != nil { return nil, fmt.Errorf("failed to decrypt BLS keyring: %w", err) } - if blsKeyring, err = dkg.LoadBLSKeyringFromBytes(decryptedKeyring); err != nil { + if blsKeyring, err = dkg.LoadBLSKeyringFromBytes(am.suite, decryptedKeyring); err != nil { return nil, fmt.Errorf("failed to decode bls keyring: %w", err) } keyrings[strings.TrimLeft(string(key), blsKeyringPrefix)] = blsKeyring diff --git a/dkg/dkg.go b/dkg/dkg.go index 3935566..857e102 100644 --- a/dkg/dkg.go +++ b/dkg/dkg.go @@ -7,11 +7,12 @@ import ( "sort" "sync" - "github.com/corestario/kyber/share" - "github.com/corestario/kyber" + "github.com/corestario/kyber/share" dkg "github.com/corestario/kyber/share/dkg/pedersen" vss "github.com/corestario/kyber/share/vss/pedersen" + "github.com/google/go-cmp/cmp" + "lukechampine.com/frand" ) // TODO: dump necessary data on disk @@ -46,6 +47,26 @@ func Init(suite vss.Suite, pubKey kyber.Point, secKey kyber.Scalar) *DKG { return &d } +func (d *DKG) Equals(other *DKG) error { + for addr, commits := range d.commits { + otherCommits := other.commits[addr] + for idx := range commits { + if !commits[idx].Equal(otherCommits[idx]) { + return fmt.Errorf("commits from %s are not equal (idx %d): %v != %v", addr, idx, commits[idx], otherCommits[idx]) + } + } + } + + for addr, deal := range d.deals { + otherDeal := other.deals[addr] + if !cmp.Equal(deal.Deal, otherDeal.Deal) { + return fmt.Errorf("deals from %s are not equal: %+v != %+v", addr, deal.Deal, otherDeal.Deal) + } + } + + return nil +} + func (d *DKG) GetPubKey() kyber.Point { return d.pubKey } @@ -90,7 +111,7 @@ func (d *DKG) calcParticipantID() int { return -1 } -func (d *DKG) InitDKGInstance() (err error) { +func (d *DKG) InitDKGInstance(seed []byte) (err error) { sort.Sort(d.pubkeys) publicKeys := d.pubkeys.GetPKs() @@ -107,7 +128,9 @@ func (d *DKG) InitDKGInstance() (err error) { d.responses = newMessageStore(int(math.Pow(float64(participantsCount)-1, 2))) - d.instance, err = dkg.NewDistKeyGenerator(d.suite, d.secKey, publicKeys, d.Threshold) + reader := frand.NewCustom(seed, 32, 20) + + d.instance, err = dkg.NewDistKeyGenerator(d.suite, d.secKey, publicKeys, d.Threshold, reader) if err != nil { return err } diff --git a/dkg/types.go b/dkg/types.go index 9bdf08d..8dfd674 100644 --- a/dkg/types.go +++ b/dkg/types.go @@ -7,10 +7,10 @@ import ( "fmt" "github.com/corestario/kyber/pairing" + vss "github.com/corestario/kyber/share/vss/pedersen" "github.com/corestario/kyber" "github.com/corestario/kyber/share" - bls12381 "github.com/depools/kyber-bls12381" ) type PK2Participant struct { @@ -143,7 +143,7 @@ func (b *BLSKeyring) Bytes() ([]byte, error) { return json.Marshal(blsKeyringJSON) } -func LoadBLSKeyringFromBytes(data []byte) (*BLSKeyring, error) { +func LoadBLSKeyringFromBytes(suite vss.Suite, data []byte) (*BLSKeyring, error) { var ( err error blsKeyringJson blsKeyringJSON @@ -154,20 +154,20 @@ func LoadBLSKeyringFromBytes(data []byte) (*BLSKeyring, error) { commitments := make([]kyber.Point, 0, len(blsKeyringJson.Commitments)) for _, commitmentBz := range blsKeyringJson.Commitments { - commitment := bls12381.NewBLS12381Suite().Point() + commitment := suite.Point() if err := commitment.UnmarshalBinary(commitmentBz); err != nil { return nil, fmt.Errorf("failed to unmarshal commitment: %w", err) } commitments = append(commitments, commitment) } - priShare, privDec := &share.PriShare{V: bls12381.NewBLS12381Suite().(pairing.Suite).G1().Scalar()}, gob.NewDecoder(bytes.NewBuffer(blsKeyringJson.Share)) + priShare, privDec := &share.PriShare{V: suite.(pairing.Suite).G1().Scalar()}, gob.NewDecoder(bytes.NewBuffer(blsKeyringJson.Share)) if err := privDec.Decode(priShare); err != nil { return nil, fmt.Errorf("failed to share: %v", err) } return &BLSKeyring{ - PubPoly: share.NewPubPoly(bls12381.NewBLS12381Suite(), nil, commitments), + PubPoly: share.NewPubPoly(suite, nil, commitments), Share: priShare, }, nil } diff --git a/go.mod b/go.mod index 65d1834..a74d208 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/corestario/kyber v1.3.0 github.com/depools/kyber-bls12381 v0.0.0-20200831104422-978ac58f592e github.com/golang/mock v1.4.4 + github.com/google/go-cmp v0.2.0 github.com/google/uuid v1.1.1 github.com/juju/fslock v0.0.0-20160525022230-4d5c94c67b4b github.com/looplab/fsm v0.1.0 @@ -19,6 +20,11 @@ require ( golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/text v0.3.3 // indirect golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect + lukechampine.com/frand v1.3.0 ) replace golang.org/x/crypto => github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5 + +replace github.com/corestario/kyber => ../kyber + +replace github.com/depools/kyber-bls12381 => ../kyber-bls12381 diff --git a/go.sum b/go.sum index 97cf72b..9f14267 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -13,8 +15,6 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/corestario/kyber v1.3.0 h1:SEWofdorUUeAJTsa9WJmrUYFyWHSWyXLgqDTFFEIzes= -github.com/corestario/kyber v1.3.0/go.mod h1:kIWfWekm8kSJNti3Fo3DCV0GHEH050MWQrdvZdefbkk= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -47,7 +47,9 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -180,6 +182,7 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191025090151-53bf42e6b339/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -216,3 +219,5 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +lukechampine.com/frand v1.3.0 h1:HFLrwEHr78+EqAfyp8OChgEzdYCVZzzj6Y+cGDQRhaI= +lukechampine.com/frand v1.3.0/go.mod h1:4S/TM2ZgrKejMcKMbeLjISpJMO+/eZ1zu3vYX9dtj3s=