From ca9c9be71f875fca49a56f335483592ca209998a Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 23 Sep 2019 19:54:06 +0300 Subject: [PATCH] vm: add CHECKSIG/VERIFY/CHECKMULTISIG implementations Fix #269. --- pkg/vm/stack.go | 37 +++++++ pkg/vm/stack_test.go | 38 +++++++ pkg/vm/vm.go | 66 +++++++++++- pkg/vm/vm_test.go | 248 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 388 insertions(+), 1 deletion(-) diff --git a/pkg/vm/stack.go b/pkg/vm/stack.go index e840bccb7..e0987637d 100644 --- a/pkg/vm/stack.go +++ b/pkg/vm/stack.go @@ -1,6 +1,7 @@ package vm import ( + "fmt" "math/big" "github.com/CityOfZion/neo-go/pkg/util" @@ -280,3 +281,39 @@ func (s *Stack) Iter(f func(*Element)) { f(e) } } + +// popSigElements pops keys or signatures from the stack as needed for +// CHECKMULTISIG. +func (s *Stack) popSigElements() ([][]byte, error) { + var num int + var elems [][]byte + item := s.Pop() + if item == nil { + return nil, fmt.Errorf("nothing on the stack") + } + switch item.value.(type) { + case *ArrayItem: + num = len(item.Array()) + if num < 1 { + return nil, fmt.Errorf("less than one element in the array") + } + elems = make([][]byte, num) + for k, v := range item.Array() { + b, ok := v.Value().([]byte) + if !ok { + return nil, fmt.Errorf("bad element %s", v.String()) + } + elems[k] = b + } + default: + num = int(item.BigInt().Int64()) + if num < 1 || num > s.Len() { + return nil, fmt.Errorf("wrong number of elements: %d", num) + } + elems = make([][]byte, num) + for i := 0; i < num; i++ { + elems[i] = s.Pop().Bytes() + } + } + return elems, nil +} diff --git a/pkg/vm/stack_test.go b/pkg/vm/stack_test.go index e4e32fab4..ffc0f4c5d 100644 --- a/pkg/vm/stack_test.go +++ b/pkg/vm/stack_test.go @@ -217,6 +217,44 @@ func TestSwapElemValues(t *testing.T) { assert.Equal(t, int64(4), s.Pop().BigInt().Int64()) } +func TestPopSigElements(t *testing.T) { + s := NewStack("test") + + _, err := s.popSigElements() + assert.NotNil(t, err) + + s.PushVal([]StackItem{}) + _, err = s.popSigElements() + assert.NotNil(t, err) + + s.PushVal([]StackItem{NewBoolItem(false)}) + _, err = s.popSigElements() + assert.NotNil(t, err) + + b1 := []byte("smth") + b2 := []byte("strange") + s.PushVal([]StackItem{NewByteArrayItem(b1), NewByteArrayItem(b2)}) + z, err := s.popSigElements() + assert.Nil(t, err) + assert.Equal(t, z, [][]byte{b1, b2}) + + s.PushVal(2) + _, err = s.popSigElements() + assert.NotNil(t, err) + + s.PushVal(b1) + s.PushVal(2) + _, err = s.popSigElements() + assert.NotNil(t, err) + + s.PushVal(b2) + s.PushVal(b1) + s.PushVal(2) + z, err = s.popSigElements() + assert.Nil(t, err) + assert.Equal(t, z, [][]byte{b1, b2}) +} + func makeElements(n int) []*Element { elems := make([]*Element, n) for i := 0; i < n; i++ { diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 7dfd040d8..23760fbb9 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -11,6 +11,7 @@ import ( "text/tabwriter" "github.com/CityOfZion/neo-go/pkg/crypto/hash" + "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/util" ) @@ -43,6 +44,8 @@ type VM struct { // Mute all output after execution. mute bool + // Hash to verify in CHECKSIG/CHECKMULTISIG. + checkhash []byte } // New returns a new VM object ready to load .avm bytecode scripts. @@ -239,6 +242,12 @@ func (v *VM) HasFailed() bool { return v.state.HasFlag(faultState) } +// SetCheckedHash sets checked hash for CHECKSIG and CHECKMULTISIG instructions. +func (v *VM) SetCheckedHash(h []byte) { + v.checkhash = make([]byte, len(h)) + copy(v.checkhash, h) +} + // execute performs an instruction cycle in the VM. Acting on the instruction (opcode). func (v *VM) execute(ctx *Context, op Instruction) { // Instead of polluting the whole VM logic with error handling, we will recover @@ -848,7 +857,62 @@ func (v *VM) execute(ctx *Context, op Instruction) { v.state = haltState } - case CHECKSIG, VERIFY, CHECKMULTISIG, NEWMAP, HASKEY, KEYS, VALUES: + case CHECKSIG, VERIFY: + var hashToCheck []byte + + keyb := v.estack.Pop().Bytes() + signature := v.estack.Pop().Bytes() + if op == 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).Bytes() + } + pkey := &keys.PublicKey{} + err := pkey.DecodeBytes(keyb) + if err != nil { + panic(err.Error()) + } + res := pkey.Verify(signature, hashToCheck) + v.estack.PushVal(res) + + case 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())) + } + 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 := true + j := 0 + for i := 0; sigok && i < len(pkeys) && j < len(sigs); { + pkey := &keys.PublicKey{} + err := pkey.DecodeBytes(pkeys[j]) + if err != nil { + panic(err.Error()) + } + if pkey.Verify(sigs[i], v.checkhash) { + i++ + } + j++ + if len(pkeys)-i > len(sigs)-j { + sigok = false + } + } + v.estack.PushVal(sigok) + + case NEWMAP, HASKEY, KEYS, VALUES: panic("unimplemented") // Cryptographic operations. diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 64fab3d56..11562c339 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -7,6 +7,8 @@ import ( "math/rand" "testing" + "github.com/CityOfZion/neo-go/pkg/crypto/hash" + "github.com/CityOfZion/neo-go/pkg/crypto/keys" "github.com/CityOfZion/neo-go/pkg/util" "github.com/stretchr/testify/assert" ) @@ -1488,6 +1490,252 @@ func TestREMOVEGood(t *testing.T) { assert.Equal(t, makeStackItem(1), vm.estack.Pop().value) } +func TestCHECKSIGNoArgs(t *testing.T) { + prog := makeProgram(CHECKSIG) + vm := load(prog) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestCHECKSIGOneArg(t *testing.T) { + prog := makeProgram(CHECKSIG) + pk, err := keys.NewPrivateKey() + assert.Nil(t, err) + pbytes := pk.PublicKey().Bytes() + vm := load(prog) + vm.estack.PushVal(pbytes) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestCHECKSIGNoSigLoaded(t *testing.T) { + prog := makeProgram(CHECKSIG) + pk, err := keys.NewPrivateKey() + assert.Nil(t, err) + msg := "NEO - An Open Network For Smart Economy" + sig, err := pk.Sign([]byte(msg)) + assert.Nil(t, err) + pbytes := pk.PublicKey().Bytes() + vm := load(prog) + vm.estack.PushVal(sig) + vm.estack.PushVal(pbytes) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestCHECKSIGBadKey(t *testing.T) { + prog := makeProgram(CHECKSIG) + pk, err := keys.NewPrivateKey() + assert.Nil(t, err) + msg := []byte("NEO - An Open Network For Smart Economy") + sig, err := pk.Sign(msg) + assert.Nil(t, err) + pbytes := pk.PublicKey().Bytes()[:4] + vm := load(prog) + vm.SetCheckedHash(hash.Sha256(msg).Bytes()) + vm.estack.PushVal(sig) + vm.estack.PushVal(pbytes) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestCHECKSIGWrongSig(t *testing.T) { + prog := makeProgram(CHECKSIG) + pk, err := keys.NewPrivateKey() + assert.Nil(t, err) + msg := []byte("NEO - An Open Network For Smart Economy") + sig, err := pk.Sign(msg) + assert.Nil(t, err) + pbytes := pk.PublicKey().Bytes() + vm := load(prog) + vm.SetCheckedHash(hash.Sha256(msg).Bytes()) + vm.estack.PushVal(util.ArrayReverse(sig)) + vm.estack.PushVal(pbytes) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, false, vm.estack.Pop().Bool()) +} + +func TestCHECKSIGGood(t *testing.T) { + prog := makeProgram(CHECKSIG) + pk, err := keys.NewPrivateKey() + assert.Nil(t, err) + msg := []byte("NEO - An Open Network For Smart Economy") + sig, err := pk.Sign(msg) + assert.Nil(t, err) + pbytes := pk.PublicKey().Bytes() + vm := load(prog) + vm.SetCheckedHash(hash.Sha256(msg).Bytes()) + vm.estack.PushVal(sig) + vm.estack.PushVal(pbytes) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, true, vm.estack.Pop().Bool()) +} + +func TestVERIFYGood(t *testing.T) { + prog := makeProgram(VERIFY) + pk, err := keys.NewPrivateKey() + assert.Nil(t, err) + msg := []byte("NEO - An Open Network For Smart Economy") + sig, err := pk.Sign(msg) + assert.Nil(t, err) + pbytes := pk.PublicKey().Bytes() + vm := load(prog) + vm.estack.PushVal(msg) + vm.estack.PushVal(sig) + vm.estack.PushVal(pbytes) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, true, vm.estack.Pop().Bool()) +} + +func TestVERIFYBad(t *testing.T) { + prog := makeProgram(VERIFY) + pk, err := keys.NewPrivateKey() + assert.Nil(t, err) + msg := []byte("NEO - An Open Network For Smart Economy") + sig, err := pk.Sign(msg) + assert.Nil(t, err) + pbytes := pk.PublicKey().Bytes() + vm := load(prog) + vm.estack.PushVal(util.ArrayReverse(msg)) + vm.estack.PushVal(sig) + vm.estack.PushVal(pbytes) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, false, vm.estack.Pop().Bool()) +} + +func TestCHECKMULTISIGNoArgs(t *testing.T) { + prog := makeProgram(CHECKMULTISIG) + vm := load(prog) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestCHECKMULTISIGOneArg(t *testing.T) { + prog := makeProgram(CHECKMULTISIG) + pk, err := keys.NewPrivateKey() + assert.Nil(t, err) + vm := load(prog) + pbytes := pk.PublicKey().Bytes() + vm.estack.PushVal([]StackItem{NewByteArrayItem(pbytes)}) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestCHECKMULTISIGNotEnoughKeys(t *testing.T) { + prog := makeProgram(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, err := pk1.Sign(msg) + assert.Nil(t, err) + sig2, err := pk2.Sign(msg) + assert.Nil(t, err) + pbytes1 := pk1.PublicKey().Bytes() + vm := load(prog) + vm.SetCheckedHash(hash.Sha256(msg).Bytes()) + vm.estack.PushVal([]StackItem{NewByteArrayItem(sig1), NewByteArrayItem(sig2)}) + vm.estack.PushVal([]StackItem{NewByteArrayItem(pbytes1)}) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestCHECKMULTISIGNoHash(t *testing.T) { + prog := makeProgram(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, err := pk1.Sign(msg) + assert.Nil(t, err) + sig2, err := pk2.Sign(msg) + assert.Nil(t, err) + 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)}) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestCHECKMULTISIGBadKey(t *testing.T) { + prog := makeProgram(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, err := pk1.Sign(msg) + assert.Nil(t, err) + sig2, err := pk2.Sign(msg) + assert.Nil(t, err) + pbytes1 := pk1.PublicKey().Bytes() + pbytes2 := pk2.PublicKey().Bytes()[:4] + vm := load(prog) + vm.SetCheckedHash(hash.Sha256(msg).Bytes()) + vm.estack.PushVal([]StackItem{NewByteArrayItem(sig1), NewByteArrayItem(sig2)}) + vm.estack.PushVal([]StackItem{NewByteArrayItem(pbytes1), NewByteArrayItem(pbytes2)}) + vm.Run() + assert.Equal(t, true, vm.HasFailed()) +} + +func TestCHECKMULTISIGBadSig(t *testing.T) { + prog := makeProgram(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, err := pk1.Sign(msg) + assert.Nil(t, err) + sig2, err := pk2.Sign(msg) + assert.Nil(t, err) + pbytes1 := pk1.PublicKey().Bytes() + pbytes2 := pk2.PublicKey().Bytes() + vm := load(prog) + vm.SetCheckedHash(hash.Sha256(msg).Bytes()) + vm.estack.PushVal([]StackItem{NewByteArrayItem(util.ArrayReverse(sig1)), NewByteArrayItem(sig2)}) + vm.estack.PushVal([]StackItem{NewByteArrayItem(pbytes1), NewByteArrayItem(pbytes2)}) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, false, vm.estack.Pop().Bool()) +} + +func TestCHECKMULTISIGGood(t *testing.T) { + prog := makeProgram(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, err := pk1.Sign(msg) + assert.Nil(t, err) + sig2, err := pk2.Sign(msg) + assert.Nil(t, err) + pbytes1 := pk1.PublicKey().Bytes() + pbytes2 := pk2.PublicKey().Bytes() + vm := load(prog) + vm.SetCheckedHash(hash.Sha256(msg).Bytes()) + vm.estack.PushVal([]StackItem{NewByteArrayItem(sig1), NewByteArrayItem(sig2)}) + vm.estack.PushVal([]StackItem{NewByteArrayItem(pbytes1), NewByteArrayItem(pbytes2)}) + vm.Run() + assert.Equal(t, false, vm.HasFailed()) + assert.Equal(t, 1, vm.estack.Len()) + assert.Equal(t, true, vm.estack.Pop().Bool()) +} + func makeProgram(opcodes ...Instruction) []byte { prog := make([]byte, len(opcodes)+1) // RET for i := 0; i < len(opcodes); i++ {