nip43.go raw
1 //go:build !(js && wasm)
2
3 package database
4
5 import (
6 "encoding/binary"
7 "fmt"
8 "time"
9
10 "github.com/dgraph-io/badger/v4"
11 "next.orly.dev/pkg/lol/chk"
12 "next.orly.dev/pkg/lol/log"
13 "next.orly.dev/pkg/nostr/encoders/hex"
14 )
15
16 // Database key prefixes for NIP-43
17 const (
18 nip43MemberPrefix = "nip43:member:"
19 nip43InvitePrefix = "nip43:invite:"
20 )
21
22 // AddNIP43Member adds a member to the NIP-43 membership list
23 func (d *D) AddNIP43Member(pubkey []byte, inviteCode string) error {
24 if len(pubkey) != 32 {
25 return fmt.Errorf("invalid pubkey length: %d", len(pubkey))
26 }
27
28 key := append([]byte(nip43MemberPrefix), pubkey...)
29
30 // Create membership record
31 membership := NIP43Membership{
32 Pubkey: pubkey,
33 AddedAt: time.Now(),
34 InviteCode: inviteCode,
35 }
36
37 // Serialize membership data
38 val := serializeNIP43Membership(membership)
39
40 return d.DB.Update(func(txn *badger.Txn) error {
41 return txn.Set(key, val)
42 })
43 }
44
45 // RemoveNIP43Member removes a member from the NIP-43 membership list
46 func (d *D) RemoveNIP43Member(pubkey []byte) error {
47 if len(pubkey) != 32 {
48 return fmt.Errorf("invalid pubkey length: %d", len(pubkey))
49 }
50
51 key := append([]byte(nip43MemberPrefix), pubkey...)
52
53 return d.DB.Update(func(txn *badger.Txn) error {
54 return txn.Delete(key)
55 })
56 }
57
58 // IsNIP43Member checks if a pubkey is a NIP-43 member
59 func (d *D) IsNIP43Member(pubkey []byte) (isMember bool, err error) {
60 if len(pubkey) != 32 {
61 return false, fmt.Errorf("invalid pubkey length: %d", len(pubkey))
62 }
63
64 key := append([]byte(nip43MemberPrefix), pubkey...)
65
66 err = d.DB.View(func(txn *badger.Txn) error {
67 _, err := txn.Get(key)
68 if err == badger.ErrKeyNotFound {
69 isMember = false
70 return nil
71 }
72 if err != nil {
73 return err
74 }
75 isMember = true
76 return nil
77 })
78
79 return isMember, err
80 }
81
82 // GetNIP43Membership retrieves membership details for a pubkey
83 func (d *D) GetNIP43Membership(pubkey []byte) (*NIP43Membership, error) {
84 if len(pubkey) != 32 {
85 return nil, fmt.Errorf("invalid pubkey length: %d", len(pubkey))
86 }
87
88 key := append([]byte(nip43MemberPrefix), pubkey...)
89 var membership *NIP43Membership
90
91 err := d.DB.View(func(txn *badger.Txn) error {
92 item, err := txn.Get(key)
93 if err != nil {
94 return err
95 }
96
97 return item.Value(func(val []byte) error {
98 membership = deserializeNIP43Membership(val)
99 return nil
100 })
101 })
102
103 if err != nil {
104 return nil, err
105 }
106
107 return membership, nil
108 }
109
110 // GetAllNIP43Members returns all NIP-43 members
111 func (d *D) GetAllNIP43Members() ([][]byte, error) {
112 var members [][]byte
113 prefix := []byte(nip43MemberPrefix)
114
115 err := d.DB.View(func(txn *badger.Txn) error {
116 opts := badger.DefaultIteratorOptions
117 opts.Prefix = prefix
118 opts.PrefetchValues = false // We only need keys
119
120 it := txn.NewIterator(opts)
121 defer it.Close()
122
123 for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
124 item := it.Item()
125 key := item.Key()
126 // Extract pubkey from key (skip prefix)
127 pubkey := make([]byte, 32)
128 copy(pubkey, key[len(prefix):])
129 members = append(members, pubkey)
130 }
131
132 return nil
133 })
134
135 return members, err
136 }
137
138 // StoreInviteCode stores an invite code with expiry
139 func (d *D) StoreInviteCode(code string, expiresAt time.Time) error {
140 key := append([]byte(nip43InvitePrefix), []byte(code)...)
141
142 // Serialize expiry time as unix timestamp
143 val := make([]byte, 8)
144 binary.BigEndian.PutUint64(val, uint64(expiresAt.Unix()))
145
146 return d.DB.Update(func(txn *badger.Txn) error {
147 entry := badger.NewEntry(key, val).WithTTL(time.Until(expiresAt))
148 return txn.SetEntry(entry)
149 })
150 }
151
152 // ValidateInviteCode checks if an invite code is valid and not expired
153 func (d *D) ValidateInviteCode(code string) (valid bool, err error) {
154 key := append([]byte(nip43InvitePrefix), []byte(code)...)
155
156 err = d.DB.View(func(txn *badger.Txn) error {
157 item, err := txn.Get(key)
158 if err == badger.ErrKeyNotFound {
159 valid = false
160 return nil
161 }
162 if err != nil {
163 return err
164 }
165
166 return item.Value(func(val []byte) error {
167 if len(val) != 8 {
168 return fmt.Errorf("invalid invite code value")
169 }
170 expiresAt := int64(binary.BigEndian.Uint64(val))
171 valid = time.Now().Unix() < expiresAt
172 return nil
173 })
174 })
175
176 return valid, err
177 }
178
179 // DeleteInviteCode removes an invite code (after use)
180 func (d *D) DeleteInviteCode(code string) error {
181 key := append([]byte(nip43InvitePrefix), []byte(code)...)
182
183 return d.DB.Update(func(txn *badger.Txn) error {
184 return txn.Delete(key)
185 })
186 }
187
188 // Helper functions for serialization
189
190 func serializeNIP43Membership(m NIP43Membership) []byte {
191 // Format: [pubkey(32)] [timestamp(8)] [invite_code_len(2)] [invite_code]
192 codeBytes := []byte(m.InviteCode)
193 codeLen := len(codeBytes)
194
195 buf := make([]byte, 32+8+2+codeLen)
196
197 // Copy pubkey
198 copy(buf[0:32], m.Pubkey)
199
200 // Write timestamp
201 binary.BigEndian.PutUint64(buf[32:40], uint64(m.AddedAt.Unix()))
202
203 // Write invite code length
204 binary.BigEndian.PutUint16(buf[40:42], uint16(codeLen))
205
206 // Write invite code
207 copy(buf[42:], codeBytes)
208
209 return buf
210 }
211
212 func deserializeNIP43Membership(data []byte) *NIP43Membership {
213 if len(data) < 42 {
214 return nil
215 }
216
217 m := &NIP43Membership{}
218
219 // Read pubkey
220 m.Pubkey = make([]byte, 32)
221 copy(m.Pubkey, data[0:32])
222
223 // Read timestamp
224 timestamp := binary.BigEndian.Uint64(data[32:40])
225 m.AddedAt = time.Unix(int64(timestamp), 0)
226
227 // Read invite code
228 codeLen := binary.BigEndian.Uint16(data[40:42])
229 if len(data) >= 42+int(codeLen) {
230 m.InviteCode = string(data[42 : 42+codeLen])
231 }
232
233 return m
234 }
235
236 // PublishNIP43MembershipEvent publishes membership change events
237 func (d *D) PublishNIP43MembershipEvent(kind int, pubkey []byte) error {
238 log.I.F("publishing NIP-43 event kind %d for pubkey %s", kind, hex.Enc(pubkey))
239
240 // Get relay identity
241 relaySecret, err := d.GetOrCreateRelayIdentitySecret()
242 if chk.E(err) {
243 return err
244 }
245
246 // This would integrate with the event publisher
247 // For now, just log it
248 log.D.F("would publish kind %d event for member %s", kind, hex.Enc(pubkey))
249
250 // The actual publishing will be done by the handler
251 _ = relaySecret
252
253 return nil
254 }
255