From 6e749f49776d74c3bed989677f3afe783e8bc627 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 2 Dec 2020 15:52:57 +0300 Subject: [PATCH] core: add tests for crypto interops Also move related test from `core/`. --- pkg/core/interop/crypto/ecdsa_test.go | 205 +++++++++++++++++++++++--- pkg/core/interop/crypto/hash_test.go | 77 ++++++---- pkg/core/interop_neo_test.go | 85 ----------- 3 files changed, 232 insertions(+), 135 deletions(-) diff --git a/pkg/core/interop/crypto/ecdsa_test.go b/pkg/core/interop/crypto/ecdsa_test.go index c2546c81c..58fab77d6 100644 --- a/pkg/core/interop/crypto/ecdsa_test.go +++ b/pkg/core/interop/crypto/ecdsa_test.go @@ -2,9 +2,14 @@ package crypto import ( "encoding/binary" + "fmt" "testing" + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/storage" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/vm" @@ -14,14 +19,116 @@ import ( "github.com/stretchr/testify/require" ) -func initCHECKMULTISIG(msg []byte, n int) ([]stackitem.Item, []stackitem.Item, map[string]*keys.PublicKey, error) { +func TestECDSASecp256r1Verify(t *testing.T) { + testECDSAVerify(t, true) +} + +func TestECDSASecp256k1Verify(t *testing.T) { + testECDSAVerify(t, false) +} + +func testECDSAVerify(t *testing.T, isR1 bool) { + var priv *keys.PrivateKey + var err error + if isR1 { + priv, err = keys.NewPrivateKey() + } else { + priv, err = keys.NewSecp256k1PrivateKey() + } + require.NoError(t, err) + + verifyFunc := ECDSASecp256r1Verify + if !isR1 { + verifyFunc = ECDSASecp256k1Verify + } + d := dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) + ic := &interop.Context{DAO: dao.NewCached(d)} + runCase := func(t *testing.T, isErr bool, result interface{}, args ...interface{}) { + ic.SpawnVM() + for i := range args { + ic.VM.Estack().PushVal(args[i]) + } + + var err error + func() { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic: %v", r) + } + }() + err = verifyFunc(ic) + }() + + if isErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, 1, ic.VM.Estack().Len()) + require.Equal(t, result, ic.VM.Estack().Pop().Value().(bool)) + } + + msg := []byte("test message") + + t.Run("success", func(t *testing.T) { + sign := priv.Sign(msg) + runCase(t, false, true, sign, priv.PublicKey().Bytes(), msg) + }) + + t.Run("signed interop item", func(t *testing.T) { + tx := transaction.New(netmode.UnitTestNet, []byte{0, 1, 2}, 1) + msg := tx.GetSignedPart() + sign := priv.Sign(msg) + runCase(t, false, true, sign, priv.PublicKey().Bytes(), stackitem.NewInterop(tx)) + }) + + t.Run("signed script container", func(t *testing.T) { + tx := transaction.New(netmode.UnitTestNet, []byte{0, 1, 2}, 1) + msg := tx.GetSignedPart() + sign := priv.Sign(msg) + ic.Container = tx + runCase(t, false, true, sign, priv.PublicKey().Bytes(), stackitem.Null{}) + }) + + t.Run("missing arguments", func(t *testing.T) { + runCase(t, true, false) + sign := priv.Sign(msg) + runCase(t, true, false, sign) + runCase(t, true, false, sign, priv.PublicKey().Bytes()) + }) + + t.Run("invalid signature", func(t *testing.T) { + sign := priv.Sign(msg) + sign[0] = ^sign[0] + runCase(t, false, false, sign, priv.PublicKey().Bytes(), msg) + }) + + t.Run("invalid public key", func(t *testing.T) { + sign := priv.Sign(msg) + pub := priv.PublicKey().Bytes() + pub[0] = 0xFF // invalid prefix + runCase(t, true, false, sign, pub, msg) + }) + + t.Run("invalid message", func(t *testing.T) { + sign := priv.Sign(msg) + runCase(t, true, false, sign, priv.PublicKey().Bytes(), + stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(msg)})) + }) +} + +func initCHECKMULTISIG(isR1 bool, msg []byte, n int) ([]stackitem.Item, []stackitem.Item, map[string]*keys.PublicKey, error) { var err error keyMap := make(map[string]*keys.PublicKey) pkeys := make([]*keys.PrivateKey, n) pubs := make([]stackitem.Item, n) for i := range pubs { - pkeys[i], err = keys.NewPrivateKey() + if isR1 { + pkeys[i], err = keys.NewPrivateKey() + } else { + pkeys[i], err = keys.NewSecp256k1PrivateKey() + } if err != nil { return nil, nil, nil, err } @@ -54,18 +161,27 @@ func subSlice(arr []stackitem.Item, indices []int) []stackitem.Item { return result } -func initCHECKMULTISIGVM(t *testing.T, n int, ik, is []int) *vm.VM { +func initCheckMultisigVMNoArgs(isR1 bool) *vm.VM { buf := make([]byte, 5) buf[0] = byte(opcode.SYSCALL) - binary.LittleEndian.PutUint32(buf[1:], ecdsaSecp256r1CheckMultisigID) + if isR1 { + binary.LittleEndian.PutUint32(buf[1:], ecdsaSecp256r1CheckMultisigID) + } else { + binary.LittleEndian.PutUint32(buf[1:], ecdsaSecp256k1CheckMultisigID) + } ic := &interop.Context{Trigger: trigger.Verification} Register(ic) v := ic.SpawnVM() v.LoadScript(buf) + return v +} + +func initCHECKMULTISIGVM(t *testing.T, isR1 bool, n int, ik, is []int) *vm.VM { + v := initCheckMultisigVMNoArgs(isR1) msg := []byte("NEO - An Open Network For Smart Economy") - pubs, sigs, _, err := initCHECKMULTISIG(msg, n) + pubs, sigs, _, err := initCHECKMULTISIG(isR1, msg, n) require.NoError(t, err) pubs = subSlice(pubs, ik) @@ -78,26 +194,34 @@ func initCHECKMULTISIGVM(t *testing.T, n int, ik, is []int) *vm.VM { return v } -func testCHECKMULTISIGGood(t *testing.T, n int, is []int) { - v := initCHECKMULTISIGVM(t, n, nil, is) +func testCHECKMULTISIGGood(t *testing.T, isR1 bool, n int, is []int) { + v := initCHECKMULTISIGVM(t, isR1, n, nil, is) require.NoError(t, v.Run()) assert.Equal(t, 1, v.Estack().Len()) assert.True(t, v.Estack().Pop().Bool()) } -func TestCHECKMULTISIGGood(t *testing.T) { - t.Run("3_1", func(t *testing.T) { testCHECKMULTISIGGood(t, 3, []int{1}) }) - t.Run("2_2", func(t *testing.T) { testCHECKMULTISIGGood(t, 2, []int{0, 1}) }) - t.Run("3_3", func(t *testing.T) { testCHECKMULTISIGGood(t, 3, []int{0, 1, 2}) }) - t.Run("3_2", func(t *testing.T) { testCHECKMULTISIGGood(t, 3, []int{0, 2}) }) - t.Run("4_2", func(t *testing.T) { testCHECKMULTISIGGood(t, 4, []int{0, 2}) }) - t.Run("10_7", func(t *testing.T) { testCHECKMULTISIGGood(t, 10, []int{2, 3, 4, 5, 6, 8, 9}) }) - t.Run("12_9", func(t *testing.T) { testCHECKMULTISIGGood(t, 12, []int{0, 1, 4, 5, 6, 7, 8, 9}) }) +func TestECDSASecp256r1CheckMultisigGood(t *testing.T) { + testCurveCHECKMULTISIGGood(t, true) } -func testCHECKMULTISIGBad(t *testing.T, isErr bool, n int, ik, is []int) { - v := initCHECKMULTISIGVM(t, n, ik, is) +func TestECDSASecp256k1CheckMultisigGood(t *testing.T) { + testCurveCHECKMULTISIGGood(t, false) +} + +func testCurveCHECKMULTISIGGood(t *testing.T, isR1 bool) { + t.Run("3_1", func(t *testing.T) { testCHECKMULTISIGGood(t, isR1, 3, []int{1}) }) + t.Run("2_2", func(t *testing.T) { testCHECKMULTISIGGood(t, isR1, 2, []int{0, 1}) }) + t.Run("3_3", func(t *testing.T) { testCHECKMULTISIGGood(t, isR1, 3, []int{0, 1, 2}) }) + t.Run("3_2", func(t *testing.T) { testCHECKMULTISIGGood(t, isR1, 3, []int{0, 2}) }) + t.Run("4_2", func(t *testing.T) { testCHECKMULTISIGGood(t, isR1, 4, []int{0, 2}) }) + t.Run("10_7", func(t *testing.T) { testCHECKMULTISIGGood(t, isR1, 10, []int{2, 3, 4, 5, 6, 8, 9}) }) + t.Run("12_9", func(t *testing.T) { testCHECKMULTISIGGood(t, isR1, 12, []int{0, 1, 4, 5, 6, 7, 8, 9}) }) +} + +func testCHECKMULTISIGBad(t *testing.T, isR1 bool, isErr bool, n int, ik, is []int) { + v := initCHECKMULTISIGVM(t, isR1, n, ik, is) if isErr { require.Error(t, v.Run()) @@ -108,14 +232,49 @@ func testCHECKMULTISIGBad(t *testing.T, isErr bool, n int, ik, is []int) { assert.False(t, v.Estack().Pop().Bool()) } -func TestCHECKMULTISIGBad(t *testing.T) { - t.Run("1_1 wrong signature", func(t *testing.T) { testCHECKMULTISIGBad(t, false, 2, []int{0}, []int{1}) }) - t.Run("3_2 wrong order", func(t *testing.T) { testCHECKMULTISIGBad(t, false, 3, []int{0, 2}, []int{2, 0}) }) - t.Run("3_2 duplicate sig", func(t *testing.T) { testCHECKMULTISIGBad(t, false, 3, nil, []int{0, 0}) }) - t.Run("1_2 too many signatures", func(t *testing.T) { testCHECKMULTISIGBad(t, true, 2, []int{0}, []int{0, 1}) }) +func TestECDSASecp256r1CheckMultisigBad(t *testing.T) { + testCurveCHECKMULTISIGBad(t, true) +} + +func TestECDSASecp256k1CheckMultisigBad(t *testing.T) { + testCurveCHECKMULTISIGBad(t, false) +} + +func testCurveCHECKMULTISIGBad(t *testing.T, isR1 bool) { + t.Run("1_1 wrong signature", func(t *testing.T) { testCHECKMULTISIGBad(t, isR1, false, 2, []int{0}, []int{1}) }) + t.Run("3_2 wrong order", func(t *testing.T) { testCHECKMULTISIGBad(t, isR1, false, 3, []int{0, 2}, []int{2, 0}) }) + t.Run("3_2 duplicate sig", func(t *testing.T) { testCHECKMULTISIGBad(t, isR1, false, 3, nil, []int{0, 0}) }) + t.Run("1_2 too many signatures", func(t *testing.T) { testCHECKMULTISIGBad(t, isR1, true, 2, []int{0}, []int{0, 1}) }) t.Run("gas limit exceeded", func(t *testing.T) { - v := initCHECKMULTISIGVM(t, 1, []int{0}, []int{0}) + v := initCHECKMULTISIGVM(t, isR1, 1, []int{0}, []int{0}) v.GasLimit = ECDSAVerifyPrice - 1 require.Error(t, v.Run()) }) + + msg := []byte("NEO - An Open Network For Smart Economy") + pubs, sigs, _, err := initCHECKMULTISIG(isR1, msg, 1) + require.NoError(t, err) + arr := stackitem.NewArray([]stackitem.Item{stackitem.NewArray(nil)}) + + t.Run("invalid message type", func(t *testing.T) { + v := initCheckMultisigVMNoArgs(isR1) + v.Estack().PushVal(sigs) + v.Estack().PushVal(pubs) + v.Estack().PushVal(stackitem.NewArray(nil)) + require.Error(t, v.Run()) + }) + t.Run("invalid public keys", func(t *testing.T) { + v := initCheckMultisigVMNoArgs(isR1) + v.Estack().PushVal(sigs) + v.Estack().PushVal(arr) + v.Estack().PushVal(msg) + require.Error(t, v.Run()) + }) + t.Run("invalid signatures", func(t *testing.T) { + v := initCheckMultisigVMNoArgs(isR1) + v.Estack().PushVal(arr) + v.Estack().PushVal(pubs) + v.Estack().PushVal(msg) + require.Error(t, v.Run()) + }) } diff --git a/pkg/core/interop/crypto/hash_test.go b/pkg/core/interop/crypto/hash_test.go index 4d87d27d5..313074d4a 100644 --- a/pkg/core/interop/crypto/hash_test.go +++ b/pkg/core/interop/crypto/hash_test.go @@ -5,42 +5,65 @@ import ( "testing" "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" - "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" - "github.com/stretchr/testify/assert" + "github.com/nspcc-dev/neo-go/pkg/crypto" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) +type testVerifiable []byte + +var _ crypto.Verifiable = testVerifiable{} + +func (v testVerifiable) GetSignedPart() []byte { + return v +} +func (v testVerifiable) GetSignedHash() util.Uint256 { + return hash.Sha256(v) +} + +func testHash0100(t *testing.T, result string, interopFunc func(*interop.Context) error) { + t.Run("good", func(t *testing.T) { + bs := []byte{1, 0} + + checkGood := func(t *testing.T, ic *interop.Context) { + require.NoError(t, interopFunc(ic)) + require.Equal(t, 1, ic.VM.Estack().Len()) + require.Equal(t, result, hex.EncodeToString(ic.VM.Estack().Pop().Bytes())) + } + t.Run("raw bytes", func(t *testing.T) { + ic := &interop.Context{VM: vm.New()} + ic.VM.Estack().PushVal(bs) + checkGood(t, ic) + }) + t.Run("interop", func(t *testing.T) { + ic := &interop.Context{VM: vm.New()} + ic.VM.Estack().PushVal(stackitem.NewInterop(testVerifiable(bs))) + checkGood(t, ic) + }) + t.Run("container", func(t *testing.T) { + ic := &interop.Context{VM: vm.New(), Container: testVerifiable(bs)} + ic.VM.Estack().PushVal(stackitem.Null{}) + checkGood(t, ic) + }) + }) + t.Run("bad message", func(t *testing.T) { + ic := &interop.Context{VM: vm.New()} + ic.VM.Estack().PushVal(stackitem.NewArray(nil)) + require.Error(t, interopFunc(ic)) + }) +} + func TestSHA256(t *testing.T) { // 0x0100 hashes to 47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254 res := "47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254" - buf := io.NewBufBinWriter() - emit.Bytes(buf.BinWriter, []byte{1, 0}) - emit.Syscall(buf.BinWriter, interopnames.NeoCryptoSHA256) - prog := buf.Bytes() - ic := &interop.Context{Trigger: trigger.Verification} - Register(ic) - v := ic.SpawnVM() - v.Load(prog) - require.NoError(t, v.Run()) - assert.Equal(t, 1, v.Estack().Len()) - assert.Equal(t, res, hex.EncodeToString(v.Estack().Pop().Bytes())) + testHash0100(t, res, Sha256) } func TestRIPEMD160(t *testing.T) { // 0x0100 hashes to 213492c0c6fc5d61497cf17249dd31cd9964b8a3 res := "213492c0c6fc5d61497cf17249dd31cd9964b8a3" - buf := io.NewBufBinWriter() - emit.Bytes(buf.BinWriter, []byte{1, 0}) - emit.Syscall(buf.BinWriter, interopnames.NeoCryptoRIPEMD160) - prog := buf.Bytes() - ic := &interop.Context{Trigger: trigger.Verification} - Register(ic) - v := ic.SpawnVM() - v.Load(prog) - require.NoError(t, v.Run()) - assert.Equal(t, 1, v.Estack().Len()) - assert.Equal(t, res, hex.EncodeToString(v.Estack().Pop().Bytes())) + testHash0100(t, res, RipeMD160) } diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 5f4462db4..5366e4d91 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -1,21 +1,18 @@ package core import ( - "fmt" "testing" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/core/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/core/interop/enumerator" "github.com/nspcc-dev/neo-go/pkg/core/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" @@ -129,88 +126,6 @@ func TestStorageFind(t *testing.T) { }) } -func TestECDSAVerify(t *testing.T) { - priv, err := keys.NewPrivateKey() - require.NoError(t, err) - - chain := newTestChain(t) - defer chain.Close() - - ic := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false), nil, nil) - runCase := func(t *testing.T, isErr bool, result interface{}, args ...interface{}) { - ic.SpawnVM() - for i := range args { - ic.VM.Estack().PushVal(args[i]) - } - - var err error - func() { - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("panic: %v", r) - } - }() - err = crypto.ECDSASecp256r1Verify(ic) - }() - - if isErr { - require.Error(t, err) - return - } - require.NoError(t, err) - require.Equal(t, 1, ic.VM.Estack().Len()) - require.Equal(t, result, ic.VM.Estack().Pop().Value().(bool)) - } - - msg := []byte("test message") - - t.Run("success", func(t *testing.T) { - sign := priv.Sign(msg) - runCase(t, false, true, sign, priv.PublicKey().Bytes(), msg) - }) - - t.Run("signed interop item", func(t *testing.T) { - tx := transaction.New(netmode.UnitTestNet, []byte{0, 1, 2}, 1) - msg := tx.GetSignedPart() - sign := priv.Sign(msg) - runCase(t, false, true, sign, priv.PublicKey().Bytes(), stackitem.NewInterop(tx)) - }) - - t.Run("signed script container", func(t *testing.T) { - tx := transaction.New(netmode.UnitTestNet, []byte{0, 1, 2}, 1) - msg := tx.GetSignedPart() - sign := priv.Sign(msg) - ic.Container = tx - runCase(t, false, true, sign, priv.PublicKey().Bytes(), stackitem.Null{}) - }) - - t.Run("missing arguments", func(t *testing.T) { - runCase(t, true, false) - sign := priv.Sign(msg) - runCase(t, true, false, sign) - runCase(t, true, false, sign, priv.PublicKey().Bytes()) - }) - - t.Run("invalid signature", func(t *testing.T) { - sign := priv.Sign(msg) - sign[0] = ^sign[0] - runCase(t, false, false, sign, priv.PublicKey().Bytes(), msg) - }) - - t.Run("invalid public key", func(t *testing.T) { - sign := priv.Sign(msg) - pub := priv.PublicKey().Bytes() - pub[0] = 0xFF // invalid prefix - runCase(t, true, false, sign, pub, msg) - }) - - t.Run("invalid message", func(t *testing.T) { - sign := priv.Sign(msg) - runCase(t, true, false, sign, priv.PublicKey().Bytes(), - stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray(msg)})) - }) -} - // Helper functions to create VM, InteropContext, TX, Account, Contract. func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) {