neo-go/pkg/core/interop/crypto/ecdsa_test.go

281 lines
7.9 KiB
Go
Raw Normal View History

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"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
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 {
if isR1 {
pkeys[i], err = keys.NewPrivateKey()
} else {
pkeys[i], err = keys.NewSecp256k1PrivateKey()
}
if err != nil {
return nil, nil, nil, err
}
pk := pkeys[i].PublicKey()
data := pk.Bytes()
pubs[i] = stackitem.NewByteArray(data)
keyMap[string(data)] = pk
}
sigs := make([]stackitem.Item, n)
for i := range sigs {
sig := pkeys[i].Sign(msg)
sigs[i] = stackitem.NewByteArray(sig)
}
return pubs, sigs, keyMap, nil
}
func subSlice(arr []stackitem.Item, indices []int) []stackitem.Item {
if indices == nil {
return arr
}
result := make([]stackitem.Item, len(indices))
for i, j := range indices {
result[i] = arr[j]
}
return result
}
func initCheckMultisigVMNoArgs(isR1 bool) *vm.VM {
buf := make([]byte, 5)
buf[0] = byte(opcode.SYSCALL)
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(isR1, 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, 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 TestECDSASecp256r1CheckMultisigGood(t *testing.T) {
testCurveCHECKMULTISIGGood(t, true)
}
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())
return
}
require.NoError(t, v.Run())
assert.Equal(t, 1, v.Estack().Len())
assert.False(t, v.Estack().Pop().Bool())
}
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, 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())
})
}