package nostr import ( "crypto/rand" "encoding/hex" "fmt" "strings" "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" ) // 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 } // 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, _ := btcec.PrivKeyFromBytes(keyBytes[:]) return &Key{ priv: priv, pub: priv.PubKey(), }, nil } // ParseKey parses a private key from hex or nsec (bech32) format. func ParseKey(s string) (*Key, error) { var privBytes []byte if strings.HasPrefix(s, "nsec1") { hrp, data, err := Bech32Decode(s) if err != nil { return nil, fmt.Errorf("invalid nsec: %w", err) } if hrp != "nsec" { return nil, fmt.Errorf("invalid prefix: expected nsec, got %s", hrp) } if len(data) != 32 { return nil, fmt.Errorf("invalid nsec data length: %d", len(data)) } privBytes = data } else { var err error privBytes, err = hex.DecodeString(s) if err != nil { return nil, fmt.Errorf("invalid hex: %w", err) } } if len(privBytes) != 32 { return nil, fmt.Errorf("private key must be 32 bytes, got %d", len(privBytes)) } priv, _ := btcec.PrivKeyFromBytes(privBytes) return &Key{ priv: priv, pub: priv.PubKey(), }, nil } // ParsePublicKey parses a public key from hex or npub (bech32) format. // The returned Key can only verify, not sign. func ParsePublicKey(s string) (*Key, error) { var pubBytes []byte if strings.HasPrefix(s, "npub1") { hrp, data, err := Bech32Decode(s) if err != nil { return nil, fmt.Errorf("invalid npub: %w", err) } if hrp != "npub" { return nil, fmt.Errorf("invalid prefix: expected npub, got %s", hrp) } if len(data) != 32 { return nil, fmt.Errorf("invalid npub data length: %d", len(data)) } pubBytes = data } else { var err error pubBytes, err = hex.DecodeString(s) if err != nil { return nil, fmt.Errorf("invalid hex: %w", err) } } if len(pubBytes) != 32 { return nil, fmt.Errorf("public key must be 32 bytes, got %d", len(pubBytes)) } pub, err := schnorr.ParsePubKey(pubBytes) if err != nil { return nil, fmt.Errorf("invalid public key: %w", err) } return &Key{ priv: nil, pub: pub, }, nil } // CanSign returns true if this key can sign events (has private key). func (k *Key) CanSign() bool { return k.priv != nil } // Public returns the public key as a 64-character hex string. func (k *Key) Public() string { return hex.EncodeToString(schnorr.SerializePubKey(k.pub)) } // Private returns the private key as a 64-character hex string. // Returns empty string if this is a public-only key. func (k *Key) Private() string { if k.priv == nil { return "" } return hex.EncodeToString(k.priv.Serialize()) } // Npub returns the public key in bech32 npub format. func (k *Key) Npub() string { pubBytes := schnorr.SerializePubKey(k.pub) npub, _ := Bech32Encode("npub", pubBytes) return npub } // Nsec returns the private key in bech32 nsec format. // Returns empty string if this is a public-only key. func (k *Key) Nsec() string { if k.priv == nil { return "" } nsec, _ := Bech32Encode("nsec", k.priv.Serialize()) return nsec } // Sign signs the event with this key. // Sets the PubKey, ID, and Sig fields on the event. // Returns an error if this is a public-only key. func (k *Key) Sign(event *Event) error { if k.priv == nil { return fmt.Errorf("cannot sign: public-only key") } // Set public key event.PubKey = k.Public() if event.CreatedAt == 0 { event.CreatedAt = time.Now().Unix() } // Compute ID event.SetID() // Hash the ID for signing idBytes, err := hex.DecodeString(event.ID) if err != nil { return fmt.Errorf("failed to decode event ID: %w", err) } // Sign with Schnorr sig, err := schnorr.Sign(k.priv, idBytes) if err != nil { return fmt.Errorf("failed to sign event: %w", err) } event.Sig = hex.EncodeToString(sig.Serialize()) return nil } // Verify verifies the event signature. // Returns true if the signature is valid, false otherwise. func (e *Event) Verify() bool { // Verify ID first if !e.CheckID() { return false } // Decode public key pubKeyBytes, err := hex.DecodeString(e.PubKey) if err != nil || len(pubKeyBytes) != 32 { return false } pubKey, err := schnorr.ParsePubKey(pubKeyBytes) if err != nil { return false } // Decode signature sigBytes, err := hex.DecodeString(e.Sig) if err != nil { return false } sig, err := schnorr.ParseSignature(sigBytes) if err != nil { return false } // Decode ID (message hash) idBytes, err := hex.DecodeString(e.ID) if err != nil { return false } return sig.Verify(idBytes, pubKey) }