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

Commit 09e9780

Browse files
committed
modify equihash for zelhash
1 parent 434f312 commit 09e9780

File tree

7 files changed

+686
-738
lines changed

7 files changed

+686
-738
lines changed

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ requires a strict 1.2Gb, so be careful if you're using verthash in memory. At th
4646
- Cuckoo Cycle is built specifically for Aeternity. There is a modification of the `sipnode` function in the current version
4747
of tromp's cuckoo algorithms that Aeternity does not use (a Nicehash dev gives more details [here](https://forum.aeternity.com/t/support-aeternity-stratum-implementation/3140/6)). It wouldn't be hard to implement other Cuckoo Cycle algorithms (cuckatoo, cuckaroo),
4848
there just isn't really a need at this point since Grin is fairly annoying. BlockCypher implements the other algorithms [here](https://github.com/blockcypher/libgrin/tree/master/core/pow).
49-
- Equihash is built around ZCash's variation of Equihash. The original implementation is left for compatibility reasons, hopefully one day
50-
I'll find a way to unify the two (though this may not be possible in a reasonable way).
5149
- All non-DAG algorithms are less organized than I would like, they'll probably be overhauled at some point for a more coherent general standard.
5250
- All testing is done on linux, windows support is hazy at best.
5351
- The library assumes the host architecture is little-endian, I'm fairly confident big-endian architectures will not function properly.

equihash/README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# Equihash
22

3-
There are two variations of Equihash - the initial implementation and the ZCash variation.
4-
As of now, they are completely incompatible. Perhaps one day they'll end up being properly
5-
unified but both work separately for now.
3+
This implementation is the ZCash variation of Equihash (the original implementation is scarcely used), along
4+
with the modifications ("twisting" of the Blake hash) required by Zelhash.

equihash/equihash.go

Lines changed: 266 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,277 @@
1+
// Copyright (c) 2021 Electric Coin Company
2+
13
package equihash
24

