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

Commit a07b4d7

Browse files
committed
implement heavyhash for kaspa
1 parent 99d185a commit a07b4d7

9 files changed

+395
-2
lines changed

heavyhash/client.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package heavyhash
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
type Client struct{}
8+
9+
func New() *Client {
10+
client := &Client{}
11+
12+
return client
13+
}
14+
15+
func NewKaspa() *Client {
16+
return New()
17+
}
18+
19+
func (c *Client) Compute(hash []byte, timestamp int64, nonce uint64) ([]byte, error) {
20+
if len(hash) != 32 {
21+
return nil, fmt.Errorf("hash must be 32 bytes")
22+
}
23+
24+
digest := heavyHash(hash, timestamp, nonce)
25+
26+
return digest, nil
27+
}

heavyhash/heavyhash.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) 2018-2019 The kaspanet developers
2+
3+
package heavyhash
4+
5+
import (
6+
"encoding/binary"
7+
8+
"github.com/sencha-dev/powkit/internal/crypto"
9+
)
10+
11+
const (
12+
size = 64
13+
iterations = size / 4
14+
)
15+
16+
func heavyHash(hash []byte, timestamp int64, nonce uint64) []byte {
17+
// initialize the matrix
18+
s0 := binary.LittleEndian.Uint64(hash[0:8])
19+
s1 := binary.LittleEndian.Uint64(hash[8:16])
20+
s2 := binary.LittleEndian.Uint64(hash[16:24])
21+
s3 := binary.LittleEndian.Uint64(hash[24:32])
22+
mat := newMatrix(s0, s1, s2, s3)
23+
24+
// build the header
25+
header := make([]byte, 32+8+32+8)
26+
copy(header[:32], hash)
27+
binary.LittleEndian.PutUint64(header[32:40], uint64(timestamp))
28+
binary.LittleEndian.PutUint64(header[72:80], nonce)
29+
header = crypto.CShake256(header, []byte("ProofOfWorkHash"), 32)
30+
31+
// initialize the vector and product arrays
32+
var v, p [size]uint16
33+
for i := 0; i < size/2; i++ {
34+
v[i*2] = uint16(header[i] >> 4)
35+
v[i*2+1] = uint16(header[i] & 0x0f)
36+
}
37+
38+
// build the product array
39+
for i := 0; i < size; i++ {
40+
var s uint16
41+
for j := 0; j < size; j++ {
42+
s += mat[i][j] * v[j]
43+
}
44+
p[i] = s >> 10
45+
}
46+
47+
// calculate the digest
48+
digest := make([]byte, 32)
49+
for i := range digest {
50+
digest[i] = header[i] ^ (byte(p[i*2]<<4) | byte(p[i*2+1]))
51+
}
52+
53+
// hash the digest a final time, reverse bytes
54+
digest = crypto.CShake256(digest, []byte("HeavyHash"), 32)
55+
for i, j := 0, len(digest)-1; i < j; i, j = i+1, j-1 {
56+
digest[i], digest[j] = digest[j], digest[i]
57+
}
58+
59+
return digest
60+
}

heavyhash/heavyhash_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package heavyhash
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/sencha-dev/powkit/internal/common/testutil"
8+
)
9+
10+
func TestHeavyHash(t *testing.T) {
11+
tests := []struct {
12+
digest []byte
13+
nonce uint64
14+
timestamp int64
15+
hash []byte
16+
}{
17+
{
18+
hash: testutil.MustDecodeHex("81553a695a0588998c413792e74ce8b8f8a096d64b3ee47387372434485c0b6f"),
19+
nonce: 0x2f8400000eba167c,
20+
timestamp: 0x000001848ca87c49,
21+
digest: testutil.MustDecodeHex("000000001726686e851f02c584d7cc8a8fbe5938ecdb3ffa2ba16c260ee1fc40"),
22+
},
23+
{
24+
hash: testutil.MustDecodeHex("9785c4d0e244b3564115fd110e8e608a688b8803baab9fa6948e9f7ba0540f4c"),
25+
nonce: 0x2f8400000ffdd00f,
26+
timestamp: 0x000001848cac94a5,
27+
digest: testutil.MustDecodeHex("000000000943f66d7c28611e552e5523bbeb5e61c52d38b86924ca6268269331"),
28+
},
29+
{
30+
hash: testutil.MustDecodeHex("2f6cea927f6dca4357c9aab4ac8b7957e3a349cc317d4631f26593b90f327256"),
31+
nonce: 0x2f84000005d52b98,
32+
timestamp: 0x000001848cae8db5,
33+
digest: testutil.MustDecodeHex("0000000018c4ec84524ee859f660392858fa50b12c1419f36f480fedcd6ee3d8"),
34+
},
35+
}
36+
37+
for i, tt := range tests {
38+
digest := heavyHash(tt.hash, tt.timestamp, tt.nonce)
39+
if bytes.Compare(digest, tt.digest) != 0 {
40+
t.Errorf("failed on %d: have %x, want %x", i, digest, tt.digest)
41+
}
42+
}
43+
}

