Skip to content

Commit cd8c29c

Browse files
interop: add keccak256 implementation
Port neo-project/neo#2925. Close #3295 Signed-off-by: Ekaterina Pavlova <[email protected]>
1 parent ef99a7a commit cd8c29c

File tree

5 files changed

+104
-0
lines changed

5 files changed

+104
-0
lines changed

pkg/compiler/native_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ func TestNativeHelpersCompile(t *testing.T) {
239239
{"bls12381Add", []string{"crypto.Bls12381Point{}", "crypto.Bls12381Point{}"}},
240240
{"bls12381Mul", []string{"crypto.Bls12381Point{}", "[]byte{1, 2, 3}", "true"}},
241241
{"bls12381Pairing", []string{"crypto.Bls12381Point{}", "crypto.Bls12381Point{}"}},
242+
{"keccak256", []string{"[]byte{1, 2, 3}"}},
242243
})
243244
runNativeTestCases(t, cs.Std.ContractMD, "std", []nativeTestCase{
244245
{"serialize", []string{"[]byte{1, 2, 3}"}},

pkg/core/native/crypto.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
2121
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
2222
"github.com/twmb/murmur3"
23+
"golang.org/x/crypto/sha3"
2324
)
2425

2526
// Crypto represents CryptoLib contract.
@@ -101,6 +102,10 @@ func newCrypto() *Crypto {
101102
md = newMethodAndPrice(c.bls12381Pairing, 1<<23, callflag.NoneFlag)
102103
c.AddMethod(md, desc)
103104

105+
desc = newDescriptor("keccak256", smartcontract.ByteArrayType,
106+
manifest.NewParameter("data", smartcontract.ByteArrayType))
107+
md = newMethodAndPrice(c.keccak256, 1<<15, callflag.NoneFlag)
108+
c.AddMethod(md, desc)
104109
return c
105110
}
106111

@@ -285,6 +290,20 @@ func (c *Crypto) bls12381Pairing(_ *interop.Context, args []stackitem.Item) stac
285290
return stackitem.NewInterop(p)
286291
}
287292

293+
func (c *Crypto) keccak256(_ *interop.Context, args []stackitem.Item) stackitem.Item {
294+
bs, err := args[0].TryBytes()
295+
if err != nil {
296+
panic(err)
297+
}
298+
299+
digest := sha3.NewLegacyKeccak256()
300+
_, err = digest.Write(bs)
301+
if err != nil {
302+
panic(err)
303+
}
304+
return stackitem.NewByteArray(digest.Sum(nil))
305+
}
306+
288307
// Metadata implements the Contract interface.
289308
func (c *Crypto) Metadata() *interop.ContractMD {
290309
return &c.ContractMD

pkg/core/native/crypto_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,65 @@ func TestSha256(t *testing.T) {
3030
})
3131
}
3232

