mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 15:47:21 +00:00
The upstream libray has removed the assembly-based implementation of keccak. We need to maintain our own library to avoid a peformance regression. --------- Co-authored-by: lightclient <lightclient@protonmail.com>
210 lines
5.5 KiB
Go
210 lines
5.5 KiB
Go
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package keccak
|
|
|
|
// Tests include all the ShortMsgKATs provided by the Keccak team at
|
|
// https://github.com/gvanas/KeccakCodePackage
|
|
//
|
|
// They only include the zero-bit case of the bitwise testvectors
|
|
// published by NIST in the draft of FIPS-202.
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/flate"
|
|
"encoding"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"hash"
|
|
"math/rand"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const (
|
|
testString = "brekeccakkeccak koax koax"
|
|
katFilename = "testdata/keccakKats.json.deflate"
|
|
)
|
|
|
|
// testDigests contains functions returning hash.Hash instances
|
|
// with output-length equal to the KAT length for SHA-3, Keccak
|
|
// and SHAKE instances.
|
|
var testDigests = map[string]func() hash.Hash{
|
|
"Keccak-256": NewLegacyKeccak256,
|
|
"Keccak-512": NewLegacyKeccak512,
|
|
}
|
|
|
|
// decodeHex converts a hex-encoded string into a raw byte string.
|
|
func decodeHex(s string) []byte {
|
|
b, err := hex.DecodeString(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// structs used to marshal JSON test-cases.
|
|
type KeccakKats struct {
|
|
Kats map[string][]struct {
|
|
Digest string `json:"digest"`
|
|
Length int64 `json:"length"`
|
|
Message string `json:"message"`
|
|
|
|
// Defined only for cSHAKE
|
|
N string `json:"N"`
|
|
S string `json:"S"`
|
|
}
|
|
}
|
|
|
|
// TestKeccakKats tests the SHA-3 and Shake implementations against all the
|
|
// ShortMsgKATs from https://github.com/gvanas/KeccakCodePackage
|
|
// (The testvectors are stored in keccakKats.json.deflate due to their length.)
|
|
func TestKeccakKats(t *testing.T) {
|
|
// Read the KATs.
|
|
deflated, err := os.Open(katFilename)
|
|
if err != nil {
|
|
t.Errorf("error opening %s: %s", katFilename, err)
|
|
}
|
|
file := flate.NewReader(deflated)
|
|
dec := json.NewDecoder(file)
|
|
var katSet KeccakKats
|
|
err = dec.Decode(&katSet)
|
|
if err != nil {
|
|
t.Errorf("error decoding KATs: %s", err)
|
|
}
|
|
|
|
for algo, function := range testDigests {
|
|
d := function()
|
|
for _, kat := range katSet.Kats[algo] {
|
|
d.Reset()
|
|
in, err := hex.DecodeString(kat.Message)
|
|
if err != nil {
|
|
t.Errorf("error decoding KAT: %s", err)
|
|
}
|
|
d.Write(in[:kat.Length/8])
|
|
got := strings.ToUpper(hex.EncodeToString(d.Sum(nil)))
|
|
if got != kat.Digest {
|
|
t.Errorf("function=%s, length=%d\nmessage:\n %s\ngot:\n %s\nwanted:\n %s",
|
|
algo, kat.Length, kat.Message, got, kat.Digest)
|
|
t.Logf("wanted %+v", kat)
|
|
t.FailNow()
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestKeccak does a basic test of the non-standardized Keccak hash functions.
|
|
func TestKeccak(t *testing.T) {
|
|
tests := []struct {
|
|
fn func() hash.Hash
|
|
data []byte
|
|
want string
|
|
}{
|
|
{
|
|
NewLegacyKeccak256,
|
|
[]byte("abc"),
|
|
"4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45",
|
|
},
|
|
{
|
|
NewLegacyKeccak512,
|
|
[]byte("abc"),
|
|
"18587dc2ea106b9a1563e32b3312421ca164c7f1f07bc922a9c83d77cea3a1e5d0c69910739025372dc14ac9642629379540c17e2a65b19d77aa511a9d00bb96",
|
|
},
|
|
}
|
|
|
|
for _, u := range tests {
|
|
h := u.fn()
|
|
h.Write(u.data)
|
|
got := h.Sum(nil)
|
|
want := decodeHex(u.want)
|
|
if !bytes.Equal(got, want) {
|
|
t.Errorf("unexpected hash for size %d: got '%x' want '%s'", h.Size()*8, got, u.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestUnalignedWrite tests that writing data in an arbitrary pattern with
|
|
// small input buffers.
|
|
func TestUnalignedWrite(t *testing.T) {
|
|
buf := sequentialBytes(0x10000)
|
|
for alg, df := range testDigests {
|
|
d := df()
|
|
d.Reset()
|
|
d.Write(buf)
|
|
want := d.Sum(nil)
|
|
d.Reset()
|
|
for i := 0; i < len(buf); {
|
|
// Cycle through offsets which make a 137 byte sequence.
|
|
// Because 137 is prime this sequence should exercise all corner cases.
|
|
offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1}
|
|
for _, j := range offsets {
|
|
if v := len(buf) - i; v < j {
|
|
j = v
|
|
}
|
|
d.Write(buf[i : i+j])
|
|
i += j
|
|
}
|
|
}
|
|
got := d.Sum(nil)
|
|
if !bytes.Equal(got, want) {
|
|
t.Errorf("Unaligned writes, alg=%s\ngot %q, want %q", alg, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// sequentialBytes produces a buffer of size consecutive bytes 0x00, 0x01, ..., used for testing.
|
|
//
|
|
// The alignment of each slice is intentionally randomized to detect alignment
|
|
// issues in the implementation. See https://golang.org/issue/37644.
|
|
// Ideally, the compiler should fuzz the alignment itself.
|
|
// (See https://golang.org/issue/35128.)
|
|
func sequentialBytes(size int) []byte {
|
|
alignmentOffset := rand.Intn(8)
|
|
result := make([]byte, size+alignmentOffset)[alignmentOffset:]
|
|
for i := range result {
|
|
result[i] = byte(i)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func TestMarshalUnmarshal(t *testing.T) {
|
|
t.Run("Keccak-256", func(t *testing.T) { testMarshalUnmarshal(t, NewLegacyKeccak256()) })
|
|
t.Run("Keccak-512", func(t *testing.T) { testMarshalUnmarshal(t, NewLegacyKeccak512()) })
|
|
}
|
|
|
|
// TODO(filippo): move this to crypto/internal/cryptotest.
|
|
func testMarshalUnmarshal(t *testing.T, h hash.Hash) {
|
|
buf := make([]byte, 200)
|
|
rand.Read(buf)
|
|
n := rand.Intn(200)
|
|
h.Write(buf)
|
|
want := h.Sum(nil)
|
|
h.Reset()
|
|
h.Write(buf[:n])
|
|
b, err := h.(encoding.BinaryMarshaler).MarshalBinary()
|
|
if err != nil {
|
|
t.Errorf("MarshalBinary: %v", err)
|
|
}
|
|
h.Write(bytes.Repeat([]byte{0}, 200))
|
|
if err := h.(encoding.BinaryUnmarshaler).UnmarshalBinary(b); err != nil {
|
|
t.Errorf("UnmarshalBinary: %v", err)
|
|
}
|
|
h.Write(buf[n:])
|
|
got := h.Sum(nil)
|
|
if !bytes.Equal(got, want) {
|
|
t.Errorf("got %x, want %x", got, want)
|
|
}
|
|
}
|
|
|
|
// BenchmarkPermutationFunction measures the speed of the permutation function
|
|
// with no input data.
|
|
func BenchmarkPermutationFunction(b *testing.B) {
|
|
b.SetBytes(int64(200))
|
|
var lanes [25]uint64
|
|
for i := 0; i < b.N; i++ {
|
|
keccakF1600(&lanes)
|
|
}
|
|
}
|