From cde4ccf01c57b23964163e3192f8ed0c8b4bb8eb Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 20 Apr 2020 11:52:05 +0300 Subject: [PATCH] vm: remove CHECKSIG/VERIFY/CHECKMULTISIG opcodes --- pkg/core/gas_price.go | 23 -- pkg/core/interop/crypto/ecdsa_test.go | 110 ++++++++++ pkg/vm/vm.go | 39 ---- pkg/vm/vm_test.go | 288 -------------------------- 4 files changed, 110 insertions(+), 350 deletions(-) create mode 100644 pkg/core/interop/crypto/ecdsa_test.go diff --git a/pkg/core/gas_price.go b/pkg/core/gas_price.go index 021297eb9..c3ec08cc9 100644 --- a/pkg/core/gas_price.go +++ b/pkg/core/gas_price.go @@ -29,29 +29,6 @@ func getPrice(v *vm.VM, op opcode.Opcode, parameter []byte) util.Fixed8 { return toFixed8(10) case opcode.HASH160, opcode.HASH256: return toFixed8(20) - case opcode.CHECKSIG, opcode.VERIFY: - return toFixed8(100) - case opcode.CHECKMULTISIG: - estack := v.Estack() - if estack.Len() == 0 { - return toFixed8(1) - } - - var cost int - - item := estack.Peek(0) - switch item.Item().(type) { - case *vm.ArrayItem, *vm.StructItem: - cost = len(item.Array()) - default: - cost = int(item.BigInt().Int64()) - } - - if cost < 1 { - return toFixed8(1) - } - - return toFixed8(int64(100 * cost)) default: return toFixed8(1) } diff --git a/pkg/core/interop/crypto/ecdsa_test.go b/pkg/core/interop/crypto/ecdsa_test.go new file mode 100644 index 000000000..da652ca97 --- /dev/null +++ b/pkg/core/interop/crypto/ecdsa_test.go @@ -0,0 +1,110 @@ +package crypto + +import ( + "encoding/binary" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "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" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func initCHECKMULTISIG(msg []byte, n int) ([]vm.StackItem, []vm.StackItem, map[string]*keys.PublicKey, error) { + var err error + + keyMap := make(map[string]*keys.PublicKey) + pkeys := make([]*keys.PrivateKey, n) + pubs := make([]vm.StackItem, n) + for i := range pubs { + pkeys[i], err = keys.NewPrivateKey() + if err != nil { + return nil, nil, nil, err + } + + pk := pkeys[i].PublicKey() + data := pk.Bytes() + pubs[i] = vm.NewByteArrayItem(data) + keyMap[string(data)] = pk + } + + sigs := make([]vm.StackItem, n) + for i := range sigs { + sig := pkeys[i].Sign(msg) + sigs[i] = vm.NewByteArrayItem(sig) + } + + return pubs, sigs, keyMap, nil +} + +func subSlice(arr []vm.StackItem, indices []int) []vm.StackItem { + if indices == nil { + return arr + } + + result := make([]vm.StackItem, len(indices)) + for i, j := range indices { + result[i] = arr[j] + } + + return result +} + +func initCHECKMULTISIGVM(t *testing.T, n int, ik, is []int) *vm.VM { + buf := make([]byte, 5) + buf[0] = byte(opcode.SYSCALL) + binary.LittleEndian.PutUint32(buf[1:], ecdsaCheckMultisigID) + + v := vm.New() + ic := &interop.Context{Trigger: trigger.Verification} + v.RegisterInteropGetter(GetInterop(ic)) + v.LoadScript(buf) + msg := []byte("NEO - An Open Network For Smart Economy") + + pubs, sigs, _, err := initCHECKMULTISIG(msg, n) + require.NoError(t, err) + + pubs = subSlice(pubs, ik) + sigs = subSlice(sigs, is) + + v.Estack().PushVal(sigs) + v.Estack().PushVal(pubs) + v.Estack().PushVal(msg) + + return v +} + +func testCHECKMULTISIGGood(t *testing.T, n int, is []int) { + v := initCHECKMULTISIGVM(t, 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 testCHECKMULTISIGBad(t *testing.T, n int, ik, is []int) { + v := initCHECKMULTISIGVM(t, n, ik, is) + + require.NoError(t, v.Run()) + assert.Equal(t, 1, v.Estack().Len()) + assert.False(t, v.Estack().Pop().Bool()) +} + +func TestCHECKMULTISIGBad(t *testing.T) { + t.Run("1_1 wrong signature", func(t *testing.T) { testCHECKMULTISIGBad(t, 2, []int{0}, []int{1}) }) + t.Run("3_2 wrong order", func(t *testing.T) { testCHECKMULTISIGBad(t, 3, []int{0, 2}, []int{2, 0}) }) + t.Run("3_2 duplicate sig", func(t *testing.T) { testCHECKMULTISIGBad(t, 3, nil, []int{0, 0}) }) +} diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 86a2c73d4..7f7a38ac6 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -1196,45 +1196,6 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro v.astack = v.Context().astack } - case opcode.CHECKSIG, opcode.VERIFY: - var hashToCheck []byte - - keyb := v.estack.Pop().Bytes() - signature := v.estack.Pop().Bytes() - if op == opcode.CHECKSIG { - if v.checkhash == nil { - panic("VM is not set up properly for signature checks") - } - hashToCheck = v.checkhash - } else { // VERIFY - msg := v.estack.Pop().Bytes() - hashToCheck = hash.Sha256(msg).BytesBE() - } - pkey := v.bytesToPublicKey(keyb) - res := pkey.Verify(signature, hashToCheck) - v.estack.PushVal(res) - - case opcode.CHECKMULTISIG: - pkeys, err := v.estack.PopSigElements() - if err != nil { - panic(fmt.Sprintf("wrong parameters: %s", err.Error())) - } - sigs, err := v.estack.PopSigElements() - if err != nil { - panic(fmt.Sprintf("wrong parameters: %s", err.Error())) - } - // It's ok to have more keys than there are signatures (it would - // just mean that some keys didn't sign), but not the other way around. - if len(pkeys) < len(sigs) { - panic("more signatures than there are keys") - } - if v.checkhash == nil { - panic("VM is not set up properly for signature checks") - } - - sigok := CheckMultisigPar(v, v.checkhash, pkeys, sigs) - v.estack.PushVal(sigok) - case opcode.NEWMAP: v.estack.Push(&Element{value: NewMapItem()}) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 24e77eea0..9ac3fe0f3 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -8,8 +8,6 @@ import ( "math/rand" "testing" - "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/io" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -2603,292 +2601,6 @@ func TestREMOVEMap(t *testing.T) { assert.Equal(t, makeStackItem(false), vm.estack.Pop().value) } -func TestCHECKSIGNoArgs(t *testing.T) { - prog := makeProgram(opcode.CHECKSIG) - vm := load(prog) - checkVMFailed(t, vm) -} - -func TestCHECKSIGOneArg(t *testing.T) { - prog := makeProgram(opcode.CHECKSIG) - pk, err := keys.NewPrivateKey() - assert.Nil(t, err) - pbytes := pk.PublicKey().Bytes() - vm := load(prog) - vm.estack.PushVal(pbytes) - checkVMFailed(t, vm) -} - -func TestCHECKSIGNoSigLoaded(t *testing.T) { - prog := makeProgram(opcode.CHECKSIG) - pk, err := keys.NewPrivateKey() - assert.Nil(t, err) - msg := "NEO - An Open Network For Smart Economy" - sig := pk.Sign([]byte(msg)) - pbytes := pk.PublicKey().Bytes() - vm := load(prog) - vm.estack.PushVal(sig) - vm.estack.PushVal(pbytes) - checkVMFailed(t, vm) -} - -func TestCHECKSIGBadKey(t *testing.T) { - prog := makeProgram(opcode.CHECKSIG) - pk, err := keys.NewPrivateKey() - assert.Nil(t, err) - msg := []byte("NEO - An Open Network For Smart Economy") - sig := pk.Sign(msg) - pbytes := pk.PublicKey().Bytes()[:4] - vm := load(prog) - vm.SetCheckedHash(hash.Sha256(msg).BytesBE()) - vm.estack.PushVal(sig) - vm.estack.PushVal(pbytes) - checkVMFailed(t, vm) -} - -func TestCHECKSIGWrongSig(t *testing.T) { - prog := makeProgram(opcode.CHECKSIG) - pk, err := keys.NewPrivateKey() - assert.Nil(t, err) - msg := []byte("NEO - An Open Network For Smart Economy") - sig := pk.Sign(msg) - pbytes := pk.PublicKey().Bytes() - vm := load(prog) - vm.SetCheckedHash(hash.Sha256(msg).BytesBE()) - vm.estack.PushVal(util.ArrayReverse(sig)) - vm.estack.PushVal(pbytes) - runVM(t, vm) - assert.Equal(t, 1, vm.estack.Len()) - assert.Equal(t, false, vm.estack.Pop().Bool()) -} - -func TestCHECKSIGGood(t *testing.T) { - prog := makeProgram(opcode.CHECKSIG) - pk, err := keys.NewPrivateKey() - assert.Nil(t, err) - msg := []byte("NEO - An Open Network For Smart Economy") - sig := pk.Sign(msg) - pbytes := pk.PublicKey().Bytes() - vm := load(prog) - vm.SetCheckedHash(hash.Sha256(msg).BytesBE()) - vm.estack.PushVal(sig) - vm.estack.PushVal(pbytes) - runVM(t, vm) - assert.Equal(t, 1, vm.estack.Len()) - assert.Equal(t, true, vm.estack.Pop().Bool()) -} - -func TestVERIFYGood(t *testing.T) { - prog := makeProgram(opcode.VERIFY) - pk, err := keys.NewPrivateKey() - assert.Nil(t, err) - msg := []byte("NEO - An Open Network For Smart Economy") - sig := pk.Sign(msg) - pbytes := pk.PublicKey().Bytes() - vm := load(prog) - vm.estack.PushVal(msg) - vm.estack.PushVal(sig) - vm.estack.PushVal(pbytes) - runVM(t, vm) - assert.Equal(t, 1, vm.estack.Len()) - assert.Equal(t, true, vm.estack.Pop().Bool()) -} - -func TestVERIFYBad(t *testing.T) { - prog := makeProgram(opcode.VERIFY) - pk, err := keys.NewPrivateKey() - assert.Nil(t, err) - msg := []byte("NEO - An Open Network For Smart Economy") - sig := pk.Sign(msg) - pbytes := pk.PublicKey().Bytes() - vm := load(prog) - vm.estack.PushVal(util.ArrayReverse(msg)) - vm.estack.PushVal(sig) - vm.estack.PushVal(pbytes) - runVM(t, vm) - assert.Equal(t, 1, vm.estack.Len()) - assert.Equal(t, false, vm.estack.Pop().Bool()) -} - -func TestCHECKMULTISIGNoArgs(t *testing.T) { - prog := makeProgram(opcode.CHECKMULTISIG) - vm := load(prog) - checkVMFailed(t, vm) -} - -func TestCHECKMULTISIGOneArg(t *testing.T) { - prog := makeProgram(opcode.CHECKMULTISIG) - pk, err := keys.NewPrivateKey() - assert.Nil(t, err) - vm := load(prog) - pbytes := pk.PublicKey().Bytes() - vm.estack.PushVal([]StackItem{NewByteArrayItem(pbytes)}) - checkVMFailed(t, vm) -} - -func TestCHECKMULTISIGNotEnoughKeys(t *testing.T) { - prog := makeProgram(opcode.CHECKMULTISIG) - pk1, err := keys.NewPrivateKey() - assert.Nil(t, err) - pk2, err := keys.NewPrivateKey() - assert.Nil(t, err) - msg := []byte("NEO - An Open Network For Smart Economy") - sig1 := pk1.Sign(msg) - sig2 := pk2.Sign(msg) - pbytes1 := pk1.PublicKey().Bytes() - vm := load(prog) - vm.SetCheckedHash(hash.Sha256(msg).BytesBE()) - vm.estack.PushVal([]StackItem{NewByteArrayItem(sig1), NewByteArrayItem(sig2)}) - vm.estack.PushVal([]StackItem{NewByteArrayItem(pbytes1)}) - checkVMFailed(t, vm) -} - -func TestCHECKMULTISIGNoHash(t *testing.T) { - prog := makeProgram(opcode.CHECKMULTISIG) - pk1, err := keys.NewPrivateKey() - assert.Nil(t, err) - pk2, err := keys.NewPrivateKey() - assert.Nil(t, err) - msg := []byte("NEO - An Open Network For Smart Economy") - sig1 := pk1.Sign(msg) - sig2 := pk2.Sign(msg) - pbytes1 := pk1.PublicKey().Bytes() - pbytes2 := pk2.PublicKey().Bytes() - vm := load(prog) - vm.estack.PushVal([]StackItem{NewByteArrayItem(sig1), NewByteArrayItem(sig2)}) - vm.estack.PushVal([]StackItem{NewByteArrayItem(pbytes1), NewByteArrayItem(pbytes2)}) - checkVMFailed(t, vm) -} - -func TestCHECKMULTISIGBadKey(t *testing.T) { - prog := makeProgram(opcode.CHECKMULTISIG) - pk1, err := keys.NewPrivateKey() - assert.Nil(t, err) - pk2, err := keys.NewPrivateKey() - assert.Nil(t, err) - msg := []byte("NEO - An Open Network For Smart Economy") - sig1 := pk1.Sign(msg) - sig2 := pk2.Sign(msg) - pbytes1 := pk1.PublicKey().Bytes() - pbytes2 := pk2.PublicKey().Bytes()[:4] - vm := load(prog) - vm.SetCheckedHash(hash.Sha256(msg).BytesBE()) - vm.estack.PushVal([]StackItem{NewByteArrayItem(sig1), NewByteArrayItem(sig2)}) - vm.estack.PushVal([]StackItem{NewByteArrayItem(pbytes1), NewByteArrayItem(pbytes2)}) - checkVMFailed(t, vm) -} - -func TestCHECKMULTISIGBadSig(t *testing.T) { - prog := makeProgram(opcode.CHECKMULTISIG) - pk1, err := keys.NewPrivateKey() - assert.Nil(t, err) - pk2, err := keys.NewPrivateKey() - assert.Nil(t, err) - msg := []byte("NEO - An Open Network For Smart Economy") - sig1 := pk1.Sign(msg) - sig2 := pk2.Sign(msg) - pbytes1 := pk1.PublicKey().Bytes() - pbytes2 := pk2.PublicKey().Bytes() - vm := load(prog) - vm.SetCheckedHash(hash.Sha256(msg).BytesBE()) - vm.estack.PushVal([]StackItem{NewByteArrayItem(util.ArrayReverse(sig1)), NewByteArrayItem(sig2)}) - vm.estack.PushVal([]StackItem{NewByteArrayItem(pbytes1), NewByteArrayItem(pbytes2)}) - runVM(t, vm) - assert.Equal(t, 1, vm.estack.Len()) - assert.Equal(t, false, vm.estack.Pop().Bool()) -} - -func initCHECKMULTISIG(msg []byte, n int) ([]StackItem, []StackItem, map[string]*keys.PublicKey, error) { - var err error - - keyMap := make(map[string]*keys.PublicKey) - pkeys := make([]*keys.PrivateKey, n) - pubs := make([]StackItem, n) - for i := range pubs { - pkeys[i], err = keys.NewPrivateKey() - if err != nil { - return nil, nil, nil, err - } - - pk := pkeys[i].PublicKey() - data := pk.Bytes() - pubs[i] = NewByteArrayItem(data) - keyMap[string(data)] = pk - } - - sigs := make([]StackItem, n) - for i := range sigs { - sig := pkeys[i].Sign(msg) - sigs[i] = NewByteArrayItem(sig) - } - - return pubs, sigs, keyMap, nil -} - -func subSlice(arr []StackItem, indices []int) []StackItem { - if indices == nil { - return arr - } - - result := make([]StackItem, len(indices)) - for i, j := range indices { - result[i] = arr[j] - } - - return result -} - -func initCHECKMULTISIGVM(t *testing.T, n int, ik, is []int) *VM { - prog := makeProgram(opcode.CHECKMULTISIG) - v := load(prog) - msg := []byte("NEO - An Open Network For Smart Economy") - - v.SetCheckedHash(hash.Sha256(msg).BytesBE()) - - pubs, sigs, _, err := initCHECKMULTISIG(msg, n) - require.NoError(t, err) - - pubs = subSlice(pubs, ik) - sigs = subSlice(sigs, is) - - v.estack.PushVal(sigs) - v.estack.PushVal(pubs) - - return v -} - -func testCHECKMULTISIGGood(t *testing.T, n int, is []int) { - v := initCHECKMULTISIGVM(t, n, nil, is) - - runVM(t, v) - 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 testCHECKMULTISIGBad(t *testing.T, n int, ik, is []int) { - v := initCHECKMULTISIGVM(t, n, ik, is) - - runVM(t, v) - assert.Equal(t, 1, v.estack.Len()) - assert.False(t, v.estack.Pop().Bool()) -} - -func TestCHECKMULTISIGBad(t *testing.T) { - t.Run("1_1 wrong signature", func(t *testing.T) { testCHECKMULTISIGBad(t, 2, []int{0}, []int{1}) }) - t.Run("3_2 wrong order", func(t *testing.T) { testCHECKMULTISIGBad(t, 3, []int{0, 2}, []int{2, 0}) }) - t.Run("3_2 duplicate sig", func(t *testing.T) { testCHECKMULTISIGBad(t, 3, nil, []int{0, 0}) }) -} - func TestSWAPGood(t *testing.T) { prog := makeProgram(opcode.SWAP) vm := load(prog)