go port
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
// Package keccak implements the legacy Keccak-256 hash used by TRON and
|
||||
// Ethereum. The Go standard library only exposes FIPS-202 SHA3/SHAKE, which
|
||||
// differ in the domain-separation padding byte (0x06 vs 0x01), so the Keccak
|
||||
// permutation is implemented directly here.
|
||||
package keccak
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// rate is the Keccak-256 sponge rate in bytes (1600 - 2*256 bits).
|
||||
const rate = 136
|
||||
|
||||
var roundConstants = [24]uint64{
|
||||
0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000,
|
||||
0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009,
|
||||
0x000000000000008a, 0x0000000000000088, 0x0000000080008009, 0x000000008000000a,
|
||||
0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 0x8000000000008003,
|
||||
0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a,
|
||||
0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008,
|
||||
}
|
||||
|
||||
// rotation offsets and pi lane permutation for the rho/pi steps.
|
||||
var rhoOffsets = [24]int{1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44}
|
||||
var piLanes = [24]int{10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1}
|
||||
|
||||
func rotl(x uint64, n int) uint64 {
|
||||
return (x << n) | (x >> (64 - n))
|
||||
}
|
||||
|
||||
// permute applies the Keccak-f[1600] permutation in place.
|
||||
func permute(a *[25]uint64) {
|
||||
for round := 0; round < 24; round++ {
|
||||
// theta
|
||||
var c [5]uint64
|
||||
for i := 0; i < 5; i++ {
|
||||
c[i] = a[i] ^ a[i+5] ^ a[i+10] ^ a[i+15] ^ a[i+20]
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
d := c[(i+4)%5] ^ rotl(c[(i+1)%5], 1)
|
||||
for j := 0; j < 25; j += 5 {
|
||||
a[j+i] ^= d
|
||||
}
|
||||
}
|
||||
// rho and pi
|
||||
last := a[1]
|
||||
for i := 0; i < 24; i++ {
|
||||
j := piLanes[i]
|
||||
last, a[j] = a[j], rotl(last, rhoOffsets[i])
|
||||
}
|
||||
// chi
|
||||
for j := 0; j < 25; j += 5 {
|
||||
var row [5]uint64
|
||||
copy(row[:], a[j:j+5])
|
||||
for i := 0; i < 5; i++ {
|
||||
a[j+i] = row[i] ^ (^row[(i+1)%5] & row[(i+2)%5])
|
||||
}
|
||||
}
|
||||
// iota
|
||||
a[0] ^= roundConstants[round]
|
||||
}
|
||||
}
|
||||
|
||||
// State is a streaming Keccak-256 sponge state.
|
||||
type State struct {
|
||||
a [25]uint64
|
||||
buf [rate]byte
|
||||
n int
|
||||
}
|
||||
|
||||
// New returns a fresh Keccak-256 hash state.
|
||||
func New() *State {
|
||||
return &State{}
|
||||
}
|
||||
|
||||
func (s *State) absorbBlock(b *[rate]byte) {
|
||||
for i := 0; i < rate/8; i++ {
|
||||
s.a[i] ^= binary.LittleEndian.Uint64(b[i*8:])
|
||||
}
|
||||
permute(&s.a)
|
||||
}
|
||||
|
||||
// Write absorbs data into the sponge. It never returns an error.
|
||||
func (s *State) Write(p []byte) (int, error) {
|
||||
total := len(p)
|
||||
for len(p) > 0 {
|
||||
n := copy(s.buf[s.n:], p)
|
||||
s.n += n
|
||||
p = p[n:]
|
||||
if s.n == rate {
|
||||
s.absorbBlock(&s.buf)
|
||||
s.n = 0
|
||||
}
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// Sum256 returns the Keccak-256 digest without disturbing the state.
|
||||
func (s *State) Sum256() [32]byte {
|
||||
clone := *s
|
||||
var out [32]byte
|
||||
clone.finalize(&out)
|
||||
return out
|
||||
}
|
||||
|
||||
// Sum appends the current digest to b, implementing hash.Hash.
|
||||
func (s *State) Sum(b []byte) []byte {
|
||||
d := s.Sum256()
|
||||
return append(b, d[:]...)
|
||||
}
|
||||
|
||||
// Reset clears the state to its initial value.
|
||||
func (s *State) Reset() {
|
||||
s.a = [25]uint64{}
|
||||
s.buf = [rate]byte{}
|
||||
s.n = 0
|
||||
}
|
||||
|
||||
// Size returns the digest length in bytes.
|
||||
func (s *State) Size() int { return 32 }
|
||||
|
||||
// BlockSize returns the sponge rate in bytes.
|
||||
func (s *State) BlockSize() int { return rate }
|
||||
|
||||
func (s *State) finalize(out *[32]byte) {
|
||||
var block [rate]byte
|
||||
copy(block[:], s.buf[:s.n])
|
||||
block[s.n] ^= 0x01
|
||||
block[rate-1] ^= 0x80
|
||||
s.absorbBlock(&block)
|
||||
for i := 0; i < 4; i++ {
|
||||
binary.LittleEndian.PutUint64(out[i*8:], s.a[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Hash256 returns the Keccak-256 digest of data.
|
||||
func Hash256(data []byte) [32]byte {
|
||||
h := New()
|
||||
h.Write(data)
|
||||
return h.Sum256()
|
||||
}
|
||||
|
||||
// Sum256 is a package-level convenience matching crypto/sha256.Sum256.
|
||||
func Sum256(data []byte) [32]byte {
|
||||
return Hash256(data)
|
||||
}
|
||||
|
||||
// compile-time assurance that State satisfies hash.Hash.
|
||||
var _ hash.Hash = (*State)(nil)
|
||||
@@ -0,0 +1,70 @@
|
||||
package keccak
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKeccak256KnownAnswers(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{"empty", "", "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"},
|
||||
{
|
||||
"abc",
|
||||
"abc",
|
||||
"4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45",
|
||||
},
|
||||
{
|
||||
"longer",
|
||||
"The quick brown fox jumps over the lazy dog",
|
||||
"4d741b6f1eb29cb2a9b9911c82f56fa8d73b04959d3d9d222895df6c0b28aa15",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
sum := Hash256([]byte(tc.in))
|
||||
got := hex.EncodeToString(sum[:])
|
||||
if got != tc.want {
|
||||
t.Fatalf("Hash256(%q) = %s, want %s", tc.in, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamingMatchesOneShot(t *testing.T) {
|
||||
data := make([]byte, 0, 500)
|
||||
for i := 0; i < 500; i++ {
|
||||
data = append(data, byte(i))
|
||||
}
|
||||
oneShot := Hash256(data)
|
||||
|
||||
h := New()
|
||||
for i := 0; i < len(data); i += 7 {
|
||||
end := i + 7
|
||||
if end > len(data) {
|
||||
end = len(data)
|
||||
}
|
||||
h.Write(data[i:end])
|
||||
}
|
||||
if got := h.Sum256(); got != oneShot {
|
||||
t.Fatalf("streaming digest differs from one-shot")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSum256DoesNotDisturbState ensures Sum256 clones the state (hash.Hash
|
||||
// semantics) so repeated calls are stable.
|
||||
func TestSum256DoesNotDisturbState(t *testing.T) {
|
||||
h := New()
|
||||
h.Write([]byte("abc"))
|
||||
first := h.Sum256()
|
||||
second := h.Sum256()
|
||||
if first != second {
|
||||
t.Fatalf("Sum256 mutated state")
|
||||
}
|
||||
if first != Hash256([]byte("abc")) {
|
||||
t.Fatalf("Sum256 mismatch with Hash256")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user