diff --git a/pkg/core/native/crypto.go b/pkg/core/native/crypto.go index b377dd82e..0eb4d6add 100644 --- a/pkg/core/native/crypto.go +++ b/pkg/core/native/crypto.go @@ -2,6 +2,7 @@ package native import ( "crypto/elliptic" + "encoding/binary" "errors" "fmt" @@ -14,6 +15,7 @@ import ( "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" + "github.com/twmb/murmur3" ) // Crypto represents CryptoLib contract. @@ -46,6 +48,12 @@ func newCrypto() *Crypto { md = newMethodAndPrice(c.ripemd160, 1<<15, callflag.NoneFlag) c.AddMethod(md, desc) + desc = newDescriptor("murmur32", smartcontract.ByteArrayType, + manifest.NewParameter("data", smartcontract.ByteArrayType), + manifest.NewParameter("seed", smartcontract.IntegerType)) + md = newMethodAndPrice(c.murmur32, 1<<13, callflag.NoneFlag) + c.AddMethod(md, desc) + desc = newDescriptor("verifyWithECDsa", smartcontract.BoolType, manifest.NewParameter("message", smartcontract.ByteArrayType), manifest.NewParameter("pubkey", smartcontract.ByteArrayType), @@ -72,6 +80,18 @@ func (c *Crypto) ripemd160(_ *interop.Context, args []stackitem.Item) stackitem. return stackitem.NewByteArray(hash.RipeMD160(bs).BytesBE()) } +func (c *Crypto) murmur32(_ *interop.Context, args []stackitem.Item) stackitem.Item { + bs, err := args[0].TryBytes() + if err != nil { + panic(err) + } + seed := toUint32(args[1]) + h := murmur3.SeedSum32(seed, bs) + result := make([]byte, 4) + binary.LittleEndian.PutUint32(result, h) + return stackitem.NewByteArray(result) +} + func (c *Crypto) verifyWithECDsa(_ *interop.Context, args []stackitem.Item) stackitem.Item { msg, err := args[0].TryBytes() if err != nil { diff --git a/pkg/core/native/crypto_test.go b/pkg/core/native/crypto_test.go index 6c20854d2..727c160a9 100644 --- a/pkg/core/native/crypto_test.go +++ b/pkg/core/native/crypto_test.go @@ -1,6 +1,7 @@ package native import ( + "encoding/binary" "encoding/hex" "math" "math/big" @@ -43,6 +44,26 @@ func TestRIPEMD160(t *testing.T) { }) } +func TestMurmur32(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.murmur32(ic, []stackitem.Item{stackitem.NewInterop(nil), stackitem.Make(5)}) + }) + }) + t.Run("good", func(t *testing.T) { + // Example from the C# node: + // https://github.com/neo-project/neo/blob/2a64c1cc809d1ff4b3a573c7c22bffbbf69a738b/tests/neo.UnitTests/Cryptography/UT_Murmur32.cs#L18 + data := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1} + seed := 10 + expected := make([]byte, 4) + binary.LittleEndian.PutUint32(expected, 378574820) + require.Equal(t, expected, c.murmur32(ic, []stackitem.Item{stackitem.NewByteArray(data), stackitem.Make(seed)}).Value().([]byte)) + }) +} + func TestCryptoLibVerifyWithECDsa(t *testing.T) { t.Run("R1", func(t *testing.T) { testECDSAVerify(t, Secp256r1) diff --git a/pkg/interop/native/crypto/crypto.go b/pkg/interop/native/crypto/crypto.go index 8bf8acf6c..8374cc26d 100644 --- a/pkg/interop/native/crypto/crypto.go +++ b/pkg/interop/native/crypto/crypto.go @@ -32,6 +32,12 @@ func Ripemd160(b []byte) interop.Hash160 { return neogointernal.CallWithToken(Hash, "ripemd160", int(contract.NoneFlag), b).(interop.Hash160) } +// Murmur32 calls `murmur32` method of native CryptoLib contract and computes Murmur32 hash of b +// using the given seed. +func Murmur32(b []byte, seed int) []byte { + return neogointernal.CallWithToken(Hash, "murmur32", int(contract.NoneFlag), b, seed).([]byte) +} + // 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 {