Merge pull request #783 from nspcc-dev/neo3/movecrypto

Implement Neo.Crypto.* interops
This commit is contained in:
Roman Khimov 2020-04-14 15:54:30 +03:00 committed by GitHub
commit ace5614a3d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 177 additions and 113 deletions

View file

@ -14,7 +14,7 @@ var (
builtinFuncs = []string{
"len", "append", "SHA256",
"SHA1", "Hash256", "Hash160",
"VerifySignature", "AppCall",
"AppCall",
"FromAddress", "Equals",
"panic",
}

View file

@ -1063,12 +1063,6 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
emit.Opcode(c.prog.BinWriter, opcode.SHA256)
case "SHA1":
emit.Opcode(c.prog.BinWriter, opcode.SHA1)
case "Hash256":
emit.Opcode(c.prog.BinWriter, opcode.HASH256)
case "Hash160":
emit.Opcode(c.prog.BinWriter, opcode.HASH160)
case "VerifySignature":
emit.Opcode(c.prog.BinWriter, opcode.VERIFY)
case "AppCall":
numArgs := len(expr.Args) - 1
c.emitReverse(numArgs)

View file

@ -1,6 +1,9 @@
package compiler
var syscalls = map[string]map[string]string{
"crypto": {
"ECDsaVerify": "Neo.Crypto.ECDsaVerify",
},
"storage": {
"GetContext": "Neo.Storage.GetContext",
"Put": "Neo.Storage.Put",

View file

@ -33,33 +33,3 @@ func TestSHA1(t *testing.T) {
`
eval(t, src, []byte{0xfa, 0x13, 0x8a, 0xe3, 0x56, 0xd3, 0x5c, 0x8d, 0x77, 0x8, 0x3c, 0x40, 0x6a, 0x5b, 0xe7, 0x37, 0x45, 0x64, 0x3a, 0xae})
}
func TestHash160(t *testing.T) {
src := `
package foo
import (
"github.com/nspcc-dev/neo-go/pkg/interop/crypto"
)
func Main() []byte {
src := []byte{0x97}
hash := crypto.Hash160(src)
return hash
}
`
eval(t, src, []byte{0x5f, 0xa4, 0x1c, 0x76, 0xf7, 0xe8, 0xca, 0x72, 0xb7, 0x18, 0xff, 0x59, 0x22, 0x91, 0xc2, 0x3a, 0x3a, 0xf5, 0x58, 0x6c})
}
func TestHash256(t *testing.T) {
src := `
package foo
import (
"github.com/nspcc-dev/neo-go/pkg/interop/crypto"
)
func Main() []byte {
src := []byte{0x97}
hash := crypto.Hash256(src)
return hash
}
`
eval(t, src, []byte{0xc0, 0x85, 0x26, 0xad, 0x17, 0x36, 0x53, 0xee, 0xb8, 0xc7, 0xf4, 0xae, 0x82, 0x8b, 0x6e, 0xa1, 0x84, 0xac, 0x5a, 0x3, 0x8a, 0xf6, 0xc3, 0x68, 0x23, 0xfa, 0x5f, 0x5d, 0xd9, 0x1b, 0x91, 0xa2})
}

View file

@ -5,24 +5,28 @@ import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// In this test we only check that needed interop
// is called with the provided arguments in the right order.
func TestVerifyGood(t *testing.T) {
msg := []byte("test message")
pub, sig := signMessage(t, msg)
src := getVerifyProg(pub, sig, msg)
eval(t, src, true)
}
v, p := vmAndCompileInterop(t, src)
p.interops[vm.InteropNameToID([]byte("Neo.Crypto.ECDsaVerify"))] = func(v *vm.VM) error {
assert.Equal(t, msg, v.Estack().Pop().Bytes())
assert.Equal(t, pub, v.Estack().Pop().Bytes())
assert.Equal(t, sig, v.Estack().Pop().Bytes())
v.Estack().PushVal(true)
return nil
}
func TestVerifyBad(t *testing.T) {
msg := []byte("test message")
pub, sig := signMessage(t, msg)
sig[0] = ^sig[0]
src := getVerifyProg(pub, sig, msg)
eval(t, src, false)
require.NoError(t, v.Run())
}
func signMessage(t *testing.T, msg []byte) ([]byte, []byte) {
@ -49,7 +53,7 @@ func getVerifyProg(pub, sig, msg []byte) string {
pub := ` + pubS + `
sig := ` + sigS + `
msg := ` + msgS + `
return crypto.VerifySignature(msg, sig, pub)
return crypto.ECDsaVerify(msg, pub, sig)
}
`
}

View file

@ -0,0 +1,57 @@
package crypto
import (
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"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/vm"
)
// ECDSAVerify checks ECDSA signature.
func ECDSAVerify(ic *interop.Context, v *vm.VM) error {
msg := getMessage(ic, v.Estack().Pop().Item())
hashToCheck := hash.Sha256(msg).BytesBE()
keyb := v.Estack().Pop().Bytes()
signature := v.Estack().Pop().Bytes()
pkey, err := keys.NewPublicKeyFromBytes(keyb)
if err != nil {
return err
}
res := pkey.Verify(signature, hashToCheck)
v.Estack().PushVal(res)
return nil
}
// ECDSACheckMultisig checks multiple ECDSA signatures at once.
func ECDSACheckMultisig(ic *interop.Context, v *vm.VM) error {
msg := getMessage(ic, v.Estack().Pop().Item())
hashToCheck := hash.Sha256(msg).BytesBE()
pkeys, err := v.Estack().PopSigElements()
if err != nil {
return fmt.Errorf("wrong parameters: %s", err.Error())
}
sigs, err := v.Estack().PopSigElements()
if err != nil {
return fmt.Errorf("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) {
return errors.New("more signatures than there are keys")
}
v.SetCheckedHash(hashToCheck)
sigok := vm.CheckMultisigPar(v, pkeys, sigs)
v.Estack().PushVal(sigok)
return nil
}
func getMessage(_ *interop.Context, item vm.StackItem) []byte {
msg, err := item.TryBytes()
if err != nil {
panic(err)
}
return msg
}

View file

@ -628,8 +628,7 @@ func assetCreate(ic *interop.Context, v *vm.VM) error {
if amount != -util.Satoshi() && (int64(amount)%int64(math.Pow10(int(MaxAssetPrecision-precision))) != 0) {
return errors.New("given asset amount has fractional component")
}
owner := &keys.PublicKey{}
err := owner.DecodeBytes(v.Estack().Pop().Bytes())
owner, err := keys.NewPublicKeyFromBytes(v.Estack().Pop().Bytes())
if err != nil {
return gherr.Wrap(err, "failed to get owner key")
}

View file

@ -1,12 +1,14 @@
package core
import (
"fmt"
"math/big"
"testing"
"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"
@ -231,6 +233,67 @@ func TestWitnessGetVerificationScript(t *testing.T) {
require.Equal(t, witness.VerificationScript, value)
}
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()), nil, nil)
runCase := func(t *testing.T, isErr bool, result interface{}, args ...interface{}) {
v := vm.New()
for i := range args {
v.Estack().PushVal(args[i])
}
var err error
func() {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
err = crypto.ECDSAVerify(ic, v)
}()
if isErr {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, 1, v.Estack().Len())
require.Equal(t, result, v.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("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 = pub[10:]
runCase(t, true, false, sign, pub, msg)
})
}
func TestPopInputFromVM(t *testing.T) {
v, tx, _, chain := createVMAndTX(t)
defer chain.Close()

View file

@ -324,8 +324,7 @@ func runtimeCheckWitness(ic *interop.Context, v *vm.VM) error {
hashOrKey := v.Estack().Pop().Bytes()
hash, err := util.Uint160DecodeBytesBE(hashOrKey)
if err != nil {
key := &keys.PublicKey{}
err = key.DecodeBytes(hashOrKey)
key, err := keys.NewPublicKeyFromBytes(hashOrKey)
if err != nil {
return errors.New("parameter given is neither a key nor a hash")
}

View file

@ -11,6 +11,7 @@ import (
"sort"
"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/smartcontract"
@ -137,6 +138,8 @@ var neoInterops = []interop.Function{
{Name: "Neo.Contract.GetStorageContext", Func: contractGetStorageContext, Price: 1},
{Name: "Neo.Contract.IsPayable", Func: contractIsPayable, Price: 1},
{Name: "Neo.Contract.Migrate", Func: contractMigrate, Price: 0},
{Name: "Neo.Crypto.ECDsaVerify", Func: crypto.ECDSAVerify, Price: 1},
{Name: "Neo.Crypto.ECDsaCheckMultiSig", Func: crypto.ECDSACheckMultisig, Price: 1},
{Name: "Neo.Enumerator.Concat", Func: enumerator.Concat, Price: 1},
{Name: "Neo.Enumerator.Create", Func: enumerator.Create, Price: 1},
{Name: "Neo.Enumerator.Next", Func: enumerator.Next, Price: 1},

View file

@ -83,14 +83,15 @@ func NewPublicKeyFromString(s string) (*PublicKey, error) {
if err != nil {
return nil, err
}
return NewPublicKeyFromBytes(b)
}
// NewPublicKeyFromBytes returns public key created from b.
func NewPublicKeyFromBytes(b []byte) (*PublicKey, error) {
pubKey := new(PublicKey)
r := io.NewBinReaderFromBuf(b)
pubKey.DecodeBinary(r)
if r.Err != nil {
return nil, r.Err
if err := pubKey.DecodeBytes(b); err != nil {
return nil, err
}
return pubKey, nil
}

View file

@ -37,6 +37,16 @@ func TestEncodeDecodePublicKey(t *testing.T) {
}
}
func TestNewPublicKeyFromBytes(t *testing.T) {
priv, err := NewPrivateKey()
require.NoError(t, err)
b := priv.PublicKey().Bytes()
pub, err := NewPublicKeyFromBytes(b)
require.NoError(t, err)
require.Equal(t, priv.PublicKey(), pub)
}
func TestDecodeFromString(t *testing.T) {
str := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"
pubKey, err := NewPublicKeyFromString(str)

View file

@ -13,17 +13,7 @@ func SHA256(b []byte) []byte {
return nil
}
// Hash160 computes the sha256 + ripemd160 of b.
func Hash160(b []byte) []byte {
return nil
}
// Hash256 computes the sha256^2 hash of b.
func Hash256(b []byte) []byte {
return nil
}
// VerifySignature checks that sig is msg's signature with pub.
func VerifySignature(msg []byte, sig []byte, pub []byte) bool {
// ECDsaVerify checks that sig is msg's signature with pub.
func ECDsaVerify(msg []byte, pub []byte, sig []byte) bool {
return false
}

View file

@ -424,9 +424,9 @@ func (s *Stack) Roll(n int) error {
return nil
}
// popSigElements pops keys or signatures from the stack as needed for
// PopSigElements pops keys or signatures from the stack as needed for
// CHECKMULTISIG.
func (s *Stack) popSigElements() ([][]byte, error) {
func (s *Stack) PopSigElements() ([][]byte, error) {
var num int
var elems [][]byte
item := s.Pop()

View file

@ -317,37 +317,37 @@ func TestRoll(t *testing.T) {
func TestPopSigElements(t *testing.T) {
s := NewStack("test")
_, err := s.popSigElements()
_, err := s.PopSigElements()
assert.NotNil(t, err)
s.PushVal([]StackItem{})
_, err = s.popSigElements()
_, err = s.PopSigElements()
assert.NotNil(t, err)
s.PushVal([]StackItem{NewBoolItem(false)})
_, err = s.popSigElements()
_, err = s.PopSigElements()
assert.NotNil(t, err)
b1 := []byte("smth")
b2 := []byte("strange")
s.PushVal([]StackItem{NewByteArrayItem(b1), NewByteArrayItem(b2)})
z, err := s.popSigElements()
z, err := s.PopSigElements()
assert.Nil(t, err)
assert.Equal(t, z, [][]byte{b1, b2})
s.PushVal(2)
_, err = s.popSigElements()
_, err = s.PopSigElements()
assert.NotNil(t, err)
s.PushVal(b1)
s.PushVal(2)
_, err = s.popSigElements()
_, err = s.PopSigElements()
assert.NotNil(t, err)
s.PushVal(b2)
s.PushVal(b1)
s.PushVal(2)
z, err = s.popSigElements()
z, err = s.PopSigElements()
assert.Nil(t, err)
assert.Equal(t, z, [][]byte{b1, b2})
}

View file

@ -1235,11 +1235,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
v.estack.PushVal(res)
case opcode.CHECKMULTISIG:
pkeys, err := v.estack.popSigElements()
pkeys, err := v.estack.PopSigElements()
if err != nil {
panic(fmt.Sprintf("wrong parameters: %s", err.Error()))
}
sigs, err := v.estack.popSigElements()
sigs, err := v.estack.PopSigElements()
if err != nil {
panic(fmt.Sprintf("wrong parameters: %s", err.Error()))
}
@ -1252,7 +1252,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
panic("VM is not set up properly for signature checks")
}
sigok := checkMultisigPar(v, pkeys, sigs)
sigok := CheckMultisigPar(v, pkeys, sigs)
v.estack.PushVal(sigok)
case opcode.NEWMAP:
@ -1332,14 +1332,6 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
b := v.estack.Pop().Bytes()
v.estack.PushVal(hash.Sha256(b).BytesBE())
case opcode.HASH160:
b := v.estack.Pop().Bytes()
v.estack.PushVal(hash.Hash160(b).BytesBE())
case opcode.HASH256:
b := v.estack.Pop().Bytes()
v.estack.PushVal(hash.DoubleSha256(b).BytesBE())
case opcode.NOP:
// unlucky ^^
@ -1446,7 +1438,8 @@ func (v *VM) getJumpOffset(ctx *Context, parameter []byte, mod int) int {
return offset
}
func checkMultisigPar(v *VM, pkeys [][]byte, sigs [][]byte) bool {
// CheckMultisigPar checks if sigs contains sufficient valid signatures.
func CheckMultisigPar(v *VM, pkeys [][]byte, sigs [][]byte) bool {
if len(sigs) == 1 {
return checkMultisig1(v, pkeys, sigs[0])
}
@ -1595,8 +1588,8 @@ func (v *VM) bytesToPublicKey(b []byte) *keys.PublicKey {
if v.keys[s] != nil {
pkey = v.keys[s]
} else {
pkey = &keys.PublicKey{}
err := pkey.DecodeBytes(b)
var err error
pkey, err = keys.NewPublicKeyFromBytes(b)
if err != nil {
panic(err.Error())
}

View file

@ -3035,28 +3035,6 @@ func TestSHA256(t *testing.T) {
assert.Equal(t, res, hex.EncodeToString(vm.estack.Pop().Bytes()))
}
func TestHASH160(t *testing.T) {
// 0x0100 hashes to fbc22d517f38e7612798ece8e5957cf6c41d8caf
res := "fbc22d517f38e7612798ece8e5957cf6c41d8caf"
prog := makeProgram(opcode.PUSHBYTES2, 1, 0,
opcode.HASH160)
vm := load(prog)
runVM(t, vm)
assert.Equal(t, 1, vm.estack.Len())
assert.Equal(t, res, hex.EncodeToString(vm.estack.Pop().Bytes()))
}
func TestHASH256(t *testing.T) {
// 0x0100 hashes to 677b2d718464ee0121475600b929c0b4155667486577d1320b18c2dc7d4b4f99
res := "677b2d718464ee0121475600b929c0b4155667486577d1320b18c2dc7d4b4f99"
prog := makeProgram(opcode.PUSHBYTES2, 1, 0,
opcode.HASH256)
vm := load(prog)
runVM(t, vm)
assert.Equal(t, 1, vm.estack.Len())
assert.Equal(t, res, hex.EncodeToString(vm.estack.Pop().Bytes()))
}
var opcodesTestCases = map[opcode.Opcode][]struct {
name string
args []interface{}