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

Commit 1dc2ee5

Browse files
committed
add cuckaroo for cortex, clean up cuckoo suite
1 parent 6be808b commit 1dc2ee5

File tree

7 files changed

+230
-78
lines changed

7 files changed

+230
-78
lines changed

README.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,19 @@ requires a strict 1.2Gb, so be careful if you're using verthash in memory. At th
3535
| Eaglesong | no | yes
3636
| BeamHashIII | no | yes
3737
| ZelHash | no | yes
38+
| Cortex | no | yes
3839

3940
# Things to Note
4041

41-
- Most of these algorithms are partially optimized but I'm sure they could be improved. That being said, that will probably never happen
42-
since these have never been intended to be used for miner clients. All of these algorithms far surpass a reasonable threshold for performance and I
43-
have no intention of hypertuning them.
44-
- Cuckoo Cycle is built specifically for Aeternity. There is a modification of the `sipnode` function in the current version
45-
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)).
42+
- Most of these algorithms are partially optimized but I'm sure they could be improved. That being said, that will probably
43+
never happen since these have never been intended to be used for miner clients. All of these algorithms far surpass
44+
a reasonable threshold for performance and I have no intention of hypertuning them.
4645
- The base ProgPow implementation ("ProgPow094") exists in the `internal/progpow` package.
4746
- Since ZelHash is such a minor Equihash variant, it is treated as just "twisted Equihash" (in `equihash/`).
4847
- All testing is done on linux, windows support is hazy at best.
4948
- The library assumes the host architecture is little-endian, I'm fairly confident big-endian architectures will not function properly.
5049
- As of now, the only other algorithms that are on the list of "maybes" are: [cryptonight](https://github.com/Equim-chan/cryptonight),
51-
[randomx](https://git.dero.io/DERO_Foundation/RandomX), X25X, and the full
52-
cuckoo suite ([cuckatoo, cuckaroo](https://github.com/blockcypher/libgrin/tree/master/core/pow)).
50+
[randomx](https://git.dero.io/DERO_Foundation/RandomX), X25X, and cuckatoo.
5351

5452
# Roadmap
5553

cuckoo/README.md

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,32 @@
11
# Cuckoo
22

3-
There are many variations of the Cuckoo Cycle algorithm, this is the variation
4-
specifically for Aeternity. That being said, the only functional difference
5-
is the `ROTL` on the `hasher.XorLanes()` response (current versions of cuckoo
6-
do not do this). The header construction varies a bit too, Aeternity uses
7-
a `uint64` nonce instead of `uint32`.
3+
There are many variations of the Cuckoo Cycle algorithm - here only the ones
4+
that are needed are implemented. `Cuckatoo32` probably should be implemented
5+
as well for Grin. The differences for each variation are as follows (along with
6+
header generation):
7+
8+
- Aeternity uses Cuckoo29 with a legacy version of the `sipnode` hasher -
9+
the only functional difference is the `ROTL` on the `hasher.XorLanes()`
10+
response (current versions of cuckoo do not do this)
11+
```go
12+
func generateHeader(hash []byte, nonce uint64) []byte {
13+
nonceBytes := make([]byte, 8)
14+
binary.LittleEndian.PutUint64(nonceBytes, nonce)
15+
hashEncoded := []byte(base64.StdEncoding.EncodeToString(hash))
16+
nonceEncoded := []byte(base64.StdEncoding.EncodeToString(nonceBytes))
17+
header := append(hashEncoded, append(nonceEncoded, make([]byte, 24)...)...)
18+
19+
return header
20+
}
21+
```
22+
- Cortex uses Cuckaroo30 with a single difference - the `sipblock` hasher
23+
uses `siphash48` instead of `siphash24`.
24+
```go
25+
func generateHeader(hash []byte, nonce uint64) []byte {
26+
nonceBytes := make([]byte, 8)
27+
binary.LittleEndian.PutUint64(nonceBytes, nonce)
28+
header := append(hash, nonceBytes...)
29+
30+
return header
31+
}
32+
```

cuckoo/client.go

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,52 +8,75 @@ import (
88
"github.com/sencha-dev/powkit/internal/crypto"
99
)
1010

11+
type CuckooVariant int
12+
13+
const (
14+
Cuckoo CuckooVariant = iota
15+
Cuckatoo
16+
Cuckaroo
17+
Cuckarood
18+
Cuckaroom
19+
Cuckarooz
20+
)
21+
1122
type Client struct {
23+
variant CuckooVariant
1224
proofSize int
1325
edgeBits int
1426
edgeMask uint64
15-
nodeBits int
16-
nodeMask uint64
27+
sipnode crypto.SipNodeFunc
28+
sipblock crypto.SipBlockFunc
1729
}
1830

19-
func New(edgeBits, nodeBits, proofSize int) *Client {
31+
func newClient(variant CuckooVariant, edgeBits, proofSize int, sipnode crypto.SipNodeFunc, sipblock crypto.SipBlockFunc) *Client {
2032
c := &Client{
33+
variant: variant,
2134
proofSize: proofSize,
2235
edgeBits: edgeBits,
2336
edgeMask: (uint64(1) << edgeBits) - 1,
24-
nodeBits: nodeBits,
25-
nodeMask: (uint64(1) << nodeBits) - 1,
37+
sipnode: sipnode,
38+
sipblock: sipblock,
2639
}
2740

2841
return c
2942
}
3043

44+
func NewCuckoo(edgeBits, proofSize int, sipnode crypto.SipNodeFunc, sipblock crypto.SipBlockFunc) *Client {
45+
return newClient(Cuckoo, edgeBits, proofSize, sipnode, sipblock)
46+
}
47+
3148
func NewAeternity() *Client {
32-
return New(29, 29, 42)
49+
return NewCuckoo(29, 42, crypto.SipNode24Legacy, nil)
3350
}
3451

35-
func (c *Client) Verify(hash []byte, nonce uint64, sols []uint64) (bool, error) {
36-
if len(hash) != 32 {
37-
return false, fmt.Errorf("hash must be 32 bytes")
38-
} else if len(sols) != 42 {
39-
return false, fmt.Errorf("sols must be 42 uint64s")
40-
}
52+
func NewCuckaroo(edgeBits, proofSize int, sipnode crypto.SipNodeFunc, sipblock crypto.SipBlockFunc) *Client {
53+
return newClient(Cuckaroo, edgeBits, proofSize, sipnode, sipblock)
54+
}
4155

42-
// encode header
43-
nonceBytes := make([]uint8, 8)
44-
binary.LittleEndian.PutUint64(nonceBytes, nonce)
45-
hashEncoded := []byte(base64.StdEncoding.EncodeToString(hash))
46-
nonceEncoded := []byte(base64.StdEncoding.EncodeToString(nonceBytes))
47-
header := append(hashEncoded, append(nonceEncoded, make([]byte, 24)...)...)
56+
func NewCortex() *Client {
57+
return NewCuckaroo(30, 42, nil, crypto.SipBlock48)
58+
}
59+
60+
func (c *Client) Verify(header []byte, sols []uint64) (bool, error) {
61+
if len(sols) != c.proofSize {
62+
return false, fmt.Errorf("sols must be %d uint64s", c.proofSize)
63+
}
4864

4965
// create siphash keys
50-
h := crypto.Blake2b256(header)
66+
hash := crypto.Blake2b256(header)
5167
keys := [4]uint64{
52-
binary.LittleEndian.Uint64(h[0:8]),
53-
binary.LittleEndian.Uint64(h[8:16]),
54-
binary.LittleEndian.Uint64(h[16:24]),
55-
binary.LittleEndian.Uint64(h[24:32]),
68+
binary.LittleEndian.Uint64(hash[0:8]),
69+
binary.LittleEndian.Uint64(hash[8:16]),
70+
binary.LittleEndian.Uint64(hash[16:24]),
71+
binary.LittleEndian.Uint64(hash[24:32]),
5672
}
5773

58-
return verify(c.proofSize, c.edgeMask, keys, sols)
74+
switch c.variant {
75+
case Cuckoo:
76+
return c.cuckoo(keys, sols)
77+
case Cuckaroo:
78+
return c.cuckaroo(keys, sols)
79+
default:
80+
return false, fmt.Errorf("unsupported cuckoo variant")
81+
}
5982
}

cuckoo/cuckaroo.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright (c) 2013-2020 John Tromp
2+
3+
package cuckoo
4+
5+
func (c *Client) cuckaroo(siphashKeys [4]uint64, edges []uint64) (bool, error) {
6+
uvs := make([]uint64, 2*c.proofSize)
7+
var xor0, xor1 uint64
8+
9+
for n := 0; n < c.proofSize; n++ {
10+
if edges[n] > c.edgeMask {
11+
return false, ErrPowTooBig
12+
} else if n < 0 && edges[n] <= edges[n-1] {
13+
return false, ErrPowTooSmall
14+
}
15+
16+
edge := c.sipblock(siphashKeys, edges[n])
17+
uvs[2*n] = edge & c.edgeMask
18+
xor0 ^= uvs[2*n]
19+
uvs[2*n+1] = (edge >> 32) & c.edgeMask
20+
xor1 ^= uvs[2*n+1]
21+
}
22+
23+
if xor0|xor1 != 0 {
24+
return false, ErrPowNotMatching
25+
}
26+
27+
var i, j, n int
28+
for {
29+
j = i
30+
k := j
31+
32+
for {
33+
k = (k + 2) % (2 * c.proofSize)
34+
if k == i {
35+
break
36+
}
37+
38+
if uvs[k] == uvs[i] {
39+
if j != i {
40+
return false, ErrPowBranch
41+
}
42+
43+
j = k
44+
}
45+
}
46+
47+
if j == i {
48+
return false, ErrPowDeadEnd
49+
}
50+
51+
i = j ^ 1
52+
n++
53+
54+
if i == 0 {
55+
break
56+
}
57+
}
58+
59+
if n != c.proofSize {
60+
return false, ErrPowShortCycle
61+
}
62+
63+
return true, nil
64+
}

cuckoo/cuckoo.go

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,26 @@
22

33
package cuckoo
44

5-
import (
6-
"fmt"
7-
)
8-
9-
import (
10-
"github.com/sencha-dev/powkit/internal/crypto"
11-
)
12-
13-
func sipnode(edgeMask uint64, siphashKeys [4]uint64, edge, uorv uint64) uint64 {
14-
hasher := crypto.NewSipHasher(siphashKeys[0], siphashKeys[1], siphashKeys[2], siphashKeys[3])
15-
hasher.Hash24(2*edge + uorv)
16-
17-
value := hasher.XorLanes()
18-
value = value<<17 | value>>47
19-
20-
return value & edgeMask
21-
}
22-
23-
func verify(proofSize int, edgeMask uint64, siphashKeys [4]uint64, edges []uint64) (bool, error) {
24-
uvs := make([]uint64, 2*proofSize)
5+
func (c *Client) cuckoo(siphashKeys [4]uint64, edges []uint64) (bool, error) {
6+
uvs := make([]uint64, 2*c.proofSize)
257
var xor0, xor1 uint64
268

27-
for n := 0; n < proofSize; n++ {
28-
if edges[n] > edgeMask {
29-
return false, fmt.Errorf("pow too big")
9+
for n := 0; n < c.proofSize; n++ {
10+
if edges[n] > c.edgeMask {
11+
return false, ErrPowTooBig
3012
} else if n < 0 && edges[n] <= edges[n-1] {
31-
return false, fmt.Errorf("pow too small")
13+
return false, ErrPowTooSmall
3214
}
3315

34-
uvs[2*n] = sipnode(edgeMask, siphashKeys, edges[n], 0)
16+
uvs[2*n] = c.sipnode(c.edgeMask, siphashKeys, edges[n], 0)
3517
xor0 ^= uvs[2*n]
3618

37-
uvs[2*n+1] = sipnode(edgeMask, siphashKeys, edges[n], 1)
19+
uvs[2*n+1] = c.sipnode(c.edgeMask, siphashKeys, edges[n], 1)
3820
xor1 ^= uvs[2*n+1]
3921
}
4022

4123
if xor0|xor1 != 0 {
42-
return false, fmt.Errorf("pow not matching")
24+
return false, ErrPowNotMatching
4325
}
4426

4527
var i, j, n int
@@ -48,22 +30,22 @@ func verify(proofSize int, edgeMask uint64, siphashKeys [4]uint64, edges []uint6
4830
k := j
4931

5032
for {
51-
k = (k + 2) % (2 * proofSize)
33+
k = (k + 2) % (2 * c.proofSize)
5234
if k == i {
5335
break
5436
}
5537

5638
if uvs[k] == uvs[i] {
5739
if j != i {
58-
return false, fmt.Errorf("pow branch")
40+
return false, ErrPowBranch
5941
}
6042

6143
j = k
6244
}
6345
}
6446

6547
if j == i {
66-
return false, fmt.Errorf("pow dead end")
48+
return false, ErrPowDeadEnd
6749
}
6850

6951
i = j ^ 1
@@ -74,8 +56,8 @@ func verify(proofSize int, edgeMask uint64, siphashKeys [4]uint64, edges []uint6
7456
}
7557
}
7658

77-
if n != proofSize {
78-
return false, fmt.Errorf("pow short cycle")
59+
if n != c.proofSize {
60+
return false, ErrPowShortCycle
7961
}
8062

8163
return true, nil

0 commit comments

Comments
 (0)