aboutsummaryrefslogtreecommitdiffstats
path: root/keys.go
diff options
context:
space:
mode:
authorClawd <ai@clawd.bot>2026-02-20 18:52:08 -0800
committerClawd <ai@clawd.bot>2026-02-20 18:52:08 -0800
commitd641f4566f051656bae79e406155c4f7f65ec338 (patch)
tree4cd01f3fa4585cf719116a303473e792ea67e82a /keys.go
parent84709fd67c02058334519ebee9c110f68b33d9b4 (diff)
embed secp256k1: replace btcec with internal pure-go implementation
This removes all external dependencies by embedding the secp256k1-learn implementation into internal/secp256k1. Changes: - Add internal/secp256k1 with field arithmetic, curve ops, keys, schnorr - Update keys.go to use internal secp256k1 package - Remove btcec/btcutil dependencies (go.mod is now clean) - All tests pass Tradeoffs: - ~10x slower crypto ops vs btcec (acceptable for nostr use case) - Not constant-time (documented limitation) - Zero external dependencies Refs: code.northwest.io/secp256k1-learn
Diffstat (limited to 'keys.go')
-rw-r--r--keys.go47
1 files changed, 21 insertions, 26 deletions
diff --git a/keys.go b/keys.go
index 3a3fb9c..b0fa8ee 100644
--- a/keys.go
+++ b/keys.go
@@ -1,34 +1,30 @@
1package nostr 1package nostr
2 2
3import ( 3import (
4 "crypto/rand"
5 "encoding/hex" 4 "encoding/hex"
6 "fmt" 5 "fmt"
7 "strings" 6 "strings"
8 "time" 7 "time"
9 8
10 "github.com/btcsuite/btcd/btcec/v2" 9 "code.northwest.io/nostr/internal/secp256k1"
11 "github.com/btcsuite/btcd/btcec/v2/schnorr"
12) 10)
13 11
14// Key represents a Nostr key, which may be a full private key or public-only. 12// Key represents a Nostr key, which may be a full private key or public-only.
15// Use GenerateKey or ParseKey for private keys, ParsePublicKey for public-only. 13// Use GenerateKey or ParseKey for private keys, ParsePublicKey for public-only.
16type Key struct { 14type Key struct {
17 priv *btcec.PrivateKey // nil for public-only keys 15 priv *secp256k1.PrivateKey // nil for public-only keys
18 pub *btcec.PublicKey // always set 16 pub *secp256k1.PublicKey // always set
19} 17}
20 18
21// GenerateKey generates a new random private key. 19// GenerateKey generates a new random private key.
22func GenerateKey() (*Key, error) { 20func GenerateKey() (*Key, error) {
23 var keyBytes [32]byte 21 priv, err := secp256k1.GeneratePrivateKey()
24 if _, err := rand.Read(keyBytes[:]); err != nil { 22 if err != nil {
25 return nil, fmt.Errorf("failed to generate random bytes: %w", err) 23 return nil, fmt.Errorf("failed to generate key: %w", err)
26 } 24 }
27
28 priv, _ := btcec.PrivKeyFromBytes(keyBytes[:])
29 return &Key{ 25 return &Key{
30 priv: priv, 26 priv: priv,
31 pub: priv.PubKey(), 27 pub: priv.PublicKey(),
32 }, nil 28 }, nil
33} 29}
34 30
@@ -56,14 +52,14 @@ func ParseKey(s string) (*Key, error) {
56 } 52 }
57 } 53 }
58 54
59 if len(privBytes) != 32 { 55 priv, err := secp256k1.NewPrivateKeyFromBytes(privBytes)
60 return nil, fmt.Errorf("private key must be 32 bytes, got %d", len(privBytes)) 56 if err != nil {
57 return nil, fmt.Errorf("invalid private key: %w", err)
61 } 58 }
62 59
63 priv, _ := btcec.PrivKeyFromBytes(privBytes)
64 return &Key{ 60 return &Key{
65 priv: priv, 61 priv: priv,
66 pub: priv.PubKey(), 62 pub: priv.PublicKey(),
67 }, nil 63 }, nil
68} 64}
69 65
@@ -96,7 +92,7 @@ func ParsePublicKey(s string) (*Key, error) {
96 return nil, fmt.Errorf("public key must be 32 bytes, got %d", len(pubBytes)) 92 return nil, fmt.Errorf("public key must be 32 bytes, got %d", len(pubBytes))
97 } 93 }
98 94
99 pub, err := schnorr.ParsePubKey(pubBytes) 95 pub, err := secp256k1.ParsePublicKeyXOnly(pubBytes)
100 if err != nil { 96 if err != nil {
101 return nil, fmt.Errorf("invalid public key: %w", err) 97 return nil, fmt.Errorf("invalid public key: %w", err)
102 } 98 }
@@ -114,7 +110,7 @@ func (k *Key) CanSign() bool {
114 110
115// Public returns the public key as a 64-character hex string. 111// Public returns the public key as a 64-character hex string.
116func (k *Key) Public() string { 112func (k *Key) Public() string {
117 return hex.EncodeToString(schnorr.SerializePubKey(k.pub)) 113 return hex.EncodeToString(k.pub.XOnlyBytes())
118} 114}
119 115
120// Private returns the private key as a 64-character hex string. 116// Private returns the private key as a 64-character hex string.
@@ -123,13 +119,12 @@ func (k *Key) Private() string {
123 if k.priv == nil { 119 if k.priv == nil {
124 return "" 120 return ""
125 } 121 }
126 return hex.EncodeToString(k.priv.Serialize()) 122 return hex.EncodeToString(k.priv.Bytes())
127} 123}
128 124
129// Npub returns the public key in bech32 npub format. 125// Npub returns the public key in bech32 npub format.
130func (k *Key) Npub() string { 126func (k *Key) Npub() string {
131 pubBytes := schnorr.SerializePubKey(k.pub) 127 npub, _ := Bech32Encode("npub", k.pub.XOnlyBytes())
132 npub, _ := Bech32Encode("npub", pubBytes)
133 return npub 128 return npub
134} 129}
135 130
@@ -139,7 +134,7 @@ func (k *Key) Nsec() string {
139 if k.priv == nil { 134 if k.priv == nil {
140 return "" 135 return ""
141 } 136 }
142 nsec, _ := Bech32Encode("nsec", k.priv.Serialize()) 137 nsec, _ := Bech32Encode("nsec", k.priv.Bytes())
143 return nsec 138 return nsec
144} 139}
145 140
@@ -168,12 +163,12 @@ func (k *Key) Sign(event *Event) error {
168 } 163 }
169 164
170 // Sign with Schnorr 165 // Sign with Schnorr
171 sig, err := schnorr.Sign(k.priv, idBytes) 166 sig, err := secp256k1.Sign(k.priv, idBytes)
172 if err != nil { 167 if err != nil {
173 return fmt.Errorf("failed to sign event: %w", err) 168 return fmt.Errorf("failed to sign event: %w", err)
174 } 169 }
175 170
176 event.Sig = hex.EncodeToString(sig.Serialize()) 171 event.Sig = hex.EncodeToString(sig.Bytes())
177 return nil 172 return nil
178} 173}
179 174
@@ -191,7 +186,7 @@ func (e *Event) Verify() bool {
191 return false 186 return false
192 } 187 }
193 188
194 pubKey, err := schnorr.ParsePubKey(pubKeyBytes) 189 pubKey, err := secp256k1.ParsePublicKeyXOnly(pubKeyBytes)
195 if err != nil { 190 if err != nil {
196 return false 191 return false
197 } 192 }
@@ -202,7 +197,7 @@ func (e *Event) Verify() bool {
202 return false 197 return false
203 } 198 }
204 199
205 sig, err := schnorr.ParseSignature(sigBytes) 200 sig, err := secp256k1.SignatureFromBytes(sigBytes)
206 if err != nil { 201 if err != nil {
207 return false 202 return false
208 } 203 }
@@ -213,5 +208,5 @@ func (e *Event) Verify() bool {
213 return false 208 return false
214 } 209 }
215 210
216 return sig.Verify(idBytes, pubKey) 211 return secp256k1.Verify(pubKey, idBytes, sig)
217} 212}