p8k.mx raw
1 // Package p8k provides a signer.I implementation backed by crypto/secp256k1.
2 package p8k
3
4 import (
5 "crypto/rand"
6 "crypto/secp256k1"
7 "crypto/sha256"
8 "fmt"
9 "io"
10 )
11
12 type Signer struct {
13 sec [32]byte
14 pub [32]byte
15 hasSec bool
16 hasPub bool
17 }
18
19 func New() (s *Signer, err error) {
20 return &Signer{}, nil
21 }
22
23 func MustNew() (s *Signer) {
24 return &Signer{}
25 }
26
27 func (s *Signer) Generate() (err error) {
28 var sk [32]byte
29 for {
30 if _, rerr := io.ReadFull(rand.Reader(), sk[:]); rerr != nil {
31 return rerr
32 }
33 if secp256k1.ValidateSecretKey(sk[:]) {
34 break
35 }
36 }
37 pk, ok := secp256k1.PubKeyFromSecKey(sk)
38 if !ok {
39 return fmt.Errorf("pubkey derivation failed")
40 }
41 s.sec = sk
42 s.pub = pk
43 s.hasSec = true
44 s.hasPub = true
45 return nil
46 }
47
48 func (s *Signer) InitSec(sec []byte) (err error) {
49 if len(sec) != 32 {
50 return fmt.Errorf("invalid secret key length: %d", len(sec))
51 }
52 var sk [32]byte
53 copy(sk[:], sec)
54 pk, ok := secp256k1.PubKeyFromSecKey(sk)
55 if !ok {
56 return fmt.Errorf("invalid secret key")
57 }
58 s.sec = sk
59 s.pub = pk
60 s.hasSec = true
61 s.hasPub = true
62 return nil
63 }
64
65 func (s *Signer) InitPub(pub []byte) (err error) {
66 if !secp256k1.ValidatePubKey(pub) {
67 return fmt.Errorf("invalid public key")
68 }
69 copy(s.pub[:], pub)
70 s.hasPub = true
71 s.hasSec = false
72 return nil
73 }
74
75 func (s *Signer) Sec() (buf []byte) {
76 if !s.hasSec {
77 return nil
78 }
79 out := []byte{:32}
80 copy(out, s.sec[:])
81 return out
82 }
83
84 func (s *Signer) Pub() (buf []byte) {
85 if !s.hasPub {
86 return nil
87 }
88 out := []byte{:32}
89 copy(out, s.pub[:])
90 return out
91 }
92
93 func (s *Signer) Sign(msg []byte) (der []byte, err error) {
94 if !s.hasSec {
95 return nil, fmt.Errorf("no secret key")
96 }
97 if len(msg) != 32 {
98 return nil, fmt.Errorf("message must be 32 bytes, got %d", len(msg))
99 }
100 var m, aux [32]byte
101 copy(m[:], msg)
102 // Aux randomness: BIP-340 recommends fresh randomness per signature, but
103 // zero aux is also valid (deterministic signing). Use deterministic for
104 // testability; callers wanting fresh aux can pre-randomize.
105 sig, ok := secp256k1.SignSchnorr(s.sec, m, aux)
106 if !ok {
107 return nil, fmt.Errorf("sign failed")
108 }
109 out := []byte{:64}
110 copy(out, sig[:])
111 return out, nil
112 }
113
114 func (s *Signer) Verify(msg, sig []byte) (good bool, err error) {
115 if !s.hasPub {
116 return false, fmt.Errorf("no public key")
117 }
118 if len(msg) != 32 {
119 return false, fmt.Errorf("message must be 32 bytes, got %d", len(msg))
120 }
121 if len(sig) != 64 {
122 return false, fmt.Errorf("signature must be 64 bytes, got %d", len(sig))
123 }
124 var m [32]byte
125 var sg [64]byte
126 copy(m[:], msg)
127 copy(sg[:], sig)
128 return secp256k1.VerifySchnorr(s.pub, m, sg), nil
129 }
130
131 func (s *Signer) Zero() {
132 for i := range s.sec {
133 s.sec[i] = 0
134 }
135 for i := range s.pub {
136 s.pub[i] = 0
137 }
138 s.hasSec = false
139 s.hasPub = false
140 }
141
142 func (s *Signer) ECDH(pub []byte) (sec []byte, err error) {
143 raw, err := s.ECDHRaw(pub)
144 if err != nil {
145 return nil, err
146 }
147 h := sha256.Sum256(raw)
148 out := []byte{:32}
149 copy(out, h[:])
150 return out, nil
151 }
152
153 func (s *Signer) ECDHRaw(pub []byte) (raw []byte, err error) {
154 if !s.hasSec {
155 return nil, fmt.Errorf("no secret key")
156 }
157 if len(pub) != 32 {
158 return nil, fmt.Errorf("pubkey must be 32 bytes, got %d", len(pub))
159 }
160 var pk [32]byte
161 copy(pk[:], pub)
162 shared, ok := secp256k1.ECDH(s.sec, pk)
163 if !ok {
164 return nil, fmt.Errorf("ecdh failed")
165 }
166 out := []byte{:32}
167 copy(out, shared[:])
168 return out, nil
169 }
170