heavyhash/matrix.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) 2018-2019 The kaspanet developers
2+
3+
package heavyhash
4+
5+
import (
6+
"math"
7+
8+
"github.com/sencha-dev/powkit/internal/crypto"
9+
)
10+
11+
const (
12+
epsilon = 1e-9
13+
)
14+
15+
type matrix [size][size]uint16
16+
17+
func newMatrix(s0, s1, s2, s3 uint64) *matrix {
18+
hasher := crypto.NewXoshiro256PlusPlusHasher(s0, s1, s2, s3)
19+
20+
var mat matrix
21+
for calculateRank(&mat) != size {
22+
for i := 0; i < size; i++ {
23+
for j := 0; j < size; j += iterations {
24+
value := hasher.Next()
25+
for k := 0; k < iterations; k++ {
26+
mat[i][j+k] = uint16(value >> (4 * k) & 0x0f)
27+
}
28+
}
29+
}
30+
}
31+
32+
return &mat
33+
}
34+
35+
func calculateRank(mat *matrix) int {
36+
var copied [size][size]float64
37+
for i := range mat {
38+
for j := range mat[i] {
39+
copied[i][j] = float64(mat[i][j])
40+
}
41+
}
42+
43+
var rank int
44+
var rowsSelected [size]bool
45+
for i := 0; i < size; i++ {
46+
var j int
47+
for j = 0; j < size; j++ {
48+
if !rowsSelected[j] && math.Abs(copied[j][i]) > epsilon {
49+
break
50+
}
51+
}
52+
53+
if j != size {
54+
rank++
55+
rowsSelected[j] = true
56+
for k := i + 1; k < size; k++ {
57+
copied[j][k] /= copied[j][i]
58+
}
59+
60+
for k := 0; k < size; k++ {
61+
if k == j || math.Abs(copied[k][i]) <= epsilon {
62+
continue
63+
}
64+
65+
for l := i + 1; l < size; l++ {
66+
copied[k][l] -= copied[j][l] * copied[k][i]
67+
}
68+
}
69+
}
70+
}
71+
72+
return rank
73+
}

