Skip to content
This repository has been archived by the owner on Feb 16, 2024. It is now read-only.

Commit

Permalink
Merge pull request #8 from sencha-dev/feature/heavyhash
Browse files Browse the repository at this point in the history
HeavyHash (Kaspa)
  • Loading branch information
nottug authored Nov 18, 2022
2 parents 8a33167 + 16707c9 commit 0858a21
Show file tree
Hide file tree
Showing 11 changed files with 403 additions and 8 deletions.
13 changes: 7 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
SHELL=/bin/bash -o pipefail

BUILDARGS=CGO_ENABLED=0

.PHONY: generate
generate:
$(BUILD_ARGS) go build -o .bin/gen-lookup ./cmd/gen-lookup
go build -o .bin/gen-lookup ./cmd/gen-lookup
go generate ./...

.PHONY: test
fmt:
go fmt ./...

test:
$(BUILD_ARGS) go test ./...
go test ./...

.PHONY: generate fmt test
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ requires a strict 1.2Gb, so be careful if you're using verthash in memory. At th
| Octopus | yes | yes
| Verthash | yes | yes
| Equihash | no | yes
| HeavyHash | no | yes
| Autolykos2 | no | yes
| Cuckoo Cycle | no | yes
| Eaglesong | no | yes
Expand Down
27 changes: 27 additions & 0 deletions heavyhash/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package heavyhash

import (
"fmt"
)

type Client struct{}

func New() *Client {
client := &Client{}

return client
}

func NewKaspa() *Client {
return New()
}

func (c *Client) Compute(hash []byte, timestamp int64, nonce uint64) ([]byte, error) {
if len(hash) != 32 {
return nil, fmt.Errorf("hash must be 32 bytes")
}

digest := heavyHash(hash, timestamp, nonce)

return digest, nil
}
60 changes: 60 additions & 0 deletions heavyhash/heavyhash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) 2018-2019 The kaspanet developers

package heavyhash

import (
"encoding/binary"

"github.com/sencha-dev/powkit/internal/crypto"
)

const (
size = 64
iterations = size / 4
)

func heavyHash(hash []byte, timestamp int64, nonce uint64) []byte {
// initialize the matrix
s0 := binary.LittleEndian.Uint64(hash[0:8])
s1 := binary.LittleEndian.Uint64(hash[8:16])
s2 := binary.LittleEndian.Uint64(hash[16:24])
s3 := binary.LittleEndian.Uint64(hash[24:32])
mat := newMatrix(s0, s1, s2, s3)

// build the header
header := make([]byte, 32+8+32+8)
copy(header[:32], hash)
binary.LittleEndian.PutUint64(header[32:40], uint64(timestamp))
binary.LittleEndian.PutUint64(header[72:80], nonce)
header = crypto.CShake256(header, []byte("ProofOfWorkHash"), 32)

// initialize the vector and product arrays
var v, p [size]uint16
for i := 0; i < size/2; i++ {
v[i*2] = uint16(header[i] >> 4)
v[i*2+1] = uint16(header[i] & 0x0f)
}

// build the product array
for i := 0; i < size; i++ {
var s uint16
for j := 0; j < size; j++ {
s += mat[i][j] * v[j]
}
p[i] = s >> 10
}

// calculate the digest
digest := make([]byte, 32)
for i := range digest {
digest[i] = header[i] ^ (byte(p[i*2]<<4) | byte(p[i*2+1]))
}

// hash the digest a final time, reverse bytes
digest = crypto.CShake256(digest, []byte("HeavyHash"), 32)
for i, j := 0, len(digest)-1; i < j; i, j = i+1, j-1 {
digest[i], digest[j] = digest[j], digest[i]
}

return digest
}
43 changes: 43 additions & 0 deletions heavyhash/heavyhash_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package heavyhash

import (
"bytes"
"testing"

"github.com/sencha-dev/powkit/internal/common/testutil"
)

func TestHeavyHash(t *testing.T) {
tests := []struct {
digest []byte
nonce uint64
timestamp int64
hash []byte
}{
{
hash: testutil.MustDecodeHex("81553a695a0588998c413792e74ce8b8f8a096d64b3ee47387372434485c0b6f"),
nonce: 0x2f8400000eba167c,
timestamp: 0x000001848ca87c49,
digest: testutil.MustDecodeHex("000000001726686e851f02c584d7cc8a8fbe5938ecdb3ffa2ba16c260ee1fc40"),
},
{
hash: testutil.MustDecodeHex("9785c4d0e244b3564115fd110e8e608a688b8803baab9fa6948e9f7ba0540f4c"),
nonce: 0x2f8400000ffdd00f,
timestamp: 0x000001848cac94a5,
digest: testutil.MustDecodeHex("000000000943f66d7c28611e552e5523bbeb5e61c52d38b86924ca6268269331"),
},
{
hash: testutil.MustDecodeHex("2f6cea927f6dca4357c9aab4ac8b7957e3a349cc317d4631f26593b90f327256"),
nonce: 0x2f84000005d52b98,
timestamp: 0x000001848cae8db5,
digest: testutil.MustDecodeHex("0000000018c4ec84524ee859f660392858fa50b12c1419f36f480fedcd6ee3d8"),
},
}

for i, tt := range tests {
digest := heavyHash(tt.hash, tt.timestamp, tt.nonce)
if bytes.Compare(digest, tt.digest) != 0 {
t.Errorf("failed on %d: have %x, want %x", i, digest, tt.digest)
}
}
}
73 changes: 73 additions & 0 deletions heavyhash/matrix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) 2018-2019 The kaspanet developers

