From f5de5d43272fa17d4c35df9e65714c3293c21e05 Mon Sep 17 00:00:00 2001 From: Lorenz Herzberger Date: Mon, 3 Jul 2023 21:32:18 +0200 Subject: [PATCH] implemented notarize asset with secp256k1 validation Signed-off-by: Lorenz Herzberger --- testutil/keeper/asset.go | 26 +++- x/asset/keeper/msg_server_notarize_asset.go | 45 ++++++- x/asset/keeper/msg_server_test.go | 54 +++++++- x/asset/testutil/expected_keepers_mock.go | 141 ++++++++++++++++++++ x/asset/types/expected_keepers.go | 4 + 5 files changed, 260 insertions(+), 10 deletions(-) create mode 100644 x/asset/testutil/expected_keepers_mock.go diff --git a/testutil/keeper/asset.go b/testutil/keeper/asset.go index 1b72791..6c650a9 100644 --- a/testutil/keeper/asset.go +++ b/testutil/keeper/asset.go @@ -3,6 +3,12 @@ package keeper import ( "testing" + "planetmint-go/testutil/sample" + "planetmint-go/x/asset/keeper" + "planetmint-go/x/asset/types" + + assettestutils "planetmint-go/x/asset/testutil" + tmdb "github.com/cometbft/cometbft-db" "github.com/cometbft/cometbft/libs/log" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" @@ -12,9 +18,8 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" typesparams "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" - "planetmint-go/x/asset/keeper" - "planetmint-go/x/asset/types" ) func AssetKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { @@ -25,6 +30,7 @@ func AssetKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { stateStore := store.NewCommitMultiStore(db) stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) + require.NoError(t, stateStore.LoadLatestVersion()) registry := codectypes.NewInterfaceRegistry() @@ -36,16 +42,26 @@ func AssetKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { memStoreKey, "AssetParams", ) + + ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) + + ctrl := gomock.NewController(t) + mk := assettestutils.NewMockMachineKeeper(ctrl) + sk, pk := sample.KeyPair() + id := sample.MachineIndex(pk, pk, pk) + mk.EXPECT().GetMachineIndex(ctx, pk).Return(id, true).AnyTimes() + mk.EXPECT().GetMachineIndex(ctx, sk).Return(id, false).AnyTimes() + mk.EXPECT().GetMachine(ctx, id).Return(sample.Machine(pk, pk, pk), true).AnyTimes() + mk.EXPECT().GetMachine(ctx, sk).Return(sample.Machine(pk, pk, pk), false).AnyTimes() + k := keeper.NewKeeper( cdc, storeKey, memStoreKey, paramsSubspace, - nil, + mk, ) - ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) - // Initialize params k.SetParams(ctx, types.DefaultParams()) diff --git a/x/asset/keeper/msg_server_notarize_asset.go b/x/asset/keeper/msg_server_notarize_asset.go index 29aa314..6e96f71 100644 --- a/x/asset/keeper/msg_server_notarize_asset.go +++ b/x/asset/keeper/msg_server_notarize_asset.go @@ -2,16 +2,55 @@ package keeper import ( "context" + "crypto/sha256" + "encoding/hex" + "errors" - sdk "github.com/cosmos/cosmos-sdk/types" "planetmint-go/x/asset/types" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" ) func (k msgServer) NotarizeAsset(goCtx context.Context, msg *types.MsgNotarizeAsset) (*types.MsgNotarizeAssetResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - // TODO: Handling the message - _ = ctx + _, found := k.machineKeeper.GetMachineIndex(ctx, msg.PubKey) + + if !found { + return &types.MsgNotarizeAssetResponse{}, errors.New("machine not found") + } + + valid := ValidateSignature(msg.Hash, msg.Signature, msg.PubKey) + if !valid { + return &types.MsgNotarizeAssetResponse{}, errors.New("invalid signature") + } + + var asset = types.Asset{ + Hash: msg.Hash, + Signature: msg.Signature, + Pubkey: msg.PubKey, + } + + k.StoreAsset(ctx, asset) return &types.MsgNotarizeAssetResponse{}, nil } + +func ValidateSignature(message string, signature string, publicKey string) bool { + // Convert the message, signature, and public key from hex to bytes + messageBytes, _ := hex.DecodeString(message) + signatureBytes, _ := hex.DecodeString(signature) + publicKeyBytes, _ := hex.DecodeString(publicKey) + + // Hash the message + hash := sha256.Sum256(messageBytes) + + // Create a secp256k1 public key object + pubKey := &secp256k1.PubKey{Key: publicKeyBytes} + + // Verify the signature + isValid := pubKey.VerifySignature(hash[:], signatureBytes) + + return isValid +} diff --git a/x/asset/keeper/msg_server_test.go b/x/asset/keeper/msg_server_test.go index 5539fe4..07493c6 100644 --- a/x/asset/keeper/msg_server_test.go +++ b/x/asset/keeper/msg_server_test.go @@ -2,13 +2,19 @@ package keeper_test import ( "context" + "crypto/sha256" + "encoding/hex" "testing" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" keepertest "planetmint-go/testutil/keeper" + "planetmint-go/testutil/sample" "planetmint-go/x/asset/keeper" "planetmint-go/x/asset/types" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func setupMsgServer(t testing.TB) (types.MsgServer, context.Context) { @@ -21,3 +27,47 @@ func TestMsgServer(t *testing.T) { require.NotNil(t, ms) require.NotNil(t, ctx) } + +func TestMsgServerNotarizeAsset(t *testing.T) { + sk, pk := sample.KeyPair() + cid := "cid" + + skBytes, err := hex.DecodeString(sk) + if err != nil { + assert.Equal(t, true, false) + } + privKey := &secp256k1.PrivKey{Key: skBytes} + + cidBytes, _ := hex.DecodeString(cid) + hash := sha256.Sum256(cidBytes) + + sign, err := privKey.Sign(hash[:]) + if err != nil { + assert.Equal(t, true, false) + } + + signatureHex := hex.EncodeToString(sign) + + msg := types.NewMsgNotarizeAsset(pk, cid, signatureHex, pk) + msgServer, ctx := setupMsgServer(t) + res, err := msgServer.NotarizeAsset(ctx, msg) + if assert.NoError(t, err) { + assert.Equal(t, &types.MsgNotarizeAssetResponse{}, res) + } +} + +func TestMsgServerNotarizeAssetMachineNotFound(t *testing.T) { + sk, _ := sample.KeyPair() + msg := types.NewMsgNotarizeAsset(sk, "cid", "sign", sk) + msgServer, ctx := setupMsgServer(t) + _, err := msgServer.NotarizeAsset(ctx, msg) + assert.EqualError(t, err, "machine not found") +} + +func TestMsgServerNotarizeAssetInvalidAsset(t *testing.T) { + _, pk := sample.KeyPair() + msg := types.NewMsgNotarizeAsset(pk, "cid", "sign", pk) + msgServer, ctx := setupMsgServer(t) + _, err := msgServer.NotarizeAsset(ctx, msg) + assert.EqualError(t, err, "invalid signature") +} diff --git a/x/asset/testutil/expected_keepers_mock.go b/x/asset/testutil/expected_keepers_mock.go new file mode 100644 index 0000000..497c948 --- /dev/null +++ b/x/asset/testutil/expected_keepers_mock.go @@ -0,0 +1,141 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: x/asset/types/expected_keepers.go + +// Package testutil is a generated GoMock package. +package testutil + +import ( + types1 "planetmint-go/x/machine/types" + reflect "reflect" + + types "github.com/cosmos/cosmos-sdk/types" + types0 "github.com/cosmos/cosmos-sdk/x/auth/types" + gomock "github.com/golang/mock/gomock" +) + +// MockAccountKeeper is a mock of AccountKeeper interface. +type MockAccountKeeper struct { + ctrl *gomock.Controller + recorder *MockAccountKeeperMockRecorder +} + +// MockAccountKeeperMockRecorder is the mock recorder for MockAccountKeeper. +type MockAccountKeeperMockRecorder struct { + mock *MockAccountKeeper +} + +// NewMockAccountKeeper creates a new mock instance. +func NewMockAccountKeeper(ctrl *gomock.Controller) *MockAccountKeeper { + mock := &MockAccountKeeper{ctrl: ctrl} + mock.recorder = &MockAccountKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { + return m.recorder +} + +// GetAccount mocks base method. +func (m *MockAccountKeeper) GetAccount(ctx types.Context, addr types.AccAddress) types0.AccountI { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAccount", ctx, addr) + ret0, _ := ret[0].(types0.AccountI) + return ret0 +} + +// GetAccount indicates an expected call of GetAccount. +func (mr *MockAccountKeeperMockRecorder) GetAccount(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccount", reflect.TypeOf((*MockAccountKeeper)(nil).GetAccount), ctx, addr) +} + +// MockBankKeeper is a mock of BankKeeper interface. +type MockBankKeeper struct { + ctrl *gomock.Controller + recorder *MockBankKeeperMockRecorder +} + +// MockBankKeeperMockRecorder is the mock recorder for MockBankKeeper. +type MockBankKeeperMockRecorder struct { + mock *MockBankKeeper +} + +// NewMockBankKeeper creates a new mock instance. +func NewMockBankKeeper(ctrl *gomock.Controller) *MockBankKeeper { + mock := &MockBankKeeper{ctrl: ctrl} + mock.recorder = &MockBankKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { + return m.recorder +} + +// SpendableCoins mocks base method. +func (m *MockBankKeeper) SpendableCoins(ctx types.Context, addr types.AccAddress) types.Coins { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SpendableCoins", ctx, addr) + ret0, _ := ret[0].(types.Coins) + return ret0 +} + +// SpendableCoins indicates an expected call of SpendableCoins. +func (mr *MockBankKeeperMockRecorder) SpendableCoins(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpendableCoins", reflect.TypeOf((*MockBankKeeper)(nil).SpendableCoins), ctx, addr) +} + +// MockMachineKeeper is a mock of MachineKeeper interface. +type MockMachineKeeper struct { + ctrl *gomock.Controller + recorder *MockMachineKeeperMockRecorder +} + +// MockMachineKeeperMockRecorder is the mock recorder for MockMachineKeeper. +type MockMachineKeeperMockRecorder struct { + mock *MockMachineKeeper +} + +// NewMockMachineKeeper creates a new mock instance. +func NewMockMachineKeeper(ctrl *gomock.Controller) *MockMachineKeeper { + mock := &MockMachineKeeper{ctrl: ctrl} + mock.recorder = &MockMachineKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMachineKeeper) EXPECT() *MockMachineKeeperMockRecorder { + return m.recorder +} + +// GetMachine mocks base method. +func (m *MockMachineKeeper) GetMachine(ctx types.Context, index types1.MachineIndex) (types1.Machine, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMachine", ctx, index) + ret0, _ := ret[0].(types1.Machine) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetMachine indicates an expected call of GetMachine. +func (mr *MockMachineKeeperMockRecorder) GetMachine(ctx, index interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMachine", reflect.TypeOf((*MockMachineKeeper)(nil).GetMachine), ctx, index) +} + +// GetMachineIndex mocks base method. +func (m *MockMachineKeeper) GetMachineIndex(ctx types.Context, pubKey string) (types1.MachineIndex, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMachineIndex", ctx, pubKey) + ret0, _ := ret[0].(types1.MachineIndex) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetMachineIndex indicates an expected call of GetMachineIndex. +func (mr *MockMachineKeeperMockRecorder) GetMachineIndex(ctx, pubKey interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMachineIndex", reflect.TypeOf((*MockMachineKeeper)(nil).GetMachineIndex), ctx, pubKey) +} \ No newline at end of file diff --git a/x/asset/types/expected_keepers.go b/x/asset/types/expected_keepers.go index 88ddaca..46de2f1 100644 --- a/x/asset/types/expected_keepers.go +++ b/x/asset/types/expected_keepers.go @@ -1,12 +1,16 @@ package types import ( + machinetypes "planetmint-go/x/machine/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/types" ) type MachineKeeper interface { // Methods imported from machine should be defined here + GetMachine(ctx sdk.Context, index machinetypes.MachineIndex) (val machinetypes.Machine, found bool) + GetMachineIndex(ctx sdk.Context, pubKey string) (val machinetypes.MachineIndex, found bool) } // AccountKeeper defines the expected account keeper used for simulations (noalias)