interop: add keccak256 implementation

Port neo-project/neo#2925.

Close #3295

Signed-off-by: Ekaterina Pavlova <ekt@morphbits.io>
This commit is contained in:
Ekaterina Pavlova 2024-01-25 16:32:03 +03:00
parent fa1c07e7e6
commit 1840c057bd
5 changed files with 97 additions and 0 deletions

View file

@ -240,6 +240,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}"}},

View file

@ -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(err)
}
digest := sha3.NewLegacyKeccak256()
_, err = digest.Write(bs)
if err != nil {
panic(err)
}
return stackitem.NewByteArray(digest.Sum(nil))
}
// Metadata implements the Contract interface.
func (c *Crypto) Metadata() *interop.ContractMD {
return &c.ContractMD

View file

@ -30,6 +30,58 @@ func TestSha256(t *testing.T) {
})
}
// TestKeccak256_Compat is a C# node compatibility test with data taken from https://github.com/Jim8y/neo/blob/560d35783e428d31e3681eaa7ee9ed00a8a50d09/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs#L340
func TestKeccak256_Compat(t *testing.T) {
c := newCrypto()
ic := &interop.Context{VM: vm.New()}
t.Run("good", func(t *testing.T) {
testCases := []struct {
name string
input []byte
expectedHash string
}{
{"good", []byte{1, 0}, "628bf3596747d233f1e6533345700066bf458fa48daedaf04a7be6c392902476"},
{"hello world", []byte("Hello, World!"), "acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f"},
{"keccak", []byte("Keccak"), "868c016b666c7d3698636ee1bd023f3f065621514ab61bf26f062c175fdbe7f2"},
{"cryptography", []byte("Cryptography"), "53d49d225dd2cfe77d8c5e2112bcc9efe77bea1c7aa5e5ede5798a36e99e2d29"},
{"testing123", []byte("Testing123"), "3f82db7b16b0818a1c6b2c6152e265f682d5ebcf497c9aad776ad38bc39cb6ca"},
{"long string", []byte("This is a longer string for Keccak256 testing purposes."), "24115e5c2359f85f6840b42acd2f7ea47bc239583e576d766fa173bf711bdd2f"},
{"blank string", []byte(""), "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := c.keccak256(ic, []stackitem.Item{stackitem.NewByteArray(tc.input)}).Value().([]byte)
outputHashHex := hex.EncodeToString(result)
require.Equal(t, tc.expectedHash, outputHashHex)
})
}
})
t.Run("errors", func(t *testing.T) {
errCases := []struct {
name string
item stackitem.Item
}{
{
name: "Null item",
item: stackitem.Null{},
},
{
name: "not a byte array",
item: stackitem.NewArray([]stackitem.Item{stackitem.NewBool(true)}),
},
}
for _, tc := range errCases {
t.Run(tc.name, func(t *testing.T) {
require.Panics(t, func() {
_ = c.keccak256(ic, []stackitem.Item{tc.item})
}, "keccak256 should panic with incorrect argument types")
})
}
})
}
func TestRIPEMD160(t *testing.T) {
c := newCrypto()
ic := &interop.Context{VM: vm.New()}

View file

@ -115,6 +115,25 @@ func TestCryptolib_TestBls12381Add_Compat(t *testing.T) {
hex.EncodeToString(arr[:]))
}
func TestKeccak256_Compat(t *testing.T) {
c := newCryptolibClient(t)
in := []byte("Keccak")
expected := "868c016b666c7d3698636ee1bd023f3f065621514ab61bf26f062c175fdbe7f2"
script := io.NewBufBinWriter()
emit.AppCall(script.BinWriter, c.Hash, "keccak256", callflag.All, in)
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())
actual := hex.EncodeToString(itm.Value().([]byte))
require.Equal(t, expected, actual)
}
func TestCryptolib_TestBls12381Mul_Compat(t *testing.T) {
c := newCryptolibClient(t)

View file

@ -92,3 +92,9 @@ 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)
}