3-
type Config struct {
4-
n uint32
5-
k uint32
6-
personal []byte
5+
import (
6+
"bytes"
7+
"encoding/binary"
8+
"fmt"
9+
10+
"github.com/sencha-dev/powkit/internal/common/convutil"
11+
"github.com/sencha-dev/powkit/internal/crypto"
12+
)
13+
14+
const (
15+
uint32Size = 4
16+
wordSize = 32
17+
wordMask = (1 << wordSize) - 1
18+
)
19+
20+
func collisionBitLength(n, k uint32) uint32 {
21+
return n / (k + 1)
22+
}
23+
24+
func collisionByteLength(n, k uint32) uint32 {
25+
return (collisionBitLength(n, k) + 7) / 8
26+
}
27+
28+
func indicesPerHashOutput(n uint32) uint32 {
29+
return 512 / n
30+
}
31+
32+
func hashOutput(n uint32) uint32 {
33+
return indicesPerHashOutput(n) * ((n + 7) / 8)
34+
}
35+
36+
func hashLength(n, k uint32) uint32 {
37+
return (k + 1) * collisionByteLength(n, k)
38+
}
39+
40+
func blakePersonal(personal []byte, n, k uint32) []byte {
41+
nBytes := make([]byte, 4)
42+
binary.LittleEndian.PutUint32(nBytes, n)
43+
44+
kBytes := make([]byte, 4)
45+
binary.LittleEndian.PutUint32(kBytes, k)
46+
47+
personalBytes := bytes.Join([][]byte{
48+
personal,
49+
nBytes,
50+
kBytes,
51+
}, nil)
52+
53+
return personalBytes
754
}
855

9-
func New(n, k uint32, personal string) *Config {
10-
cfg := &Config{
11-
n: n,
12-
k: k,
13-
personal: []byte(personal),
56+
func expandArray(input []byte, bitLen, bytePad uint32) ([]byte, error) {
57+
if bitLen < 8 {
58+
return nil, fmt.Errorf("bitLen must be no less than 8")
59+
} else if wordSize < bitLen {
60+
return nil, fmt.Errorf("bitLen must be no greater than %d", wordSize-7)
1461
}
1562

16-
return cfg
63+
inputLen := uint32(len(input))
64+
outputWidth := (bitLen+7)/8 + bytePad
65+
outputLen := 8 * outputWidth * inputLen / bitLen
66+
67+
if outputLen == inputLen {
68+
return input, nil
69+
}
70+
71+
output := make([]byte, outputLen)
72+
var bitLenMask uint32 = (1 << bitLen) - 1
73+
74+
var accBits, accValue, j uint32
75+
for i := range input {
76+
accValue = (accValue << 8) | uint32(input[i])
77+
accBits += 8
78+
79+
if accBits >= bitLen {
80+
accBits -= bitLen
81+
for x := bytePad; x < outputWidth; x++ {
82+
p1 := accValue >> (accBits + (8 * (outputWidth - x - 1)))
83+
p2 := (bitLenMask >> (8 * (outputWidth - x - 1))) & 0xFF
84+
output[j+x] = uint8(p1 & p2)
85+
}
86+
87+
j += outputWidth
88+
}
89+
}
90+
91+
return output, nil
1792
}
1893

19-
func (cfg *Config) TraditionalVerify(seed, input []byte, nonce uint32) bool {
20-
return TraditionalVerify(cfg.n, cfg.k, cfg.personal, seed, input, nonce)
94+
func indicesFromMinimal(n, k uint32, minimal []byte) ([]uint32, error) {
95+
cBitLen := collisionBitLength(n, k)
96+
minimalLen := uint32(len(minimal))
97+
98+
if minimalLen != ((1<<k)*(cBitLen+1))/8 {
99+
return nil, fmt.Errorf("invalid minimal for parameters")
100+
}
101+
102+
if (((cBitLen + 1) + 7) / 8) > 4 {
103+
return nil, fmt.Errorf("invalid n, k parameters")
104+
}
105+
106+
bytePad := uint32Size - ((cBitLen+1)+7)/8
107+
indices, err := expandArray(minimal, cBitLen+1, bytePad)
108+
if err != nil {
109+
return nil, err
110+
}
111+
112+
return convutil.BytesToUint32Array(indices, binary.BigEndian), nil
21113
}
22114

23-
func (cfg *Config) ZCashVerify(header, soln []byte) (bool, error) {
24-
return ZCashVerify(cfg.n, cfg.k, cfg.personal, header, soln)
115+
func hasCollision(a, b *node, len uint32) bool {
116+
for i := uint32(0); i < len; i++ {
117+
if a.hash[i] != b.hash[i] {
118+
return false
119+
}
120+
}
121+
122+
return true
123+
}
124+
125+
func distinctIndices(a, b *node) bool {
126+
for _, i := range a.indices {
127+
for _, j := range b.indices {
128+
if i == j {
129+
return false
130+
}
131+
}
132+
}
133+
134+
return true
135+
}
136+
137+
func validateSubtrees(n, k uint32, a, b *node) error {
138+
if !hasCollision(a, b, collisionByteLength(n, k)) {
139+
return fmt.Errorf("collision")
140+
} else if b.indicesBefore(a) {
141+
return fmt.Errorf("out of order")
142+
} else if !distinctIndices(a, b) {
143+
return fmt.Errorf("duplicate indices")
144+
}
145+
146+
return nil
147+
}
148+
149+
type node struct {
150+
hash []byte
151+
indices []uint32
152+
}
153+
154+
func (n *node) indicesBefore(b *node) bool {
155+
return n.indices[0] < b.indices[0]
156+
}
157+
158+
func hashBlakeWithOffset(initialState, personalState []byte, offset, hashLength uint32) []byte {
159+
newState := make([]byte, len(initialState)+4)
160+
copy(newState, initialState)
161+
binary.LittleEndian.PutUint32(newState[len(initialState):], offset)
162+
163+
return crypto.Blake2b(newState, personalState, int(hashLength))
164+
}
165+
166+
func generateHash(initialState, personalState []byte, g, hashLength uint32, twist bool) []byte {
167+
if !twist {
168+
return hashBlakeWithOffset(initialState, personalState, g, hashLength)
169+
}
170+
171+
myHash := make([]uint32, 16)
172+
startIndex := g & 0xFFFFFFF0
173+
for g2 := startIndex; g2 <= g; g2++ {
174+
tmpHash := hashBlakeWithOffset(initialState, personalState, g2, hashLength)
175+
for i := 0; i < 16; i++ {
176+
myHash[i] += binary.LittleEndian.Uint32(tmpHash[i*4 : (i+1)*4])
177+
}
178+
}
179+
180+
hash := convutil.Uint32ArrayToBytes(myHash, binary.LittleEndian)
181+
for j := 15; j < int(hashLength); j += 16 {
182+
hash[j] &= 0xF8
183+
}
184+
185+
return hash
186+
}
187+
188+
func newNode(n, k uint32, personal []byte, twist bool, state []byte, i uint32) (*node, error) {
189+
g := i / indicesPerHashOutput(n)
190+
hashLength := hashOutput(n)
191+
personalState := blakePersonal(personal, n, k)
192+
hash := generateHash(state, personalState, g, hashLength, twist)
193+
194+
start := (i % indicesPerHashOutput(n)) * ((n + 7) / 8)
195+
end := start + ((n + 7) / 8)
196+
197+
minimalHash, err := expandArray(hash[start:end], collisionBitLength(n, k), 0)
198+
if err != nil {
199+
return nil, err
200+
}
201+
202+
return &node{
203+
hash: minimalHash,
204+
indices: []uint32{i},
205+
}, nil
206+
}
207+
208+
func newNodeFromChildrenRef(a, b *node, trim uint32) *node {
209+
len := uint32(len(a.hash))
210+
hash := make([]byte, len-trim)
211+
for i := trim; i < len; i++ {
212+
hash[i-trim] = a.hash[i] ^ b.hash[i]
213+
}
214+
215+
indices := make([]uint32, 0)
216+
if a.indicesBefore(b) {
217+
indices = append(indices, a.indices...)
218+
indices = append(indices, b.indices...)
219+
} else {
220+
indices = append(indices, b.indices...)
221+
indices = append(indices, a.indices...)
222+
}
223+
224+
n := &node{
225+
hash: hash,
226+
indices: indices,
227+
}
228+
229+
return n
230+
}
231+
232+
func isValidSolutionIterative(n, k uint32, personal []byte, twist bool, state []byte, indices []uint32) (bool, error) {
233+
var err error
234+
rows := make([]*node, len(indices))
235+
for i := range indices {
236+
rows[i], err = newNode(n, k, personal, twist, state, indices[i])
237+
if err != nil {
238+
return false, err
239+
}
240+
}
241+
242+
hashLen := hashLength(n, k)
243+
for len(rows) > 1 {
244+
curRows := make([]*node, 0)
245+
for i := 0; i < len(rows); i += 2 {
246+
a := rows[i]
247+
b := rows[i+1]
248+
err := validateSubtrees(n, k, a, b)
249+
if err != nil {
250+
return false, err
251+
}
252+
253+
row := newNodeFromChildrenRef(a, b, collisionByteLength(n, k))
254+
curRows = append(curRows, row)
255+
}
256+
257+
rows = curRows
258+
hashLen -= collisionByteLength(n, k)
259+
}
260+
261+
for i := uint32(0); i < hashLen; i++ {
262+
if rows[0].hash[i] != 0 {
263+
return false, nil
264+
}
265+
}
266+
267+
return true, nil
268+
}
269+
270+
func verify(n, k uint32, personal []byte, twist bool, header, soln []byte) (bool, error) {
271+
indices, err := indicesFromMinimal(n, k, soln)
272+
if err != nil {
273+
return false, err
274+
}
275+
276+
return isValidSolutionIterative(n, k, personal, twist, header, indices)
25277
}

0 commit comments

Comments
 (0)