From d641f4566f051656bae79e406155c4f7f65ec338 Mon Sep 17 00:00:00 2001 From: Clawd Date: Fri, 20 Feb 2026 18:52:08 -0800 Subject: 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 --- keys.go | 47 +++++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 26 deletions(-) (limited to 'keys.go') diff --git a/keys.go b/keys.go index 3a3fb9c..b0fa8ee 100644 --- a/keys.go +++ b/keys.go @@ -1,34 +1,30 @@ package nostr import ( - "crypto/rand" "encoding/hex" "fmt" "strings" "time" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr" + "code.northwest.io/nostr/internal/secp256k1" ) // Key represents a Nostr key, which may be a full private key or public-only. // Use GenerateKey or ParseKey for private keys, ParsePublicKey for public-only. type Key struct { - priv *btcec.PrivateKey // nil for public-only keys - pub *btcec.PublicKey // always set + priv *secp256k1.PrivateKey // nil for public-only keys + pub *secp256k1.PublicKey // always set } // GenerateKey generates a new random private key. func GenerateKey() (*Key, error) { - var keyBytes [32]byte - if _, err := rand.Read(keyBytes[:]); err != nil { - return nil, fmt.Errorf("failed to generate random bytes: %w", err) + priv, err := secp256k1.GeneratePrivateKey() + if err != nil { + return nil, fmt.Errorf("failed to generate key: %w", err) } - - priv, _ := btcec.PrivKeyFromBytes(keyBytes[:]) return &Key{ priv: priv, - pub: priv.PubKey(), + pub: priv.PublicKey(), }, nil } @@ -56,14 +52,14 @@ func ParseKey(s string) (*Key, error) { } } - if len(privBytes) != 32 { - return nil, fmt.Errorf("private key must be 32 bytes, got %d", len(privBytes)) + priv, err := secp256k1.NewPrivateKeyFromBytes(privBytes) + if err != nil { + return nil, fmt.Errorf("invalid private key: %w", err) } - priv, _ := btcec.PrivKeyFromBytes(privBytes) return &Key{ priv: priv, - pub: priv.PubKey(), + pub: priv.PublicKey(), }, nil } @@ -96,7 +92,7 @@ func ParsePublicKey(s string) (*Key, error) { return nil, fmt.Errorf("public key must be 32 bytes, got %d", len(pubBytes)) } - pub, err := schnorr.ParsePubKey(pubBytes) + pub, err := secp256k1.ParsePublicKeyXOnly(pubBytes) if err != nil { return nil, fmt.Errorf("invalid public key: %w", err) } @@ -114,7 +110,7 @@ func (k *Key) CanSign() bool { // Public returns the public key as a 64-character hex string. func (k *Key) Public() string { - return hex.EncodeToString(schnorr.SerializePubKey(k.pub)) + return hex.EncodeToString(k.pub.XOnlyBytes()) } // Private returns the private key as a 64-character hex string. @@ -123,13 +119,12 @@ func (k *Key) Private() string { if k.priv == nil { return "" } - return hex.EncodeToString(k.priv.Serialize()) + return hex.EncodeToString(k.priv.Bytes()) } // Npub returns the public key in bech32 npub format. func (k *Key) Npub() string { - pubBytes := schnorr.SerializePubKey(k.pub) - npub, _ := Bech32Encode("npub", pubBytes) + npub, _ := Bech32Encode("npub", k.pub.XOnlyBytes()) return npub } @@ -139,7 +134,7 @@ func (k *Key) Nsec() string { if k.priv == nil { return "" } - nsec, _ := Bech32Encode("nsec", k.priv.Serialize()) + nsec, _ := Bech32Encode("nsec", k.priv.Bytes()) return nsec } @@ -168,12 +163,12 @@ func (k *Key) Sign(event *Event) error { } // Sign with Schnorr - sig, err := schnorr.Sign(k.priv, idBytes) + sig, err := secp256k1.Sign(k.priv, idBytes) if err != nil { return fmt.Errorf("failed to sign event: %w", err) } - event.Sig = hex.EncodeToString(sig.Serialize()) + event.Sig = hex.EncodeToString(sig.Bytes()) return nil } @@ -191,7 +186,7 @@ func (e *Event) Verify() bool { return false } - pubKey, err := schnorr.ParsePubKey(pubKeyBytes) + pubKey, err := secp256k1.ParsePublicKeyXOnly(pubKeyBytes) if err != nil { return false } @@ -202,7 +197,7 @@ func (e *Event) Verify() bool { return false } - sig, err := schnorr.ParseSignature(sigBytes) + sig, err := secp256k1.SignatureFromBytes(sigBytes) if err != nil { return false } @@ -213,5 +208,5 @@ func (e *Event) Verify() bool { return false } - return sig.Verify(idBytes, pubKey) + return secp256k1.Verify(pubKey, idBytes, sig) } -- cgit v1.2.3