From 24a26ea7eac52005d4ad3fec3d27093a29246235 Mon Sep 17 00:00:00 2001 From: Ekaterina Pavlova Date: Thu, 25 Jan 2024 16:32:03 +0300 Subject: [PATCH] interop: add keccak256 implementation Port neo-project/neo#2925. Close #3295 Signed-off-by: Ekaterina Pavlova --- pkg/compiler/native_test.go | 1 + pkg/core/native/crypto.go | 19 +++++++++++ pkg/core/native/crypto_test.go | 34 +++++++++++++++++++ pkg/core/native/native_test/cryptolib_test.go | 20 +++++++++++ pkg/interop/native/crypto/crypto.go | 5 +++ 5 files changed, 79 insertions(+) diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go index ba0add4e4e..2fbb5176d6 100644 --- a/pkg/compiler/native_test.go +++ b/pkg/compiler/native_test.go @@ -239,6 +239,7 @@ func TestNativeHelpersCompile(t *testing.T) { {"bls12381Add", []string{"crypto.Bls12381Point{}", "crypto.Bls12381Point{}"}}, {"bls12381Mul", []string{"crypto.Bls12381Point{}", "[]byte{1, 2, 3}", "true"}}, {"bls12381Pairing", []string{"crypto.Bls12381Point{}", "crypto.Bls12381Point{}"}}, + {"keccak256", []string{"[]byte{1, 2, 3}"}}, }) runNativeTestCases(t, cs.Std.ContractMD, "std", []nativeTestCase{ {"serialize", []string{"[]byte{1, 2, 3}"}}, diff --git a/pkg/core/native/crypto.go b/pkg/core/native/crypto.go index dc025d54aa..f3e1a71448 100644 --- a/pkg/core/native/crypto.go +++ b/pkg/core/native/crypto.go @@ -20,6 +20,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/twmb/murmur3" + "golang.org/x/crypto/sha3" ) // Crypto represents CryptoLib contract. @@ -101,6 +102,10 @@ func newCrypto() *Crypto { md = newMethodAndPrice(c.bls12381Pairing, 1<<23, callflag.NoneFlag) c.AddMethod(md, desc) + desc = newDescriptor("keccak256", smartcontract.ByteArrayType, + manifest.NewParameter("data", smartcontract.ByteArrayType)) + md = newMethodAndPrice(c.keccak256, 1<<15, callflag.NoneFlag) + c.AddMethod(md, desc) return c } @@ -285,6 +290,20 @@ func (c *Crypto) bls12381Pairing(_ *interop.Context, args []stackitem.Item) stac return stackitem.NewInterop(p) } +func (c *Crypto) keccak256(_ *interop.Context, args []stackitem.Item) stackitem.Item { + bs, err := args[0].TryBytes() + if err != nil { + panic(fmt.Errorf("failed to convert stack item to bytes: %w", err)) + } + + digest := sha3.NewLegacyKeccak256() + _, err = digest.Write(bs) + if err != nil { + panic(fmt.Errorf("failed to write to keccak256 digest: %w", err)) + } + return stackitem.NewByteArray(digest.Sum(nil)) +} + // Metadata implements the Contract interface. func (c *Crypto) Metadata() *interop.ContractMD { return &c.ContractMD diff --git a/pkg/core/native/crypto_test.go b/pkg/core/native/crypto_test.go index 47688e2c2e..50a711fcd5 100644 --- a/pkg/core/native/crypto_test.go +++ b/pkg/core/native/crypto_test.go @@ -29,6 +29,40 @@ func TestSha256(t *testing.T) { require.Equal(t, "47dc540c94ceb704a23875c11273e16bb0b8a87aed84de911f2133568115f254", hex.EncodeToString(c.sha256(ic, []stackitem.Item{stackitem.NewByteArray([]byte{1, 0})}).Value().([]byte))) }) } +func TestKeccak256_Compat(t *testing.T) { + c := newCrypto() + ic := &interop.Context{VM: vm.New()} + + testCases := []struct { + name string + input []byte + expectedHash string + expectPanic bool + }{ + {"bad arg type", nil, "", true}, + {"good", []byte{1, 0}, "628bf3596747d233f1e6533345700066bf458fa48daedaf04a7be6c392902476", false}, + {"hello world", []byte("Hello, World!"), "acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f", false}, + {"keccak", []byte("Keccak"), "868c016b666c7d3698636ee1bd023f3f065621514ab61bf26f062c175fdbe7f2", false}, + {"cryptography", []byte("Cryptography"), "53d49d225dd2cfe77d8c5e2112bcc9efe77bea1c7aa5e5ede5798a36e99e2d29", false}, + {"testing123", []byte("Testing123"), "3f82db7b16b0818a1c6b2c6152e265f682d5ebcf497c9aad776ad38bc39cb6ca", false}, + {"long string", []byte("This is a longer string for Keccak256 testing purposes."), "24115e5c2359f85f6840b42acd2f7ea47bc239583e576d766fa173bf711bdd2f", false}, + {"blank string", []byte(""), "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", false}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if tc.expectPanic { + require.Panics(t, func() { + _ = c.keccak256(ic, []stackitem.Item{stackitem.NewInterop(nil)}) + }) + } else { + result := c.keccak256(ic, []stackitem.Item{stackitem.NewByteArray(tc.input)}).Value().([]byte) + outputHashHex := hex.EncodeToString(result) + require.Equal(t, tc.expectedHash, outputHashHex) + } + }) + } +} func TestRIPEMD160(t *testing.T) { c := newCrypto() diff --git a/pkg/core/native/native_test/cryptolib_test.go b/pkg/core/native/native_test/cryptolib_test.go index 8c42108101..04259ed59e 100644 --- a/pkg/core/native/native_test/cryptolib_test.go +++ b/pkg/core/native/native_test/cryptolib_test.go @@ -115,6 +115,26 @@ func TestCryptolib_TestBls12381Add_Compat(t *testing.T) { hex.EncodeToString(arr[:])) } +func TestKeccak256_Compat(t *testing.T) { + c := newCryptolibClient(t) + + inputData := []byte("Keccak") + + expectedHashHex := "868c016b666c7d3698636ee1bd023f3f065621514ab61bf26f062c175fdbe7f2" + + script := io.NewBufBinWriter() + emit.AppCall(script.BinWriter, c.Hash, "keccak256", callflag.All, inputData) + stack, err := c.TestInvokeScript(t, script.Bytes(), c.Signers) + + require.NoError(t, err) + require.Equal(t, 1, stack.Len()) + itm := stack.Pop().Item() + require.Equal(t, stackitem.ByteArrayT, itm.Type()) + actualHash := itm.Value().([]byte) + actualHashHex := hex.EncodeToString(actualHash) + require.Equal(t, expectedHashHex, actualHashHex) +} + func TestCryptolib_TestBls12381Mul_Compat(t *testing.T) { c := newCryptolibClient(t) diff --git a/pkg/interop/native/crypto/crypto.go b/pkg/interop/native/crypto/crypto.go index 2d82357c00..5a47ba59dc 100644 --- a/pkg/interop/native/crypto/crypto.go +++ b/pkg/interop/native/crypto/crypto.go @@ -92,3 +92,8 @@ func Bls12381Mul(x Bls12381Point, mul []byte, neg bool) Bls12381Point { func Bls12381Pairing(g1, g2 Bls12381Point) Bls12381Point { return neogointernal.CallWithToken(Hash, "bls12381Pairing", int(contract.NoneFlag), g1, g2).(Bls12381Point) } + +// Keccak256 calls `keccak256` method of native CryptoLib contract and computes Keccak256 hash of b. +func Keccak256(b []byte) interop.Hash256 { + return neogointernal.CallWithToken(Hash, "keccak256", int(contract.NoneFlag), b).(interop.Hash256) +}