From a68843ba4524f1fdfc1e207c76bd298c43f81d3f Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Tue, 2 Apr 2024 15:30:55 +0200 Subject: [PATCH] Add Ledger unit tests --- .github/workflows/tm2.yml | 2 +- tm2/Makefile | 2 +- .../keys/client/add_ledger_skipped_test.go | 10 ++ tm2/pkg/crypto/keys/client/add_ledger_test.go | 170 ++++++++++++++++++ tm2/pkg/crypto/ledger/discover.go | 19 ++ tm2/pkg/crypto/ledger/discover_mock.go | 69 +++++++ tm2/pkg/crypto/ledger/ledger_secp256k1.go | 12 -- 7 files changed, 270 insertions(+), 14 deletions(-) create mode 100644 tm2/pkg/crypto/keys/client/add_ledger_skipped_test.go create mode 100644 tm2/pkg/crypto/keys/client/add_ledger_test.go create mode 100644 tm2/pkg/crypto/ledger/discover.go create mode 100644 tm2/pkg/crypto/ledger/discover_mock.go diff --git a/.github/workflows/tm2.yml b/.github/workflows/tm2.yml index ced6054f9e9..9ea54e27512 100644 --- a/.github/workflows/tm2.yml +++ b/.github/workflows/tm2.yml @@ -79,7 +79,7 @@ jobs: working-directory: tm2 run: | export GOPATH=$HOME/go - export GOTEST_FLAGS="-v -p 1 -timeout=20m -coverprofile=coverage.out -covermode=atomic" + export GOTEST_FLAGS="-v -p 1 -timeout=20m -coverprofile=coverage.out -covermode=atomic -tags='ledger_mock'" make ${{ matrix.args }} touch coverage.out - uses: actions/upload-artifact@v4 diff --git a/tm2/Makefile b/tm2/Makefile index 3103ef220b2..f48b6e47a95 100644 --- a/tm2/Makefile +++ b/tm2/Makefile @@ -20,7 +20,7 @@ GOFMT_FLAGS ?= -w # flags for `make imports`. GOIMPORTS_FLAGS ?= $(GOFMT_FLAGS) # test suite flags. -GOTEST_FLAGS ?= -v -p 1 -timeout=30m +GOTEST_FLAGS ?= -v -p 1 -timeout=30m -tags='ledger_mock' ######################################## # Dev tools diff --git a/tm2/pkg/crypto/keys/client/add_ledger_skipped_test.go b/tm2/pkg/crypto/keys/client/add_ledger_skipped_test.go new file mode 100644 index 00000000000..770593e337b --- /dev/null +++ b/tm2/pkg/crypto/keys/client/add_ledger_skipped_test.go @@ -0,0 +1,10 @@ +//go:build !ledger_mock +// +build !ledger_mock + +package client + +import "testing" + +func TestAdd_Ledger(t *testing.T) { + t.Skip("Please enable the 'ledger_mock' build tags") +} diff --git a/tm2/pkg/crypto/keys/client/add_ledger_test.go b/tm2/pkg/crypto/keys/client/add_ledger_test.go new file mode 100644 index 00000000000..5a4e0cd6f92 --- /dev/null +++ b/tm2/pkg/crypto/keys/client/add_ledger_test.go @@ -0,0 +1,170 @@ +//go:build ledger_mock +// +build ledger_mock + +package client + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/crypto/keys" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Make sure to run these tests with the following tag enabled: +// -tags='ledger_mock' +func TestAdd_Ledger(t *testing.T) { + t.Parallel() + + t.Run("valid ledger reference added", func(t *testing.T) { + t.Parallel() + + var ( + kbHome = t.TempDir() + baseOptions = BaseOptions{ + InsecurePasswordStdin: true, + Home: kbHome, + } + + keyName = "key-name" + ) + + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + io := commands.NewTestIO() + io.SetIn(strings.NewReader("test1234\ntest1234\n")) + + // Create the command + cmd := NewRootCmdWithBaseConfig(io, baseOptions) + + args := []string{ + "add", + "ledger", + "--insecure-password-stdin", + "--home", + kbHome, + keyName, + } + + require.NoError(t, cmd.ParseAndRun(ctx, args)) + + // Check the keybase + kb, err := keys.NewKeyBaseFromDir(kbHome) + require.NoError(t, err) + + original, err := kb.GetByName(keyName) + require.NoError(t, err) + require.NotNil(t, original) + }) + + t.Run("valid ledger reference added, overwrite", func(t *testing.T) { + t.Parallel() + + var ( + kbHome = t.TempDir() + baseOptions = BaseOptions{ + InsecurePasswordStdin: true, + Home: kbHome, + } + + keyName = "key-name" + ) + + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + io := commands.NewTestIO() + io.SetIn(strings.NewReader("test1234\ntest1234\n")) + + // Create the command + cmd := NewRootCmdWithBaseConfig(io, baseOptions) + + args := []string{ + "add", + "ledger", + "--insecure-password-stdin", + "--home", + kbHome, + keyName, + } + + require.NoError(t, cmd.ParseAndRun(ctx, args)) + + // Check the keybase + kb, err := keys.NewKeyBaseFromDir(kbHome) + require.NoError(t, err) + + original, err := kb.GetByName(keyName) + require.NoError(t, err) + require.NotNil(t, original) + + io.SetIn(strings.NewReader("y\ntest1234\ntest1234\n")) + + cmd = NewRootCmdWithBaseConfig(io, baseOptions) + require.NoError(t, cmd.ParseAndRun(ctx, args)) + + newKey, err := kb.GetByName(keyName) + require.NoError(t, err) + + // Make sure the different key is generated and overwritten + assert.NotEqual(t, original.GetAddress(), newKey.GetAddress()) + }) + + t.Run("valid ledger reference added, no overwrite permission", func(t *testing.T) { + t.Parallel() + + var ( + kbHome = t.TempDir() + baseOptions = BaseOptions{ + InsecurePasswordStdin: true, + Home: kbHome, + } + + keyName = "key-name" + ) + + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + io := commands.NewTestIO() + io.SetIn(strings.NewReader("test1234\ntest1234\n")) + + // Create the command + cmd := NewRootCmdWithBaseConfig(io, baseOptions) + + args := []string{ + "add", + "ledger", + "--insecure-password-stdin", + "--home", + kbHome, + keyName, + } + + require.NoError(t, cmd.ParseAndRun(ctx, args)) + + // Check the keybase + kb, err := keys.NewKeyBaseFromDir(kbHome) + require.NoError(t, err) + + original, err := kb.GetByName(keyName) + require.NoError(t, err) + require.NotNil(t, original) + + io.SetIn(strings.NewReader("n\ntest1234\ntest1234\n")) + + cmd = NewRootCmdWithBaseConfig(io, baseOptions) + require.ErrorIs(t, cmd.ParseAndRun(ctx, args), errOverwriteAborted) + + newKey, err := kb.GetByName(keyName) + require.NoError(t, err) + + // Make sure the key is not overwritten + assert.Equal(t, original.GetAddress(), newKey.GetAddress()) + }) +} diff --git a/tm2/pkg/crypto/ledger/discover.go b/tm2/pkg/crypto/ledger/discover.go new file mode 100644 index 00000000000..9375378f1ec --- /dev/null +++ b/tm2/pkg/crypto/ledger/discover.go @@ -0,0 +1,19 @@ +//go:build !ledger_mock +// +build !ledger_mock + +package ledger + +import ( + ledger_go "github.com/cosmos/ledger-cosmos-go" +) + +// discoverLedger defines a function to be invoked at runtime for discovering +// a connected Ledger device. +var discoverLedger discoverLedgerFn = func() (LedgerSECP256K1, error) { + device, err := ledger_go.FindLedgerCosmosUserApp() + if err != nil { + return nil, err + } + + return device, nil +} diff --git a/tm2/pkg/crypto/ledger/discover_mock.go b/tm2/pkg/crypto/ledger/discover_mock.go new file mode 100644 index 00000000000..9693ee1f2e8 --- /dev/null +++ b/tm2/pkg/crypto/ledger/discover_mock.go @@ -0,0 +1,69 @@ +//go:build ledger_mock +// +build ledger_mock + +package ledger + +import ( + btcec "github.com/btcsuite/btcd/btcec/v2" + "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" +) + +// discoverLedger defines a function to be invoked at runtime for discovering +// a connected Ledger device. +var discoverLedger discoverLedgerFn = func() (LedgerSECP256K1, error) { + privateKey := secp256k1.GenPrivKey() + + _, pubKeyObject := btcec.PrivKeyFromBytes(privateKey[:]) + + return &MockLedger{ + GetAddressPubKeySECP256K1Fn: func(data []uint32, str string) ([]byte, string, error) { + return pubKeyObject.SerializeCompressed(), privateKey.PubKey().Address().String(), nil + }, + }, nil +} + +type ( + closeDelegate func() error + getPublicKeySECP256K1Delegate func([]uint32) ([]byte, error) + getAddressPubKeySECP256K1Delegate func([]uint32, string) ([]byte, string, error) + signSECP256K1Delegate func([]uint32, []byte, byte) ([]byte, error) +) + +type MockLedger struct { + CloseFn closeDelegate + GetPublicKeySECP256K1Fn getPublicKeySECP256K1Delegate + GetAddressPubKeySECP256K1Fn getAddressPubKeySECP256K1Delegate + SignSECP256K1Fn signSECP256K1Delegate +} + +func (m *MockLedger) Close() error { + if m.CloseFn != nil { + return m.CloseFn() + } + + return nil +} + +func (m *MockLedger) GetPublicKeySECP256K1(data []uint32) ([]byte, error) { + if m.GetPublicKeySECP256K1Fn != nil { + return m.GetPublicKeySECP256K1Fn(data) + } + + return nil, nil +} + +func (m *MockLedger) GetAddressPubKeySECP256K1(data []uint32, str string) ([]byte, string, error) { + if m.GetAddressPubKeySECP256K1Fn != nil { + return m.GetAddressPubKeySECP256K1Fn(data, str) + } + + return nil, "", nil +} + +func (m *MockLedger) SignSECP256K1(d1 []uint32, d2 []byte, d3 byte) ([]byte, error) { + if m.SignSECP256K1Fn != nil { + return m.SignSECP256K1Fn(d1, d2, d3) + } + + return nil, nil +} diff --git a/tm2/pkg/crypto/ledger/ledger_secp256k1.go b/tm2/pkg/crypto/ledger/ledger_secp256k1.go index f154dbf376c..56877b813a5 100644 --- a/tm2/pkg/crypto/ledger/ledger_secp256k1.go +++ b/tm2/pkg/crypto/ledger/ledger_secp256k1.go @@ -9,7 +9,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2/ecdsa" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" - ledger "github.com/cosmos/ledger-cosmos-go" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/hd" @@ -45,17 +44,6 @@ type ( } ) -// discoverLedger defines a function to be invoked at runtime for discovering -// a connected Ledger device. -var discoverLedger discoverLedgerFn = func() (LedgerSECP256K1, error) { - device, err := ledger.FindLedgerCosmosUserApp() - if err != nil { - return nil, err - } - - return device, nil -} - // NewPrivKeyLedgerSecp256k1Unsafe will generate a new key and store the public key for later use. // // This function is marked as unsafe as it will retrieve a pubkey without user verification.