heavyhash/matrix_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package heavyhash
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestNewMatrix(t *testing.T) {
8+
tests := []struct {
9+
state [4]uint64
10+
first [64]uint16
11+
last [64]uint16
12+
}{
13+
{
14+
state: [4]uint64{
15+
0,
16+
0,
17+
0,
18+
1,
19+
},
20+
first: [64]uint16{
21+
0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
22+
0x1, 0x1, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
23+
0x1, 0x1, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x0, 0x0, 0x0,
24+
0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x0, 0x0, 0x0, 0x2, 0x2, 0x0, 0x0, 0x0,
25+
},
26+
last: [64]uint16{
27+
0x8, 0x7, 0x8, 0x7, 0x7, 0x5, 0xf, 0x8, 0x2, 0x0, 0x6, 0x5, 0x3, 0x4, 0x5, 0x4,
28+
0x8, 0x4, 0x9, 0x9, 0x2, 0x5, 0x2, 0x0, 0x2, 0xc, 0x3, 0x7, 0x7, 0x7, 0xa, 0xb,
29+
0xa, 0x5, 0x7, 0x3, 0xb, 0xe, 0xe, 0x9, 0x3, 0x5, 0xe, 0x4, 0x0, 0xd, 0x7, 0x9,
30+
0xf, 0x9, 0x8, 0x9, 0x0, 0x4, 0x7, 0x4, 0x7, 0x4, 0x9, 0x8, 0x6, 0x8, 0x1, 0xa,
31+
},
32+
},
33+
{
34+
state: [4]uint64{
35+
124123,
36+
591204,
37+
959691,
38+
959109,
39+
},
40+
first: [64]uint16{
41+
0xb, 0xd, 0x4, 0xe, 0x1, 0x0, 0x0, 0xb, 0x3, 0x4, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0,
42+
0xa, 0x4, 0x1, 0xc, 0x0, 0x8, 0xd, 0x9, 0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0,
43+
0xd, 0xa, 0xb, 0xd, 0xe, 0x5, 0x7, 0x2, 0x3, 0x0, 0x0, 0x2, 0xe, 0xb, 0x4, 0x4,
44+
0xb, 0x5, 0xf, 0xc, 0x9, 0xd, 0x2, 0x8, 0x6, 0x0, 0xb, 0x0, 0x4, 0x0, 0xe, 0x0,
45+
},
46+
last: [64]uint16{
47+
0x1, 0x9, 0x3, 0x4, 0x0, 0x6, 0x7, 0xe, 0xa, 0xd, 0xd, 0x6, 0x9, 0x6, 0xa, 0x0,
48+
0x1, 0x3, 0x0, 0x7, 0x6, 0xd, 0x0, 0x7, 0xf, 0x8, 0x8, 0x7, 0x1, 0x2, 0x0, 0x8,
49+
0x9, 0x0, 0x4, 0x4, 0xb, 0xd, 0x9, 0x8, 0xa, 0x4, 0x8, 0x1, 0x2, 0x3, 0x6, 0x2,
50+
0x5, 0x8, 0x4, 0x0, 0xd, 0xe, 0x6, 0x6, 0x9, 0xc, 0x0, 0x3, 0x7, 0xd, 0xa, 0x0,
51+
},
52+
},
53+
{
54+
state: [4]uint64{
55+
59013294024,
56+
99520455,
57+
512351,
58+
9599690203505,
59+
},
60+
first: [64]uint16{
61+
0xc, 0xc, 0x7, 0x6, 0x7, 0xf, 0x9, 0x5, 0x2, 0xb, 0xb, 0xc, 0xb, 0x6, 0x4, 0x6,
62+
0x2, 0xe, 0x4, 0xf, 0xc, 0x9, 0x0, 0x6, 0xd, 0x6, 0x7, 0xd, 0x1, 0x5, 0xb, 0x5,
63+
0x7, 0x1, 0xa, 0xe, 0xb, 0xb, 0xb, 0x9, 0x0, 0x4, 0x1, 0x5, 0x0, 0xd, 0x8, 0x6,
64+
0x1, 0x6, 0xc, 0xb, 0xe, 0x5, 0x6, 0xb, 0x1, 0xb, 0xf, 0x6, 0xd, 0x1, 0x1, 0xf,
65+
},
66+
last: [64]uint16{
67+
0x7, 0xd, 0x5, 0x5, 0x1, 0x7, 0xf, 0xd, 0x9, 0xa, 0xb, 0xe, 0x3, 0x9, 0x5, 0x2,
68+
0x6, 0x5, 0x6, 0x6, 0x2, 0x9, 0x3, 0x2, 0x0, 0x0, 0xf, 0xd, 0x9, 0x8, 0x9, 0x2,
69+
0x9, 0x6, 0x7, 0x0, 0xf, 0xd, 0x2, 0x2, 0x6, 0x5, 0x3, 0x6, 0xc, 0xe, 0xd, 0x7,
70+
0xc, 0xd, 0x2, 0xa, 0x7, 0x9, 0xe, 0x7, 0x3, 0xb, 0x3, 0x3, 0x4, 0xc, 0x9, 0x7,
71+
},
72+
},
73+
}
74+
75+
for i, tt := range tests {
76+
mat := newMatrix(tt.state[0], tt.state[1], tt.state[2], tt.state[3])
77+
for j := range mat[0] {
78+
if mat[0][j] != tt.first[j] {
79+
t.Errorf("failed on %d: mat[0][%d]: have %d, want %d", i, j, mat[0][j], tt.first[j])
80+
}
81+
}
82+
83+
for j := range mat[len(mat)-1] {
84+
if mat[len(mat)-1][j] != tt.last[j] {
85+
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])
86+
}
87+
}
88+
}
89+
}

internal/crypto/blake.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"github.com/dchest/blake2b"
55
)
66

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

20-
h.Write(inp)
20+
h.Write(data)
2121

2222
return h.Sum(nil)
2323
}

internal/crypto/shakehash.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package crypto
2+
3+
import (
4+
"golang.org/x/crypto/sha3"
5+
)
6+
7+
func CShake256(data, personal []byte, size int) []byte {
8+
out := make([]byte, size)
9+
h := sha3.NewCShake256(nil, personal)
10+
h.Write(data)
11+
h.Read(out)
12+
13+
return out
14+
}

internal/crypto/xoshiro256plusplus.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package crypto
2+
3+
func rotl(a, b uint64) uint64 {
4+
return (a << b) | (a >> (64 - b))
5+
}
6+
7+
type Xoshiro256PlusPlusHasher struct {
8+
s0 uint64
9+
s1 uint64
10+
s2 uint64
11+
s3 uint64
12+
}
13+
14+
func NewXoshiro256PlusPlusHasher(s0, s1, s2, s3 uint64) Xoshiro256PlusPlusHasher {
15+
hasher := Xoshiro256PlusPlusHasher{
16+
s0: s0,
17+
s1: s1,
18+
s2: s2,
19+
s3: s3,
20+
}
21+
22+
return hasher
23+
}
24+
25+
func (h *Xoshiro256PlusPlusHasher) Next() uint64 {
26+
value := rotl(h.s0+h.s3, 23) + h.s0
27+
state := h.s1 << 17
28+
29+
h.s2 ^= h.s0
30+
h.s3 ^= h.s1
31+
h.s1 ^= h.s2
32+
h.s0 ^= h.s3
33+
34+
h.s2 ^= state
35+
h.s3 = rotl(h.s3, 45)
36+
37+
return value
38+
}

0 commit comments

Comments
 (0)