package heavyhash

import (
"math"

"github.com/sencha-dev/powkit/internal/crypto"
)

const (
epsilon = 1e-9
)

type matrix [size][size]uint16

func newMatrix(s0, s1, s2, s3 uint64) *matrix {
hasher := crypto.NewXoshiro256PlusPlusHasher(s0, s1, s2, s3)

var mat matrix
for calculateRank(&mat) != size {
for i := 0; i < size; i++ {
for j := 0; j < size; j += iterations {
value := hasher.Next()
for k := 0; k < iterations; k++ {
mat[i][j+k] = uint16(value >> (4 * k) & 0x0f)
}
}
}
}

return &mat
}

func calculateRank(mat *matrix) int {
var copied [size][size]float64
for i := range mat {
for j := range mat[i] {
copied[i][j] = float64(mat[i][j])
}
}

var rank int
var rowsSelected [size]bool
for i := 0; i < size; i++ {
var j int
for j = 0; j < size; j++ {
if !rowsSelected[j] && math.Abs(copied[j][i]) > epsilon {
break
}
}

if j != size {
rank++
rowsSelected[j] = true
for k := i + 1; k < size; k++ {
copied[j][k] /= copied[j][i]
}

for k := 0; k < size; k++ {
if k == j || math.Abs(copied[k][i]) <= epsilon {
continue
}

for l := i + 1; l < size; l++ {
copied[k][l] -= copied[j][l] * copied[k][i]
}
}
}
}

return rank
}
89 changes: 89 additions & 0 deletions heavyhash/matrix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package heavyhash

import (
"testing"
)

