Skip to content
File

Blob: spec/pow/pow.go

go99 lines
1package pow
2 
3import (
4 "crypto/ed25519"
5 "fmt"
6 "time"
7 
8 "go.miragespace.co/specter/spec/protocol"
9 "go.miragespace.co/specter/util/hashcash"
10 
11 "github.com/twitchtv/twirp"
12)
13 
14type Parameters struct {
15 GetSubject func(pubKey ed25519.PublicKey) string
16 Expires time.Duration
17 Difficulty int
18}
19 
20type Decoded struct {
21 PubKey ed25519.PublicKey
22 Subject string
23}
24 
25func VerifySolution(req *protocol.ProofOfWork, p Parameters) (*Decoded, error) {
26 if len(req.GetPubKey()) != ed25519.PublicKeySize {
27 return nil, twirp.InvalidArgumentError("pub_key", "public key length is not ed25519")
28 }
29 
30 if len(req.GetSignature()) != ed25519.SignatureSize {
31 return nil, twirp.InvalidArgumentError("signature", "signature length is not ed25519")
32 }
33 
34 if len(req.GetSolution()) == 0 {
35 return nil, twirp.RequiredArgumentError("solution")
36 }
37 
38 pubKey := ed25519.PublicKey(req.GetPubKey())
39 
40 if !ed25519.Verify(pubKey, []byte(req.GetSolution()), req.GetSignature()) {
41 return nil, twirp.InvalidArgumentError("signature", "not a valid signature of solution by pub_key")
42 }
43 
44 hc, err := hashcash.Parse(req.GetSolution())
45 if err != nil {
46 return nil, twirp.InvalidArgumentError("solution", err.Error())
47 }
48 
49 if hc.Difficulty != p.Difficulty {
50 return nil, twirp.InvalidArgumentError("solution", "solution the wrong difficulty")
51 }
52 
53 if hc.ExpiresAt.IsZero() {
54 return nil, twirp.InvalidArgumentError("solution", "solution cannot have zero-value expiresAt")
55 }
56 
57 if time.Since(hc.ExpiresAt).Abs() > p.Expires*2 {
58 return nil, twirp.InvalidArgumentError("solution", "solution expires too far away from now")
59 }
60 
61 subject := p.GetSubject(pubKey)
62 if err := hc.Verify(subject); err != nil {
63 return nil, twirp.InvalidArgumentError("solution", err.Error())
64 }
65 
66 return &Decoded{
67 PubKey: pubKey,
68 Subject: subject,
69 }, nil
70}
71 
72func GenerateSolution(privKey ed25519.PrivateKey, p Parameters) (*protocol.ProofOfWork, error) {
73 if len(privKey) != ed25519.PrivateKeySize {
74 return nil, fmt.Errorf("private key length is not ed25519")
75 }
76 
77 var (
78 pubKey = privKey.Public().(ed25519.PublicKey)
79 subject = p.GetSubject(pubKey)
80 )
81 
82 hc := hashcash.New(hashcash.Hashcash{
83 Subject: subject,
84 Difficulty: p.Difficulty,
85 ExpiresAt: time.Now().Add(p.Expires),
86 })
87 
88 if err := hc.Solve(p.Difficulty); err != nil {
89 return nil, err
90 }
91 
92 sig := ed25519.Sign(privKey, []byte(hc.String()))
93 return &protocol.ProofOfWork{
94 PubKey: pubKey,
95 Signature: sig,
96 Solution: hc.String(),
97 }, nil
98}