33+
// https://github.com/Jim8y/neo/blob/560d35783e428d31e3681eaa7ee9ed00a8a50d09/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs#L340
34+
func TestKeccak256_Compat(t *testing.T) {
35+
c := newCrypto()
36+
ic := &interop.Context{VM: vm.New()}
37+
38+
testCases := []struct {
39+
name string
40+
input []byte
41+
expectedHash string
42+
fail bool
43+
}{
44+
{"bad arg type", nil, "", true},
45+
{"good", []byte{1, 0}, "628bf3596747d233f1e6533345700066bf458fa48daedaf04a7be6c392902476", false},
46+
{"hello world", []byte("Hello, World!"), "acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f", false},
47+
{"keccak", []byte("Keccak"), "868c016b666c7d3698636ee1bd023f3f065621514ab61bf26f062c175fdbe7f2", false},
48+
{"cryptography", []byte("Cryptography"), "53d49d225dd2cfe77d8c5e2112bcc9efe77bea1c7aa5e5ede5798a36e99e2d29", false},
49+
{"testing123", []byte("Testing123"), "3f82db7b16b0818a1c6b2c6152e265f682d5ebcf497c9aad776ad38bc39cb6ca", false},
50+
{"long string", []byte("This is a longer string for Keccak256 testing purposes."), "24115e5c2359f85f6840b42acd2f7ea47bc239583e576d766fa173bf711bdd2f", false},
51+
{"blank string", []byte(""), "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", false},
52+
}
53+
54+
for _, tc := range testCases {
55+
t.Run(tc.name, func(t *testing.T) {
56+
if tc.fail {
57+
require.Panics(t, func() {
58+
_ = c.keccak256(ic, []stackitem.Item{stackitem.NewInterop(nil)})
59+
})
60+
} else {
61+
result := c.keccak256(ic, []stackitem.Item{stackitem.NewByteArray(tc.input)}).Value().([]byte)
62+
outputHashHex := hex.EncodeToString(result)
63+
require.Equal(t, tc.expectedHash, outputHashHex)
64+
}
65+
})
66+
}
67+
t.Run("keccak errors", func(t *testing.T) {
68+
failureTestCases := []struct {
69+
name string
70+
item stackitem.Item
71+
}{
72+
{
73+
name: "Nil item",
74+
item: stackitem.NewInterop(nil),
75+
},
76+
{
77+
name: "Non-byteable type",
78+
item: stackitem.NewArray([]stackitem.Item{stackitem.NewBool(true)}),
79+
},
80+
}
81+
82+
for _, tc := range failureTestCases {
83+
t.Run(tc.name, func(t *testing.T) {
84+
require.Panics(t, func() {
85+
_ = c.keccak256(ic, []stackitem.Item{tc.item})
86+
}, "keccak256 should panic with incorrect argument types")
87+
})
88+
}
89+
})
90+
}
91+
3392
func TestRIPEMD160(t *testing.T) {
3493
c := newCrypto()
3594
ic := &interop.Context{VM: vm.New()}

pkg/core/native/native_test/cryptolib_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,26 @@ func TestCryptolib_TestBls12381Add_Compat(t *testing.T) {
115115
hex.EncodeToString(arr[:]))
116116
}
117117

118+
func TestKeccak256_Compat(t *testing.T) {
119+
c := newCryptolibClient(t)
120+
121+
inputData := []byte("Keccak")
122+
123+
expectedHashHex := "868c016b666c7d3698636ee1bd023f3f065621514ab61bf26f062c175fdbe7f2"
124+
125+
script := io.NewBufBinWriter()
126+
emit.AppCall(script.BinWriter, c.Hash, "keccak256", callflag.All, inputData)
127+
stack, err := c.TestInvokeScript(t, script.Bytes(), c.Signers)
128+
129+
require.NoError(t, err)
130+
require.Equal(t, 1, stack.Len())
131+
itm := stack.Pop().Item()
132+
require.Equal(t, stackitem.ByteArrayT, itm.Type())
133+
actualHash := itm.Value().([]byte)
134+
actualHashHex := hex.EncodeToString(actualHash)
135+
require.Equal(t, expectedHashHex, actualHashHex)
136+
}
137+
118138
func TestCryptolib_TestBls12381Mul_Compat(t *testing.T) {
119139
c := newCryptolibClient(t)
120140

pkg/interop/native/crypto/crypto.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,8 @@ func Bls12381Mul(x Bls12381Point, mul []byte, neg bool) Bls12381Point {
9292
func Bls12381Pairing(g1, g2 Bls12381Point) Bls12381Point {
9393
return neogointernal.CallWithToken(Hash, "bls12381Pairing", int(contract.NoneFlag), g1, g2).(Bls12381Point)
9494
}
95+
96+
// Keccak256 calls `keccak256` method of native CryptoLib contract and computes Keccak256 hash of b.
97+
func Keccak256(b []byte) interop.Hash256 {
98+
return neogointernal.CallWithToken(Hash, "keccak256", int(contract.NoneFlag), b).(interop.Hash256)
99+
}

0 commit comments

Comments
 (0)