summaryrefslogtreecommitdiffstats
path: root/keys.go
blob: 3a3fb9cdb53a515fc6c7e706dd6f6bceb3719338 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
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)
}