From b5185d5d1af8c4f45fc20bab23986e35b8c5c225 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 22 Jul 2020 10:46:28 +0300 Subject: [PATCH 1/6] core: refactor System.Storage.Get[ReadOnly]Context interops Part of #1055. Split methods, as they have a lot of common code. This also fixex nil error of storageGetReadOnlyContext in case when contract does not have storage. --- pkg/core/interop_system.go | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 17f6b00bd..745b20a9b 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -333,6 +333,17 @@ func storageGet(ic *interop.Context, v *vm.VM) error { // storageGetContext returns storage context (scripthash). func storageGetContext(ic *interop.Context, v *vm.VM) error { + return storageGetContextInternal(ic, v, false) +} + +// storageGetReadOnlyContext returns read-only context (scripthash). +func storageGetReadOnlyContext(ic *interop.Context, v *vm.VM) error { + return storageGetContextInternal(ic, v, true) +} + +// storageGetContextInternal is internal version of storageGetContext and +// storageGetReadOnlyContext which allows to specify ReadOnly context flag. +func storageGetContextInternal(ic *interop.Context, v *vm.VM, isReadOnly bool) error { contract, err := ic.DAO.GetContractState(v.GetCurrentScriptHash()) if err != nil { return err @@ -342,24 +353,7 @@ func storageGetContext(ic *interop.Context, v *vm.VM) error { } sc := &StorageContext{ ID: contract.ID, - ReadOnly: false, - } - v.Estack().PushVal(stackitem.NewInterop(sc)) - return nil -} - -// storageGetReadOnlyContext returns read-only context (scripthash). -func storageGetReadOnlyContext(ic *interop.Context, v *vm.VM) error { - contract, err := ic.DAO.GetContractState(v.GetCurrentScriptHash()) - if err != nil { - return err - } - if !contract.HasStorage() { - return err - } - sc := &StorageContext{ - ID: contract.ID, - ReadOnly: true, + ReadOnly: isReadOnly, } v.Estack().PushVal(stackitem.NewInterop(sc)) return nil From 47eadcdf2a2e073dc422eb33ba3c63e22ee3bf19 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 22 Jul 2020 11:05:10 +0300 Subject: [PATCH 2/6] core: adjust System.Storage.Put interop Part of #1055. Maximum storage key len has been changed. Also added maximum storage value len restriction. --- pkg/core/interop_system.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 745b20a9b..2b17b2d6f 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -23,7 +23,10 @@ import ( const ( // MaxStorageKeyLen is the maximum length of a key for storage items. - MaxStorageKeyLen = 1024 + MaxStorageKeyLen = 64 + // MaxStorageValueLen is the maximum length of a value for storage items. + // It is set to be the maximum value for uint16. + MaxStorageValueLen = 65535 // MaxTraceableBlocks is the maximum number of blocks before current chain // height we're able to give information about. MaxTraceableBlocks = transaction.MaxValidUntilBlockIncrement @@ -363,6 +366,9 @@ func putWithContextAndFlags(ic *interop.Context, v *vm.VM, stc *StorageContext, if len(key) > MaxStorageKeyLen { return errors.New("key is too big") } + if len(value) > MaxStorageValueLen { + return errors.New("value is too big") + } if stc.ReadOnly { return errors.New("StorageContext is read only") } From 84bf87df52ded0f20d071c0d1544d0c806e311b0 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 22 Jul 2020 11:22:58 +0300 Subject: [PATCH 3/6] core: adjust System.Storage.PutEx interop Part of #1055. Added System.Storage.PutEx to compiler. Added StorageFlag in order to denote whether item is constant or not. --- pkg/compiler/syscall.go | 1 + pkg/core/interop_system.go | 13 ++++++++++++- pkg/interop/storage/storage.go | 7 +++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index 276acb26b..910847a94 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -77,5 +77,6 @@ var syscalls = map[string]map[string]Syscall{ "GetContext": {"System.Storage.GetContext", false}, "GetReadOnlyContext": {"System.Storage.GetReadOnlyContext", false}, "Put": {"System.Storage.Put", false}, + "PutEx": {"System.Storage.PutEx", false}, }, } diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 2b17b2d6f..68b47a97b 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -41,6 +41,17 @@ type StorageContext struct { ReadOnly bool } +// StorageFlag represents storage flag which denotes whether the stored value is +// a constant. +type StorageFlag byte + +const ( + // None is a storage flag for non-constant items. + None StorageFlag = 0 + // Constant is a storage flag for constant items. + Constant StorageFlag = 0x01 +) + // getBlockHashFromElement converts given vm.Element to block hash using given // Blockchainer if needed. Interop functions accept both block numbers and // block hashes as parameters, thus this function is needed. @@ -404,7 +415,7 @@ func storagePutInternal(ic *interop.Context, v *vm.VM, getFlag bool) error { if getFlag { flag = int(v.Estack().Pop().BigInt().Int64()) } - return putWithContextAndFlags(ic, v, stc, key, value, flag == 1) + return putWithContextAndFlags(ic, v, stc, key, value, int(Constant)&flag != 0) } // storagePut puts key-value pair into the storage. diff --git a/pkg/interop/storage/storage.go b/pkg/interop/storage/storage.go index eb6e1faa6..8ee4ca0e2 100644 --- a/pkg/interop/storage/storage.go +++ b/pkg/interop/storage/storage.go @@ -37,6 +37,13 @@ func GetReadOnlyContext() Context { return Context{} } // runtime.Serialize. This function uses `System.Storage.Put` syscall. func Put(ctx Context, key interface{}, value interface{}) {} +// PutEx is an advanced version of Put which saves given value with given key +// and given ReadOnly flag in the storage using given Context. `flag` argument +// can either be odd for constant storage items or even for variable storage items. +// Refer to Put function description for details on how to pass the remaining +// arguments. This function uses `System.Storage.PutEx` syscall. +func PutEx(ctx Context, key interface{}, value interface{}, flag int64) {} + // Get retrieves value stored for the given key using given Context. See Put // documentation on possible key and value types. If the value is not present in // the database it returns nil. This function uses `System.Storage.Get` syscall. From b8d82b49ec5b0de06afda9a843c5464aab4a8390 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 22 Jul 2020 12:22:20 +0300 Subject: [PATCH 4/6] core: add Neo.Crypto.RIPEMD160 interop Closes #1193. --- pkg/compiler/syscall.go | 1 + pkg/core/interop/crypto/hash.go | 8 ++++++++ pkg/core/interop/crypto/hash_test.go | 16 ++++++++++++++++ pkg/core/interop/crypto/interop.go | 7 +++++++ pkg/core/interops.go | 1 + pkg/interop/crypto/crypto.go | 5 +++++ 6 files changed, 38 insertions(+) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index 910847a94..026cb1b8b 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -35,6 +35,7 @@ var syscalls = map[string]map[string]Syscall{ "ECDSASecp256k1CheckMultisig": {"Neo.Crypto.CheckMultisigWithECDsaSecp256k1", false}, "ECDsaSecp256r1Verify": {"Neo.Crypto.VerifyWithECDsaSecp256r1", false}, "ECDSASecp256r1CheckMultisig": {"Neo.Crypto.CheckMultisigWithECDsaSecp256r1", false}, + "RIPEMD160": {"Neo.Crypto.RIPEMD160", false}, }, "enumerator": { "Concat": {"System.Enumerator.Concat", false}, diff --git a/pkg/core/interop/crypto/hash.go b/pkg/core/interop/crypto/hash.go index 836d7d693..30936706f 100644 --- a/pkg/core/interop/crypto/hash.go +++ b/pkg/core/interop/crypto/hash.go @@ -13,3 +13,11 @@ func Sha256(ic *interop.Context, v *vm.VM) error { v.Estack().PushVal(h) return nil } + +// RipeMD160 returns RipeMD160 hash of the data. +func RipeMD160(ic *interop.Context, v *vm.VM) error { + msg := getMessage(ic, v.Estack().Pop().Item()) + h := hash.RipeMD160(msg).BytesBE() + v.Estack().PushVal(h) + return nil +} diff --git a/pkg/core/interop/crypto/hash_test.go b/pkg/core/interop/crypto/hash_test.go index 479eb1742..566bcfb6e 100644 --- a/pkg/core/interop/crypto/hash_test.go +++ b/pkg/core/interop/crypto/hash_test.go @@ -28,3 +28,19 @@ func TestSHA256(t *testing.T) { assert.Equal(t, 1, v.Estack().Len()) assert.Equal(t, res, hex.EncodeToString(v.Estack().Pop().Bytes())) } + +func TestRIPEMD160(t *testing.T) { + // 0x0100 hashes to 213492c0c6fc5d61497cf17249dd31cd9964b8a3 + res := "213492c0c6fc5d61497cf17249dd31cd9964b8a3" + buf := io.NewBufBinWriter() + emit.Bytes(buf.BinWriter, []byte{1, 0}) + emit.Syscall(buf.BinWriter, "Neo.Crypto.RIPEMD160") + prog := buf.Bytes() + v := vm.New() + ic := &interop.Context{Trigger: trigger.Verification} + v.RegisterInteropGetter(GetInterop(ic)) + v.Load(prog) + require.NoError(t, v.Run()) + assert.Equal(t, 1, v.Estack().Len()) + assert.Equal(t, res, hex.EncodeToString(v.Estack().Pop().Bytes())) +} diff --git a/pkg/core/interop/crypto/interop.go b/pkg/core/interop/crypto/interop.go index 77dc58479..aff3410d4 100644 --- a/pkg/core/interop/crypto/interop.go +++ b/pkg/core/interop/crypto/interop.go @@ -10,6 +10,7 @@ var ( ecdsaSecp256r1VerifyID = emit.InteropNameToID([]byte("Neo.Crypto.VerifyWithECDsaSecp256r1")) ecdsaSecp256r1CheckMultisigID = emit.InteropNameToID([]byte("Neo.Crypto.CheckMultisigWithECDsaSecp256r1")) sha256ID = emit.InteropNameToID([]byte("Neo.Crypto.SHA256")) + ripemd160ID = emit.InteropNameToID([]byte("Neo.Crypto.RIPEMD160")) ) // GetInterop returns interop getter for crypto-related stuff. @@ -34,6 +35,12 @@ func GetInterop(ic *interop.Context) func(uint32) *vm.InteropFuncPrice { return Sha256(ic, v) }, } + case ripemd160ID: + return &vm.InteropFuncPrice{ + Func: func(v *vm.VM) error { + return RipeMD160(ic, v) + }, + } default: return nil } diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 750713ce5..f7e7f1cb6 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -147,6 +147,7 @@ var neoInterops = []interop.Function{ {Name: "Neo.Crypto.CheckMultisigWithECDsaSecp256r1", Func: crypto.ECDSASecp256r1CheckMultisig, Price: 0}, {Name: "Neo.Crypto.CheckMultisigWithECDsaSecp256k1", Func: crypto.ECDSASecp256k1CheckMultisig, Price: 0}, {Name: "Neo.Crypto.SHA256", Func: crypto.Sha256, Price: 1000000}, + {Name: "Neo.Crypto.RIPEMD160", Func: crypto.RipeMD160, Price: 1000000}, {Name: "Neo.Native.Deploy", Func: native.Deploy, Price: 0, AllowedTriggers: trigger.Application, RequiredFlags: smartcontract.AllowModifyStates}, } diff --git a/pkg/interop/crypto/crypto.go b/pkg/interop/crypto/crypto.go index 6e894465b..4f986f128 100644 --- a/pkg/interop/crypto/crypto.go +++ b/pkg/interop/crypto/crypto.go @@ -8,6 +8,11 @@ func SHA256(b []byte) []byte { return nil } +// RIPEMD160 computes RIPEMD160 hash of b. It uses `Neo.Crypto.RIPEMD160` syscall. +func RIPEMD160(b []byte) []byte { + return nil +} + // 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 []byte, sig []byte) bool { From 2bbe2185475e39fa345310cab65c1afa66b24ad7 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 22 Jul 2020 16:19:18 +0300 Subject: [PATCH 5/6] compiler: move SHA256 from builtins to syscalls Now it can be processed as a normal syscall. --- pkg/compiler/analysis.go | 2 +- pkg/compiler/codegen.go | 2 -- pkg/compiler/syscall.go | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 955ded585..41526aba0 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -16,7 +16,7 @@ var ( goBuiltins = []string{"len", "append", "panic"} // Custom builtin utility functions. customBuiltins = []string{ - "SHA256", "AppCall", + "AppCall", "FromAddress", "Equals", "ToBool", "ToByteArray", "ToInteger", } diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index c63499bd1..eed015ce1 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -1220,8 +1220,6 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { typ = stackitem.BooleanT } c.emitConvert(typ) - case "SHA256": - emit.Syscall(c.prog.BinWriter, "Neo.Crypto.SHA256") case "AppCall": c.emitReverse(len(expr.Args)) buf := c.getByteArray(expr.Args[0]) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index 026cb1b8b..6b482ef80 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -36,6 +36,7 @@ var syscalls = map[string]map[string]Syscall{ "ECDsaSecp256r1Verify": {"Neo.Crypto.VerifyWithECDsaSecp256r1", false}, "ECDSASecp256r1CheckMultisig": {"Neo.Crypto.CheckMultisigWithECDsaSecp256r1", false}, "RIPEMD160": {"Neo.Crypto.RIPEMD160", false}, + "SHA256": {"Neo.Crypto.SHA256", false}, }, "enumerator": { "Concat": {"System.Enumerator.Concat", false}, From 6e44499cec27f0e4ba501cc81b713529c5f3c312 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 22 Jul 2020 12:36:28 +0300 Subject: [PATCH 6/6] core: adjust Neo.Native.Deploy interop Part of #1055. It could be that context.Block is nill. --- pkg/core/native/interop.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index c36200acf..f2e3e487a 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -12,7 +12,7 @@ import ( // Deploy deploys native contract. func Deploy(ic *interop.Context, _ *vm.VM) error { - if ic.Block.Index != 0 { + if ic.Block == nil || ic.Block.Index != 0 { return errors.New("native contracts can be deployed only at 0 block") }