-
Notifications
You must be signed in to change notification settings - Fork 1
/
pow.go
115 lines (103 loc) · 2.67 KB
/
pow.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package pow
import (
"crypto/rand"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"strings"
"github.com/ncw/gmp"
)
const version = "s"
var (
mod = gmp.NewInt(0)
exp = gmp.NewInt(0)
one = gmp.NewInt(1)
two = gmp.NewInt(2)
)
func init() {
mod.Lsh(one, 1279)
mod.Sub(mod, one)
exp.Lsh(one, 1277)
}
type Challenge struct {
d uint32
x *gmp.Int
}
// DecodeChallenge decodes a redpwnpow challenge produced by String.
func DecodeChallenge(v string) (*Challenge, error) {
parts := strings.SplitN(v, ".", 3)
if len(parts) != 3 || parts[0] != version {
return nil, errors.New("incorrect version")
}
dBytes, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
return nil, err
}
if len(dBytes) > 4 {
return nil, errors.New("difficulty too long")
}
// pad start with 0s to 4 bytes
dBytes = append(make([]byte, 4-len(dBytes)), dBytes...)
xBytes, err := base64.StdEncoding.DecodeString(parts[2])
if err != nil {
return nil, err
}
d := binary.BigEndian.Uint32(dBytes)
x := gmp.NewInt(0).SetBytes(xBytes)
return &Challenge{d: d, x: x}, nil
}
// GenerateChallenge creates a new random challenge.
func GenerateChallenge(d uint32) *Challenge {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return &Challenge{
x: gmp.NewInt(0).SetBytes(b),
d: d,
}
}
// String encodes the challenge in a format that can be decoded by DecodeChallenge.
func (c *Challenge) String() string {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, c.d)
return fmt.Sprintf("%s.%s.%s", version, base64.StdEncoding.EncodeToString(b), base64.StdEncoding.EncodeToString(c.x.Bytes()))
}
// Solve solves the challenge and returns a solution proof that can be checked by Check.
func (c *Challenge) Solve() string {
x := gmp.NewInt(0).Set(c.x) // dont mutate c.x
for i := uint32(0); i < c.d; i++ {
x.Exp(x, exp, mod)
x.Xor(x, one)
}
return fmt.Sprintf("%s.%s", version, base64.StdEncoding.EncodeToString(x.Bytes()))
}
func decodeSolution(s string) (*gmp.Int, error) {
parts := strings.SplitN(s, ".", 2)
if len(parts) != 2 || parts[0] != version {
return nil, errors.New("incorrect version")
}
yBytes, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
return nil, err
}
return gmp.NewInt(0).SetBytes(yBytes), nil
}
// Check verifies that a solution proof from Solve is correct.
func (c *Challenge) Check(s string) (bool, error) {
y, err := decodeSolution(s)
if err != nil {
return false, fmt.Errorf("decode solution: %w", err)
}
for i := uint32(0); i < c.d; i++ {
y.Xor(y, one)
y.Exp(y, two, mod)
}
x := gmp.NewInt(0).Set(c.x) // dont mutate c.x
if x.Cmp(y) == 0 {
return true, nil
}
x.Sub(mod, c.x)
return x.Cmp(y) == 0, nil
}