func TestNewMatrix(t *testing.T) {
tests := []struct {
state [4]uint64
first [64]uint16
last [64]uint16
}{
{
state: [4]uint64{
0,
0,
0,
1,
},
first: [64]uint16{
0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1, 0x1, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1, 0x1, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x0, 0x0, 0x0,
0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x0, 0x0, 0x0, 0x2, 0x2, 0x0, 0x0, 0x0,
},
last: [64]uint16{
0x8, 0x7, 0x8, 0x7, 0x7, 0x5, 0xf, 0x8, 0x2, 0x0, 0x6, 0x5, 0x3, 0x4, 0x5, 0x4,
0x8, 0x4, 0x9, 0x9, 0x2, 0x5, 0x2, 0x0, 0x2, 0xc, 0x3, 0x7, 0x7, 0x7, 0xa, 0xb,
0xa, 0x5, 0x7, 0x3, 0xb, 0xe, 0xe, 0x9, 0x3, 0x5, 0xe, 0x4, 0x0, 0xd, 0x7, 0x9,
0xf, 0x9, 0x8, 0x9, 0x0, 0x4, 0x7, 0x4, 0x7, 0x4, 0x9, 0x8, 0x6, 0x8, 0x1, 0xa,
},
},
{
state: [4]uint64{
124123,
591204,
959691,
959109,
},
first: [64]uint16{
0xb, 0xd, 0x4, 0xe, 0x1, 0x0, 0x0, 0xb, 0x3, 0x4, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0,
0xa, 0x4, 0x1, 0xc, 0x0, 0x8, 0xd, 0x9, 0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0,
0xd, 0xa, 0xb, 0xd, 0xe, 0x5, 0x7, 0x2, 0x3, 0x0, 0x0, 0x2, 0xe, 0xb, 0x4, 0x4,
0xb, 0x5, 0xf, 0xc, 0x9, 0xd, 0x2, 0x8, 0x6, 0x0, 0xb, 0x0, 0x4, 0x0, 0xe, 0x0,
},
last: [64]uint16{
0x1, 0x9, 0x3, 0x4, 0x0, 0x6, 0x7, 0xe, 0xa, 0xd, 0xd, 0x6, 0x9, 0x6, 0xa, 0x0,
0x1, 0x3, 0x0, 0x7, 0x6, 0xd, 0x0, 0x7, 0xf, 0x8, 0x8, 0x7, 0x1, 0x2, 0x0, 0x8,
0x9, 0x0, 0x4, 0x4, 0xb, 0xd, 0x9, 0x8, 0xa, 0x4, 0x8, 0x1, 0x2, 0x3, 0x6, 0x2,
0x5, 0x8, 0x4, 0x0, 0xd, 0xe, 0x6, 0x6, 0x9, 0xc, 0x0, 0x3, 0x7, 0xd, 0xa, 0x0,
},
},
{
state: [4]uint64{
59013294024,
99520455,
512351,
9599690203505,
},
first: [64]uint16{
0xc, 0xc, 0x7, 0x6, 0x7, 0xf, 0x9, 0x5, 0x2, 0xb, 0xb, 0xc, 0xb, 0x6, 0x4, 0x6,
0x2, 0xe, 0x4, 0xf, 0xc, 0x9, 0x0, 0x6, 0xd, 0x6, 0x7, 0xd, 0x1, 0x5, 0xb, 0x5,
0x7, 0x1, 0xa, 0xe, 0xb, 0xb, 0xb, 0x9, 0x0, 0x4, 0x1, 0x5, 0x0, 0xd, 0x8, 0x6,
0x1, 0x6, 0xc, 0xb, 0xe, 0x5, 0x6, 0xb, 0x1, 0xb, 0xf, 0x6, 0xd, 0x1, 0x1, 0xf,
},
last: [64]uint16{
0x7, 0xd, 0x5, 0x5, 0x1, 0x7, 0xf, 0xd, 0x9, 0xa, 0xb, 0xe, 0x3, 0x9, 0x5, 0x2,
0x6, 0x5, 0x6, 0x6, 0x2, 0x9, 0x3, 0x2, 0x0, 0x0, 0xf, 0xd, 0x9, 0x8, 0x9, 0x2,
0x9, 0x6, 0x7, 0x0, 0xf, 0xd, 0x2, 0x2, 0x6, 0x5, 0x3, 0x6, 0xc, 0xe, 0xd, 0x7,
0xc, 0xd, 0x2, 0xa, 0x7, 0x9, 0xe, 0x7, 0x3, 0xb, 0x3, 0x3, 0x4, 0xc, 0x9, 0x7,
},
},
}

for i, tt := range tests {
mat := newMatrix(tt.state[0], tt.state[1], tt.state[2], tt.state[3])
for j := range mat[0] {
if mat[0][j] != tt.first[j] {
t.Errorf("failed on %d: mat[0][%d]: have %d, want %d", i, j, mat[0][j], tt.first[j])
}
}

for j := range mat[len(mat)-1] {
if mat[len(mat)-1][j] != tt.last[j] {
t.Errorf("failed on %d: mat[%d][%d]: have %d, want %d", i, len(mat)-1, j, mat[len(mat)-1][j], tt.last[j])
}
}
}
}
4 changes: 2 additions & 2 deletions internal/crypto/blake.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"github.com/dchest/blake2b"
)

func Blake2b(inp, personal []byte, size int) []byte {
func Blake2b(data, personal []byte, size int) []byte {
h, err := blake2b.New(&blake2b.Config{
Size: uint8(size),
Key: nil,
Expand All @@ -17,7 +17,7 @@ func Blake2b(inp, personal []byte, size int) []byte {
panic(err)
}

h.Write(inp)
h.Write(data)

return h.Sum(nil)
}
Expand Down
14 changes: 14 additions & 0 deletions internal/crypto/shakehash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package crypto

import (
"golang.org/x/crypto/sha3"
)

func CShake256(data, personal []byte, size int) []byte {
out := make([]byte, size)
h := sha3.NewCShake256(nil, personal)
h.Write(data)
h.Read(out)

return out
}
38 changes: 38 additions & 0 deletions internal/crypto/xoshiro256plusplus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package crypto

func rotl(a, b uint64) uint64 {
return (a << b) | (a >> (64 - b))
}

type Xoshiro256PlusPlusHasher struct {
s0 uint64
s1 uint64
s2 uint64
s3 uint64
}

func NewXoshiro256PlusPlusHasher(s0, s1, s2, s3 uint64) Xoshiro256PlusPlusHasher {
hasher := Xoshiro256PlusPlusHasher{
s0: s0,
s1: s1,
s2: s2,
s3: s3,
}

return hasher
}

func (h *Xoshiro256PlusPlusHasher) Next() uint64 {
value := rotl(h.s0+h.s3, 23) + h.s0
state := h.s1 << 17

h.s2 ^= h.s0
h.s3 ^= h.s1
h.s1 ^= h.s2
h.s0 ^= h.s3

h.s2 ^= state
h.s3 = rotl(h.s3, 45)

return value
}
Loading

0 comments on commit 0858a21

Please sign in to comment.