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{ builtinFuncs = []string{
"len", "append", "SHA256", "len", "append", "SHA256",
"SHA1", "Hash256", "Hash160", "SHA1", "Hash256", "Hash160",
"VerifySignature", "AppCall", "AppCall",
"FromAddress", "Equals", "FromAddress", "Equals",
"panic", "panic",
} }

View file

@ -1063,12 +1063,6 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
emit.Opcode(c.prog.BinWriter, opcode.SHA256) emit.Opcode(c.prog.BinWriter, opcode.SHA256)
case "SHA1": case "SHA1":
emit.Opcode(c.prog.BinWriter, opcode.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": case "AppCall":
numArgs := len(expr.Args) - 1 numArgs := len(expr.Args) - 1
c.emitReverse(numArgs) c.emitReverse(numArgs)

View file

@ -1,6 +1,9 @@
package compiler package compiler
var syscalls = map[string]map[string]string{ var syscalls = map[string]map[string]string{
"crypto": {
"ECDsaVerify": "Neo.Crypto.ECDsaVerify",
},
"storage": { "storage": {
"GetContext": "Neo.Storage.GetContext", "GetContext": "Neo.Storage.GetContext",
"Put": "Neo.Storage.Put", "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}) 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" "testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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" "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) { func TestVerifyGood(t *testing.T) {
msg := []byte("test message") msg := []byte("test message")
pub, sig := signMessage(t, msg) pub, sig := signMessage(t, msg)
src := getVerifyProg(pub, sig, 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) { require.NoError(t, v.Run())
msg := []byte("test message")
pub, sig := signMessage(t, msg)
sig[0] = ^sig[0]
src := getVerifyProg(pub, sig, msg)
eval(t, src, false)
} }
func signMessage(t *testing.T, msg []byte) ([]byte, []byte) { func signMessage(t *testing.T, msg []byte) ([]byte, []byte) {
@ -49,7 +53,7 @@ func getVerifyProg(pub, sig, msg []byte) string {
pub := ` + pubS + ` pub := ` + pubS + `
sig := ` + sigS + ` sig := ` + sigS + `
msg := ` + msgS + ` 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) { if amount != -util.Satoshi() && (int64(amount)%int64(math.Pow10(int(MaxAssetPrecision-precision))) != 0) {
return errors.New("given asset amount has fractional component") return errors.New("given asset amount has fractional component")
} }
owner := &keys.PublicKey{} owner, err := keys.NewPublicKeyFromBytes(v.Estack().Pop().Bytes())
err := owner.DecodeBytes(v.Estack().Pop().Bytes())
if err != nil { if err != nil {
return gherr.Wrap(err, "failed to get owner key") return gherr.Wrap(err, "failed to get owner key")
} }

View file

@ -1,12 +1,14 @@
package core package core
import ( import (
"fmt"
"math/big" "math/big"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/core/block" "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/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "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/enumerator"
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator" "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/state"
@ -231,6 +233,67 @@ func TestWitnessGetVerificationScript(t *testing.T) {
require.Equal(t, witness.VerificationScript, value) 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) { func TestPopInputFromVM(t *testing.T) {
v, tx, _, chain := createVMAndTX(t) v, tx, _, chain := createVMAndTX(t)
defer chain.Close() defer chain.Close()

View file

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

View file

@ -11,6 +11,7 @@ import (
"sort" "sort"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "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/enumerator"
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "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.GetStorageContext", Func: contractGetStorageContext, Price: 1},
{Name: "Neo.Contract.IsPayable", Func: contractIsPayable, Price: 1}, {Name: "Neo.Contract.IsPayable", Func: contractIsPayable, Price: 1},
{Name: "Neo.Contract.Migrate", Func: contractMigrate, Price: 0}, {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.Concat", Func: enumerator.Concat, Price: 1},
{Name: "Neo.Enumerator.Create", Func: enumerator.Create, Price: 1}, {Name: "Neo.Enumerator.Create", Func: enumerator.Create, Price: 1},
{Name: "Neo.Enumerator.Next", Func: enumerator.Next, 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 { if err != nil {
return nil, err return nil, err
} }
return NewPublicKeyFromBytes(b)
}
// NewPublicKeyFromBytes returns public key created from b.
func NewPublicKeyFromBytes(b []byte) (*PublicKey, error) {
pubKey := new(PublicKey) pubKey := new(PublicKey)
r := io.NewBinReaderFromBuf(b) if err := pubKey.DecodeBytes(b); err != nil {
pubKey.DecodeBinary(r) return nil, err
if r.Err != nil {
return nil, r.Err
} }
return pubKey, nil 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) { func TestDecodeFromString(t *testing.T) {
str := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c" str := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"
pubKey, err := NewPublicKeyFromString(str) pubKey, err := NewPublicKeyFromString(str)

View file

@ -13,17 +13,7 @@ func SHA256(b []byte) []byte {
return nil return nil
} }
// Hash160 computes the sha256 + ripemd160 of b. // ECDsaVerify checks that sig is msg's signature with pub.
func Hash160(b []byte) []byte { func ECDsaVerify(msg []byte, pub []byte, sig []byte) bool {
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 {
return false return false
} }

View file

@ -424,9 +424,9 @@ func (s *Stack) Roll(n int) error {
return nil 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. // CHECKMULTISIG.
func (s *Stack) popSigElements() ([][]byte, error) { func (s *Stack) PopSigElements() ([][]byte, error) {
var num int var num int
var elems [][]byte var elems [][]byte
item := s.Pop() item := s.Pop()

View file

@ -317,37 +317,37 @@ func TestRoll(t *testing.T) {
func TestPopSigElements(t *testing.T) { func TestPopSigElements(t *testing.T) {
s := NewStack("test") s := NewStack("test")
_, err := s.popSigElements() _, err := s.PopSigElements()
assert.NotNil(t, err) assert.NotNil(t, err)
s.PushVal([]StackItem{}) s.PushVal([]StackItem{})
_, err = s.popSigElements() _, err = s.PopSigElements()
assert.NotNil(t, err) assert.NotNil(t, err)
s.PushVal([]StackItem{NewBoolItem(false)}) s.PushVal([]StackItem{NewBoolItem(false)})
_, err = s.popSigElements() _, err = s.PopSigElements()
assert.NotNil(t, err) assert.NotNil(t, err)
b1 := []byte("smth") b1 := []byte("smth")
b2 := []byte("strange") b2 := []byte("strange")
s.PushVal([]StackItem{NewByteArrayItem(b1), NewByteArrayItem(b2)}) s.PushVal([]StackItem{NewByteArrayItem(b1), NewByteArrayItem(b2)})
z, err := s.popSigElements() z, err := s.PopSigElements()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, z, [][]byte{b1, b2}) assert.Equal(t, z, [][]byte{b1, b2})
s.PushVal(2) s.PushVal(2)
_, err = s.popSigElements() _, err = s.PopSigElements()
assert.NotNil(t, err) assert.NotNil(t, err)
s.PushVal(b1) s.PushVal(b1)
s.PushVal(2) s.PushVal(2)
_, err = s.popSigElements() _, err = s.PopSigElements()
assert.NotNil(t, err) assert.NotNil(t, err)
s.PushVal(b2) s.PushVal(b2)
s.PushVal(b1) s.PushVal(b1)
s.PushVal(2) s.PushVal(2)
z, err = s.popSigElements() z, err = s.PopSigElements()
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, z, [][]byte{b1, b2}) 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) v.estack.PushVal(res)
case opcode.CHECKMULTISIG: case opcode.CHECKMULTISIG:
pkeys, err := v.estack.popSigElements() pkeys, err := v.estack.PopSigElements()
if err != nil { if err != nil {
panic(fmt.Sprintf("wrong parameters: %s", err.Error())) panic(fmt.Sprintf("wrong parameters: %s", err.Error()))
} }
sigs, err := v.estack.popSigElements() sigs, err := v.estack.PopSigElements()
if err != nil { if err != nil {
panic(fmt.Sprintf("wrong parameters: %s", err.Error())) 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") panic("VM is not set up properly for signature checks")
} }
sigok := checkMultisigPar(v, pkeys, sigs) sigok := CheckMultisigPar(v, pkeys, sigs)
v.estack.PushVal(sigok) v.estack.PushVal(sigok)
case opcode.NEWMAP: 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() b := v.estack.Pop().Bytes()
v.estack.PushVal(hash.Sha256(b).BytesBE()) 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: case opcode.NOP:
// unlucky ^^ // unlucky ^^
@ -1446,7 +1438,8 @@ func (v *VM) getJumpOffset(ctx *Context, parameter []byte, mod int) int {
return offset 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 { if len(sigs) == 1 {
return checkMultisig1(v, pkeys, sigs[0]) return checkMultisig1(v, pkeys, sigs[0])
} }
@ -1595,8 +1588,8 @@ func (v *VM) bytesToPublicKey(b []byte) *keys.PublicKey {
if v.keys[s] != nil { if v.keys[s] != nil {
pkey = v.keys[s] pkey = v.keys[s]
} else { } else {
pkey = &keys.PublicKey{} var err error
err := pkey.DecodeBytes(b) pkey, err = keys.NewPublicKeyFromBytes(b)
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
} }

View file

@ -3035,28 +3035,6 @@ func TestSHA256(t *testing.T) {
assert.Equal(t, res, hex.EncodeToString(vm.estack.Pop().Bytes())) 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 { var opcodesTestCases = map[opcode.Opcode][]struct {
name string name string
args []interface{} args []interface{}