This repository has been archived by the owner on Feb 16, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from sencha-dev/feature/heavyhash
HeavyHash (Kaspa)
- Loading branch information
Showing
11 changed files
with
403 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.