From 100f2db3fb6df23f23f9f3659cb8e74aef1acd6c Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Mon, 15 Feb 2021 18:43:10 +0300 Subject: [PATCH 01/11] native: implement CryptoLib contract --- pkg/compiler/native_test.go | 13 +++ pkg/compiler/syscall_test.go | 2 - pkg/compiler/util_test.go | 34 ------ pkg/core/interop/crypto/hash.go | 39 ------- pkg/core/interop/crypto/hash_test.go | 69 ------------- pkg/core/interop/crypto/interop.go | 4 - pkg/core/interop/interopnames/names.go | 4 - pkg/core/interops.go | 2 - pkg/core/native/contract.go | 5 + pkg/core/native/crypto.go | 138 +++++++++++++++++++++++++ pkg/core/native/crypto_test.go | 41 ++++++++ pkg/core/native/designate.go | 2 +- pkg/core/native/ledger.go | 2 +- pkg/core/native/name_service.go | 2 +- pkg/core/native/native_gas.go | 2 +- pkg/core/native/native_neo.go | 2 +- pkg/core/native/nativenames/names.go | 1 + pkg/core/native/oracle.go | 2 +- pkg/core/native/policy.go | 2 +- pkg/interop/crypto/crypto.go | 10 -- pkg/interop/native/crypto/crypto.go | 34 ++++++ pkg/rpc/server/server_test.go | 2 +- 22 files changed, 240 insertions(+), 172 deletions(-) delete mode 100644 pkg/compiler/util_test.go delete mode 100644 pkg/core/interop/crypto/hash.go delete mode 100644 pkg/core/interop/crypto/hash_test.go create mode 100644 pkg/core/native/crypto.go create mode 100644 pkg/core/native/crypto_test.go create mode 100644 pkg/interop/native/crypto/crypto.go diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go index 7c194f9b5..9a52c5a97 100644 --- a/pkg/compiler/native_test.go +++ b/pkg/compiler/native_test.go @@ -9,6 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/native" + "github.com/nspcc-dev/neo-go/pkg/interop/native/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/native/gas" "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger" "github.com/nspcc-dev/neo-go/pkg/interop/native/management" @@ -37,6 +38,7 @@ func TestContractHashes(t *testing.T) { require.Equal(t, []byte(ledger.Hash), cs.Ledger.Hash.BytesBE()) require.Equal(t, []byte(management.Hash), cs.Management.Hash.BytesBE()) require.Equal(t, []byte(notary.Hash), cs.Notary.Hash.BytesBE()) + require.Equal(t, []byte(crypto.Hash), cs.Crypto.Hash.BytesBE()) } // testPrintHash is a helper for updating contract hashes. @@ -77,6 +79,11 @@ func TestNameServiceRecordType(t *testing.T) { require.EqualValues(t, native.RecordTypeAAAA, nameservice.TypeAAAA) } +func TestCryptoLibNamedCurve(t *testing.T) { + require.EqualValues(t, native.Secp256k1, crypto.Secp256k1) + require.EqualValues(t, native.Secp256r1, crypto.Secp256r1) +} + type nativeTestCase struct { method string params []string @@ -88,6 +95,7 @@ func TestNativeHelpersCompile(t *testing.T) { u160 := `interop.Hash160("aaaaaaaaaaaaaaaaaaaa")` u256 := `interop.Hash256("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")` pub := `interop.PublicKey("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")` + sig := `interop.Signature("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")` nep17TestCases := []nativeTestCase{ {"balanceOf", []string{u160}}, {"decimals", nil}, @@ -176,6 +184,11 @@ func TestNativeHelpersCompile(t *testing.T) { {"update", []string{"nil", "nil"}}, {"updateWithData", []string{"nil", "nil", "123"}}, }) + runNativeTestCases(t, cs.Crypto.ContractMD, "crypto", []nativeTestCase{ + {"sha256", []string{"[]byte{1, 2, 3}"}}, + {"ripemd160", []string{"[]byte{1, 2, 3}"}}, + {"verifyWithECDsa", []string{"[]byte{1, 2, 3}", pub, sig, "crypto.Secp256k1"}}, + }) } func runNativeTestCases(t *testing.T, ctr interop.ContractMD, name string, testCases []nativeTestCase) { diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index ace3adb1b..325b73abd 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -103,8 +103,6 @@ func TestSyscallExecution(t *testing.T) { "crypto.ECDsaSecp256k1Verify": {interopnames.NeoCryptoVerifyWithECDsaSecp256k1, []string{b, pub, sig}, false}, "crypto.ECDSASecp256r1CheckMultisig": {interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1, []string{b, pubs, sigs}, false}, "crypto.ECDSASecp256k1CheckMultisig": {interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1, []string{b, pubs, sigs}, false}, - "crypto.SHA256": {interopnames.NeoCryptoSHA256, []string{b}, false}, - "crypto.RIPEMD160": {interopnames.NeoCryptoRIPEMD160, []string{b}, false}, } ic := &interop.Context{} core.SpawnVM(ic) // set Functions field diff --git a/pkg/compiler/util_test.go b/pkg/compiler/util_test.go deleted file mode 100644 index 67eeb8118..000000000 --- a/pkg/compiler/util_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package compiler_test - -import ( - "testing" - - "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/smartcontract/trigger" - "github.com/stretchr/testify/require" -) - -func TestSHA256(t *testing.T) { - src := ` - package foo - import ( - "github.com/nspcc-dev/neo-go/pkg/interop/crypto" - ) - func Main() []byte { - src := []byte{0x97} - hash := crypto.SHA256(src) - return hash - } - ` - v := vmAndCompile(t, src) - ic := &interop.Context{Trigger: trigger.Verification} - ic.VM = v - crypto.Register(ic) - v.SyscallHandler = ic.SyscallHandler - require.NoError(t, v.Run()) - require.True(t, v.Estack().Len() >= 1) - - h := []byte{0x2a, 0xa, 0xb7, 0x32, 0xb4, 0xe9, 0xd8, 0x5e, 0xf7, 0xdc, 0x25, 0x30, 0x3b, 0x64, 0xab, 0x52, 0x7c, 0x25, 0xa4, 0xd7, 0x78, 0x15, 0xeb, 0xb5, 0x79, 0xf3, 0x96, 0xec, 0x6c, 0xac, 0xca, 0xd3} - require.Equal(t, h, v.PopResult()) -} diff --git a/pkg/core/interop/crypto/hash.go b/pkg/core/interop/crypto/hash.go deleted file mode 100644 index f00efb3a3..000000000 --- a/pkg/core/interop/crypto/hash.go +++ /dev/null @@ -1,39 +0,0 @@ -package crypto - -import ( - "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/crypto" - "github.com/nspcc-dev/neo-go/pkg/crypto/hash" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" -) - -// Sha256 returns sha256 hash of the data. -func Sha256(ic *interop.Context) error { - h, err := getMessageHash(ic, ic.VM.Estack().Pop().Item()) - if err != nil { - return err - } - ic.VM.Estack().PushVal(h.BytesBE()) - return nil -} - -// RipeMD160 returns RipeMD160 hash of the data. -func RipeMD160(ic *interop.Context) error { - var msg []byte - - item := ic.VM.Estack().Pop().Item() - switch val := item.(type) { - case *stackitem.Interop: - msg = val.Value().(crypto.Verifiable).GetSignedPart() - case stackitem.Null: - msg = ic.Container.GetSignedPart() - default: - var err error - if msg, err = val.TryBytes(); err != nil { - return err - } - } - h := hash.RipeMD160(msg).BytesBE() - ic.VM.Estack().PushVal(h) - return nil -} diff --git a/pkg/core/interop/crypto/hash_test.go b/pkg/core/interop/crypto/hash_test.go deleted file mode 100644 index 313074d4a..000000000 --- a/pkg/core/interop/crypto/hash_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package crypto - -import ( - "encoding/hex" - "testing" - - "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/crypto" - "github.com/nspcc-dev/neo-go/pkg/crypto/hash" - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" - "github.com/stretchr/testify/require" -) - -type testVerifiable []byte - -var _ crypto.Verifiable = testVerifiable{} - -func (v testVerifiable) GetSignedPart() []byte { - return v -} -func (v testVerifiable) GetSignedHash() util.Uint256 { - return hash.Sha256(v) -} - -func testHash0100(t *testing.T, result string, interopFunc func(*interop.Context) error) { - t.Run("good", func(t *testing.T) { - bs := []byte{1, 0} - - checkGood := func(t *testing.T, ic *interop.Context) { - require.NoError(t, interopFunc(ic)) - require.Equal(t, 1, ic.VM.Estack().Len()) - require.Equal(t, result, hex.EncodeToString(ic.VM.Estack().Pop().Bytes())) - } - t.Run("raw bytes", func(t *testing.T) { - ic := &interop.Context{VM: vm.New()} - ic.VM.Estack().PushVal(bs) - checkGood(t, ic) - }) - t.Run("interop", func(t *testing.T) { - ic := &interop.Context{VM: vm.New()} - ic.VM.Estack().PushVal(stackitem.NewInterop(testVerifiable(bs))) - checkGood(t, ic) - }) - t.Run("container", func(t *testing.T) { - ic := &interop.Context{VM: vm.New(), Container: testVerifiable(bs)} - ic.VM.Estack().PushVal(stackitem.Null{}) - checkGood(t, ic) - }) - }) - t.Run("bad message", func(t *testing.T) { - ic := &interop.Context{VM: vm.New()} - ic.VM.Estack().PushVal(stackitem.NewArray(nil)) - require.Error(t, interopFunc(ic)) - }) -} - -func TestSHA256(t *testing.T) { - // 0x0100 hashes to 47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254 - res := "47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254" - testHash0100(t, res, Sha256) -} - -func TestRIPEMD160(t *testing.T) { - // 0x0100 hashes to 213492c0c6fc5d61497cf17249dd31cd9964b8a3 - res := "213492c0c6fc5d61497cf17249dd31cd9964b8a3" - testHash0100(t, res, RipeMD160) -} diff --git a/pkg/core/interop/crypto/interop.go b/pkg/core/interop/crypto/interop.go index 74e7815df..6d8172f99 100644 --- a/pkg/core/interop/crypto/interop.go +++ b/pkg/core/interop/crypto/interop.go @@ -10,8 +10,6 @@ var ( ecdsaSecp256k1VerifyID = interopnames.ToID([]byte(interopnames.NeoCryptoVerifyWithECDsaSecp256k1)) ecdsaSecp256r1CheckMultisigID = interopnames.ToID([]byte(interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1)) ecdsaSecp256k1CheckMultisigID = interopnames.ToID([]byte(interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1)) - sha256ID = interopnames.ToID([]byte(interopnames.NeoCryptoSHA256)) - ripemd160ID = interopnames.ToID([]byte(interopnames.NeoCryptoRIPEMD160)) ) var cryptoInterops = []interop.Function{ @@ -19,8 +17,6 @@ var cryptoInterops = []interop.Function{ {ID: ecdsaSecp256k1VerifyID, Func: ECDSASecp256k1Verify}, {ID: ecdsaSecp256r1CheckMultisigID, Func: ECDSASecp256r1CheckMultisig}, {ID: ecdsaSecp256k1CheckMultisigID, Func: ECDSASecp256k1CheckMultisig}, - {ID: sha256ID, Func: Sha256}, - {ID: ripemd160ID, Func: RipeMD160}, } func init() { diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index d342284f1..9b58c2796 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -51,8 +51,6 @@ const ( NeoCryptoVerifyWithECDsaSecp256k1 = "Neo.Crypto.VerifyWithECDsaSecp256k1" NeoCryptoCheckMultisigWithECDsaSecp256r1 = "Neo.Crypto.CheckMultisigWithECDsaSecp256r1" NeoCryptoCheckMultisigWithECDsaSecp256k1 = "Neo.Crypto.CheckMultisigWithECDsaSecp256k1" - NeoCryptoSHA256 = "Neo.Crypto.SHA256" - NeoCryptoRIPEMD160 = "Neo.Crypto.RIPEMD160" ) var names = []string{ @@ -105,6 +103,4 @@ var names = []string{ NeoCryptoVerifyWithECDsaSecp256k1, NeoCryptoCheckMultisigWithECDsaSecp256r1, NeoCryptoCheckMultisigWithECDsaSecp256k1, - NeoCryptoSHA256, - NeoCryptoRIPEMD160, } diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 9b961ccff..db4844216 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -93,8 +93,6 @@ var neoInterops = []interop.Function{ Price: fee.ECDSAVerifyPrice, ParamCount: 3}, {Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1, Func: crypto.ECDSASecp256r1CheckMultisig, Price: 0, ParamCount: 3}, {Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1, Func: crypto.ECDSASecp256k1CheckMultisig, Price: 0, ParamCount: 3}, - {Name: interopnames.NeoCryptoSHA256, Func: crypto.Sha256, Price: 1 << 15, ParamCount: 1}, - {Name: interopnames.NeoCryptoRIPEMD160, Func: crypto.RipeMD160, Price: 1 << 15, ParamCount: 1}, } // initIDinInteropsSlice initializes IDs from names in one given diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 7a0479144..64b8c73a9 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -24,6 +24,7 @@ type Contracts struct { Designate *Designate NameService *NameService Notary *Notary + Crypto *Crypto Contracts []interop.Contract // persistScript is vm script which executes "onPersist" method of every native contract. persistScript []byte @@ -61,6 +62,10 @@ func NewContracts(p2pSigExtensionsEnabled bool) *Contracts { cs.Management = mgmt cs.Contracts = append(cs.Contracts, mgmt) + c := newCrypto() + cs.Crypto = c + cs.Contracts = append(cs.Contracts, c) + ledger := newLedger() cs.Ledger = ledger cs.Contracts = append(cs.Contracts, ledger) diff --git a/pkg/core/native/crypto.go b/pkg/core/native/crypto.go new file mode 100644 index 000000000..b377dd82e --- /dev/null +++ b/pkg/core/native/crypto.go @@ -0,0 +1,138 @@ +package native + +import ( + "crypto/elliptic" + "errors" + "fmt" + + "github.com/btcsuite/btcd/btcec" + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "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/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +// Crypto represents CryptoLib contract. +type Crypto struct { + interop.ContractMD +} + +// NamedCurve identifies named elliptic curves. +type NamedCurve byte + +// Various named elliptic curves. +const ( + Secp256k1 NamedCurve = 22 + Secp256r1 NamedCurve = 23 +) + +const cryptoContractID = -3 + +func newCrypto() *Crypto { + c := &Crypto{ContractMD: *interop.NewContractMD(nativenames.CryptoLib, cryptoContractID)} + defer c.UpdateHash() + + desc := newDescriptor("sha256", smartcontract.ByteArrayType, + manifest.NewParameter("data", smartcontract.ByteArrayType)) + md := newMethodAndPrice(c.sha256, 1<<15, callflag.NoneFlag) + c.AddMethod(md, desc) + + desc = newDescriptor("ripemd160", smartcontract.ByteArrayType, + manifest.NewParameter("data", smartcontract.ByteArrayType)) + md = newMethodAndPrice(c.ripemd160, 1<<15, callflag.NoneFlag) + c.AddMethod(md, desc) + + desc = newDescriptor("verifyWithECDsa", smartcontract.BoolType, + manifest.NewParameter("message", smartcontract.ByteArrayType), + manifest.NewParameter("pubkey", smartcontract.ByteArrayType), + manifest.NewParameter("signature", smartcontract.ByteArrayType), + manifest.NewParameter("curve", smartcontract.IntegerType)) + md = newMethodAndPrice(c.verifyWithECDsa, 1<<15, callflag.NoneFlag) + c.AddMethod(md, desc) + return c +} + +func (c *Crypto) sha256(_ *interop.Context, args []stackitem.Item) stackitem.Item { + bs, err := args[0].TryBytes() + if err != nil { + panic(err) + } + return stackitem.NewByteArray(hash.Sha256(bs).BytesBE()) +} + +func (c *Crypto) ripemd160(_ *interop.Context, args []stackitem.Item) stackitem.Item { + bs, err := args[0].TryBytes() + if err != nil { + panic(err) + } + return stackitem.NewByteArray(hash.RipeMD160(bs).BytesBE()) +} + +func (c *Crypto) verifyWithECDsa(_ *interop.Context, args []stackitem.Item) stackitem.Item { + msg, err := args[0].TryBytes() + if err != nil { + panic(fmt.Errorf("invalid message stackitem: %w", err)) + } + hashToCheck := hash.Sha256(msg) + pubkey, err := args[1].TryBytes() + if err != nil { + panic(fmt.Errorf("invalid pubkey stackitem: %w", err)) + } + signature, err := args[2].TryBytes() + if err != nil { + panic(fmt.Errorf("invalid signature stackitem: %w", err)) + } + curve, err := curveFromStackitem(args[3]) + if err != nil { + panic(fmt.Errorf("invalid curve stackitem: %w", err)) + } + pkey, err := keys.NewPublicKeyFromBytes(pubkey, curve) + if err != nil { + panic(fmt.Errorf("failed to decode pubkey: %w", err)) + } + res := pkey.Verify(signature, hashToCheck.BytesBE()) + return stackitem.NewBool(res) +} + +func curveFromStackitem(si stackitem.Item) (elliptic.Curve, error) { + curve, err := si.TryInteger() + if err != nil { + return nil, err + } + if !curve.IsInt64() { + return nil, errors.New("not an int64") + } + c := curve.Int64() + switch c { + case int64(Secp256k1): + return btcec.S256(), nil + case int64(Secp256r1): + return elliptic.P256(), nil + default: + return nil, errors.New("unsupported curve type") + } +} + +// Metadata implements Contract interface. +func (c *Crypto) Metadata() *interop.ContractMD { + return &c.ContractMD +} + +// Initialize implements Contract interface. +func (c *Crypto) Initialize(ic *interop.Context) error { + return nil +} + +// OnPersist implements Contract interface. +func (c *Crypto) OnPersist(ic *interop.Context) error { + return nil +} + +// PostPersist implements Contract interface. +func (c *Crypto) PostPersist(ic *interop.Context) error { + return nil +} diff --git a/pkg/core/native/crypto_test.go b/pkg/core/native/crypto_test.go new file mode 100644 index 000000000..37f61ef60 --- /dev/null +++ b/pkg/core/native/crypto_test.go @@ -0,0 +1,41 @@ +package native + +import ( + "encoding/hex" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +func TestSha256(t *testing.T) { + c := newCrypto() + ic := &interop.Context{VM: vm.New()} + + t.Run("bad arg type", func(t *testing.T) { + require.Panics(t, func() { + c.sha256(ic, []stackitem.Item{stackitem.NewInterop(nil)}) + }) + }) + t.Run("good", func(t *testing.T) { + // 0x0100 hashes to 47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254 + require.Equal(t, "47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254", hex.EncodeToString(c.sha256(ic, []stackitem.Item{stackitem.NewByteArray([]byte{1, 0})}).Value().([]byte))) + }) +} + +func TestRIPEMD160(t *testing.T) { + c := newCrypto() + ic := &interop.Context{VM: vm.New()} + + t.Run("bad arg type", func(t *testing.T) { + require.Panics(t, func() { + c.ripemd160(ic, []stackitem.Item{stackitem.NewInterop(nil)}) + }) + }) + t.Run("good", func(t *testing.T) { + // 0x0100 hashes to 213492c0c6fc5d61497cf17249dd31cd9964b8a3 + require.Equal(t, "213492c0c6fc5d61497cf17249dd31cd9964b8a3", hex.EncodeToString(c.ripemd160(ic, []stackitem.Item{stackitem.NewByteArray([]byte{1, 0})}).Value().([]byte))) + }) +} diff --git a/pkg/core/native/designate.go b/pkg/core/native/designate.go index 44e40978d..1186d6d7a 100644 --- a/pkg/core/native/designate.go +++ b/pkg/core/native/designate.go @@ -52,7 +52,7 @@ type roleData struct { } const ( - designateContractID = -6 + designateContractID = -8 // maxNodeCount is the maximum number of nodes to set the role for. maxNodeCount = 32 diff --git a/pkg/core/native/ledger.go b/pkg/core/native/ledger.go index 60ea77594..4c76da1d5 100644 --- a/pkg/core/native/ledger.go +++ b/pkg/core/native/ledger.go @@ -26,7 +26,7 @@ type Ledger struct { } const ( - ledgerContractID = -2 + ledgerContractID = -4 prefixBlockHash = 9 prefixCurrentBlock = 12 diff --git a/pkg/core/native/name_service.go b/pkg/core/native/name_service.go index 23d263156..dbaefde72 100644 --- a/pkg/core/native/name_service.go +++ b/pkg/core/native/name_service.go @@ -54,7 +54,7 @@ const ( ) const ( - nameServiceID = -8 + nameServiceID = -10 prefixRoots = 10 prefixDomainPrice = 22 diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 8f18c9789..61e3b79bb 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -18,7 +18,7 @@ type GAS struct { NEO *NEO } -const gasContractID = -4 +const gasContractID = -6 // GASFactor is a divisor for finding GAS integral value. const GASFactor = NEOTotalSupply diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 17b35acdc..543b09842 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -50,7 +50,7 @@ type NEO struct { } const ( - neoContractID = -3 + neoContractID = -5 // NEOTotalSupply is the total amount of NEO in the system. NEOTotalSupply = 100000000 // prefixCandidate is a prefix used to store validator's data. diff --git a/pkg/core/native/nativenames/names.go b/pkg/core/native/nativenames/names.go index 0f37e5ba8..440057744 100644 --- a/pkg/core/native/nativenames/names.go +++ b/pkg/core/native/nativenames/names.go @@ -11,4 +11,5 @@ const ( Designation = "RoleManagement" Notary = "Notary" NameService = "NameService" + CryptoLib = "CryptoLib" ) diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index e6029cc62..bc2859fc8 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -47,7 +47,7 @@ type Oracle struct { } const ( - oracleContractID = -7 + oracleContractID = -9 maxURLLength = 256 maxFilterLength = 128 maxCallbackLength = 32 diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index 368d4926c..882ccc935 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -19,7 +19,7 @@ import ( ) const ( - policyContractID = -5 + policyContractID = -7 defaultExecFeeFactor = interop.DefaultBaseExecFee defaultFeePerByte = 1000 diff --git a/pkg/interop/crypto/crypto.go b/pkg/interop/crypto/crypto.go index 28702d7e9..f988632d0 100644 --- a/pkg/interop/crypto/crypto.go +++ b/pkg/interop/crypto/crypto.go @@ -8,16 +8,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" ) -// SHA256 computes SHA256 hash of b. It uses `Neo.Crypto.SHA256` syscall. -func SHA256(b []byte) interop.Hash256 { - return neogointernal.Syscall1("Neo.Crypto.SHA256", b).(interop.Hash256) -} - -// RIPEMD160 computes RIPEMD160 hash of b. It uses `Neo.Crypto.RIPEMD160` syscall. -func RIPEMD160(b []byte) interop.Hash160 { - return neogointernal.Syscall1("Neo.Crypto.RIPEMD160", b).(interop.Hash160) -} - // ECDsaSecp256r1Verify checks that sig is correct msg's signature for a given pub // (serialized public key). It uses `Neo.Crypto.VerifyWithECDsaSecp256r1` syscall. func ECDsaSecp256r1Verify(msg []byte, pub interop.PublicKey, sig interop.Signature) bool { diff --git a/pkg/interop/native/crypto/crypto.go b/pkg/interop/native/crypto/crypto.go new file mode 100644 index 000000000..32684d551 --- /dev/null +++ b/pkg/interop/native/crypto/crypto.go @@ -0,0 +1,34 @@ +package crypto + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" +) + +// Hash represents CryptoLib contract hash. +const Hash = "\x1b\xf5\x75\xab\x11\x89\x68\x84\x13\x61\x0a\x35\xa1\x28\x86\xcd\xe0\xb6\x6c\x72" + +// NamedCurve represents named elliptic curve. +type NamedCurve byte + +// Various named elliptic curves. +const ( + Secp256k1 NamedCurve = 22 + Secp256r1 NamedCurve = 23 +) + +// Sha256 calls `sha256` method of native CryptoLib contract and computes SHA256 hash of b. +func Sha256(b []byte) interop.Hash256 { + return contract.Call(interop.Hash160(Hash), "sha256", contract.NoneFlag, b).(interop.Hash256) +} + +// Ripemd160 calls `ripemd160` method of native CryptoLib contract and computes RIPEMD160 hash of b. +func Ripemd160(b []byte) interop.Hash160 { + return contract.Call(interop.Hash160(Hash), "ripemd160", contract.NoneFlag, b).(interop.Hash160) +} + +// VerifyWithECDsa calls `verifyWithECDsa` method of native CryptoLib contract and checks that sig is +// correct msg's signature for a given pub (serialized public key on a given curve). +func VerifyWithECDsa(msg []byte, pub interop.PublicKey, sig interop.Signature, curve NamedCurve) bool { + return contract.Call(interop.Hash160(Hash), "verifyWithECDsa", contract.NoneFlag, msg, pub, sig, curve).(bool) +} diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 84a9a0269..166d856bd 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -183,7 +183,7 @@ var rpcTestCases = map[string][]rpcTestCase{ check: func(t *testing.T, e *executor, cs interface{}) { res, ok := cs.(*state.Contract) require.True(t, ok) - assert.Equal(t, int32(-5), res.ID) + assert.Equal(t, int32(-7), res.ID) }, }, { From 078870fcebce27a79e0a5227c85e5b7d43d733a5 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 5 Mar 2021 12:49:58 +0300 Subject: [PATCH 02/11] compiler: fix contract.IsStandard syscall test --- pkg/compiler/syscall_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index 325b73abd..7c756c627 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -72,7 +72,7 @@ func TestSyscallExecution(t *testing.T) { "contract.Call": {interopnames.SystemContractCall, []string{u160, `"m"`, "1", "3"}, false}, "contract.CreateMultisigAccount": {interopnames.SystemContractCreateMultisigAccount, []string{"1", pubs}, false}, "contract.CreateStandardAccount": {interopnames.SystemContractCreateStandardAccount, []string{pub}, false}, - "contract.IsStandard": {interopnames.SystemContractIsStandard, []string{b}, false}, + "contract.IsStandard": {interopnames.SystemContractIsStandard, []string{u160}, false}, "contract.GetCallFlags": {interopnames.SystemContractGetCallFlags, nil, false}, "iterator.Create": {interopnames.SystemIteratorCreate, []string{pubs}, false}, "iterator.Next": {interopnames.SystemIteratorNext, []string{"iterator.Iterator{}"}, false}, From 2b90d4455f13b46545fb4a441a1f070791eeb543 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 3 Mar 2021 18:59:10 +0300 Subject: [PATCH 03/11] native: implement StdLib contract --- pkg/compiler/native_test.go | 15 ++ pkg/core/native/contract.go | 5 + pkg/core/native/nativenames/names.go | 1 + pkg/core/native/std.go | 273 +++++++++++++++++++++++++++ pkg/interop/native/std/std.go | 97 ++++++++++ 5 files changed, 391 insertions(+) create mode 100644 pkg/core/native/std.go create mode 100644 pkg/interop/native/std/std.go diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go index 9a52c5a97..ae99a46ea 100644 --- a/pkg/compiler/native_test.go +++ b/pkg/compiler/native_test.go @@ -19,6 +19,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/interop/native/oracle" "github.com/nspcc-dev/neo-go/pkg/interop/native/policy" "github.com/nspcc-dev/neo-go/pkg/interop/native/roles" + "github.com/nspcc-dev/neo-go/pkg/interop/native/std" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/util" @@ -39,6 +40,7 @@ func TestContractHashes(t *testing.T) { require.Equal(t, []byte(management.Hash), cs.Management.Hash.BytesBE()) require.Equal(t, []byte(notary.Hash), cs.Notary.Hash.BytesBE()) require.Equal(t, []byte(crypto.Hash), cs.Crypto.Hash.BytesBE()) + require.Equal(t, []byte(std.Hash), cs.Std.Hash.BytesBE()) } // testPrintHash is a helper for updating contract hashes. @@ -189,6 +191,18 @@ func TestNativeHelpersCompile(t *testing.T) { {"ripemd160", []string{"[]byte{1, 2, 3}"}}, {"verifyWithECDsa", []string{"[]byte{1, 2, 3}", pub, sig, "crypto.Secp256k1"}}, }) + runNativeTestCases(t, cs.Std.ContractMD, "std", []nativeTestCase{ + {"serialize", []string{"[]byte{1, 2, 3}"}}, + {"deserialize", []string{"[]byte{1, 2, 3}"}}, + {"jsonSerialize", []string{"[]byte{1, 2, 3}"}}, + {"jsonDeserialize", []string{"[]byte{1, 2, 3}"}}, + {"base64Encode", []string{"[]byte{1, 2, 3}"}}, + {"base64Decode", []string{"[]byte{1, 2, 3}"}}, + {"base58Encode", []string{"[]byte{1, 2, 3}"}}, + {"base58Decode", []string{"[]byte{1, 2, 3}"}}, + {"itoa", []string{"4", "10"}}, + {"atoi", []string{`"4"`, "10"}}, + }) } func runNativeTestCases(t *testing.T, ctr interop.ContractMD, name string, testCases []nativeTestCase) { @@ -218,6 +232,7 @@ func runNativeTestCase(t *testing.T, ctr interop.ContractMD, name, method string } methodUpper := strings.ToUpper(method[:1]) + method[1:] // ASCII only methodUpper = strings.ReplaceAll(methodUpper, "Gas", "GAS") + methodUpper = strings.ReplaceAll(methodUpper, "Json", "JSON") src := fmt.Sprintf(srcTmpl, name, name, methodUpper, strings.Join(params, ",")) v, s := vmAndCompileInterop(t, src) diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 64b8c73a9..20566f969 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -25,6 +25,7 @@ type Contracts struct { NameService *NameService Notary *Notary Crypto *Crypto + Std *Std Contracts []interop.Contract // persistScript is vm script which executes "onPersist" method of every native contract. persistScript []byte @@ -62,6 +63,10 @@ func NewContracts(p2pSigExtensionsEnabled bool) *Contracts { cs.Management = mgmt cs.Contracts = append(cs.Contracts, mgmt) + s := newStd() + cs.Std = s + cs.Contracts = append(cs.Contracts, s) + c := newCrypto() cs.Crypto = c cs.Contracts = append(cs.Contracts, c) diff --git a/pkg/core/native/nativenames/names.go b/pkg/core/native/nativenames/names.go index 440057744..c7cbe2ca2 100644 --- a/pkg/core/native/nativenames/names.go +++ b/pkg/core/native/nativenames/names.go @@ -12,4 +12,5 @@ const ( Notary = "Notary" NameService = "NameService" CryptoLib = "CryptoLib" + StdLib = "StdLib" ) diff --git a/pkg/core/native/std.go b/pkg/core/native/std.go new file mode 100644 index 000000000..ed559000f --- /dev/null +++ b/pkg/core/native/std.go @@ -0,0 +1,273 @@ +package native + +import ( + "encoding/base64" + "encoding/hex" + "errors" + "math/big" + "strings" + + "github.com/mr-tron/base58" + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +// Std represents StdLib contract. +type Std struct { + interop.ContractMD +} + +const stdContractID = -2 + +var ( + // ErrInvalidBase is returned when base is invalid. + ErrInvalidBase = errors.New("invalid base") + // ErrInvalidFormat is returned when string is not a number. + ErrInvalidFormat = errors.New("invalid format") +) + +func newStd() *Std { + s := &Std{ContractMD: *interop.NewContractMD(nativenames.StdLib, stdContractID)} + defer s.UpdateHash() + + desc := newDescriptor("serialize", smartcontract.ByteArrayType, + manifest.NewParameter("item", smartcontract.AnyType)) + md := newMethodAndPrice(s.serialize, 1<<12, callflag.NoneFlag) + s.AddMethod(md, desc) + + desc = newDescriptor("deserialize", smartcontract.AnyType, + manifest.NewParameter("data", smartcontract.ByteArrayType)) + md = newMethodAndPrice(s.deserialize, 1<<14, callflag.NoneFlag) + s.AddMethod(md, desc) + + desc = newDescriptor("jsonSerialize", smartcontract.ByteArrayType, + manifest.NewParameter("item", smartcontract.AnyType)) + md = newMethodAndPrice(s.jsonSerialize, 1<<12, callflag.NoneFlag) + s.AddMethod(md, desc) + + desc = newDescriptor("jsonDeserialize", smartcontract.AnyType, + manifest.NewParameter("json", smartcontract.ByteArrayType)) + md = newMethodAndPrice(s.jsonDeserialize, 1<<14, callflag.NoneFlag) + s.AddMethod(md, desc) + + desc = newDescriptor("itoa", smartcontract.StringType, + manifest.NewParameter("value", smartcontract.IntegerType), + manifest.NewParameter("base", smartcontract.IntegerType)) + md = newMethodAndPrice(s.itoa, 1<<12, callflag.NoneFlag) + s.AddMethod(md, desc) + + desc = newDescriptor("atoi", smartcontract.IntegerType, + manifest.NewParameter("value", smartcontract.StringType), + manifest.NewParameter("base", smartcontract.IntegerType)) + md = newMethodAndPrice(s.atoi, 1<<12, callflag.NoneFlag) + s.AddMethod(md, desc) + + desc = newDescriptor("base64Encode", smartcontract.StringType, + manifest.NewParameter("data", smartcontract.ByteArrayType)) + md = newMethodAndPrice(s.base64Encode, 1<<12, callflag.NoneFlag) + s.AddMethod(md, desc) + + desc = newDescriptor("base64Decode", smartcontract.ByteArrayType, + manifest.NewParameter("s", smartcontract.StringType)) + md = newMethodAndPrice(s.base64Decode, 1<<12, callflag.NoneFlag) + s.AddMethod(md, desc) + + desc = newDescriptor("base58Encode", smartcontract.StringType, + manifest.NewParameter("data", smartcontract.ByteArrayType)) + md = newMethodAndPrice(s.base58Encode, 1<<12, callflag.NoneFlag) + s.AddMethod(md, desc) + + desc = newDescriptor("base58Decode", smartcontract.ByteArrayType, + manifest.NewParameter("s", smartcontract.StringType)) + md = newMethodAndPrice(s.base58Decode, 1<<12, callflag.NoneFlag) + s.AddMethod(md, desc) + + return s +} + +func (s *Std) serialize(_ *interop.Context, args []stackitem.Item) stackitem.Item { + data, err := stackitem.SerializeItem(args[0]) + if err != nil { + panic(err) + } + if len(data) > stackitem.MaxSize { + panic(errors.New("too big item")) + } + + return stackitem.NewByteArray(data) +} + +func (s *Std) deserialize(_ *interop.Context, args []stackitem.Item) stackitem.Item { + data, err := args[0].TryBytes() + if err != nil { + panic(err) + } + + item, err := stackitem.DeserializeItem(data) + if err != nil { + panic(err) + } + + return item +} + +func (s *Std) jsonSerialize(_ *interop.Context, args []stackitem.Item) stackitem.Item { + data, err := stackitem.ToJSON(args[0]) + if err != nil { + panic(err) + } + if len(data) > stackitem.MaxSize { + panic(errors.New("too big item")) + } + + return stackitem.NewByteArray(data) +} + +func (s *Std) jsonDeserialize(_ *interop.Context, args []stackitem.Item) stackitem.Item { + data, err := args[0].TryBytes() + if err != nil { + panic(err) + } + + item, err := stackitem.FromJSON(data) + if err != nil { + panic(err) + } + + return item +} + +func (s *Std) itoa(_ *interop.Context, args []stackitem.Item) stackitem.Item { + num := toBigInt(args[0]) + base := toBigInt(args[1]) + if !base.IsInt64() { + panic(ErrInvalidBase) + } + var str string + switch b := base.Int64(); b { + case 10: + str = num.Text(10) + case 16: + if num.Sign() == 0 { + str = "0" + break + } + bs := bigint.ToBytes(num) + reverse(bs) + str = hex.EncodeToString(bs) + if pad := bs[0] & 0xF8; pad == 0 || pad == 0xF8 { + str = str[1:] + } + str = strings.ToUpper(str) + default: + panic(ErrInvalidBase) + } + return stackitem.NewByteArray([]byte(str)) +} + +func (s *Std) atoi(_ *interop.Context, args []stackitem.Item) stackitem.Item { + num := toString(args[0]) + base := toBigInt(args[1]) + if !base.IsInt64() { + panic(ErrInvalidBase) + } + var bi *big.Int + switch b := base.Int64(); b { + case 10: + var ok bool + bi, ok = new(big.Int).SetString(num, int(b)) + if !ok { + panic(ErrInvalidFormat) + } + case 16: + changed := len(num)%2 != 0 + if changed { + num = "0" + num + } + bs, err := hex.DecodeString(num) + if err != nil { + panic(ErrInvalidFormat) + } + if changed && bs[0]&0x8 != 0 { + bs[0] |= 0xF0 + } + reverse(bs) + bi = bigint.FromBytes(bs) + default: + panic(ErrInvalidBase) + } + + return stackitem.NewBigInteger(bi) +} + +func reverse(b []byte) { + l := len(b) + for i := 0; i < l/2; i++ { + b[i], b[l-i-1] = b[l-i-1], b[i] + } +} + +func (s *Std) base64Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item { + src, err := args[0].TryBytes() + if err != nil { + panic(err) + } + result := base64.StdEncoding.EncodeToString(src) + + return stackitem.NewByteArray([]byte(result)) +} + +func (s *Std) base64Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item { + src := toString(args[0]) + result, err := base64.StdEncoding.DecodeString(src) + if err != nil { + panic(err) + } + + return stackitem.NewByteArray(result) +} + +func (s *Std) base58Encode(_ *interop.Context, args []stackitem.Item) stackitem.Item { + src, err := args[0].TryBytes() + if err != nil { + panic(err) + } + result := base58.Encode(src) + + return stackitem.NewByteArray([]byte(result)) +} + +func (s *Std) base58Decode(_ *interop.Context, args []stackitem.Item) stackitem.Item { + src := toString(args[0]) + result, err := base58.Decode(src) + if err != nil { + panic(err) + } + + return stackitem.NewByteArray(result) +} + +// Metadata implements Contract interface. +func (s *Std) Metadata() *interop.ContractMD { + return &s.ContractMD +} + +// Initialize implements Contract interface. +func (s *Std) Initialize(ic *interop.Context) error { + return nil +} + +// OnPersist implements Contract interface. +func (s *Std) OnPersist(ic *interop.Context) error { + return nil +} + +// PostPersist implements Contract interface. +func (s *Std) PostPersist(ic *interop.Context) error { + return nil +} diff --git a/pkg/interop/native/std/std.go b/pkg/interop/native/std/std.go new file mode 100644 index 000000000..3fcc5332a --- /dev/null +++ b/pkg/interop/native/std/std.go @@ -0,0 +1,97 @@ +package std + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" +) + +// Hash represents StdLib contract hash. +const Hash = "\xc0\xef\x39\xce\xe0\xe4\xe9\x25\xc6\xc2\xa0\x6a\x79\xe1\x44\x0d\xd8\x6f\xce\xac" + +// Serialize calls `serialize` method of StdLib native contract and serializes +// any given item into a byte slice. It works for all regular VM types (not ones +// from interop package) and allows to save them in storage or pass into Notify +// and then Deserialize them on the next run or in the external event receiver. +func Serialize(item interface{}) []byte { + return contract.Call(interop.Hash160(Hash), "serialize", contract.NoneFlag, + item).([]byte) +} + +// Deserialize calls `deserialize` method of StdLib native contract and unpacks +// previously serialized value from a byte slice, it's the opposite of Serialize. +func Deserialize(b []byte) interface{} { + return contract.Call(interop.Hash160(Hash), "deserialize", contract.NoneFlag, + b) +} + +// JSONSerialize serializes value to json. It uses `jsonSerialize` method of StdLib native +// contract. +// Serialization format is the following: +// []byte -> base64 string +// bool -> json boolean +// nil -> Null +// string -> base64 encoded sequence of underlying bytes +// (u)int* -> integer, only value in -2^53..2^53 are allowed +// []interface{} -> json array +// map[type1]type2 -> json object with string keys marshaled as strings (not base64). +func JSONSerialize(item interface{}) []byte { + return contract.Call(interop.Hash160(Hash), "jsonSerialize", contract.NoneFlag, + item).([]byte) +} + +// JSONDeserialize deserializes value from json. It uses `jsonDeserialize` method of StdLib +// native contract. +// It performs deserialization as follows: +// strings -> []byte (string) from base64 +// integers -> (u)int* types +// null -> interface{}(nil) +// arrays -> []interface{} +// maps -> map[string]interface{} +func JSONDeserialize(data []byte) interface{} { + return contract.Call(interop.Hash160(Hash), "jsonDeserialize", contract.NoneFlag, + data) +} + +// Base64Encode calls `base64Encode` method of StdLib native contract and encodes +// given byte slice into a base64 string and returns byte representation of this +// string. +func Base64Encode(b []byte) string { + return contract.Call(interop.Hash160(Hash), "base64Encode", contract.NoneFlag, + b).(string) +} + +// Base64Decode calls `base64Decode` method of StdLib native contract and decodes +// given base64 string represented as a byte slice into byte slice. +func Base64Decode(b []byte) []byte { + return contract.Call(interop.Hash160(Hash), "base64Decode", contract.NoneFlag, + b).([]byte) +} + +// Base58Encode calls `base58Encode` method of StdLib native contract and encodes +// given byte slice into a base58 string and returns byte representation of this +// string. +func Base58Encode(b []byte) string { + return contract.Call(interop.Hash160(Hash), "base58Encode", contract.NoneFlag, + b).(string) +} + +// Base58Decode calls `base58Decode` method of StdLib native contract and decodes +// given base58 string represented as a byte slice into a new byte slice. +func Base58Decode(b []byte) []byte { + return contract.Call(interop.Hash160(Hash), "base58Decode", contract.NoneFlag, + b).([]byte) +} + +// Itoa converts num in a given base to string. Base should be either 10 or 16. +// It uses `itoa` method of StdLib native contract. +func Itoa(num int, base int) string { + return contract.Call(interop.Hash160(Hash), "itoa", contract.NoneFlag, + num, base).(string) +} + +// Atoi converts string to a number in a given base. Base should be either 10 or 16. +// It uses `atoi` method of StdLib native contract. +func Atoi(s string, base int) int { + return contract.Call(interop.Hash160(Hash), "atoi", contract.NoneFlag, + s, base).(int) +} From f65485b73565f645268e361a3d3259e151db4248 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 4 Mar 2021 13:26:16 +0300 Subject: [PATCH 04/11] core: remove System.Binary.Itoa and System.Binary.Atoi syscalls And move their tests to native StdLib. --- examples/timer/timer.go | 6 +- pkg/compiler/inline_test.go | 17 +++-- pkg/compiler/syscall_test.go | 2 - pkg/compiler/vm_test.go | 30 +++----- pkg/core/interop/binary/itoa.go | 91 ------------------------ pkg/core/interop/binary/itoa_test.go | 98 -------------------------- pkg/core/interop/interopnames/names.go | 4 -- pkg/core/interops.go | 2 - pkg/core/native/std_test.go | 96 +++++++++++++++++++++++++ pkg/interop/binary/binary.go | 12 ---- 10 files changed, 117 insertions(+), 241 deletions(-) delete mode 100644 pkg/core/interop/binary/itoa.go delete mode 100644 pkg/core/interop/binary/itoa_test.go create mode 100644 pkg/core/native/std_test.go diff --git a/examples/timer/timer.go b/examples/timer/timer.go index a38d90ce1..6cbc02d2f 100644 --- a/examples/timer/timer.go +++ b/examples/timer/timer.go @@ -2,8 +2,8 @@ package timer import ( "github.com/nspcc-dev/neo-go/pkg/interop" - "github.com/nspcc-dev/neo-go/pkg/interop/binary" "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/native/std" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neo-go/pkg/interop/util" @@ -35,7 +35,7 @@ func _deploy(_ interface{}, isUpdate bool) { sh := runtime.GetCallingScriptHash() storage.Put(ctx, mgmtKey, sh) storage.Put(ctx, ticksKey, defaultTicks) - i := binary.Itoa(defaultTicks, 10) + i := std.Itoa(defaultTicks, 10) runtime.Log("Timer set to " + i + " ticks.") } @@ -61,7 +61,7 @@ func Tick() bool { return contract.Call(runtime.GetExecutingScriptHash(), "selfDestroy", contract.All).(bool) } storage.Put(ctx, ticksKey, ticksLeft) - i := binary.Itoa(ticksLeft.(int), 10) + i := std.Itoa(ticksLeft.(int), 10) runtime.Log(i + " ticks left.") return true } diff --git a/pkg/compiler/inline_test.go b/pkg/compiler/inline_test.go index c7dee4599..eeb9bfe3a 100644 --- a/pkg/compiler/inline_test.go +++ b/pkg/compiler/inline_test.go @@ -118,13 +118,14 @@ func TestInline(t *testing.T) { func TestInlineInLoop(t *testing.T) { t.Run("simple", func(t *testing.T) { src := `package foo - import "github.com/nspcc-dev/neo-go/pkg/interop/binary" + import "github.com/nspcc-dev/neo-go/pkg/interop/storage" import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline" func Main() int { sum := 0 values := []int{10, 11} for _, v := range values { - binary.Itoa(v, 10) + _ = v // use 'v' + storage.GetContext() // push something on stack to check it's dropped sum += inline.VarSum(1, 2, 3, 4) } return sum @@ -133,14 +134,16 @@ func TestInlineInLoop(t *testing.T) { }) t.Run("inlined argument", func(t *testing.T) { src := `package foo - import "github.com/nspcc-dev/neo-go/pkg/interop/binary" + import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + import "github.com/nspcc-dev/neo-go/pkg/interop/storage" import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline" func Main() int { sum := 0 values := []int{10, 11} for _, v := range values { - binary.Itoa(v, 10) - sum += inline.VarSum(1, 2, 3, binary.Atoi("4", 10)) + _ = v // use 'v' + storage.GetContext() // push something on stack to check it's dropped + sum += inline.VarSum(1, 2, 3, runtime.GetTime()) // runtime.GetTime always returns 4 in these tests } return sum }` @@ -148,12 +151,12 @@ func TestInlineInLoop(t *testing.T) { }) t.Run("check clean stack on return", func(t *testing.T) { src := `package foo - import "github.com/nspcc-dev/neo-go/pkg/interop/binary" + import "github.com/nspcc-dev/neo-go/pkg/interop/storage" import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline" func Main() int { values := []int{10, 11, 12} for _, v := range values { - binary.Itoa(v, 10) + storage.GetContext() // push something on stack to check it's dropped if v == 11 { return inline.VarSum(2, 20, 200) } diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index 7c756c627..01f91038e 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -61,13 +61,11 @@ func TestSyscallExecution(t *testing.T) { sigs := "[]interop.Signature{" + sig + "}" sctx := "storage.Context{}" interops := map[string]syscallTestCase{ - "binary.Atoi": {interopnames.SystemBinaryAtoi, []string{`"123"`, "10"}, false}, "binary.Base58Decode": {interopnames.SystemBinaryBase58Decode, []string{b}, false}, "binary.Base58Encode": {interopnames.SystemBinaryBase58Encode, []string{b}, false}, "binary.Base64Decode": {interopnames.SystemBinaryBase64Decode, []string{b}, false}, "binary.Base64Encode": {interopnames.SystemBinaryBase64Encode, []string{b}, false}, "binary.Deserialize": {interopnames.SystemBinaryDeserialize, []string{b}, false}, - "binary.Itoa": {interopnames.SystemBinaryItoa, []string{"123", "10"}, false}, "binary.Serialize": {interopnames.SystemBinarySerialize, []string{"10"}, false}, "contract.Call": {interopnames.SystemContractCall, []string{u160, `"m"`, "1", "3"}, false}, "contract.CreateMultisigAccount": {interopnames.SystemContractCreateMultisigAccount, []string{"1", pubs}, false}, diff --git a/pkg/compiler/vm_test.go b/pkg/compiler/vm_test.go index 1f414a42b..4752ef708 100644 --- a/pkg/compiler/vm_test.go +++ b/pkg/compiler/vm_test.go @@ -3,8 +3,6 @@ package compiler_test import ( "errors" "fmt" - "math/big" - "strconv" "strings" "testing" @@ -109,8 +107,7 @@ func newStoragePlugin() *storagePlugin { s.interops[interopnames.ToID([]byte(interopnames.SystemStoragePut))] = s.Put s.interops[interopnames.ToID([]byte(interopnames.SystemStorageGetContext))] = s.GetContext s.interops[interopnames.ToID([]byte(interopnames.SystemRuntimeNotify))] = s.Notify - s.interops[interopnames.ToID([]byte(interopnames.SystemBinaryAtoi))] = s.Atoi - s.interops[interopnames.ToID([]byte(interopnames.SystemBinaryItoa))] = s.Itoa + s.interops[interopnames.ToID([]byte(interopnames.SystemRuntimeGetTime))] = s.GetTime return s } @@ -126,24 +123,6 @@ func (s *storagePlugin) syscallHandler(v *vm.VM, id uint32) error { return errors.New("syscall not found") } -func (s *storagePlugin) Atoi(v *vm.VM) error { - str := v.Estack().Pop().String() - base := v.Estack().Pop().BigInt().Int64() - n, err := strconv.ParseInt(str, int(base), 64) - if err != nil { - return err - } - v.Estack().PushVal(big.NewInt(n)) - return nil -} - -func (s *storagePlugin) Itoa(v *vm.VM) error { - n := v.Estack().Pop().BigInt() - base := v.Estack().Pop().BigInt() - v.Estack().PushVal(n.Text(int(base.Int64()))) - return nil -} - func (s *storagePlugin) Notify(v *vm.VM) error { name := v.Estack().Pop().String() item := stackitem.NewArray(v.Estack().Pop().Array()) @@ -185,3 +164,10 @@ func (s *storagePlugin) GetContext(vm *vm.VM) error { vm.Estack().PushVal(10) return nil } + +func (s *storagePlugin) GetTime(vm *vm.VM) error { + // Pushing anything on the stack here will work. This is just to satisfy + // the compiler, thinking it has pushed the context ^^. + vm.Estack().PushVal(4) + return nil +} diff --git a/pkg/core/interop/binary/itoa.go b/pkg/core/interop/binary/itoa.go deleted file mode 100644 index b31918836..000000000 --- a/pkg/core/interop/binary/itoa.go +++ /dev/null @@ -1,91 +0,0 @@ -package binary - -import ( - "encoding/hex" - "errors" - "math/big" - "strings" - - "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" -) - -var ( - // ErrInvalidBase is returned when base is invalid. - ErrInvalidBase = errors.New("invalid base") - // ErrInvalidFormat is returned when string is not a number. - ErrInvalidFormat = errors.New("invalid format") -) - -// Itoa converts number to string. -func Itoa(ic *interop.Context) error { - num := ic.VM.Estack().Pop().BigInt() - base := ic.VM.Estack().Pop().BigInt() - if !base.IsInt64() { - return ErrInvalidBase - } - var s string - switch b := base.Int64(); b { - case 10: - s = num.Text(10) - case 16: - if num.Sign() == 0 { - s = "0" - break - } - bs := bigint.ToBytes(num) - reverse(bs) - s = hex.EncodeToString(bs) - if pad := bs[0] & 0xF8; pad == 0 || pad == 0xF8 { - s = s[1:] - } - s = strings.ToUpper(s) - default: - return ErrInvalidBase - } - ic.VM.Estack().PushVal(s) - return nil -} - -// Atoi converts string to number. -func Atoi(ic *interop.Context) error { - num := ic.VM.Estack().Pop().String() - base := ic.VM.Estack().Pop().BigInt() - if !base.IsInt64() { - return ErrInvalidBase - } - var bi *big.Int - switch b := base.Int64(); b { - case 10: - var ok bool - bi, ok = new(big.Int).SetString(num, int(b)) - if !ok { - return ErrInvalidFormat - } - case 16: - changed := len(num)%2 != 0 - if changed { - num = "0" + num - } - bs, err := hex.DecodeString(num) - if err != nil { - return ErrInvalidFormat - } - if changed && bs[0]&0x8 != 0 { - bs[0] |= 0xF0 - } - reverse(bs) - bi = bigint.FromBytes(bs) - default: - return ErrInvalidBase - } - ic.VM.Estack().PushVal(bi) - return nil -} - -func reverse(b []byte) { - l := len(b) - for i := 0; i < l/2; i++ { - b[i], b[l-i-1] = b[l-i-1], b[i] - } -} diff --git a/pkg/core/interop/binary/itoa_test.go b/pkg/core/interop/binary/itoa_test.go deleted file mode 100644 index faf189076..000000000 --- a/pkg/core/interop/binary/itoa_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package binary - -import ( - "errors" - "math" - "math/big" - "testing" - - "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/vm" - "github.com/stretchr/testify/require" -) - -func TestItoa(t *testing.T) { - var testCases = []struct { - num *big.Int - base *big.Int - result string - }{ - {big.NewInt(0), big.NewInt(10), "0"}, - {big.NewInt(0), big.NewInt(16), "0"}, - {big.NewInt(1), big.NewInt(10), "1"}, - {big.NewInt(-1), big.NewInt(10), "-1"}, - {big.NewInt(1), big.NewInt(16), "1"}, - {big.NewInt(7), big.NewInt(16), "7"}, - {big.NewInt(8), big.NewInt(16), "08"}, - {big.NewInt(65535), big.NewInt(16), "0FFFF"}, - {big.NewInt(15), big.NewInt(16), "0F"}, - {big.NewInt(-1), big.NewInt(16), "F"}, - } - - for _, tc := range testCases { - ic := &interop.Context{VM: vm.New()} - ic.VM.Estack().PushVal(tc.base) - ic.VM.Estack().PushVal(tc.num) - require.NoError(t, Itoa(ic)) - require.Equal(t, tc.result, ic.VM.Estack().Pop().String()) - - ic = &interop.Context{VM: vm.New()} - ic.VM.Estack().PushVal(tc.base) - ic.VM.Estack().PushVal(tc.result) - - require.NoError(t, Atoi(ic)) - require.Equal(t, tc.num, ic.VM.Estack().Pop().BigInt()) - } - - t.Run("-1", func(t *testing.T) { - for _, s := range []string{"FF", "FFF", "FFFF"} { - ic := &interop.Context{VM: vm.New()} - ic.VM.Estack().PushVal(16) - ic.VM.Estack().PushVal(s) - - require.NoError(t, Atoi(ic)) - require.Equal(t, big.NewInt(-1), ic.VM.Estack().Pop().BigInt()) - } - }) -} - -func TestItoaError(t *testing.T) { - var testCases = []struct { - num *big.Int - base *big.Int - err error - }{ - {big.NewInt(1), big.NewInt(13), ErrInvalidBase}, - {big.NewInt(-1), new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(10)), ErrInvalidBase}, - } - - for _, tc := range testCases { - ic := &interop.Context{VM: vm.New()} - ic.VM.Estack().PushVal(tc.base) - ic.VM.Estack().PushVal(tc.num) - err := Itoa(ic) - require.True(t, errors.Is(err, tc.err), "got: %v", err) - } -} - -func TestAtoiError(t *testing.T) { - var testCases = []struct { - num string - base *big.Int - err error - }{ - {"1", big.NewInt(13), ErrInvalidBase}, - {"1", new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(16)), ErrInvalidBase}, - {"1_000", big.NewInt(10), ErrInvalidFormat}, - {"FE", big.NewInt(10), ErrInvalidFormat}, - {"XD", big.NewInt(16), ErrInvalidFormat}, - } - - for _, tc := range testCases { - ic := &interop.Context{VM: vm.New()} - ic.VM.Estack().PushVal(tc.base) - ic.VM.Estack().PushVal(tc.num) - err := Atoi(ic) - require.True(t, errors.Is(err, tc.err), "got: %v", err) - } -} diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index 9b58c2796..47dd802f9 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -2,13 +2,11 @@ package interopnames // Names of all used interops. const ( - SystemBinaryAtoi = "System.Binary.Atoi" SystemBinaryBase58Decode = "System.Binary.Base58Decode" SystemBinaryBase58Encode = "System.Binary.Base58Encode" SystemBinaryBase64Decode = "System.Binary.Base64Decode" SystemBinaryBase64Encode = "System.Binary.Base64Encode" SystemBinaryDeserialize = "System.Binary.Deserialize" - SystemBinaryItoa = "System.Binary.Itoa" SystemBinarySerialize = "System.Binary.Serialize" SystemCallbackCreate = "System.Callback.Create" SystemCallbackCreateFromMethod = "System.Callback.CreateFromMethod" @@ -54,13 +52,11 @@ const ( ) var names = []string{ - SystemBinaryAtoi, SystemBinaryBase58Decode, SystemBinaryBase58Encode, SystemBinaryBase64Decode, SystemBinaryBase64Encode, SystemBinaryDeserialize, - SystemBinaryItoa, SystemBinarySerialize, SystemCallbackCreate, SystemCallbackCreateFromMethod, diff --git a/pkg/core/interops.go b/pkg/core/interops.go index db4844216..6044af302 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -32,13 +32,11 @@ func SpawnVM(ic *interop.Context) *vm.VM { // All lists are sorted, keep 'em this way, please. var systemInterops = []interop.Function{ - {Name: interopnames.SystemBinaryAtoi, Func: binary.Atoi, Price: 1 << 12, ParamCount: 2}, {Name: interopnames.SystemBinaryBase58Decode, Func: binary.DecodeBase58, Price: 1 << 12, ParamCount: 1}, {Name: interopnames.SystemBinaryBase58Encode, Func: binary.EncodeBase58, Price: 1 << 12, ParamCount: 1}, {Name: interopnames.SystemBinaryBase64Decode, Func: binary.DecodeBase64, Price: 1 << 12, ParamCount: 1}, {Name: interopnames.SystemBinaryBase64Encode, Func: binary.EncodeBase64, Price: 1 << 12, ParamCount: 1}, {Name: interopnames.SystemBinaryDeserialize, Func: binary.Deserialize, Price: 1 << 14, ParamCount: 1}, - {Name: interopnames.SystemBinaryItoa, Func: binary.Itoa, Price: 1 << 12, ParamCount: 2}, {Name: interopnames.SystemBinarySerialize, Func: binary.Serialize, Price: 1 << 12, ParamCount: 1}, {Name: interopnames.SystemContractCall, Func: contract.Call, Price: 1 << 15, RequiredFlags: callflag.ReadStates | callflag.AllowCall, ParamCount: 4}, diff --git a/pkg/core/native/std_test.go b/pkg/core/native/std_test.go new file mode 100644 index 000000000..ee1baaca8 --- /dev/null +++ b/pkg/core/native/std_test.go @@ -0,0 +1,96 @@ +package native + +import ( + "math" + "math/big" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +func TestStdLibItoaAtoi(t *testing.T) { + s := newStd() + ic := &interop.Context{VM: vm.New()} + var actual stackitem.Item + + t.Run("itoa-atoi", func(t *testing.T) { + var testCases = []struct { + num *big.Int + base *big.Int + result string + }{ + {big.NewInt(0), big.NewInt(10), "0"}, + {big.NewInt(0), big.NewInt(16), "0"}, + {big.NewInt(1), big.NewInt(10), "1"}, + {big.NewInt(-1), big.NewInt(10), "-1"}, + {big.NewInt(1), big.NewInt(16), "1"}, + {big.NewInt(7), big.NewInt(16), "7"}, + {big.NewInt(8), big.NewInt(16), "08"}, + {big.NewInt(65535), big.NewInt(16), "0FFFF"}, + {big.NewInt(15), big.NewInt(16), "0F"}, + {big.NewInt(-1), big.NewInt(16), "F"}, + } + + for _, tc := range testCases { + require.NotPanics(t, func() { + actual = s.itoa(ic, []stackitem.Item{stackitem.Make(tc.num), stackitem.Make(tc.base)}) + }) + require.Equal(t, stackitem.Make(tc.result), actual) + + require.NotPanics(t, func() { + actual = s.atoi(ic, []stackitem.Item{stackitem.Make(tc.result), stackitem.Make(tc.base)}) + }) + require.Equal(t, stackitem.Make(tc.num), actual) + } + + t.Run("-1", func(t *testing.T) { + for _, str := range []string{"FF", "FFF", "FFFF"} { + require.NotPanics(t, func() { + actual = s.atoi(ic, []stackitem.Item{stackitem.Make(str), stackitem.Make(16)}) + }) + + require.Equal(t, stackitem.Make(-1), actual) + } + }) + }) + + t.Run("itoa error", func(t *testing.T) { + var testCases = []struct { + num *big.Int + base *big.Int + err error + }{ + {big.NewInt(1), big.NewInt(13), ErrInvalidBase}, + {big.NewInt(-1), new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(10)), ErrInvalidBase}, + } + + for _, tc := range testCases { + require.PanicsWithError(t, tc.err.Error(), func() { + _ = s.itoa(ic, []stackitem.Item{stackitem.Make(tc.num), stackitem.Make(tc.base)}) + }) + } + }) + + t.Run("atoi error", func(t *testing.T) { + var testCases = []struct { + num string + base *big.Int + err error + }{ + {"1", big.NewInt(13), ErrInvalidBase}, + {"1", new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(16)), ErrInvalidBase}, + {"1_000", big.NewInt(10), ErrInvalidFormat}, + {"FE", big.NewInt(10), ErrInvalidFormat}, + {"XD", big.NewInt(16), ErrInvalidFormat}, + } + + for _, tc := range testCases { + require.PanicsWithError(t, tc.err.Error(), func() { + _ = s.atoi(ic, []stackitem.Item{stackitem.Make(tc.num), stackitem.Make(tc.base)}) + }) + } + }) +} diff --git a/pkg/interop/binary/binary.go b/pkg/interop/binary/binary.go index 3cf244e22..9d25a589e 100644 --- a/pkg/interop/binary/binary.go +++ b/pkg/interop/binary/binary.go @@ -44,15 +44,3 @@ func Base58Encode(b []byte) string { func Base58Decode(b []byte) []byte { return neogointernal.Syscall1("System.Binary.Base58Decode", b).([]byte) } - -// Itoa converts num in a given base to string. Base should be either 10 or 16. -// It uses `System.Binary.Itoa` syscall. -func Itoa(num int, base int) string { - return neogointernal.Syscall2("System.Binary.Itoa", num, base).(string) -} - -// Atoi converts string to a number in a given base. Base should be either 10 or 16. -// It uses `System.Binary.Atoi` syscall. -func Atoi(s string, base int) int { - return neogointernal.Syscall2("System.Binary.Atoi", s, base).(int) -} From 4d2ad4b9e2c6a1cad074d68c5ec35d39f44adfea Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 4 Mar 2021 13:52:22 +0300 Subject: [PATCH 05/11] core: remove System.Json.[Serialize, Deserialize] interops And move their tests to native StdLib. --- pkg/compiler/syscall_test.go | 2 - pkg/core/interop/interopnames/names.go | 4 -- pkg/core/interop/json/json.go | 28 ------------ pkg/core/interop/json/json_test.go | 63 -------------------------- pkg/core/interops.go | 3 -- pkg/core/native/std_test.go | 44 ++++++++++++++++++ pkg/interop/json/json.go | 30 ------------ 7 files changed, 44 insertions(+), 130 deletions(-) delete mode 100644 pkg/core/interop/json/json.go delete mode 100644 pkg/core/interop/json/json_test.go delete mode 100644 pkg/interop/json/json.go diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index 01f91038e..d190f7664 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -75,8 +75,6 @@ func TestSyscallExecution(t *testing.T) { "iterator.Create": {interopnames.SystemIteratorCreate, []string{pubs}, false}, "iterator.Next": {interopnames.SystemIteratorNext, []string{"iterator.Iterator{}"}, false}, "iterator.Value": {interopnames.SystemIteratorValue, []string{"iterator.Iterator{}"}, false}, - "json.FromJSON": {interopnames.SystemJSONDeserialize, []string{b}, false}, - "json.ToJSON": {interopnames.SystemJSONSerialize, []string{b}, false}, "runtime.CheckWitness": {interopnames.SystemRuntimeCheckWitness, []string{b}, false}, "runtime.GasLeft": {interopnames.SystemRuntimeGasLeft, nil, false}, "runtime.GetCallingScriptHash": {interopnames.SystemRuntimeGetCallingScriptHash, nil, false}, diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index 47dd802f9..ab915a976 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -23,8 +23,6 @@ const ( SystemIteratorCreate = "System.Iterator.Create" SystemIteratorNext = "System.Iterator.Next" SystemIteratorValue = "System.Iterator.Value" - SystemJSONDeserialize = "System.Json.Deserialize" - SystemJSONSerialize = "System.Json.Serialize" SystemRuntimeCheckWitness = "System.Runtime.CheckWitness" SystemRuntimeGasLeft = "System.Runtime.GasLeft" SystemRuntimeGetCallingScriptHash = "System.Runtime.GetCallingScriptHash" @@ -73,8 +71,6 @@ var names = []string{ SystemIteratorCreate, SystemIteratorNext, SystemIteratorValue, - SystemJSONDeserialize, - SystemJSONSerialize, SystemRuntimeCheckWitness, SystemRuntimeGasLeft, SystemRuntimeGetCallingScriptHash, diff --git a/pkg/core/interop/json/json.go b/pkg/core/interop/json/json.go deleted file mode 100644 index 5be269939..000000000 --- a/pkg/core/interop/json/json.go +++ /dev/null @@ -1,28 +0,0 @@ -package json - -import ( - "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" -) - -// Serialize handles System.JSON.Serialize syscall. -func Serialize(ic *interop.Context) error { - item := ic.VM.Estack().Pop().Item() - data, err := stackitem.ToJSON(item) - if err != nil { - return err - } - ic.VM.Estack().PushVal(data) - return nil -} - -// Deserialize handles System.JSON.Deserialize syscall. -func Deserialize(ic *interop.Context) error { - data := ic.VM.Estack().Pop().Bytes() - item, err := stackitem.FromJSON(data) - if err != nil { - return err - } - ic.VM.Estack().PushVal(item) - return nil -} diff --git a/pkg/core/interop/json/json_test.go b/pkg/core/interop/json/json_test.go deleted file mode 100644 index 056a29c76..000000000 --- a/pkg/core/interop/json/json_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package json - -import ( - "encoding/binary" - "testing" - - "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" - "github.com/nspcc-dev/neo-go/pkg/vm/opcode" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" - "github.com/stretchr/testify/require" -) - -var ( - serializeID = interopnames.ToID([]byte(interopnames.SystemJSONSerialize)) - deserializeID = interopnames.ToID([]byte(interopnames.SystemJSONDeserialize)) -) - -var jsonInterops = []interop.Function{ - {ID: serializeID, Func: Serialize}, - {ID: deserializeID, Func: Deserialize}, -} - -func init() { - interop.Sort(jsonInterops) -} - -func getTestFunc(id uint32, arg interface{}, result interface{}) func(t *testing.T) { - prog := make([]byte, 5) - prog[0] = byte(opcode.SYSCALL) - binary.LittleEndian.PutUint32(prog[1:], id) - - return func(t *testing.T) { - ic := &interop.Context{} - ic.Functions = append(ic.Functions, jsonInterops) - v := ic.SpawnVM() - v.LoadScript(prog) - v.Estack().PushVal(arg) - if result == nil { - require.Error(t, v.Run()) - return - } - require.NoError(t, v.Run()) - require.Equal(t, stackitem.Make(result), v.Estack().Pop().Item()) - } -} - -func TestSerialize(t *testing.T) { - t.Run("Serialize", func(t *testing.T) { - t.Run("Good", getTestFunc(serializeID, 42, []byte("42"))) - t.Run("Bad", func(t *testing.T) { - arr := stackitem.NewArray([]stackitem.Item{ - stackitem.NewByteArray(make([]byte, stackitem.MaxSize/2)), - stackitem.NewByteArray(make([]byte, stackitem.MaxSize/2)), - }) - getTestFunc(serializeID, arr, nil)(t) - }) - }) - t.Run("Deserialize", func(t *testing.T) { - t.Run("Good", getTestFunc(deserializeID, []byte("42"), 42)) - t.Run("Bad", getTestFunc(deserializeID, []byte("{]"), nil)) - }) -} diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 6044af302..0952efc7c 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -15,7 +15,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/interop/iterator" - "github.com/nspcc-dev/neo-go/pkg/core/interop/json" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" @@ -50,8 +49,6 @@ var systemInterops = []interop.Function{ {Name: interopnames.SystemIteratorCreate, Func: iterator.Create, Price: 1 << 4, ParamCount: 1}, {Name: interopnames.SystemIteratorNext, Func: iterator.Next, Price: 1 << 15, ParamCount: 1}, {Name: interopnames.SystemIteratorValue, Func: iterator.Value, Price: 1 << 4, ParamCount: 1}, - {Name: interopnames.SystemJSONDeserialize, Func: json.Deserialize, Price: 1 << 14, ParamCount: 1}, - {Name: interopnames.SystemJSONSerialize, Func: json.Serialize, Price: 1 << 12, ParamCount: 1}, {Name: interopnames.SystemRuntimeCheckWitness, Func: runtime.CheckWitness, Price: 1 << 10, RequiredFlags: callflag.NoneFlag, ParamCount: 1}, {Name: interopnames.SystemRuntimeGasLeft, Func: runtime.GasLeft, Price: 1 << 4}, diff --git a/pkg/core/native/std_test.go b/pkg/core/native/std_test.go index ee1baaca8..afbe7460f 100644 --- a/pkg/core/native/std_test.go +++ b/pkg/core/native/std_test.go @@ -94,3 +94,47 @@ func TestStdLibItoaAtoi(t *testing.T) { } }) } + +func TestStdLibJSON(t *testing.T) { + s := newStd() + ic := &interop.Context{VM: vm.New()} + var actual stackitem.Item + + t.Run("JSONSerialize", func(t *testing.T) { + t.Run("Good", func(t *testing.T) { + require.NotPanics(t, func() { + actual = s.jsonSerialize(ic, []stackitem.Item{stackitem.Make(42)}) + }) + + require.Equal(t, stackitem.Make([]byte("42")), actual) + }) + + t.Run("Bad", func(t *testing.T) { + arr := stackitem.NewArray([]stackitem.Item{ + stackitem.NewByteArray(make([]byte, stackitem.MaxSize/2)), + stackitem.NewByteArray(make([]byte, stackitem.MaxSize/2)), + }) + require.Panics(t, func() { + _ = s.jsonSerialize(ic, []stackitem.Item{arr}) + }) + }) + }) + + t.Run("JSONDeserialize", func(t *testing.T) { + t.Run("Good", func(t *testing.T) { + require.NotPanics(t, func() { + actual = s.jsonDeserialize(ic, []stackitem.Item{stackitem.Make("42")}) + }) + + require.Equal(t, stackitem.Make(42), actual) + }) + t.Run("Bad", func(t *testing.T) { + require.Panics(t, func() { + _ = s.jsonDeserialize(ic, []stackitem.Item{stackitem.Make("{]")}) + }) + require.Panics(t, func() { + _ = s.jsonDeserialize(ic, []stackitem.Item{stackitem.NewInterop(nil)}) + }) + }) + }) +} diff --git a/pkg/interop/json/json.go b/pkg/interop/json/json.go deleted file mode 100644 index 05eede334..000000000 --- a/pkg/interop/json/json.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -Package json provides various JSON serialization/deserialization routines. -*/ -package json - -import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" - -// ToJSON serializes value to json. It uses `System.Json.Serialize` syscall. -// Serialization format is the following: -// []byte -> base64 string -// bool -> json boolean -// nil -> Null -// string -> base64 encoded sequence of underlying bytes -// (u)int* -> integer, only value in -2^53..2^53 are allowed -// []interface{} -> json array -// map[type1]type2 -> json object with string keys marshaled as strings (not base64). -func ToJSON(item interface{}) []byte { - return neogointernal.Syscall1("System.Json.Serialize", item).([]byte) -} - -// FromJSON deserializes value from json. It uses `System.Json.Deserialize` syscall. -// It performs deserialization as follows: -// strings -> []byte (string) from base64 -// integers -> (u)int* types -// null -> interface{}(nil) -// arrays -> []interface{} -// maps -> map[string]interface{} -func FromJSON(data []byte) interface{} { - return neogointernal.Syscall1("System.Json.Deserialize", data).(interface{}) -} From 5c9c168ee57abedc02c601e08e6163d140efb258 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 4 Mar 2021 13:29:35 +0300 Subject: [PATCH 06/11] core: remove System.Binary.[Base64*, Base58*] syscalls And move their tests to native StdLib. --- pkg/compiler/syscall_test.go | 16 +++----- pkg/core/interop/binary/encode.go | 41 -------------------- pkg/core/interop/binary/encode_test.go | 43 --------------------- pkg/core/interop/interopnames/names.go | 8 ---- pkg/core/interops.go | 4 -- pkg/core/native/std_test.go | 52 ++++++++++++++++++++++++++ pkg/interop/binary/binary.go | 24 ------------ 7 files changed, 58 insertions(+), 130 deletions(-) diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index d190f7664..ba62874f6 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -61,10 +61,6 @@ func TestSyscallExecution(t *testing.T) { sigs := "[]interop.Signature{" + sig + "}" sctx := "storage.Context{}" interops := map[string]syscallTestCase{ - "binary.Base58Decode": {interopnames.SystemBinaryBase58Decode, []string{b}, false}, - "binary.Base58Encode": {interopnames.SystemBinaryBase58Encode, []string{b}, false}, - "binary.Base64Decode": {interopnames.SystemBinaryBase64Decode, []string{b}, false}, - "binary.Base64Encode": {interopnames.SystemBinaryBase64Encode, []string{b}, false}, "binary.Deserialize": {interopnames.SystemBinaryDeserialize, []string{b}, false}, "binary.Serialize": {interopnames.SystemBinarySerialize, []string{"10"}, false}, "contract.Call": {interopnames.SystemContractCall, []string{u160, `"m"`, "1", "3"}, false}, @@ -203,20 +199,20 @@ func TestNotify(t *testing.T) { func TestSyscallInGlobalInit(t *testing.T) { src := `package foo - import "github.com/nspcc-dev/neo-go/pkg/interop/binary" - var a = binary.Base58Decode([]byte("5T")) - func Main() []byte { + import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + var a = runtime.CheckWitness([]byte("5T")) + func Main() bool { return a }` v, s := vmAndCompileInterop(t, src) - s.interops[interopnames.ToID([]byte(interopnames.SystemBinaryBase58Decode))] = func(v *vm.VM) error { + s.interops[interopnames.ToID([]byte(interopnames.SystemRuntimeCheckWitness))] = func(v *vm.VM) error { s := v.Estack().Pop().Value().([]byte) require.Equal(t, "5T", string(s)) - v.Estack().PushVal([]byte{1, 2}) + v.Estack().PushVal(true) return nil } require.NoError(t, v.Run()) - require.Equal(t, []byte{1, 2}, v.Estack().Pop().Value()) + require.Equal(t, true, v.Estack().Pop().Value()) } func TestOpcode(t *testing.T) { diff --git a/pkg/core/interop/binary/encode.go b/pkg/core/interop/binary/encode.go index 0cc294fef..75ab0d515 100644 --- a/pkg/core/interop/binary/encode.go +++ b/pkg/core/interop/binary/encode.go @@ -1,9 +1,6 @@ package binary import ( - "encoding/base64" - - "github.com/mr-tron/base58" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/vm" ) @@ -17,41 +14,3 @@ func Serialize(ic *interop.Context) error { func Deserialize(ic *interop.Context) error { return vm.RuntimeDeserialize(ic.VM) } - -// EncodeBase64 encodes top stack item into a base64 string. -func EncodeBase64(ic *interop.Context) error { - src := ic.VM.Estack().Pop().Bytes() - result := base64.StdEncoding.EncodeToString(src) - ic.VM.Estack().PushVal([]byte(result)) - return nil -} - -// DecodeBase64 decodes top stack item from base64 string to byte array. -func DecodeBase64(ic *interop.Context) error { - src := ic.VM.Estack().Pop().String() - result, err := base64.StdEncoding.DecodeString(src) - if err != nil { - return err - } - ic.VM.Estack().PushVal(result) - return nil -} - -// EncodeBase58 encodes top stack item into a base58 string. -func EncodeBase58(ic *interop.Context) error { - src := ic.VM.Estack().Pop().Bytes() - result := base58.Encode(src) - ic.VM.Estack().PushVal([]byte(result)) - return nil -} - -// DecodeBase58 decodes top stack item from base58 string to byte array. -func DecodeBase58(ic *interop.Context) error { - src := ic.VM.Estack().Pop().String() - result, err := base58.Decode(src) - if err != nil { - return err - } - ic.VM.Estack().PushVal(result) - return nil -} diff --git a/pkg/core/interop/binary/encode_test.go b/pkg/core/interop/binary/encode_test.go index eeda9f3a2..3318b3588 100644 --- a/pkg/core/interop/binary/encode_test.go +++ b/pkg/core/interop/binary/encode_test.go @@ -1,11 +1,9 @@ package binary import ( - "encoding/base64" "math/big" "testing" - "github.com/mr-tron/base58" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/vm" @@ -49,44 +47,3 @@ func TestRuntimeSerialize(t *testing.T) { }) }) } - -func TestRuntimeEncodeDecode(t *testing.T) { - original := []byte("my pretty string") - encoded64 := base64.StdEncoding.EncodeToString(original) - encoded58 := base58.Encode(original) - v := vm.New() - ic := &interop.Context{VM: v} - - t.Run("Encode64", func(t *testing.T) { - v.Estack().PushVal(original) - require.NoError(t, EncodeBase64(ic)) - actual := v.Estack().Pop().Bytes() - require.Equal(t, []byte(encoded64), actual) - }) - t.Run("Encode58", func(t *testing.T) { - v.Estack().PushVal(original) - require.NoError(t, EncodeBase58(ic)) - actual := v.Estack().Pop().Bytes() - require.Equal(t, []byte(encoded58), actual) - }) - t.Run("Decode64/positive", func(t *testing.T) { - v.Estack().PushVal(encoded64) - require.NoError(t, DecodeBase64(ic)) - actual := v.Estack().Pop().Bytes() - require.Equal(t, original, actual) - }) - t.Run("Decode64/error", func(t *testing.T) { - v.Estack().PushVal(encoded64 + "%") - require.Error(t, DecodeBase64(ic)) - }) - t.Run("Decode58/positive", func(t *testing.T) { - v.Estack().PushVal(encoded58) - require.NoError(t, DecodeBase58(ic)) - actual := v.Estack().Pop().Bytes() - require.Equal(t, original, actual) - }) - t.Run("Decode58/error", func(t *testing.T) { - v.Estack().PushVal(encoded58 + "%") - require.Error(t, DecodeBase58(ic)) - }) -} diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index ab915a976..1858cbacc 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -2,10 +2,6 @@ package interopnames // Names of all used interops. const ( - SystemBinaryBase58Decode = "System.Binary.Base58Decode" - SystemBinaryBase58Encode = "System.Binary.Base58Encode" - SystemBinaryBase64Decode = "System.Binary.Base64Decode" - SystemBinaryBase64Encode = "System.Binary.Base64Encode" SystemBinaryDeserialize = "System.Binary.Deserialize" SystemBinarySerialize = "System.Binary.Serialize" SystemCallbackCreate = "System.Callback.Create" @@ -50,10 +46,6 @@ const ( ) var names = []string{ - SystemBinaryBase58Decode, - SystemBinaryBase58Encode, - SystemBinaryBase64Decode, - SystemBinaryBase64Encode, SystemBinaryDeserialize, SystemBinarySerialize, SystemCallbackCreate, diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 0952efc7c..58bc3840b 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -31,10 +31,6 @@ func SpawnVM(ic *interop.Context) *vm.VM { // All lists are sorted, keep 'em this way, please. var systemInterops = []interop.Function{ - {Name: interopnames.SystemBinaryBase58Decode, Func: binary.DecodeBase58, Price: 1 << 12, ParamCount: 1}, - {Name: interopnames.SystemBinaryBase58Encode, Func: binary.EncodeBase58, Price: 1 << 12, ParamCount: 1}, - {Name: interopnames.SystemBinaryBase64Decode, Func: binary.DecodeBase64, Price: 1 << 12, ParamCount: 1}, - {Name: interopnames.SystemBinaryBase64Encode, Func: binary.EncodeBase64, Price: 1 << 12, ParamCount: 1}, {Name: interopnames.SystemBinaryDeserialize, Func: binary.Deserialize, Price: 1 << 14, ParamCount: 1}, {Name: interopnames.SystemBinarySerialize, Func: binary.Serialize, Price: 1 << 12, ParamCount: 1}, {Name: interopnames.SystemContractCall, Func: contract.Call, Price: 1 << 15, diff --git a/pkg/core/native/std_test.go b/pkg/core/native/std_test.go index afbe7460f..ea89b9530 100644 --- a/pkg/core/native/std_test.go +++ b/pkg/core/native/std_test.go @@ -1,10 +1,12 @@ package native import ( + "encoding/base64" "math" "math/big" "testing" + "github.com/mr-tron/base58" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -138,3 +140,53 @@ func TestStdLibJSON(t *testing.T) { }) }) } + +func TestStdLibEncodeDecode(t *testing.T) { + s := newStd() + original := []byte("my pretty string") + encoded64 := base64.StdEncoding.EncodeToString(original) + encoded58 := base58.Encode(original) + ic := &interop.Context{VM: vm.New()} + var actual stackitem.Item + + t.Run("Encode64", func(t *testing.T) { + require.NotPanics(t, func() { + actual = s.base64Encode(ic, []stackitem.Item{stackitem.Make(original)}) + }) + require.Equal(t, stackitem.Make(encoded64), actual) + }) + t.Run("Encode58", func(t *testing.T) { + require.NotPanics(t, func() { + actual = s.base58Encode(ic, []stackitem.Item{stackitem.Make(original)}) + }) + require.Equal(t, stackitem.Make(encoded58), actual) + }) + t.Run("Decode64/positive", func(t *testing.T) { + require.NotPanics(t, func() { + actual = s.base64Decode(ic, []stackitem.Item{stackitem.Make(encoded64)}) + }) + require.Equal(t, stackitem.Make(original), actual) + }) + t.Run("Decode64/error", func(t *testing.T) { + require.Panics(t, func() { + _ = s.base64Decode(ic, []stackitem.Item{stackitem.Make(encoded64 + "%")}) + }) + require.Panics(t, func() { + _ = s.base64Decode(ic, []stackitem.Item{stackitem.NewInterop(nil)}) + }) + }) + t.Run("Decode58/positive", func(t *testing.T) { + require.NotPanics(t, func() { + actual = s.base58Decode(ic, []stackitem.Item{stackitem.Make(encoded58)}) + }) + require.Equal(t, stackitem.Make(original), actual) + }) + t.Run("Decode58/error", func(t *testing.T) { + require.Panics(t, func() { + _ = s.base58Decode(ic, []stackitem.Item{stackitem.Make(encoded58 + "%")}) + }) + require.Panics(t, func() { + _ = s.base58Decode(ic, []stackitem.Item{stackitem.NewInterop(nil)}) + }) + }) +} diff --git a/pkg/interop/binary/binary.go b/pkg/interop/binary/binary.go index 9d25a589e..a96f5f527 100644 --- a/pkg/interop/binary/binary.go +++ b/pkg/interop/binary/binary.go @@ -20,27 +20,3 @@ func Serialize(item interface{}) []byte { func Deserialize(b []byte) interface{} { return neogointernal.Syscall1("System.Binary.Deserialize", b) } - -// Base64Encode encodes given byte slice into a base64 string and returns byte -// representation of this string. It uses `System.Binary.Base64Encode` interop. -func Base64Encode(b []byte) string { - return neogointernal.Syscall1("System.Binary.Base64Encode", b).(string) -} - -// Base64Decode decodes given base64 string represented as a byte slice into -// byte slice. It uses `System.Binary.Base64Decode` interop. -func Base64Decode(b []byte) []byte { - return neogointernal.Syscall1("System.Binary.Base64Decode", b).([]byte) -} - -// Base58Encode encodes given byte slice into a base58 string and returns byte -// representation of this string. It uses `System.Binary.Base58Encode` syscall. -func Base58Encode(b []byte) string { - return neogointernal.Syscall1("System.Binary.Base58Encode", b).(string) -} - -// Base58Decode decodes given base58 string represented as a byte slice into -// a new byte slice. It uses `System.Binary.Base58Decode` syscall. -func Base58Decode(b []byte) []byte { - return neogointernal.Syscall1("System.Binary.Base58Decode", b).([]byte) -} From 14ade421012f5539469a15f9f78c7e950e705cd3 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 4 Mar 2021 15:46:56 +0300 Subject: [PATCH 07/11] core: remove System.Binary.[Serialize, Deserialize] syscalls And move their tests to native StdLib. --- pkg/compiler/syscall_test.go | 2 - pkg/core/interop/binary/encode.go | 16 --- pkg/core/interop/binary/encode_test.go | 49 -------- pkg/core/interop/interopnames/names.go | 4 - pkg/core/interop_system_test.go | 25 ++-- pkg/core/interops.go | 3 - pkg/core/native/std_test.go | 136 +++++++++++++++++++++ pkg/core/native_oracle_test.go | 8 +- pkg/core/oracle_test.go | 4 +- pkg/interop/binary/binary.go | 22 ---- pkg/vm/cli/cli_test.go | 10 +- pkg/vm/interop.go | 33 ----- pkg/vm/vm_test.go | 163 ------------------------- 13 files changed, 163 insertions(+), 312 deletions(-) delete mode 100644 pkg/core/interop/binary/encode.go delete mode 100644 pkg/core/interop/binary/encode_test.go delete mode 100644 pkg/interop/binary/binary.go diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index ba62874f6..08e42b6ec 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -61,8 +61,6 @@ func TestSyscallExecution(t *testing.T) { sigs := "[]interop.Signature{" + sig + "}" sctx := "storage.Context{}" interops := map[string]syscallTestCase{ - "binary.Deserialize": {interopnames.SystemBinaryDeserialize, []string{b}, false}, - "binary.Serialize": {interopnames.SystemBinarySerialize, []string{"10"}, false}, "contract.Call": {interopnames.SystemContractCall, []string{u160, `"m"`, "1", "3"}, false}, "contract.CreateMultisigAccount": {interopnames.SystemContractCreateMultisigAccount, []string{"1", pubs}, false}, "contract.CreateStandardAccount": {interopnames.SystemContractCreateStandardAccount, []string{pub}, false}, diff --git a/pkg/core/interop/binary/encode.go b/pkg/core/interop/binary/encode.go deleted file mode 100644 index 75ab0d515..000000000 --- a/pkg/core/interop/binary/encode.go +++ /dev/null @@ -1,16 +0,0 @@ -package binary - -import ( - "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/vm" -) - -// Serialize serializes top stack item into a ByteArray. -func Serialize(ic *interop.Context) error { - return vm.RuntimeSerialize(ic.VM) -} - -// Deserialize deserializes ByteArray from a stack into an item. -func Deserialize(ic *interop.Context) error { - return vm.RuntimeDeserialize(ic.VM) -} diff --git a/pkg/core/interop/binary/encode_test.go b/pkg/core/interop/binary/encode_test.go deleted file mode 100644 index 3318b3588..000000000 --- a/pkg/core/interop/binary/encode_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package binary - -import ( - "math/big" - "testing" - - "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/vm" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" - "github.com/stretchr/testify/require" -) - -func TestRuntimeSerialize(t *testing.T) { - t.Run("recursive", func(t *testing.T) { - arr := stackitem.NewArray(nil) - arr.Append(arr) - ic := &interop.Context{VM: vm.New()} - ic.VM.Estack().PushVal(arr) - require.Error(t, Serialize(ic)) - }) - t.Run("big item", func(t *testing.T) { - ic := &interop.Context{VM: vm.New()} - ic.VM.Estack().PushVal(make([]byte, stackitem.MaxSize)) - require.Error(t, Serialize(ic)) - }) - t.Run("good", func(t *testing.T) { - ic := &interop.Context{VM: vm.New()} - ic.VM.Estack().PushVal(42) - require.NoError(t, Serialize(ic)) - - w := io.NewBufBinWriter() - stackitem.EncodeBinaryStackItem(stackitem.Make(42), w.BinWriter) - require.NoError(t, w.Err) - - encoded := w.Bytes() - require.Equal(t, encoded, ic.VM.Estack().Pop().Bytes()) - - ic.VM.Estack().PushVal(encoded) - require.NoError(t, Deserialize(ic)) - require.Equal(t, big.NewInt(42), ic.VM.Estack().Pop().BigInt()) - - t.Run("bad", func(t *testing.T) { - encoded[0] ^= 0xFF - ic.VM.Estack().PushVal(encoded) - require.Error(t, Deserialize(ic)) - }) - }) -} diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index 1858cbacc..127d05121 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -2,8 +2,6 @@ package interopnames // Names of all used interops. const ( - SystemBinaryDeserialize = "System.Binary.Deserialize" - SystemBinarySerialize = "System.Binary.Serialize" SystemCallbackCreate = "System.Callback.Create" SystemCallbackCreateFromMethod = "System.Callback.CreateFromMethod" SystemCallbackCreateFromSyscall = "System.Callback.CreateFromSyscall" @@ -46,8 +44,6 @@ const ( ) var names = []string{ - SystemBinaryDeserialize, - SystemBinarySerialize, SystemCallbackCreate, SystemCallbackCreateFromMethod, SystemCallbackCreateFromSyscall, diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index ff2d090f8..bd3084beb 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -316,6 +316,7 @@ func TestStorageDelete(t *testing.T) { // getTestContractState returns 2 contracts second of which is allowed to call the first. func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) { mgmtHash := bc.ManagementContractHash() + stdHash := bc.contracts.Std.Hash w := io.NewBufBinWriter() emit.Opcodes(w.BinWriter, opcode.ABORT) @@ -339,16 +340,20 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) { emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.SUB, opcode.CONVERT, opcode.Opcode(stackitem.BooleanT), opcode.RET) deployOff := w.Len() - emit.Opcodes(w.BinWriter, opcode.SWAP, opcode.JMPIF, 2+8+1+1+5+3) - emit.String(w.BinWriter, "create") // 8 bytes - emit.Int(w.BinWriter, 2) // 1 byte - emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte - emit.Syscall(w.BinWriter, interopnames.SystemBinarySerialize) // 5 bytes - emit.Opcodes(w.BinWriter, opcode.CALL, 3+8+1+1+5+3, opcode.RET) - emit.String(w.BinWriter, "update") // 8 bytes - emit.Int(w.BinWriter, 2) // 1 byte - emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte - emit.Syscall(w.BinWriter, interopnames.SystemBinarySerialize) // 5 bytes + emit.Opcodes(w.BinWriter, opcode.SWAP, opcode.JMPIF, 2+8+1+1+1+1+39+3) + emit.String(w.BinWriter, "create") // 8 bytes + emit.Int(w.BinWriter, 2) // 1 byte + emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte + emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`) + emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`) + emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes + emit.Opcodes(w.BinWriter, opcode.CALL, 3+8+1+1+1+1+39+3, opcode.RET) + emit.String(w.BinWriter, "update") // 8 bytes + emit.Int(w.BinWriter, 2) // 1 byte + emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte + emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`) + emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`) + emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes emit.Opcodes(w.BinWriter, opcode.CALL, 3, opcode.RET) putValOff := w.Len() emit.String(w.BinWriter, "initial") diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 58bc3840b..3e871e239 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -10,7 +10,6 @@ package core import ( "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/core/interop/binary" "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" "github.com/nspcc-dev/neo-go/pkg/core/interop/crypto" "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" @@ -31,8 +30,6 @@ func SpawnVM(ic *interop.Context) *vm.VM { // All lists are sorted, keep 'em this way, please. var systemInterops = []interop.Function{ - {Name: interopnames.SystemBinaryDeserialize, Func: binary.Deserialize, Price: 1 << 14, ParamCount: 1}, - {Name: interopnames.SystemBinarySerialize, Func: binary.Serialize, Price: 1 << 12, ParamCount: 1}, {Name: interopnames.SystemContractCall, Func: contract.Call, Price: 1 << 15, RequiredFlags: callflag.ReadStates | callflag.AllowCall, ParamCount: 4}, {Name: interopnames.SystemContractCallNative, Func: native.Call, Price: 0, ParamCount: 1}, diff --git a/pkg/core/native/std_test.go b/pkg/core/native/std_test.go index ea89b9530..f5a3bf15c 100644 --- a/pkg/core/native/std_test.go +++ b/pkg/core/native/std_test.go @@ -2,14 +2,17 @@ package native import ( "encoding/base64" + "encoding/hex" "math" "math/big" "testing" "github.com/mr-tron/base58" "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -190,3 +193,136 @@ func TestStdLibEncodeDecode(t *testing.T) { }) }) } + +func TestStdLibSerialize(t *testing.T) { + s := newStd() + ic := &interop.Context{VM: vm.New()} + + t.Run("recursive", func(t *testing.T) { + arr := stackitem.NewArray(nil) + arr.Append(arr) + require.Panics(t, func() { + _ = s.serialize(ic, []stackitem.Item{arr}) + }) + }) + t.Run("big item", func(t *testing.T) { + require.Panics(t, func() { + _ = s.serialize(ic, []stackitem.Item{stackitem.NewByteArray(make([]byte, stackitem.MaxSize))}) + }) + }) + t.Run("good", func(t *testing.T) { + var ( + actualSerialized stackitem.Item + actualDeserialized stackitem.Item + ) + require.NotPanics(t, func() { + actualSerialized = s.serialize(ic, []stackitem.Item{stackitem.Make(42)}) + }) + + w := io.NewBufBinWriter() + stackitem.EncodeBinaryStackItem(stackitem.Make(42), w.BinWriter) + require.NoError(t, w.Err) + + encoded := w.Bytes() + require.Equal(t, stackitem.Make(encoded), actualSerialized) + + require.NotPanics(t, func() { + actualDeserialized = s.deserialize(ic, []stackitem.Item{actualSerialized}) + }) + require.Equal(t, stackitem.Make(42), actualDeserialized) + + t.Run("bad", func(t *testing.T) { + encoded[0] ^= 0xFF + require.Panics(t, func() { + _ = s.deserialize(ic, []stackitem.Item{stackitem.Make(encoded)}) + }) + }) + }) +} + +func TestStdLibSerializeDeserialize(t *testing.T) { + s := newStd() + ic := &interop.Context{VM: vm.New()} + var actual stackitem.Item + + checkSerializeDeserialize := func(t *testing.T, value interface{}, expected stackitem.Item) { + require.NotPanics(t, func() { + actual = s.serialize(ic, []stackitem.Item{stackitem.Make(value)}) + }) + require.NotPanics(t, func() { + actual = s.deserialize(ic, []stackitem.Item{actual}) + }) + require.Equal(t, expected, actual) + } + + t.Run("Bool", func(t *testing.T) { + checkSerializeDeserialize(t, true, stackitem.NewBool(true)) + }) + t.Run("ByteArray", func(t *testing.T) { + checkSerializeDeserialize(t, []byte{1, 2, 3}, stackitem.NewByteArray([]byte{1, 2, 3})) + }) + t.Run("Integer", func(t *testing.T) { + checkSerializeDeserialize(t, 48, stackitem.NewBigInteger(big.NewInt(48))) + }) + t.Run("Array", func(t *testing.T) { + arr := stackitem.NewArray([]stackitem.Item{ + stackitem.Make(true), + stackitem.Make(123), + stackitem.NewMap()}) + checkSerializeDeserialize(t, arr, arr) + }) + t.Run("Struct", func(t *testing.T) { + st := stackitem.NewStruct([]stackitem.Item{ + stackitem.Make(true), + stackitem.Make(123), + stackitem.NewMap(), + }) + checkSerializeDeserialize(t, st, st) + }) + t.Run("Map", func(t *testing.T) { + item := stackitem.NewMap() + item.Add(stackitem.Make(true), stackitem.Make([]byte{1, 2, 3})) + item.Add(stackitem.Make([]byte{0}), stackitem.Make(false)) + checkSerializeDeserialize(t, item, item) + }) + t.Run("Serialize MapCompat", func(t *testing.T) { + resHex := "480128036b6579280576616c7565" + res, err := hex.DecodeString(resHex) + require.NoError(t, err) + + item := stackitem.NewMap() + item.Add(stackitem.Make([]byte("key")), stackitem.Make([]byte("value"))) + require.NotPanics(t, func() { + actual = s.serialize(ic, []stackitem.Item{stackitem.Make(item)}) + }) + bytes, err := actual.TryBytes() + require.NoError(t, err) + assert.Equal(t, res, bytes) + }) + t.Run("Serialize Interop", func(t *testing.T) { + require.Panics(t, func() { + actual = s.serialize(ic, []stackitem.Item{stackitem.NewInterop("kek")}) + }) + }) + t.Run("Serialize Array bad", func(t *testing.T) { + item := stackitem.NewArray([]stackitem.Item{stackitem.NewBool(true), stackitem.NewBool(true)}) + item.Value().([]stackitem.Item)[1] = item + require.Panics(t, func() { + actual = s.serialize(ic, []stackitem.Item{item}) + }) + }) + t.Run("Deserialize unknown", func(t *testing.T) { + data, err := stackitem.SerializeItem(stackitem.NewBigInteger(big.NewInt(123))) + require.NoError(t, err) + + data[0] = 0xFF + require.Panics(t, func() { + actual = s.deserialize(ic, []stackitem.Item{stackitem.Make(data)}) + }) + }) + t.Run("Deserialize not a byte array", func(t *testing.T) { + require.Panics(t, func() { + actual = s.deserialize(ic, []stackitem.Item{stackitem.NewInterop(nil)}) + }) + }) +} diff --git a/pkg/core/native_oracle_test.go b/pkg/core/native_oracle_test.go index 16f792c0c..2f692f705 100644 --- a/pkg/core/native_oracle_test.go +++ b/pkg/core/native_oracle_test.go @@ -29,7 +29,7 @@ import ( ) // getTestContractState returns test contract which uses oracles. -func getOracleContractState(h util.Uint160) *state.Contract { +func getOracleContractState(h util.Uint160, stdHash util.Uint160) *state.Contract { w := io.NewBufBinWriter() emit.Int(w.BinWriter, 5) emit.Opcodes(w.BinWriter, opcode.PACK) @@ -49,7 +49,9 @@ func getOracleContractState(h util.Uint160) *state.Contract { emit.Opcodes(w.BinWriter, opcode.ABORT) emit.Int(w.BinWriter, 4) // url, userData, code, result emit.Opcodes(w.BinWriter, opcode.PACK) - emit.Syscall(w.BinWriter, interopnames.SystemBinarySerialize) + emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`) + emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`) + emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes emit.String(w.BinWriter, "lastOracleResponse") emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext) emit.Syscall(w.BinWriter, interopnames.SystemStoragePut) @@ -117,7 +119,7 @@ func TestOracle_Request(t *testing.T) { bc := newTestChain(t) orc := bc.contracts.Oracle - cs := getOracleContractState(orc.Hash) + cs := getOracleContractState(orc.Hash, bc.contracts.Std.Hash) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) gasForResponse := int64(2000_1234) diff --git a/pkg/core/oracle_test.go b/pkg/core/oracle_test.go index 6cb63922c..968296a3b 100644 --- a/pkg/core/oracle_test.go +++ b/pkg/core/oracle_test.go @@ -130,7 +130,7 @@ func TestOracle(t *testing.T) { orc1.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset) orc2.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset) - cs := getOracleContractState(bc.contracts.Oracle.Hash) + cs := getOracleContractState(bc.contracts.Oracle.Hash, bc.contracts.Std.Hash) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) putOracleRequest(t, cs.Hash, bc, "http://get.1234", nil, "handle", []byte{}, 10_000_000) @@ -271,7 +271,7 @@ func TestOracleFull(t *testing.T) { orc.OnTransaction = func(tx *transaction.Transaction) { _ = mp.Add(tx, bc) } bc.SetOracle(orc) - cs := getOracleContractState(bc.contracts.Oracle.Hash) + cs := getOracleContractState(bc.contracts.Oracle.Hash, bc.contracts.Std.Hash) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) go bc.Run() diff --git a/pkg/interop/binary/binary.go b/pkg/interop/binary/binary.go deleted file mode 100644 index a96f5f527..000000000 --- a/pkg/interop/binary/binary.go +++ /dev/null @@ -1,22 +0,0 @@ -/* -Package binary provides binary serialization routines. -*/ -package binary - -import ( - "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" -) - -// Serialize serializes any given item into a byte slice. It works for all -// regular VM types (not ones from interop package) and allows to save them in -// storage or pass into Notify and then Deserialize them on the next run or in -// the external event receiver. It uses `System.Binary.Serialize` syscall. -func Serialize(item interface{}) []byte { - return neogointernal.Syscall1("System.Binary.Serialize", item).([]byte) -} - -// Deserialize unpacks previously serialized value from a byte slice, it's the -// opposite of Serialize. It uses `System.Binary.Deserialize` syscall. -func Deserialize(b []byte) interface{} { - return neogointernal.Syscall1("System.Binary.Deserialize", b) -} diff --git a/pkg/vm/cli/cli_test.go b/pkg/vm/cli/cli_test.go index b32ce15a0..477e996ec 100644 --- a/pkg/vm/cli/cli_test.go +++ b/pkg/vm/cli/cli_test.go @@ -299,8 +299,8 @@ func TestRunWithDifferentArguments(t *testing.T) { func TestPrintOps(t *testing.T) { w := io.NewBufBinWriter() - emit.Opcodes(w.BinWriter, opcode.PUSH1) - emit.Syscall(w.BinWriter, interopnames.SystemBinarySerialize) + emit.String(w.BinWriter, "log") + emit.Syscall(w.BinWriter, interopnames.SystemRuntimeLog) emit.Instruction(w.BinWriter, opcode.PUSHDATA1, []byte{3, 1, 2, 3}) script := w.Bytes() e := newTestVMCLI(t) @@ -312,9 +312,9 @@ func TestPrintOps(t *testing.T) { e.checkNextLine(t, ".*no program loaded") e.checkNextLine(t, fmt.Sprintf("READY: loaded %d instructions", len(script))) e.checkNextLine(t, "INDEX.*OPCODE.*PARAMETER") - e.checkNextLine(t, "0.*PUSH1") - e.checkNextLine(t, "1.*SYSCALL.*System\\.Binary\\.Serialize") - e.checkNextLine(t, "6.*PUSHDATA1.*010203") + e.checkNextLine(t, "0.*PUSHDATA1.*6c6f67") + e.checkNextLine(t, "5.*SYSCALL.*System\\.Runtime\\.Log") + e.checkNextLine(t, "10.*PUSHDATA1.*010203") } func TestLoadAbort(t *testing.T) { diff --git a/pkg/vm/interop.go b/pkg/vm/interop.go index 129357886..d4e153c0a 100644 --- a/pkg/vm/interop.go +++ b/pkg/vm/interop.go @@ -19,10 +19,6 @@ type interopIDFuncPrice struct { } var defaultVMInterops = []interopIDFuncPrice{ - {ID: interopnames.ToID([]byte(interopnames.SystemBinaryDeserialize)), - Func: RuntimeDeserialize, Price: 1 << 14}, - {ID: interopnames.ToID([]byte(interopnames.SystemBinarySerialize)), - Func: RuntimeSerialize, Price: 1 << 12}, {ID: interopnames.ToID([]byte(interopnames.SystemRuntimeLog)), Func: runtimeLog, Price: 1 << 15, RequiredFlags: callflag.AllowNotify}, {ID: interopnames.ToID([]byte(interopnames.SystemRuntimeNotify)), @@ -68,35 +64,6 @@ func runtimeNotify(vm *VM) error { return nil } -// RuntimeSerialize handles System.Binary.Serialize syscall. -func RuntimeSerialize(vm *VM) error { - item := vm.Estack().Pop() - data, err := stackitem.SerializeItem(item.value) - if err != nil { - return err - } else if len(data) > stackitem.MaxSize { - return errors.New("too big item") - } - - vm.Estack().PushVal(data) - - return nil -} - -// RuntimeDeserialize handles System.Binary.Deserialize syscall. -func RuntimeDeserialize(vm *VM) error { - data := vm.Estack().Pop().Bytes() - - item, err := stackitem.DeserializeItem(data) - if err != nil { - return err - } - - vm.Estack().Push(&Element{value: item}) - - return nil -} - // init sorts the global defaultVMInterops value. func init() { sort.Slice(defaultVMInterops, func(i, j int) bool { diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 937dca127..b937a9282 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -3,7 +3,6 @@ package vm import ( "bytes" "encoding/binary" - "encoding/hex" "errors" "fmt" "math" @@ -531,168 +530,6 @@ func getSyscallProg(name string) (prog []byte) { return buf.Bytes() } -func getSerializeProg() (prog []byte) { - prog = append(prog, getSyscallProg(interopnames.SystemBinarySerialize)...) - prog = append(prog, getSyscallProg(interopnames.SystemBinaryDeserialize)...) - prog = append(prog, byte(opcode.RET)) - - return -} - -func testSerialize(t *testing.T, vm *VM) { - err := vm.Step() - require.NoError(t, err) - require.Equal(t, 1, vm.estack.Len()) - require.IsType(t, (*stackitem.ByteArray)(nil), vm.estack.Top().value) - - err = vm.Step() - require.NoError(t, err) - require.Equal(t, 1, vm.estack.Len()) -} - -func TestSerializeBool(t *testing.T) { - vm := load(getSerializeProg()) - vm.estack.PushVal(true) - - testSerialize(t, vm) - - require.IsType(t, (*stackitem.Bool)(nil), vm.estack.Top().value) - require.Equal(t, true, vm.estack.Top().Bool()) -} - -func TestSerializeByteArray(t *testing.T) { - vm := load(getSerializeProg()) - value := []byte{1, 2, 3} - vm.estack.PushVal(value) - - testSerialize(t, vm) - - require.IsType(t, (*stackitem.ByteArray)(nil), vm.estack.Top().value) - require.Equal(t, value, vm.estack.Top().Bytes()) -} - -func TestSerializeInteger(t *testing.T) { - vm := load(getSerializeProg()) - value := int64(123) - vm.estack.PushVal(value) - - testSerialize(t, vm) - - require.IsType(t, (*stackitem.BigInteger)(nil), vm.estack.Top().value) - require.Equal(t, value, vm.estack.Top().BigInt().Int64()) -} - -func TestSerializeArray(t *testing.T) { - vm := load(getSerializeProg()) - item := stackitem.NewArray([]stackitem.Item{ - stackitem.Make(true), - stackitem.Make(123), - stackitem.NewMap(), - }) - - vm.estack.Push(&Element{value: item}) - - testSerialize(t, vm) - - require.IsType(t, (*stackitem.Array)(nil), vm.estack.Top().value) - require.Equal(t, item.Value().([]stackitem.Item), vm.estack.Top().Array()) -} - -func TestSerializeArrayBad(t *testing.T) { - vm := load(getSerializeProg()) - item := stackitem.NewArray(makeArrayOfType(2, stackitem.BooleanT)) - item.Value().([]stackitem.Item)[1] = item - - vm.estack.Push(&Element{value: item}) - - err := vm.Step() - require.Error(t, err) - require.True(t, vm.HasFailed()) -} - -func TestSerializeDupInteger(t *testing.T) { - prog := makeProgram( - opcode.PUSH0, opcode.NEWARRAY, opcode.INITSSLOT, 1, - opcode.DUP, opcode.PUSH2, opcode.DUP, opcode.STSFLD0, opcode.APPEND, - opcode.DUP, opcode.LDSFLD0, opcode.APPEND, - ) - vm := load(append(prog, getSerializeProg()...)) - - runVM(t, vm) -} - -func TestSerializeStruct(t *testing.T) { - vm := load(getSerializeProg()) - item := stackitem.NewStruct([]stackitem.Item{ - stackitem.Make(true), - stackitem.Make(123), - stackitem.NewMap(), - }) - - vm.estack.Push(&Element{value: item}) - - testSerialize(t, vm) - - require.IsType(t, (*stackitem.Struct)(nil), vm.estack.Top().value) - require.Equal(t, item.Value().([]stackitem.Item), vm.estack.Top().Array()) -} - -func TestDeserializeUnknown(t *testing.T) { - prog := append(getSyscallProg(interopnames.SystemBinaryDeserialize), byte(opcode.RET)) - - data, err := stackitem.SerializeItem(stackitem.NewBigInteger(big.NewInt(123))) - require.NoError(t, err) - - data[0] = 0xFF - - runWithArgs(t, prog, nil, data) -} - -func TestSerializeMap(t *testing.T) { - vm := load(getSerializeProg()) - item := stackitem.NewMap() - item.Add(stackitem.Make(true), stackitem.Make([]byte{1, 2, 3})) - item.Add(stackitem.Make([]byte{0}), stackitem.Make(false)) - - vm.estack.Push(&Element{value: item}) - - testSerialize(t, vm) - - require.IsType(t, (*stackitem.Map)(nil), vm.estack.Top().value) - require.Equal(t, item.Value(), vm.estack.Top().value.(*stackitem.Map).Value()) -} - -func TestSerializeMapCompat(t *testing.T) { - resHex := "480128036b6579280576616c7565" - res, err := hex.DecodeString(resHex) - require.NoError(t, err) - - // Create a map, push key and value, add KV to map, serialize. - buf := io.NewBufBinWriter() - emit.Opcodes(buf.BinWriter, opcode.NEWMAP) - emit.Opcodes(buf.BinWriter, opcode.DUP) - emit.Bytes(buf.BinWriter, []byte("key")) - emit.Bytes(buf.BinWriter, []byte("value")) - emit.Opcodes(buf.BinWriter, opcode.SETITEM) - emit.Syscall(buf.BinWriter, interopnames.SystemBinarySerialize) - require.NoError(t, buf.Err) - - vm := load(buf.Bytes()) - runVM(t, vm) - assert.Equal(t, res, vm.estack.Pop().Bytes()) -} - -func TestSerializeInterop(t *testing.T) { - vm := load(getSerializeProg()) - item := stackitem.NewInterop("kek") - - vm.estack.Push(&Element{value: item}) - - err := vm.Step() - require.Error(t, err) - require.True(t, vm.HasFailed()) -} - func getTestCallFlagsFunc(syscall []byte, flags callflag.CallFlag, result interface{}) func(t *testing.T) { return func(t *testing.T) { script := append([]byte{byte(opcode.SYSCALL)}, syscall...) From 4e6c1092b8cf722434899fe90639c41bca437661 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 4 Mar 2021 20:56:54 +0300 Subject: [PATCH 08/11] core: add Neo.Crypto.CheckSig interop --- pkg/compiler/syscall_test.go | 1 + pkg/core/interop/crypto/ecdsa.go | 14 ++++++ pkg/core/interop/crypto/ecdsa_test.go | 61 ++++++++++++++++++++++++++ pkg/core/interop/crypto/interop.go | 2 + pkg/core/interop/interopnames/names.go | 2 + pkg/core/interops.go | 1 + pkg/interop/crypto/crypto.go | 6 +++ 7 files changed, 87 insertions(+) diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index 08e42b6ec..648267be1 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -93,6 +93,7 @@ func TestSyscallExecution(t *testing.T) { "crypto.ECDsaSecp256k1Verify": {interopnames.NeoCryptoVerifyWithECDsaSecp256k1, []string{b, pub, sig}, false}, "crypto.ECDSASecp256r1CheckMultisig": {interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1, []string{b, pubs, sigs}, false}, "crypto.ECDSASecp256k1CheckMultisig": {interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1, []string{b, pubs, sigs}, false}, + "crypto.CheckSig": {interopnames.NeoCryptoCheckSig, []string{pub, sig}, false}, } ic := &interop.Context{} core.SpawnVM(ic) // set Functions field diff --git a/pkg/core/interop/crypto/ecdsa.go b/pkg/core/interop/crypto/ecdsa.go index a31ca3e70..2c2b6931d 100644 --- a/pkg/core/interop/crypto/ecdsa.go +++ b/pkg/core/interop/crypto/ecdsa.go @@ -99,3 +99,17 @@ func getMessageHash(ic *interop.Context, item stackitem.Item) (util.Uint256, err } return hash.Sha256(msg), nil } + +// ECDSASecp256r1CheckSig checks ECDSA signature using Secp256r1 elliptic curve. +func ECDSASecp256r1CheckSig(ic *interop.Context) error { + hashToCheck := ic.Container.GetSignedHash() + keyb := ic.VM.Estack().Pop().Bytes() + signature := ic.VM.Estack().Pop().Bytes() + pkey, err := keys.NewPublicKeyFromBytes(keyb, elliptic.P256()) + if err != nil { + return err + } + res := pkey.Verify(signature, hashToCheck.BytesBE()) + ic.VM.Estack().PushVal(res) + return nil +} diff --git a/pkg/core/interop/crypto/ecdsa_test.go b/pkg/core/interop/crypto/ecdsa_test.go index 1939514f2..262f66f2b 100644 --- a/pkg/core/interop/crypto/ecdsa_test.go +++ b/pkg/core/interop/crypto/ecdsa_test.go @@ -279,3 +279,64 @@ func testCurveCHECKMULTISIGBad(t *testing.T, isR1 bool) { require.Error(t, v.Run()) }) } + +func TestCheckSig(t *testing.T) { + priv, err := keys.NewPrivateKey() + require.NoError(t, err) + + verifyFunc := ECDSASecp256r1CheckSig + 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)) + } + + tx := transaction.New(netmode.UnitTestNet, []byte{0, 1, 2}, 1) + msg := tx.GetSignedPart() + ic.Container = tx + + t.Run("success", func(t *testing.T) { + sign := priv.Sign(msg) + runCase(t, false, true, sign, priv.PublicKey().Bytes()) + }) + + t.Run("missing argument", func(t *testing.T) { + runCase(t, true, false) + sign := priv.Sign(msg) + runCase(t, true, false, sign) + }) + + t.Run("invalid signature", func(t *testing.T) { + sign := priv.Sign(msg) + sign[0] = ^sign[0] + runCase(t, false, false, sign, priv.PublicKey().Bytes()) + }) + + 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) + }) +} diff --git a/pkg/core/interop/crypto/interop.go b/pkg/core/interop/crypto/interop.go index 6d8172f99..d754d8411 100644 --- a/pkg/core/interop/crypto/interop.go +++ b/pkg/core/interop/crypto/interop.go @@ -10,6 +10,7 @@ var ( ecdsaSecp256k1VerifyID = interopnames.ToID([]byte(interopnames.NeoCryptoVerifyWithECDsaSecp256k1)) ecdsaSecp256r1CheckMultisigID = interopnames.ToID([]byte(interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1)) ecdsaSecp256k1CheckMultisigID = interopnames.ToID([]byte(interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1)) + neoCryptoCheckSigID = interopnames.ToID([]byte(interopnames.NeoCryptoCheckSig)) ) var cryptoInterops = []interop.Function{ @@ -17,6 +18,7 @@ var cryptoInterops = []interop.Function{ {ID: ecdsaSecp256k1VerifyID, Func: ECDSASecp256k1Verify}, {ID: ecdsaSecp256r1CheckMultisigID, Func: ECDSASecp256r1CheckMultisig}, {ID: ecdsaSecp256k1CheckMultisigID, Func: ECDSASecp256k1CheckMultisig}, + {ID: neoCryptoCheckSigID, Func: ECDSASecp256r1CheckSig}, } func init() { diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index 127d05121..d81787f55 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -41,6 +41,7 @@ const ( NeoCryptoVerifyWithECDsaSecp256k1 = "Neo.Crypto.VerifyWithECDsaSecp256k1" NeoCryptoCheckMultisigWithECDsaSecp256r1 = "Neo.Crypto.CheckMultisigWithECDsaSecp256r1" NeoCryptoCheckMultisigWithECDsaSecp256k1 = "Neo.Crypto.CheckMultisigWithECDsaSecp256k1" + NeoCryptoCheckSig = "Neo.Crypto.CheckSig" ) var names = []string{ @@ -83,4 +84,5 @@ var names = []string{ NeoCryptoVerifyWithECDsaSecp256k1, NeoCryptoCheckMultisigWithECDsaSecp256r1, NeoCryptoCheckMultisigWithECDsaSecp256k1, + NeoCryptoCheckSig, } diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 3e871e239..28a33dc7d 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -81,6 +81,7 @@ var neoInterops = []interop.Function{ Price: fee.ECDSAVerifyPrice, ParamCount: 3}, {Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1, Func: crypto.ECDSASecp256r1CheckMultisig, Price: 0, ParamCount: 3}, {Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1, Func: crypto.ECDSASecp256k1CheckMultisig, Price: 0, ParamCount: 3}, + {Name: interopnames.NeoCryptoCheckSig, Func: crypto.ECDSASecp256r1CheckSig, Price: fee.ECDSAVerifyPrice, ParamCount: 2}, } // initIDinInteropsSlice initializes IDs from names in one given diff --git a/pkg/interop/crypto/crypto.go b/pkg/interop/crypto/crypto.go index f988632d0..d92bee275 100644 --- a/pkg/interop/crypto/crypto.go +++ b/pkg/interop/crypto/crypto.go @@ -31,3 +31,9 @@ func ECDSASecp256r1CheckMultisig(msg []byte, pubs []interop.PublicKey, sigs []in func ECDSASecp256k1CheckMultisig(msg []byte, pubs []interop.PublicKey, sigs []interop.Signature) bool { return neogointernal.Syscall3("Neo.Crypto.CheckMultisigWithECDsaSecp256k1", msg, pubs, sigs).(bool) } + +// CheckSig checks that sig is correct script-container's signature for a given pub +// (serialized public key). It uses `Neo.Crypto.CheckSig` syscall. +func CheckSig(pub interop.PublicKey, sig interop.Signature) bool { + return neogointernal.Syscall2("Neo.Crypto.CheckSig", pub, sig).(bool) +} From cdaca7be3e3beddcdb0992af0042a9cdaab526ba Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 5 Mar 2021 10:18:03 +0300 Subject: [PATCH 09/11] core: use Neo.Crypto.CheckSig for standard signature verification --- .docker/wallets/wallet1.json | 8 +- .docker/wallets/wallet1_solo.json | 10 +- .docker/wallets/wallet2.json | 8 +- .docker/wallets/wallet3.json | 8 +- .docker/wallets/wallet4.json | 8 +- cli/nep17_test.go | 4 +- cli/testdata/chain50x2.acc | Bin 45471 -> 45374 bytes cli/testdata/testwallet.json | 32 +++++- cli/testdata/wallet1_solo.json | 14 +-- cli/testdata/wallets/testwallet_NEO3.json | 8 +- cli/wallet_test.go | 8 +- internal/keytestcases/testcases.go | 24 ++--- internal/testchain/transaction.go | 16 +-- pkg/compiler/syscall_test.go | 2 - pkg/compiler/verify_test.go | 11 +- pkg/consensus/testdata/wallet1.json | 25 +---- pkg/consensus/testdata/wallet2.json | 8 +- pkg/consensus/testdata/wallet3.json | 8 +- pkg/consensus/testdata/wallet4.json | 8 +- pkg/core/blockchain_test.go | 2 +- pkg/core/fee/calculate.go | 2 +- pkg/core/helper_test.go | 10 +- pkg/core/interop/crypto/ecdsa.go | 28 ----- pkg/core/interop/crypto/ecdsa_test.go | 98 ------------------ pkg/core/interop/crypto/interop.go | 4 - pkg/core/interop/interopnames/names.go | 4 - pkg/core/interops.go | 4 - pkg/core/native/crypto_test.go | 81 +++++++++++++++ pkg/crypto/keys/publickey.go | 4 +- pkg/crypto/keys/publickey_test.go | 2 +- pkg/interop/crypto/crypto.go | 12 --- pkg/rpc/client/rpc_test.go | 6 +- pkg/rpc/server/client_test.go | 8 +- pkg/rpc/server/server_test.go | 28 ++--- ...est_verify.go => invokescript_contract.go} | 0 pkg/rpc/server/testdata/test_verify.avm | Bin 89 -> 0 bytes pkg/rpc/server/testdata/testblocks.acc | Bin 9809 -> 9801 bytes .../server/testdata/verification_contract.go | 4 +- pkg/services/notary/testdata/notary1.json | 66 +++++++++++- pkg/services/notary/testdata/notary2.json | 31 +++++- pkg/services/oracle/testdata/oracle1.json | 31 +++++- pkg/services/oracle/testdata/oracle2.json | 31 +++++- pkg/vm/contract_checks.go | 8 +- pkg/vm/contract_checks_test.go | 16 +-- pkg/wallet/account_test.go | 8 +- pkg/wallet/testdata/wallet1.json | 8 +- pkg/wallet/testdata/wallet2.json | 14 +-- pkg/wallet/wallet_test.go | 4 +- scripts/gendump/main.go | 2 +- 49 files changed, 404 insertions(+), 322 deletions(-) rename pkg/rpc/server/testdata/{test_verify.go => invokescript_contract.go} (100%) delete mode 100755 pkg/rpc/server/testdata/test_verify.avm diff --git a/.docker/wallets/wallet1.json b/.docker/wallets/wallet1.json index 27a48a3ed..1141c9777 100644 --- a/.docker/wallets/wallet1.json +++ b/.docker/wallets/wallet1.json @@ -2,11 +2,11 @@ "version": "3.0", "accounts": [ { - "address": "NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc", - "key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux", + "address": "NTh9TnZTstvAePEYWDGLLxidBikJE24uTo", + "key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL", "label": "", "contract": { - "script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcILQZVEDXg=", + "script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcJBdHR2qg==", "parameters": [ { "name": "parameter0", @@ -20,7 +20,7 @@ }, { "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", - "key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux", + "key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL", "label": "", "contract": { "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", diff --git a/.docker/wallets/wallet1_solo.json b/.docker/wallets/wallet1_solo.json index 1612925d6..da7a6d01e 100644 --- a/.docker/wallets/wallet1_solo.json +++ b/.docker/wallets/wallet1_solo.json @@ -2,11 +2,11 @@ "version": "3.0", "accounts": [ { - "address": "NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc", - "key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux", + "address": "NTh9TnZTstvAePEYWDGLLxidBikJE24uTo", + "key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL", "label": "", "contract": { - "script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcILQZVEDXg=", + "script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcJBdHR2qg==", "parameters": [ { "name": "parameter0", @@ -20,7 +20,7 @@ }, { "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", - "key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux", + "key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL", "label": "", "contract": { "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", @@ -45,7 +45,7 @@ }, { "address": "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK", - "key": "6PYN7LvaWqBNw7Xb7a52LSbPnP91kyuzYi3HncGvQwQoYAY2W8DncTgpux", + "key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL", "label": "", "contract": { "script": "EQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CEQtBE43vrw==", diff --git a/.docker/wallets/wallet2.json b/.docker/wallets/wallet2.json index 11b12bf5a..35284cba6 100644 --- a/.docker/wallets/wallet2.json +++ b/.docker/wallets/wallet2.json @@ -2,11 +2,11 @@ "version": "3.0", "accounts": [ { - "address": "NWvKSwutC8D6VKmmPxAEgFKx2NLvFhn8q5", - "key": "6PYKEHagXJ3mDLdga1FoyTGRtPdJgPz6Gb8sjEFwZvRu7ncD9PVZfHtMzL", + "address": "NUREbqw2kfbPgDeEz8Dac2QxntGGqqFMm7", + "key": "6PYXADog3RQCwKRhqQsobwZEFopdcCJuMfPosM9pXPaDWSguKvznLdpADk", "label": "", "contract": { - "script": "DCECEDp/fdAWVYWX95YNJ8UWpDlP2Wi55lFV60sBPkBAQG4LQZVEDXg=", + "script": "DCECEDp/fdAWVYWX95YNJ8UWpDlP2Wi55lFV60sBPkBAQG5BdHR2qg==", "parameters": [ { "name": "parameter0", @@ -20,7 +20,7 @@ }, { "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", - "key": "6PYKEHagXJ3mDLdga1FoyTGRtPdJgPz6Gb8sjEFwZvRu7ncD9PVZfHtMzL", + "key": "6PYXADog3RQCwKRhqQsobwZEFopdcCJuMfPosM9pXPaDWSguKvznLdpADk", "label": "", "contract": { "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", diff --git a/.docker/wallets/wallet3.json b/.docker/wallets/wallet3.json index e4dd102f7..6369f8030 100644 --- a/.docker/wallets/wallet3.json +++ b/.docker/wallets/wallet3.json @@ -2,11 +2,11 @@ "version": "3.0", "accounts": [ { - "address": "NNB3RsnTABEwoKEudNG92njds91WtiCuxd", - "key": "6PYLjn1Zw3RQmP3CkDxoZvYtMpu7ZUdjHnvu7wPuohUcXWCMh9vY661R8A", + "address": "NQP81vKVRmwZHveX8C9Rbf2qejSpT1W1Eu", + "key": "6PYScv3Vgvdi9EkhDNvHXdvQeuaXK9gRwXDmytCswZMNpTzMLvfgR3U5dK", "label": "", "contract": { - "script": "DCED2QwH32PmkM53kS4Qq1GsyUS2aGAje2CMT4+DCece5pkLQZVEDXg=", + "script": "DCED2QwH32PmkM53kS4Qq1GsyUS2aGAje2CMT4+DCece5plBdHR2qg==", "parameters": [ { "name": "parameter0", @@ -20,7 +20,7 @@ }, { "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", - "key": "6PYLjn1Zw3RQmP3CkDxoZvYtMpu7ZUdjHnvu7wPuohUcXWCMh9vY661R8A", + "key": "6PYScv3Vgvdi9EkhDNvHXdvQeuaXK9gRwXDmytCswZMNpTzMLvfgR3U5dK", "label": "", "contract": { "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", diff --git a/.docker/wallets/wallet4.json b/.docker/wallets/wallet4.json index 2fe62f899..ab3783401 100644 --- a/.docker/wallets/wallet4.json +++ b/.docker/wallets/wallet4.json @@ -2,11 +2,11 @@ "version": "3.0", "accounts": [ { - "address": "Nfzo95iBXfeAGx5rdjPedZRAqHKh9hwMdR", - "key": "6PYLbYYg9jUgzJQpKhpvNExa2UEgtp4356XPg56pHuCpE7gQmj84ESNjYW", + "address": "NLA34vf8eXGGUhRjVaYe5f8YsyYHTehbDZ", + "key": "6PYVwp1Sdg9DfTzvg42PZxgzMDf5a5FYBgT6ynKKzwmSHuhGkipoNjyW3a", "label": "", "contract": { - "script": "DCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWILQZVEDXg=", + "script": "DCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWJBdHR2qg==", "parameters": [ { "name": "parameter0", @@ -20,7 +20,7 @@ }, { "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", - "key": "6PYLbYYg9jUgzJQpKhpvNExa2UEgtp4356XPg56pHuCpE7gQmj84ESNjYW", + "key": "6PYVwp1Sdg9DfTzvg42PZxgzMDf5a5FYBgT6ynKKzwmSHuhGkipoNjyW3a", "label": "", "contract": { "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", diff --git a/cli/nep17_test.go b/cli/nep17_test.go index 3994b318a..c81d00ef9 100644 --- a/cli/nep17_test.go +++ b/cli/nep17_test.go @@ -51,7 +51,7 @@ func TestNEP17Balance(t *testing.T) { }) t.Run("all accounts", func(t *testing.T) { e.Run(t, cmdbase...) - addr1, err := address.StringToUint160("NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc") + addr1, err := address.StringToUint160("NTh9TnZTstvAePEYWDGLLxidBikJE24uTo") require.NoError(t, err) e.checkNextLine(t, "^Account "+address.Uint160ToString(addr1)) e.checkNextLine(t, "^\\s*GAS:\\s+GasToken \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") @@ -136,7 +136,7 @@ func TestNEP17Transfer(t *testing.T) { require.Equal(t, big.NewInt(1), b) t.Run("default address", func(t *testing.T) { - const validatorDefault = "NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc" + const validatorDefault = "NTh9TnZTstvAePEYWDGLLxidBikJE24uTo" e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "nep17", "multitransfer", "--rpc-endpoint", "http://"+e.RPC.Addr, diff --git a/cli/testdata/chain50x2.acc b/cli/testdata/chain50x2.acc index 02f46c5e44fe4368721275534f505ef836610985..f40e752f172555cb37245d61918a6059a4c42c71 100644 GIT binary patch literal 45374 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 K0z>HI{r&@xdX4n} literal 45471 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 diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 3bd6b5394daab5eb85ba2a12ccdab1c0becb42af..1cb4dcb1878e19d4e2d8d1b52f0ff8c7a12c5540 100644 GIT binary patch delta 4863 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 Date: Tue, 9 Mar 2021 17:38:41 +0300 Subject: [PATCH 10/11] core: remove Neo.Crypto.CheckMultisigWithECDsaSecp256k1 Koblitz RIP. --- pkg/compiler/syscall_test.go | 1 - pkg/core/interop/crypto/ecdsa.go | 15 +---- pkg/core/interop/crypto/ecdsa_test.go | 78 ++++++++++---------------- pkg/core/interop/crypto/interop.go | 2 - pkg/core/interop/interopnames/names.go | 2 - pkg/core/interops.go | 1 - pkg/interop/crypto/crypto.go | 6 -- 7 files changed, 32 insertions(+), 73 deletions(-) diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index e9598ae9f..cfb2f6bf6 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -90,7 +90,6 @@ func TestSyscallExecution(t *testing.T) { "storage.Put": {interopnames.SystemStoragePut, []string{sctx, b, b}, true}, "storage.ConvertContextToReadOnly": {interopnames.SystemStorageAsReadOnly, []string{sctx}, false}, "crypto.ECDSASecp256r1CheckMultisig": {interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1, []string{b, pubs, sigs}, false}, - "crypto.ECDSASecp256k1CheckMultisig": {interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1, []string{b, pubs, sigs}, false}, "crypto.CheckSig": {interopnames.NeoCryptoCheckSig, []string{pub, sig}, false}, } ic := &interop.Context{} diff --git a/pkg/core/interop/crypto/ecdsa.go b/pkg/core/interop/crypto/ecdsa.go index a6dea4311..91004f902 100644 --- a/pkg/core/interop/crypto/ecdsa.go +++ b/pkg/core/interop/crypto/ecdsa.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" - "github.com/btcsuite/btcd/btcec" "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/crypto" @@ -19,18 +18,6 @@ import ( // ECDSASecp256r1CheckMultisig checks multiple ECDSA signatures at once using // Secp256r1 elliptic curve. func ECDSASecp256r1CheckMultisig(ic *interop.Context) error { - return ecdsaCheckMultisig(ic, elliptic.P256()) -} - -// ECDSASecp256k1CheckMultisig checks multiple ECDSA signatures at once using -// Secp256k1 elliptic curve. -func ECDSASecp256k1CheckMultisig(ic *interop.Context) error { - return ecdsaCheckMultisig(ic, btcec.S256()) -} - -// ecdsaCheckMultisig is internal representation of ECDSASecp256r1CheckMultisig and -// ECDSASecp256k1CheckMultisig -func ecdsaCheckMultisig(ic *interop.Context, curve elliptic.Curve) error { hashToCheck, err := getMessageHash(ic, ic.VM.Estack().Pop().Item()) if err != nil { return err @@ -51,7 +38,7 @@ func ecdsaCheckMultisig(ic *interop.Context, curve elliptic.Curve) error { if len(pkeys) < len(sigs) { return errors.New("more signatures than there are keys") } - sigok := vm.CheckMultisigPar(ic.VM, curve, hashToCheck.BytesBE(), pkeys, sigs) + sigok := vm.CheckMultisigPar(ic.VM, elliptic.P256(), hashToCheck.BytesBE(), pkeys, sigs) ic.VM.Estack().PushVal(sigok) return nil } diff --git a/pkg/core/interop/crypto/ecdsa_test.go b/pkg/core/interop/crypto/ecdsa_test.go index 097cf593e..73140dd4b 100644 --- a/pkg/core/interop/crypto/ecdsa_test.go +++ b/pkg/core/interop/crypto/ecdsa_test.go @@ -20,18 +20,14 @@ import ( "github.com/stretchr/testify/require" ) -func initCHECKMULTISIG(isR1 bool, msg []byte, n int) ([]stackitem.Item, []stackitem.Item, map[string]*keys.PublicKey, error) { +func initCHECKMULTISIG(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() - } + pkeys[i], err = keys.NewPrivateKey() if err != nil { return nil, nil, nil, err } @@ -64,14 +60,10 @@ func subSlice(arr []stackitem.Item, indices []int) []stackitem.Item { return result } -func initCheckMultisigVMNoArgs(isR1 bool) *vm.VM { +func initCheckMultisigVMNoArgs() *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) - } + binary.LittleEndian.PutUint32(buf[1:], ecdsaSecp256r1CheckMultisigID) ic := &interop.Context{Trigger: trigger.Verification} Register(ic) @@ -80,11 +72,11 @@ func initCheckMultisigVMNoArgs(isR1 bool) *vm.VM { return v } -func initCHECKMULTISIGVM(t *testing.T, isR1 bool, n int, ik, is []int) *vm.VM { - v := initCheckMultisigVMNoArgs(isR1) +func initCHECKMULTISIGVM(t *testing.T, n int, ik, is []int) *vm.VM { + v := initCheckMultisigVMNoArgs() msg := []byte("NEO - An Open Network For Smart Economy") - pubs, sigs, _, err := initCHECKMULTISIG(isR1, msg, n) + pubs, sigs, _, err := initCHECKMULTISIG(msg, n) require.NoError(t, err) pubs = subSlice(pubs, ik) @@ -97,8 +89,8 @@ func initCHECKMULTISIGVM(t *testing.T, isR1 bool, n int, ik, is []int) *vm.VM { return v } -func testCHECKMULTISIGGood(t *testing.T, isR1 bool, n int, is []int) { - v := initCHECKMULTISIGVM(t, isR1, n, nil, is) +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()) @@ -106,25 +98,21 @@ func testCHECKMULTISIGGood(t *testing.T, isR1 bool, n int, is []int) { } func TestECDSASecp256r1CheckMultisigGood(t *testing.T) { - testCurveCHECKMULTISIGGood(t, true) + testCurveCHECKMULTISIGGood(t) } -func TestECDSASecp256k1CheckMultisigGood(t *testing.T) { - testCurveCHECKMULTISIGGood(t, false) +func testCurveCHECKMULTISIGGood(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 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) +func testCHECKMULTISIGBad(t *testing.T, isErr bool, n int, ik, is []int) { + v := initCHECKMULTISIGVM(t, n, ik, is) if isErr { require.Error(t, v.Run()) @@ -136,45 +124,41 @@ func testCHECKMULTISIGBad(t *testing.T, isR1 bool, isErr bool, n int, ik, is []i } func TestECDSASecp256r1CheckMultisigBad(t *testing.T) { - testCurveCHECKMULTISIGBad(t, true) + testCurveCHECKMULTISIGBad(t) } -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}) }) +func testCurveCHECKMULTISIGBad(t *testing.T) { + t.Run("1_1 wrong signature", func(t *testing.T) { testCHECKMULTISIGBad(t, false, 2, []int{0}, []int{1}) }) + t.Run("3_2 wrong order", func(t *testing.T) { testCHECKMULTISIGBad(t, false, 3, []int{0, 2}, []int{2, 0}) }) + t.Run("3_2 duplicate sig", func(t *testing.T) { testCHECKMULTISIGBad(t, false, 3, nil, []int{0, 0}) }) + t.Run("1_2 too many signatures", func(t *testing.T) { testCHECKMULTISIGBad(t, 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 := initCHECKMULTISIGVM(t, 1, []int{0}, []int{0}) v.GasLimit = fee.ECDSAVerifyPrice - 1 require.Error(t, v.Run()) }) msg := []byte("NEO - An Open Network For Smart Economy") - pubs, sigs, _, err := initCHECKMULTISIG(isR1, msg, 1) + pubs, sigs, _, err := initCHECKMULTISIG(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 := initCheckMultisigVMNoArgs() 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 := initCheckMultisigVMNoArgs() 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 := initCheckMultisigVMNoArgs() v.Estack().PushVal(arr) v.Estack().PushVal(pubs) v.Estack().PushVal(msg) diff --git a/pkg/core/interop/crypto/interop.go b/pkg/core/interop/crypto/interop.go index 6c7b465fb..8a06c7fc3 100644 --- a/pkg/core/interop/crypto/interop.go +++ b/pkg/core/interop/crypto/interop.go @@ -7,13 +7,11 @@ import ( var ( ecdsaSecp256r1CheckMultisigID = interopnames.ToID([]byte(interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1)) - ecdsaSecp256k1CheckMultisigID = interopnames.ToID([]byte(interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1)) neoCryptoCheckSigID = interopnames.ToID([]byte(interopnames.NeoCryptoCheckSig)) ) var cryptoInterops = []interop.Function{ {ID: ecdsaSecp256r1CheckMultisigID, Func: ECDSASecp256r1CheckMultisig}, - {ID: ecdsaSecp256k1CheckMultisigID, Func: ECDSASecp256k1CheckMultisig}, {ID: neoCryptoCheckSigID, Func: ECDSASecp256r1CheckSig}, } diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index 85ad88278..8e043beba 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -38,7 +38,6 @@ const ( SystemStoragePut = "System.Storage.Put" SystemStorageAsReadOnly = "System.Storage.AsReadOnly" NeoCryptoCheckMultisigWithECDsaSecp256r1 = "Neo.Crypto.CheckMultisigWithECDsaSecp256r1" - NeoCryptoCheckMultisigWithECDsaSecp256k1 = "Neo.Crypto.CheckMultisigWithECDsaSecp256k1" NeoCryptoCheckSig = "Neo.Crypto.CheckSig" ) @@ -79,6 +78,5 @@ var names = []string{ SystemStoragePut, SystemStorageAsReadOnly, NeoCryptoCheckMultisigWithECDsaSecp256r1, - NeoCryptoCheckMultisigWithECDsaSecp256k1, NeoCryptoCheckSig, } diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 064cdc777..4a91c0e95 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -76,7 +76,6 @@ var systemInterops = []interop.Function{ var neoInterops = []interop.Function{ {Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1, Func: crypto.ECDSASecp256r1CheckMultisig, Price: 0, ParamCount: 3}, - {Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1, Func: crypto.ECDSASecp256k1CheckMultisig, Price: 0, ParamCount: 3}, {Name: interopnames.NeoCryptoCheckSig, Func: crypto.ECDSASecp256r1CheckSig, Price: fee.ECDSAVerifyPrice, ParamCount: 2}, } diff --git a/pkg/interop/crypto/crypto.go b/pkg/interop/crypto/crypto.go index 3d605b90a..2cea41e56 100644 --- a/pkg/interop/crypto/crypto.go +++ b/pkg/interop/crypto/crypto.go @@ -14,12 +14,6 @@ func ECDSASecp256r1CheckMultisig(msg []byte, pubs []interop.PublicKey, sigs []in return neogointernal.Syscall3("Neo.Crypto.CheckMultisigWithECDsaSecp256r1", msg, pubs, sigs).(bool) } -// ECDSASecp256k1CheckMultisig checks multiple ECDSA signatures at once. It uses -// `Neo.Crypto.CheckMultisigWithECDsaSecp256k1` syscall. -func ECDSASecp256k1CheckMultisig(msg []byte, pubs []interop.PublicKey, sigs []interop.Signature) bool { - return neogointernal.Syscall3("Neo.Crypto.CheckMultisigWithECDsaSecp256k1", msg, pubs, sigs).(bool) -} - // CheckSig checks that sig is correct script-container's signature for a given pub // (serialized public key). It uses `Neo.Crypto.CheckSig` syscall. func CheckSig(pub interop.PublicKey, sig interop.Signature) bool { From 9015e508478028296a95215505a716f6ae6c984b Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 9 Mar 2021 18:11:21 +0300 Subject: [PATCH 11/11] core: refactor Neo.Crypto.CheckMultisigWithECDsaSecpr1 Rename it to Neo.Crypto.CheckMultisig and remove `message` parameter. --- .docker/wallets/wallet1.json | 4 +- .docker/wallets/wallet1_solo.json | 8 +-- .docker/wallets/wallet2.json | 4 +- .docker/wallets/wallet3.json | 4 +- .docker/wallets/wallet4.json | 4 +- cli/executor_test.go | 4 +- cli/nep17_test.go | 6 +- cli/testdata/chain50x2.acc | Bin 45374 -> 45322 bytes cli/testdata/wallet1_solo.json | 12 ++-- cli/testdata/wallets/testwallet_NEO3.json | 4 +- cli/wallet_test.go | 8 +-- pkg/compiler/syscall_test.go | 60 ++++++++--------- pkg/consensus/testdata/wallet1.json | 4 +- pkg/consensus/testdata/wallet2.json | 4 +- pkg/consensus/testdata/wallet3.json | 4 +- pkg/consensus/testdata/wallet4.json | 4 +- pkg/core/fee/calculate.go | 2 +- pkg/core/interop/crypto/ecdsa.go | 25 +------ pkg/core/interop/crypto/ecdsa_test.go | 36 +++++----- pkg/core/interop/crypto/interop.go | 6 +- pkg/core/interop/interopnames/names.go | 76 +++++++++++----------- pkg/core/interops.go | 2 +- pkg/core/oracle_test.go | 6 +- pkg/core/util_test.go | 6 +- pkg/interop/crypto/crypto.go | 13 ++-- pkg/rpc/server/server_test.go | 4 +- pkg/rpc/server/testdata/testblocks.acc | Bin 9801 -> 9788 bytes pkg/smartcontract/contract.go | 4 +- pkg/smartcontract/contract_test.go | 3 +- pkg/vm/contract_checks.go | 9 ++- pkg/vm/contract_checks_test.go | 8 +-- pkg/wallet/account_test.go | 4 +- pkg/wallet/testdata/wallet1.json | 4 +- pkg/wallet/testdata/wallet2.json | 4 +- 34 files changed, 157 insertions(+), 189 deletions(-) diff --git a/.docker/wallets/wallet1.json b/.docker/wallets/wallet1.json index 1141c9777..1ade05d76 100644 --- a/.docker/wallets/wallet1.json +++ b/.docker/wallets/wallet1.json @@ -19,11 +19,11 @@ "isdefault": false }, { - "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", + "address": "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6", "key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL", "label": "", "contract": { - "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEF7zmyl", "parameters": [ { "name": "parameter0", diff --git a/.docker/wallets/wallet1_solo.json b/.docker/wallets/wallet1_solo.json index da7a6d01e..2fcf9fd44 100644 --- a/.docker/wallets/wallet1_solo.json +++ b/.docker/wallets/wallet1_solo.json @@ -19,11 +19,11 @@ "isdefault": false }, { - "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", + "address": "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6", "key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL", "label": "", "contract": { - "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEF7zmyl", "parameters": [ { "name": "parameter0", @@ -44,11 +44,11 @@ "isdefault": false }, { - "address": "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK", + "address": "NNudMSGzEoktFzdYGYoNb3bzHzbmM1genF", "key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL", "label": "", "contract": { - "script": "EQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CEQtBE43vrw==", + "script": "EQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CEUF7zmyl", "parameters": [ { "name": "parameter0", diff --git a/.docker/wallets/wallet2.json b/.docker/wallets/wallet2.json index 35284cba6..d09780587 100644 --- a/.docker/wallets/wallet2.json +++ b/.docker/wallets/wallet2.json @@ -19,11 +19,11 @@ "isdefault": false }, { - "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", + "address": "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6", "key": "6PYXADog3RQCwKRhqQsobwZEFopdcCJuMfPosM9pXPaDWSguKvznLdpADk", "label": "", "contract": { - "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEF7zmyl", "parameters": [ { "name": "parameter0", diff --git a/.docker/wallets/wallet3.json b/.docker/wallets/wallet3.json index 6369f8030..2b7691e7f 100644 --- a/.docker/wallets/wallet3.json +++ b/.docker/wallets/wallet3.json @@ -19,11 +19,11 @@ "isdefault": false }, { - "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", + "address": "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6", "key": "6PYScv3Vgvdi9EkhDNvHXdvQeuaXK9gRwXDmytCswZMNpTzMLvfgR3U5dK", "label": "", "contract": { - "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEF7zmyl", "parameters": [ { "name": "parameter0", diff --git a/.docker/wallets/wallet4.json b/.docker/wallets/wallet4.json index ab3783401..92909322b 100644 --- a/.docker/wallets/wallet4.json +++ b/.docker/wallets/wallet4.json @@ -19,11 +19,11 @@ "isdefault": false }, { - "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", + "address": "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6", "key": "6PYVwp1Sdg9DfTzvg42PZxgzMDf5a5FYBgT6ynKKzwmSHuhGkipoNjyW3a", "label": "", "contract": { - "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEF7zmyl", "parameters": [ { "name": "parameter0", diff --git a/cli/executor_test.go b/cli/executor_test.go index 92435afc6..64b488145 100644 --- a/cli/executor_test.go +++ b/cli/executor_test.go @@ -31,8 +31,8 @@ import ( const ( validatorWIF = "KxyjQ8eUa4FHt3Gvioyt1Wz29cTUrE4eTqX3yFSk1YFCsPL8uNsY" - validatorAddr = "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK" - multisigAddr = "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY" + validatorAddr = "NNudMSGzEoktFzdYGYoNb3bzHzbmM1genF" + multisigAddr = "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6" validatorWallet = "testdata/wallet1_solo.json" ) diff --git a/cli/nep17_test.go b/cli/nep17_test.go index c81d00ef9..6930cfe85 100644 --- a/cli/nep17_test.go +++ b/cli/nep17_test.go @@ -60,12 +60,12 @@ func TestNEP17Balance(t *testing.T) { e.checkNextLine(t, "^\\s*Updated:") e.checkNextLine(t, "^\\s*$") - addr2, err := address.StringToUint160("NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY") + addr2, err := address.StringToUint160("NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6") require.NoError(t, err) e.checkNextLine(t, "^Account "+address.Uint160ToString(addr2)) e.checkNextLine(t, "^\\s*$") - addr3, err := address.StringToUint160("NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK") + addr3, err := address.StringToUint160("NNudMSGzEoktFzdYGYoNb3bzHzbmM1genF") require.NoError(t, err) e.checkNextLine(t, "^Account "+address.Uint160ToString(addr3)) // The order of assets is undefined. @@ -85,7 +85,7 @@ func TestNEP17Balance(t *testing.T) { } e.checkNextLine(t, "^\\s*$") - addr4, err := address.StringToUint160("NWTDxsHVde5qSjRkTRUAg6i8xC3JSWEC9k") // deployed verify.go contract + addr4, err := address.StringToUint160("NTe3yHH5zsaEGvEHTsFRpCjTef6Aod4yb6") // deployed verify.go contract require.NoError(t, err) e.checkNextLine(t, "^Account "+address.Uint160ToString(addr4)) e.checkEOF(t) diff --git a/cli/testdata/chain50x2.acc b/cli/testdata/chain50x2.acc index f40e752f172555cb37245d61918a6059a4c42c71..cae5ac69f7dc2be0a0c6e1cfb49b2424c3663215 100644 GIT binary patch literal 45322 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 HPzU=DkRM)8 literal 45374 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 K0z>HI{r&@xdX4n} diff --git a/cli/testdata/wallet1_solo.json b/cli/testdata/wallet1_solo.json index 5bad532d4..bab9b7b66 100644 --- a/cli/testdata/wallet1_solo.json +++ b/cli/testdata/wallet1_solo.json @@ -19,11 +19,11 @@ "isdefault": true }, { - "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", + "address": "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6", "key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL", "label": "", "contract": { - "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEF7zmyl", "parameters": [ { "name": "parameter0", @@ -44,11 +44,11 @@ "isdefault": false }, { - "address": "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK", + "address": "NNudMSGzEoktFzdYGYoNb3bzHzbmM1genF", "key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL", "label": "", "contract": { - "script": "EQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CEQtBE43vrw==", + "script": "EQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CEUF7zmyl", "parameters": [ { "name": "parameter0", @@ -61,11 +61,11 @@ "isdefault": false }, { - "address" : "NWTDxsHVde5qSjRkTRUAg6i8xC3JSWEC9k", + "address" : "NTe3yHH5zsaEGvEHTsFRpCjTef6Aod4yb6", "key" : "6PYSgdjUPVjo3ZJLg2CsheXnEZzyvUuSm4jCtXP6X7FT82sAQHWt2wpu5A", "label" : "", "contract" : { - "script" : "VwMAQS1RCDAhcAwUVVQtU+0PVUb61E1umZEoZwIvzl7bMHFoE87bKGnbKJdA", + "script" : "VwEAEdsgQFcAA0A=", "deployed" : true, "parameters" : [] }, diff --git a/cli/testdata/wallets/testwallet_NEO3.json b/cli/testdata/wallets/testwallet_NEO3.json index 1141c9777..1ade05d76 100644 --- a/cli/testdata/wallets/testwallet_NEO3.json +++ b/cli/testdata/wallets/testwallet_NEO3.json @@ -19,11 +19,11 @@ "isdefault": false }, { - "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", + "address": "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6", "key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL", "label": "", "contract": { - "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEF7zmyl", "parameters": [ { "name": "parameter0", diff --git a/cli/wallet_test.go b/cli/wallet_test.go index 10ae7d669..67d44fa4b 100644 --- a/cli/wallet_test.go +++ b/cli/wallet_test.go @@ -346,12 +346,12 @@ func TestDumpKeys(t *testing.T) { e.checkNextLine(t, "NTh9TnZTstvAePEYWDGLLxidBikJE24uTo") e.checkNextLine(t, pubRegex) e.checkNextLine(t, "^\\s*$") - e.checkNextLine(t, "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY") + e.checkNextLine(t, "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6") for i := 0; i < 4; i++ { e.checkNextLine(t, pubRegex) } e.checkNextLine(t, "^\\s*$") - e.checkNextLine(t, "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK") + e.checkNextLine(t, "NNudMSGzEoktFzdYGYoNb3bzHzbmM1genF") e.checkNextLine(t, pubRegex) e.checkEOF(t) }) @@ -363,7 +363,7 @@ func TestDumpKeys(t *testing.T) { e.checkEOF(t) }) t.Run("3/4 multisig", func(t *testing.T) { - cmd := append(cmd, "-a", "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY") + cmd := append(cmd, "-a", "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6") e.Run(t, cmd...) e.checkNextLine(t, "3 out of 4 multisig contract") for i := 0; i < 4; i++ { @@ -372,7 +372,7 @@ func TestDumpKeys(t *testing.T) { e.checkEOF(t) }) t.Run("1/1 multisig", func(t *testing.T) { - cmd := append(cmd, "--address", "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK") + cmd := append(cmd, "--address", "NNudMSGzEoktFzdYGYoNb3bzHzbmM1genF") e.Run(t, cmd...) e.checkNextLine(t, "1 out of 1 multisig contract") e.checkNextLine(t, pubRegex) diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index cfb2f6bf6..c9a960629 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -61,36 +61,36 @@ func TestSyscallExecution(t *testing.T) { sigs := "[]interop.Signature{" + sig + "}" sctx := "storage.Context{}" interops := map[string]syscallTestCase{ - "contract.Call": {interopnames.SystemContractCall, []string{u160, `"m"`, "1", "3"}, false}, - "contract.CreateMultisigAccount": {interopnames.SystemContractCreateMultisigAccount, []string{"1", pubs}, false}, - "contract.CreateStandardAccount": {interopnames.SystemContractCreateStandardAccount, []string{pub}, false}, - "contract.IsStandard": {interopnames.SystemContractIsStandard, []string{u160}, false}, - "contract.GetCallFlags": {interopnames.SystemContractGetCallFlags, nil, false}, - "iterator.Create": {interopnames.SystemIteratorCreate, []string{pubs}, false}, - "iterator.Next": {interopnames.SystemIteratorNext, []string{"iterator.Iterator{}"}, false}, - "iterator.Value": {interopnames.SystemIteratorValue, []string{"iterator.Iterator{}"}, false}, - "runtime.CheckWitness": {interopnames.SystemRuntimeCheckWitness, []string{b}, false}, - "runtime.GasLeft": {interopnames.SystemRuntimeGasLeft, nil, false}, - "runtime.GetCallingScriptHash": {interopnames.SystemRuntimeGetCallingScriptHash, nil, false}, - "runtime.GetEntryScriptHash": {interopnames.SystemRuntimeGetEntryScriptHash, nil, false}, - "runtime.GetExecutingScriptHash": {interopnames.SystemRuntimeGetExecutingScriptHash, nil, false}, - "runtime.GetInvocationCounter": {interopnames.SystemRuntimeGetInvocationCounter, nil, false}, - "runtime.GetNotifications": {interopnames.SystemRuntimeGetNotifications, []string{u160}, false}, - "runtime.GetScriptContainer": {interopnames.SystemRuntimeGetScriptContainer, nil, false}, - "runtime.GetTime": {interopnames.SystemRuntimeGetTime, nil, false}, - "runtime.GetTrigger": {interopnames.SystemRuntimeGetTrigger, nil, false}, - "runtime.Log": {interopnames.SystemRuntimeLog, []string{`"msg"`}, true}, - "runtime.Notify": {interopnames.SystemRuntimeNotify, []string{`"ev"`, "1"}, true}, - "runtime.Platform": {interopnames.SystemRuntimePlatform, nil, false}, - "storage.Delete": {interopnames.SystemStorageDelete, []string{sctx, b}, true}, - "storage.Find": {interopnames.SystemStorageFind, []string{sctx, b, "storage.None"}, false}, - "storage.Get": {interopnames.SystemStorageGet, []string{sctx, b}, false}, - "storage.GetContext": {interopnames.SystemStorageGetContext, nil, false}, - "storage.GetReadOnlyContext": {interopnames.SystemStorageGetReadOnlyContext, nil, false}, - "storage.Put": {interopnames.SystemStoragePut, []string{sctx, b, b}, true}, - "storage.ConvertContextToReadOnly": {interopnames.SystemStorageAsReadOnly, []string{sctx}, false}, - "crypto.ECDSASecp256r1CheckMultisig": {interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1, []string{b, pubs, sigs}, false}, - "crypto.CheckSig": {interopnames.NeoCryptoCheckSig, []string{pub, sig}, false}, + "contract.Call": {interopnames.SystemContractCall, []string{u160, `"m"`, "1", "3"}, false}, + "contract.CreateMultisigAccount": {interopnames.SystemContractCreateMultisigAccount, []string{"1", pubs}, false}, + "contract.CreateStandardAccount": {interopnames.SystemContractCreateStandardAccount, []string{pub}, false}, + "contract.IsStandard": {interopnames.SystemContractIsStandard, []string{u160}, false}, + "contract.GetCallFlags": {interopnames.SystemContractGetCallFlags, nil, false}, + "iterator.Create": {interopnames.SystemIteratorCreate, []string{pubs}, false}, + "iterator.Next": {interopnames.SystemIteratorNext, []string{"iterator.Iterator{}"}, false}, + "iterator.Value": {interopnames.SystemIteratorValue, []string{"iterator.Iterator{}"}, false}, + "runtime.CheckWitness": {interopnames.SystemRuntimeCheckWitness, []string{b}, false}, + "runtime.GasLeft": {interopnames.SystemRuntimeGasLeft, nil, false}, + "runtime.GetCallingScriptHash": {interopnames.SystemRuntimeGetCallingScriptHash, nil, false}, + "runtime.GetEntryScriptHash": {interopnames.SystemRuntimeGetEntryScriptHash, nil, false}, + "runtime.GetExecutingScriptHash": {interopnames.SystemRuntimeGetExecutingScriptHash, nil, false}, + "runtime.GetInvocationCounter": {interopnames.SystemRuntimeGetInvocationCounter, nil, false}, + "runtime.GetNotifications": {interopnames.SystemRuntimeGetNotifications, []string{u160}, false}, + "runtime.GetScriptContainer": {interopnames.SystemRuntimeGetScriptContainer, nil, false}, + "runtime.GetTime": {interopnames.SystemRuntimeGetTime, nil, false}, + "runtime.GetTrigger": {interopnames.SystemRuntimeGetTrigger, nil, false}, + "runtime.Log": {interopnames.SystemRuntimeLog, []string{`"msg"`}, true}, + "runtime.Notify": {interopnames.SystemRuntimeNotify, []string{`"ev"`, "1"}, true}, + "runtime.Platform": {interopnames.SystemRuntimePlatform, nil, false}, + "storage.Delete": {interopnames.SystemStorageDelete, []string{sctx, b}, true}, + "storage.Find": {interopnames.SystemStorageFind, []string{sctx, b, "storage.None"}, false}, + "storage.Get": {interopnames.SystemStorageGet, []string{sctx, b}, false}, + "storage.GetContext": {interopnames.SystemStorageGetContext, nil, false}, + "storage.GetReadOnlyContext": {interopnames.SystemStorageGetReadOnlyContext, nil, false}, + "storage.Put": {interopnames.SystemStoragePut, []string{sctx, b, b}, true}, + "storage.ConvertContextToReadOnly": {interopnames.SystemStorageAsReadOnly, []string{sctx}, false}, + "crypto.CheckMultisig": {interopnames.NeoCryptoCheckMultisig, []string{pubs, sigs}, false}, + "crypto.CheckSig": {interopnames.NeoCryptoCheckSig, []string{pub, sig}, false}, } ic := &interop.Context{} core.SpawnVM(ic) // set Functions field diff --git a/pkg/consensus/testdata/wallet1.json b/pkg/consensus/testdata/wallet1.json index 1141c9777..1ade05d76 100644 --- a/pkg/consensus/testdata/wallet1.json +++ b/pkg/consensus/testdata/wallet1.json @@ -19,11 +19,11 @@ "isdefault": false }, { - "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", + "address": "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6", "key": "6PYL8Gnjsz4RBKX18jx5ZAQTDH7PKkZwEVjPKEkjNzCDNFE6TKZwaFLibL", "label": "", "contract": { - "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEF7zmyl", "parameters": [ { "name": "parameter0", diff --git a/pkg/consensus/testdata/wallet2.json b/pkg/consensus/testdata/wallet2.json index 35284cba6..d09780587 100644 --- a/pkg/consensus/testdata/wallet2.json +++ b/pkg/consensus/testdata/wallet2.json @@ -19,11 +19,11 @@ "isdefault": false }, { - "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", + "address": "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6", "key": "6PYXADog3RQCwKRhqQsobwZEFopdcCJuMfPosM9pXPaDWSguKvznLdpADk", "label": "", "contract": { - "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEF7zmyl", "parameters": [ { "name": "parameter0", diff --git a/pkg/consensus/testdata/wallet3.json b/pkg/consensus/testdata/wallet3.json index 6369f8030..2b7691e7f 100644 --- a/pkg/consensus/testdata/wallet3.json +++ b/pkg/consensus/testdata/wallet3.json @@ -19,11 +19,11 @@ "isdefault": false }, { - "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", + "address": "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6", "key": "6PYScv3Vgvdi9EkhDNvHXdvQeuaXK9gRwXDmytCswZMNpTzMLvfgR3U5dK", "label": "", "contract": { - "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEF7zmyl", "parameters": [ { "name": "parameter0", diff --git a/pkg/consensus/testdata/wallet4.json b/pkg/consensus/testdata/wallet4.json index ab3783401..92909322b 100644 --- a/pkg/consensus/testdata/wallet4.json +++ b/pkg/consensus/testdata/wallet4.json @@ -19,11 +19,11 @@ "isdefault": false }, { - "address": "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY", + "address": "NgEisvCqr2h8wpRxQb7bVPWUZdbVCY8Uo6", "key": "6PYVwp1Sdg9DfTzvg42PZxgzMDf5a5FYBgT6ynKKzwmSHuhGkipoNjyW3a", "label": "", "contract": { - "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw==", + "script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEF7zmyl", "parameters": [ { "name": "parameter0", diff --git a/pkg/core/fee/calculate.go b/pkg/core/fee/calculate.go index 24c7c096f..7a64caa2a 100644 --- a/pkg/core/fee/calculate.go +++ b/pkg/core/fee/calculate.go @@ -24,7 +24,7 @@ func Calculate(base int64, script []byte) (int64, int) { sizeInv := 66 * m size += io.GetVarSize(sizeInv) + sizeInv + io.GetVarSize(script) netFee += calculateMultisig(base, m) + calculateMultisig(base, n) - netFee += Opcode(base, opcode.PUSHNULL) + base*ECDSAVerifyPrice*int64(n) + netFee += base * ECDSAVerifyPrice * int64(n) } else { // We can support more contract types in the future. } diff --git a/pkg/core/interop/crypto/ecdsa.go b/pkg/core/interop/crypto/ecdsa.go index 91004f902..78b9ddb1e 100644 --- a/pkg/core/interop/crypto/ecdsa.go +++ b/pkg/core/interop/crypto/ecdsa.go @@ -7,21 +7,14 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/crypto" - "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/util" "github.com/nspcc-dev/neo-go/pkg/vm" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // ECDSASecp256r1CheckMultisig checks multiple ECDSA signatures at once using // Secp256r1 elliptic curve. func ECDSASecp256r1CheckMultisig(ic *interop.Context) error { - hashToCheck, err := getMessageHash(ic, ic.VM.Estack().Pop().Item()) - if err != nil { - return err - } + hashToCheck := ic.Container.GetSignedHash() pkeys, err := ic.VM.Estack().PopSigElements() if err != nil { return fmt.Errorf("wrong parameters: %w", err) @@ -43,22 +36,6 @@ func ECDSASecp256r1CheckMultisig(ic *interop.Context) error { return nil } -func getMessageHash(ic *interop.Context, item stackitem.Item) (util.Uint256, error) { - var msg []byte - switch val := item.(type) { - case *stackitem.Interop: - return val.Value().(crypto.Verifiable).GetSignedHash(), nil - case stackitem.Null: - return ic.Container.GetSignedHash(), nil - default: - var err error - if msg, err = val.TryBytes(); err != nil { - return util.Uint256{}, err - } - } - return hash.Sha256(msg), nil -} - // ECDSASecp256r1CheckSig checks ECDSA signature using Secp256r1 elliptic curve. func ECDSASecp256r1CheckSig(ic *interop.Context) error { hashToCheck := ic.Container.GetSignedHash() diff --git a/pkg/core/interop/crypto/ecdsa_test.go b/pkg/core/interop/crypto/ecdsa_test.go index 73140dd4b..e82f64414 100644 --- a/pkg/core/interop/crypto/ecdsa_test.go +++ b/pkg/core/interop/crypto/ecdsa_test.go @@ -13,6 +13,7 @@ import ( "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/util" "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" @@ -60,12 +61,15 @@ func subSlice(arr []stackitem.Item, indices []int) []stackitem.Item { return result } -func initCheckMultisigVMNoArgs() *vm.VM { +func initCheckMultisigVMNoArgs(container *transaction.Transaction) *vm.VM { buf := make([]byte, 5) buf[0] = byte(opcode.SYSCALL) - binary.LittleEndian.PutUint32(buf[1:], ecdsaSecp256r1CheckMultisigID) + binary.LittleEndian.PutUint32(buf[1:], neoCryptoCheckMultisigID) - ic := &interop.Context{Trigger: trigger.Verification} + ic := &interop.Context{ + Trigger: trigger.Verification, + Container: container, + } Register(ic) v := ic.SpawnVM() v.LoadScript(buf) @@ -73,10 +77,13 @@ func initCheckMultisigVMNoArgs() *vm.VM { } func initCHECKMULTISIGVM(t *testing.T, n int, ik, is []int) *vm.VM { - v := initCheckMultisigVMNoArgs() - msg := []byte("NEO - An Open Network For Smart Economy") + tx := transaction.New(netmode.UnitTestNet, []byte("NEO - An Open Network For Smart Economy"), 10) + tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}} + tx.Scripts = []transaction.Witness{{}} - pubs, sigs, _, err := initCHECKMULTISIG(msg, n) + v := initCheckMultisigVMNoArgs(tx) + + pubs, sigs, _, err := initCHECKMULTISIG(tx.GetSignedPart(), n) require.NoError(t, err) pubs = subSlice(pubs, ik) @@ -84,7 +91,6 @@ func initCHECKMULTISIGVM(t *testing.T, n int, ik, is []int) *vm.VM { v.Estack().PushVal(sigs) v.Estack().PushVal(pubs) - v.Estack().PushVal(msg) return v } @@ -142,26 +148,20 @@ func testCurveCHECKMULTISIGBad(t *testing.T) { pubs, sigs, _, err := initCHECKMULTISIG(msg, 1) require.NoError(t, err) arr := stackitem.NewArray([]stackitem.Item{stackitem.NewArray(nil)}) + tx := transaction.New(netmode.UnitTestNet, []byte("NEO - An Open Network For Smart Economy"), 10) + tx.Signers = []transaction.Signer{{Account: util.Uint160{1, 2, 3}}} + tx.Scripts = []transaction.Witness{{}} - t.Run("invalid message type", func(t *testing.T) { - v := initCheckMultisigVMNoArgs() - 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() + v := initCheckMultisigVMNoArgs(tx) 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() + v := initCheckMultisigVMNoArgs(tx) v.Estack().PushVal(arr) v.Estack().PushVal(pubs) - v.Estack().PushVal(msg) require.Error(t, v.Run()) }) } diff --git a/pkg/core/interop/crypto/interop.go b/pkg/core/interop/crypto/interop.go index 8a06c7fc3..ebfd1c719 100644 --- a/pkg/core/interop/crypto/interop.go +++ b/pkg/core/interop/crypto/interop.go @@ -6,12 +6,12 @@ import ( ) var ( - ecdsaSecp256r1CheckMultisigID = interopnames.ToID([]byte(interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1)) - neoCryptoCheckSigID = interopnames.ToID([]byte(interopnames.NeoCryptoCheckSig)) + neoCryptoCheckMultisigID = interopnames.ToID([]byte(interopnames.NeoCryptoCheckMultisig)) + neoCryptoCheckSigID = interopnames.ToID([]byte(interopnames.NeoCryptoCheckSig)) ) var cryptoInterops = []interop.Function{ - {ID: ecdsaSecp256r1CheckMultisigID, Func: ECDSASecp256r1CheckMultisig}, + {ID: neoCryptoCheckMultisigID, Func: ECDSASecp256r1CheckMultisig}, {ID: neoCryptoCheckSigID, Func: ECDSASecp256r1CheckSig}, } diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index 8e043beba..96f7e0ddd 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -2,43 +2,43 @@ package interopnames // Names of all used interops. const ( - SystemCallbackCreate = "System.Callback.Create" - SystemCallbackCreateFromMethod = "System.Callback.CreateFromMethod" - SystemCallbackCreateFromSyscall = "System.Callback.CreateFromSyscall" - SystemCallbackInvoke = "System.Callback.Invoke" - SystemContractCall = "System.Contract.Call" - SystemContractCallNative = "System.Contract.CallNative" - SystemContractCreateMultisigAccount = "System.Contract.CreateMultisigAccount" - SystemContractCreateStandardAccount = "System.Contract.CreateStandardAccount" - SystemContractIsStandard = "System.Contract.IsStandard" - SystemContractGetCallFlags = "System.Contract.GetCallFlags" - SystemContractNativeOnPersist = "System.Contract.NativeOnPersist" - SystemContractNativePostPersist = "System.Contract.NativePostPersist" - SystemIteratorCreate = "System.Iterator.Create" - SystemIteratorNext = "System.Iterator.Next" - SystemIteratorValue = "System.Iterator.Value" - SystemRuntimeCheckWitness = "System.Runtime.CheckWitness" - SystemRuntimeGasLeft = "System.Runtime.GasLeft" - SystemRuntimeGetCallingScriptHash = "System.Runtime.GetCallingScriptHash" - SystemRuntimeGetEntryScriptHash = "System.Runtime.GetEntryScriptHash" - SystemRuntimeGetExecutingScriptHash = "System.Runtime.GetExecutingScriptHash" - SystemRuntimeGetInvocationCounter = "System.Runtime.GetInvocationCounter" - SystemRuntimeGetNotifications = "System.Runtime.GetNotifications" - SystemRuntimeGetScriptContainer = "System.Runtime.GetScriptContainer" - SystemRuntimeGetTime = "System.Runtime.GetTime" - SystemRuntimeGetTrigger = "System.Runtime.GetTrigger" - SystemRuntimeLog = "System.Runtime.Log" - SystemRuntimeNotify = "System.Runtime.Notify" - SystemRuntimePlatform = "System.Runtime.Platform" - SystemStorageDelete = "System.Storage.Delete" - SystemStorageFind = "System.Storage.Find" - SystemStorageGet = "System.Storage.Get" - SystemStorageGetContext = "System.Storage.GetContext" - SystemStorageGetReadOnlyContext = "System.Storage.GetReadOnlyContext" - SystemStoragePut = "System.Storage.Put" - SystemStorageAsReadOnly = "System.Storage.AsReadOnly" - NeoCryptoCheckMultisigWithECDsaSecp256r1 = "Neo.Crypto.CheckMultisigWithECDsaSecp256r1" - NeoCryptoCheckSig = "Neo.Crypto.CheckSig" + SystemCallbackCreate = "System.Callback.Create" + SystemCallbackCreateFromMethod = "System.Callback.CreateFromMethod" + SystemCallbackCreateFromSyscall = "System.Callback.CreateFromSyscall" + SystemCallbackInvoke = "System.Callback.Invoke" + SystemContractCall = "System.Contract.Call" + SystemContractCallNative = "System.Contract.CallNative" + SystemContractCreateMultisigAccount = "System.Contract.CreateMultisigAccount" + SystemContractCreateStandardAccount = "System.Contract.CreateStandardAccount" + SystemContractIsStandard = "System.Contract.IsStandard" + SystemContractGetCallFlags = "System.Contract.GetCallFlags" + SystemContractNativeOnPersist = "System.Contract.NativeOnPersist" + SystemContractNativePostPersist = "System.Contract.NativePostPersist" + SystemIteratorCreate = "System.Iterator.Create" + SystemIteratorNext = "System.Iterator.Next" + SystemIteratorValue = "System.Iterator.Value" + SystemRuntimeCheckWitness = "System.Runtime.CheckWitness" + SystemRuntimeGasLeft = "System.Runtime.GasLeft" + SystemRuntimeGetCallingScriptHash = "System.Runtime.GetCallingScriptHash" + SystemRuntimeGetEntryScriptHash = "System.Runtime.GetEntryScriptHash" + SystemRuntimeGetExecutingScriptHash = "System.Runtime.GetExecutingScriptHash" + SystemRuntimeGetInvocationCounter = "System.Runtime.GetInvocationCounter" + SystemRuntimeGetNotifications = "System.Runtime.GetNotifications" + SystemRuntimeGetScriptContainer = "System.Runtime.GetScriptContainer" + SystemRuntimeGetTime = "System.Runtime.GetTime" + SystemRuntimeGetTrigger = "System.Runtime.GetTrigger" + SystemRuntimeLog = "System.Runtime.Log" + SystemRuntimeNotify = "System.Runtime.Notify" + SystemRuntimePlatform = "System.Runtime.Platform" + SystemStorageDelete = "System.Storage.Delete" + SystemStorageFind = "System.Storage.Find" + SystemStorageGet = "System.Storage.Get" + SystemStorageGetContext = "System.Storage.GetContext" + SystemStorageGetReadOnlyContext = "System.Storage.GetReadOnlyContext" + SystemStoragePut = "System.Storage.Put" + SystemStorageAsReadOnly = "System.Storage.AsReadOnly" + NeoCryptoCheckMultisig = "Neo.Crypto.CheckMultisig" + NeoCryptoCheckSig = "Neo.Crypto.CheckSig" ) var names = []string{ @@ -77,6 +77,6 @@ var names = []string{ SystemStorageGetReadOnlyContext, SystemStoragePut, SystemStorageAsReadOnly, - NeoCryptoCheckMultisigWithECDsaSecp256r1, + NeoCryptoCheckMultisig, NeoCryptoCheckSig, } diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 4a91c0e95..ee30a429b 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -75,7 +75,7 @@ var systemInterops = []interop.Function{ } var neoInterops = []interop.Function{ - {Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256r1, Func: crypto.ECDSASecp256r1CheckMultisig, Price: 0, ParamCount: 3}, + {Name: interopnames.NeoCryptoCheckMultisig, Func: crypto.ECDSASecp256r1CheckMultisig, Price: 0, ParamCount: 2}, {Name: interopnames.NeoCryptoCheckSig, Func: crypto.ECDSASecp256r1CheckSig, Price: fee.ECDSAVerifyPrice, ParamCount: 2}, } diff --git a/pkg/core/oracle_test.go b/pkg/core/oracle_test.go index 968296a3b..332a5a338 100644 --- a/pkg/core/oracle_test.go +++ b/pkg/core/oracle_test.go @@ -97,9 +97,9 @@ func TestCreateResponseTx(t *testing.T) { bc.SetOracle(orc) tx, err := orc.CreateResponseTx(int64(req.GasForResponse), 1, resp) require.NoError(t, err) - assert.Equal(t, 167, tx.Size()) - assert.Equal(t, int64(2216640), tx.NetworkFee) - assert.Equal(t, int64(97783360), tx.SystemFee) + assert.Equal(t, 166, tx.Size()) + assert.Equal(t, int64(2215610), tx.NetworkFee) + assert.Equal(t, int64(97784390), tx.SystemFee) } func TestOracle_InvalidWallet(t *testing.T) { diff --git a/pkg/core/util_test.go b/pkg/core/util_test.go index 8772038ef..4cd46e6ca 100644 --- a/pkg/core/util_test.go +++ b/pkg/core/util_test.go @@ -17,14 +17,14 @@ func TestGenesisBlockMainNet(t *testing.T) { block, err := createGenesisBlock(cfg.ProtocolConfiguration) require.NoError(t, err) - expect := "d71dfebcc59d42b2f3b3f0e0d6b3b77a4880276db1df92c08c7c1bac94bece35" + expect := "de3bfe3e328af04d48f62bd7a9c533641cc0e1fb6a7741c5119d6a6eaedc5269" assert.Equal(t, expect, block.Hash().StringLE()) } func TestGetConsensusAddressMainNet(t *testing.T) { var ( - consensusAddr = "NiVihDFvZacZhujTWkBhRz32UDuNRp416f" - consensusScript = "f7b4d00143932f3b6243cfc06cb4a68f22c739e2" + consensusAddr = "NSX179gdoQmF8nu34rQdL4dYAfdCQhHtQS" + consensusScript = "4870eaa62eee7c76b76d2ae933d4c027f5f5c77d" ) cfg, err := config.Load("../../config", netmode.MainNet) diff --git a/pkg/interop/crypto/crypto.go b/pkg/interop/crypto/crypto.go index 2cea41e56..726a3e2cf 100644 --- a/pkg/interop/crypto/crypto.go +++ b/pkg/interop/crypto/crypto.go @@ -8,14 +8,15 @@ import ( "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" ) -// ECDSASecp256r1CheckMultisig checks multiple ECDSA signatures at once. It uses -// `Neo.Crypto.CheckMultisigWithECDsaSecp256r1` syscall. -func ECDSASecp256r1CheckMultisig(msg []byte, pubs []interop.PublicKey, sigs []interop.Signature) bool { - return neogointernal.Syscall3("Neo.Crypto.CheckMultisigWithECDsaSecp256r1", msg, pubs, sigs).(bool) +// CheckMultisig checks that script container (transaction) is signed by multiple +// ECDSA keys at once. It uses `Neo.Crypto.CheckMultisig` syscall. +func CheckMultisig(pubs []interop.PublicKey, sigs []interop.Signature) bool { + return neogointernal.Syscall2("Neo.Crypto.CheckMultisig", pubs, sigs).(bool) } -// CheckSig checks that sig is correct script-container's signature for a given pub -// (serialized public key). It uses `Neo.Crypto.CheckSig` syscall. +// CheckSig checks that sig is correct signature of the script container +// (transaction) for a given pub (serialized public key). It uses +// `Neo.Crypto.CheckSig` syscall. func CheckSig(pub interop.PublicKey, sig interop.Signature) bool { return neogointernal.Syscall2("Neo.Crypto.CheckSig", pub, sig).(bool) } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index c817561df..2a5c18335 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -60,8 +60,8 @@ type rpcTestCase struct { } const testContractHash = "1e1c3024bd955ff3baf7cb92e3b7608c7bb3712b" -const deploymentTxHash = "9b14d575ae0f45a4e2765929a28a118693428edb8143615cc2ae58d5039d1c38" -const genesisBlockHash = "9e7cf6fcfc8d0d6831fac75fa895535a5f1960f45a34754b57bff4d4929635c5" +const deploymentTxHash = "7cf43b182dee2e8bd2c5209cd230799aeba1b5b13000db682d917c89eacd1eae" +const genesisBlockHash = "d237e3500d8b4cf0df3fd9c4c053016afae141207a6c732303bdd91aff444ecc" const verifyContractHash = "5bb4bac40e961e334ba7bd36d2496010f67e246e" const verifyContractAVM = "VwMAQS1RCDAhcAwUVVQtU+0PVUb61E1umZEoZwIvzl7bMHFoE87bKGnbKJdA" diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 1cb4dcb1878e19d4e2d8d1b52f0ff8c7a12c5540..f6ff51a72b692ac24d2516f90ea290e9adeddb36 100644 GIT binary patch delta 3994 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 delta 4047 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 e`bY