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
|
# Minimal Nostr Go Library - Implementation Plan
## Overview
Build a minimal Go library for Nostr split into two modules:
**Module 1: Core** (`nostr-go` root) - 1 external dep
- Types, signing, serialization
- `github.com/btcsuite/btcd/btcec/v2` - BIP-340 Schnorr signatures
**Module 2: Relay** (`nostr-go/relay`) - 1 additional dep
- WebSocket connection, pub/sub
- `github.com/coder/websocket` - WebSocket library
- Imports core module
Users who only need types/signing don't pull in websocket dependencies.
## Package Structure
```
nostr-go/
├── go.mod # Core module
├── event.go # Event struct, ID computation, serialization
├── tags.go # Tag/Tags types and helpers
├── kinds.go # Event kind constants
├── filter.go # Filter struct and matching logic
├── keys.go # Key generation, signing, verification
├── bech32.go # Bech32 encoding/decoding (our impl, ~150 lines)
├── nip19.go # npub/nsec/note/nprofile encode/decode
├── envelope.go # Protocol messages (EVENT, REQ, OK, etc.)
├── *_test.go
│
└── relay/
├── go.mod # Relay module (imports core)
├── relay.go # WebSocket connection primitives
├── subscription.go # Subscription handling
└── *_test.go
```
## Core Types
### Event (event.go)
```go
type Event struct {
ID string `json:"id"` // 64-char hex (SHA256)
PubKey string `json:"pubkey"` // 64-char hex (x-only pubkey)
CreatedAt int64 `json:"created_at"`
Kind int `json:"kind"`
Tags Tags `json:"tags"`
Content string `json:"content"`
Sig string `json:"sig"` // 128-char hex (Schnorr sig)
}
```
**Design note**: Starting with hex strings for simplicity. Can evaluate byte arrays (`[32]byte`, `[64]byte`) later if type safety becomes important.
Key methods:
- `Serialize() []byte` - Canonical JSON for ID computation: `[0,"pubkey",created_at,kind,tags,"content"]`
- `ComputeID() string` - SHA256 hash of serialized form
- `Sign(privKeyHex string) error` - Sign with Schnorr, sets PubKey/ID/Sig
- `Verify() bool` - Verify signature
### Tags (tags.go)
```go
type Tag []string
type Tags []Tag
```
Methods: `Key()`, `Value()`, `Find(key)`, `FindAll(key)`, `GetD()`
### Filter (filter.go)
```go
type Filter struct {
IDs []string `json:"ids,omitempty"`
Kinds []int `json:"kinds,omitempty"`
Authors []string `json:"authors,omitempty"`
Tags map[string][]string `json:"-"` // Custom marshal for #e, #p
Since *int64 `json:"since,omitempty"`
Until *int64 `json:"until,omitempty"`
Limit int `json:"limit,omitempty"`
}
```
Methods: `Matches(event) bool`, custom `MarshalJSON`/`UnmarshalJSON` for tag filters
### Kinds (kinds.go)
Essential constants only:
```go
const (
KindMetadata = 0
KindTextNote = 1
KindContactList = 3
KindEncryptedDM = 4
KindDeletion = 5
KindRepost = 6
KindReaction = 7
)
```
Helpers: `IsRegular()`, `IsReplaceable()`, `IsEphemeral()`, `IsAddressable()`
### Envelopes (envelope.go)
Protocol messages as types with `Label()` and `MarshalJSON()`:
- Client→Relay: `EventEnvelope`, `ReqEnvelope`, `CloseEnvelope`
- Relay→Client: `EventEnvelope`, `OKEnvelope`, `EOSEEnvelope`, `ClosedEnvelope`, `NoticeEnvelope`
- `ParseEnvelope(data []byte) (Envelope, error)`
## Keys & Signing (keys.go)
Using `github.com/btcsuite/btcd/btcec/v2/schnorr`:
```go
func GenerateKey() (string, error)
func GetPublicKey(privKeyHex string) (string, error)
func (e *Event) Sign(privKeyHex string) error
func (e *Event) Verify() bool
```
## NIP-19 Encoding (nip19.go)
Bech32 encoding for human-readable identifiers:
```go
func EncodePublicKey(pubKeyHex string) (string, error) // -> npub1...
func EncodeSecretKey(secKeyHex string) (string, error) // -> nsec1...
func EncodeNote(eventID string) (string, error) // -> note1...
func DecodePublicKey(npub string) (string, error) // npub1... -> hex
func DecodeSecretKey(nsec string) (string, error) // nsec1... -> hex
func DecodeNote(note string) (string, error) // note1... -> hex
// TLV-encoded types (nprofile, nevent, naddr) can be added later
```
## WebSocket Primitives (relay.go)
Simple design - no complex goroutine orchestration:
```go
type Relay struct {
URL string
conn *websocket.Conn
mu sync.Mutex
}
func Connect(ctx context.Context, url string) (*Relay, error)
func (r *Relay) Close() error
func (r *Relay) Send(ctx context.Context, env Envelope) error
func (r *Relay) Receive(ctx context.Context) (Envelope, error)
func (r *Relay) Publish(ctx context.Context, event *Event) error
func (r *Relay) Subscribe(ctx context.Context, id string, filters ...Filter) (*Subscription, error)
type Subscription struct {
ID string
Events chan *Event
EOSE chan struct{}
}
func (s *Subscription) Listen() error
func (s *Subscription) Close() error
```
## Implementation Order
### Phase 1: Core Module (nostr-go)
1. **go.mod** - Module definition with btcec/v2 dependency
2. **event.go, tags.go, kinds.go** - Core types, serialization, ID computation
3. **keys.go** - Schnorr signing with btcec/v2
4. **bech32.go** - Bech32 encode/decode (~150 lines)
5. **nip19.go** - npub/nsec/note encoding
6. **filter.go** - Filter struct with custom JSON and matching
7. **envelope.go** - All envelope types and ParseEnvelope
8. **Core tests**
### Phase 2: Relay Module (nostr-go/relay)
1. **relay/go.mod** - Module definition with websocket dep, imports core
2. **relay/relay.go** - WebSocket connection primitives
3. **relay/subscription.go** - Subscription handling
4. **Relay tests**
## What's Omitted (v0.1)
- NIP-42 AUTH
- NIP-04 encrypted DMs
- Connection pooling / relay pool
- Automatic reconnection
- Advanced kinds (10000+)
## Verification
1. Unit tests for each module
2. Integration test: connect to `wss://relay.damus.io`, publish event, subscribe
3. Verify signature interop with existing Nostr clients/libraries
|