helpers.go raw
1 package directory
2
3 import (
4 "github.com/minio/sha256-simd"
5 "encoding/hex"
6 "fmt"
7 "strconv"
8 "strings"
9 "time"
10
11 "next.orly.dev/pkg/lol/chk"
12 "next.orly.dev/pkg/lol/errorf"
13 "next.orly.dev/pkg/nostr/crypto/ec/schnorr"
14 "next.orly.dev/pkg/nostr/crypto/ec/secp256k1"
15 "next.orly.dev/pkg/nostr/interfaces/signer/p8k"
16 "next.orly.dev/pkg/nostr/encoders/bech32encoding"
17 "next.orly.dev/pkg/nostr/encoders/event"
18 )
19
20 // IdentityTagBuilder helps construct identity tags with proper signatures.
21 type IdentityTagBuilder struct {
22 identityPrivkey []byte
23 identityPubkey []byte
24 npubIdentity string
25 }
26
27 // NewIdentityTagBuilder creates a new identity tag builder with the given
28 // identity private key.
29 func NewIdentityTagBuilder(identityPrivkey []byte) (builder *IdentityTagBuilder, err error) {
30 if len(identityPrivkey) != 32 {
31 return nil, errorf.E("identity private key must be 32 bytes")
32 }
33
34 // Derive public key from secret key using p8k signer
35 var signer *p8k.Signer
36 if signer, err = p8k.New(); chk.E(err) {
37 return nil, errorf.E("failed to create signer: %w", err)
38 }
39 if err = signer.InitSec(identityPrivkey); chk.E(err) {
40 return nil, errorf.E("failed to initialize signer: %w", err)
41 }
42 identityPubkeyBytes := signer.Pub()
43
44 // Parse public key for npub encoding
45 var identityPubkey *secp256k1.PublicKey
46 if identityPubkey, err = schnorr.ParsePubKey(identityPubkeyBytes); chk.E(err) {
47 return nil, errorf.E("failed to parse public key: %w", err)
48 }
49
50 // Encode as npub
51 var npubIdentity []byte
52 if npubIdentity, err = bech32encoding.PublicKeyToNpub(identityPubkey); chk.E(err) {
53 return nil, errorf.E("failed to encode npub: %w", err)
54 }
55
56 return &IdentityTagBuilder{
57 identityPrivkey: identityPrivkey,
58 identityPubkey: identityPubkeyBytes,
59 npubIdentity: string(npubIdentity),
60 }, nil
61 }
62
63 // CreateIdentityTag creates a signed identity tag for the given delegate pubkey.
64 func (builder *IdentityTagBuilder) CreateIdentityTag(delegatePubkey []byte) (identityTag *IdentityTag, err error) {
65 if len(delegatePubkey) != 32 {
66 return nil, errorf.E("delegate pubkey must be 32 bytes")
67 }
68
69 // Generate nonce
70 var nonceHex string
71 if nonceHex, err = GenerateNonceHex(16); chk.E(err) {
72 return nil, errorf.E("failed to generate nonce: %w", err)
73 }
74
75 // Create message: nonce + delegate_pubkey_hex + identity_pubkey_hex
76 delegatePubkeyHex := hex.EncodeToString(delegatePubkey)
77 identityPubkeyHex := hex.EncodeToString(builder.identityPubkey)
78 message := nonceHex + delegatePubkeyHex + identityPubkeyHex
79
80 // Hash and sign using p8k signer
81 hash := sha256.Sum256([]byte(message))
82 var signer *p8k.Signer
83 if signer, err = p8k.New(); chk.E(err) {
84 return nil, errorf.E("failed to create signer: %w", err)
85 }
86 if err = signer.InitSec(builder.identityPrivkey); chk.E(err) {
87 return nil, errorf.E("failed to initialize signer: %w", err)
88 }
89 var signature []byte
90 if signature, err = signer.Sign(hash[:]); chk.E(err) {
91 return nil, errorf.E("failed to sign identity tag: %w", err)
92 }
93
94 identityTag = &IdentityTag{
95 NPubIdentity: builder.npubIdentity,
96 Nonce: nonceHex,
97 Signature: hex.EncodeToString(signature),
98 }
99
100 return
101 }
102
103 // GetNPubIdentity returns the npub-encoded identity.
104 func (builder *IdentityTagBuilder) GetNPubIdentity() string {
105 return builder.npubIdentity
106 }
107
108 // GetIdentityPubkey returns the raw identity public key.
109 func (builder *IdentityTagBuilder) GetIdentityPubkey() []byte {
110 return builder.identityPubkey
111 }
112
113 // KeyPoolManager helps manage HD key derivation and advertisement.
114 type KeyPoolManager struct {
115 masterSeed []byte
116 identityIndex uint32
117 currentIndices map[KeyPurpose]int
118 }
119
120 // NewKeyPoolManager creates a new key pool manager with the given master seed.
121 func NewKeyPoolManager(masterSeed []byte, identityIndex uint32) *KeyPoolManager {
122 return &KeyPoolManager{
123 masterSeed: masterSeed,
124 identityIndex: identityIndex,
125 currentIndices: make(map[KeyPurpose]int),
126 }
127 }
128
129 // GenerateDerivationPath creates a BIP32 derivation path for the given purpose and index.
130 func (kpm *KeyPoolManager) GenerateDerivationPath(purpose KeyPurpose, index int) string {
131 var usageIndex int
132 switch purpose {
133 case KeyPurposeSigning:
134 usageIndex = 0
135 case KeyPurposeEncryption:
136 usageIndex = 1
137 case KeyPurposeDelegation:
138 usageIndex = 2
139 default:
140 usageIndex = 0
141 }
142
143 return fmt.Sprintf("m/39103'/1237'/%d'/%d/%d", kpm.identityIndex, usageIndex, index)
144 }
145
146 // GetNextKeyIndex returns the next available key index for the given purpose.
147 func (kpm *KeyPoolManager) GetNextKeyIndex(purpose KeyPurpose) int {
148 current := kpm.currentIndices[purpose]
149 kpm.currentIndices[purpose] = current + 1
150 return current
151 }
152
153 // SetKeyIndex sets the current key index for the given purpose.
154 func (kpm *KeyPoolManager) SetKeyIndex(purpose KeyPurpose, index int) {
155 kpm.currentIndices[purpose] = index
156 }
157
158 // GetCurrentKeyIndex returns the current key index for the given purpose.
159 func (kpm *KeyPoolManager) GetCurrentKeyIndex(purpose KeyPurpose) int {
160 return kpm.currentIndices[purpose]
161 }
162
163 // TrustCalculator helps calculate trust scores and inheritance.
164 type TrustCalculator struct {
165 acts map[string]*TrustAct
166 }
167
168 // NewTrustCalculator creates a new trust calculator.
169 func NewTrustCalculator() *TrustCalculator {
170 return &TrustCalculator{
171 acts: make(map[string]*TrustAct),
172 }
173 }
174
175 // AddAct adds a trust act to the calculator.
176 func (tc *TrustCalculator) AddAct(act *TrustAct) {
177 key := act.GetTargetPubkey()
178 tc.acts[key] = act
179 }
180
181 // GetTrustLevel returns the trust level for a given pubkey.
182 func (tc *TrustCalculator) GetTrustLevel(pubkey string) TrustLevel {
183 if act, exists := tc.acts[pubkey]; exists {
184 if !act.IsExpired() {
185 return act.GetTrustLevel()
186 }
187 }
188 return TrustLevelNone // Return 0 for no trust
189 }
190
191 // CalculateInheritedTrust calculates inherited trust through the web of trust.
192 // With numeric trust levels, inherited trust is calculated by multiplying
193 // the trust percentages at each hop, reducing trust over distance.
194 func (tc *TrustCalculator) CalculateInheritedTrust(
195 fromPubkey, toPubkey string,
196 ) TrustLevel {
197 // Direct trust
198 if directTrust := tc.GetTrustLevel(toPubkey); directTrust > 0 {
199 return directTrust
200 }
201
202 // Look for inherited trust through intermediate nodes
203 var maxInheritedTrust TrustLevel = 0
204 for intermediatePubkey, act := range tc.acts {
205 if act.IsExpired() {
206 continue
207 }
208
209 // Check if we trust the intermediate node
210 intermediateLevel := tc.GetTrustLevel(intermediatePubkey)
211 if intermediateLevel == 0 {
212 continue
213 }
214
215 // Check if intermediate node trusts the target
216 targetLevel := tc.GetTrustLevel(toPubkey)
217 if targetLevel == 0 {
218 continue
219 }
220
221 // Calculate inherited trust level (multiply percentages)
222 inheritedLevel := tc.combinesTrustLevels(intermediateLevel, targetLevel)
223 if inheritedLevel > maxInheritedTrust {
224 maxInheritedTrust = inheritedLevel
225 }
226 }
227
228 return maxInheritedTrust
229 }
230
231 // combinesTrustLevels combines two trust levels to calculate inherited trust.
232 // With numeric trust levels (0-100), inherited trust is calculated by
233 // multiplying the two percentages: (level1 * level2) / 100
234 // This naturally reduces trust over distance.
235 func (tc *TrustCalculator) combinesTrustLevels(level1, level2 TrustLevel) TrustLevel {
236 // Multiply percentages: (level1% * level2%) = (level1 * level2) / 100
237 // Example: 75% trust * 50% trust = 37.5% inherited trust
238 combined := (uint16(level1) * uint16(level2)) / 100
239 return TrustLevel(combined)
240 }
241
242 // ReplicationFilter helps determine which events should be replicated.
243 type ReplicationFilter struct {
244 trustCalculator *TrustCalculator
245 acts map[string]*TrustAct
246 }
247
248 // NewReplicationFilter creates a new replication filter.
249 func NewReplicationFilter(trustCalculator *TrustCalculator) *ReplicationFilter {
250 return &ReplicationFilter{
251 trustCalculator: trustCalculator,
252 acts: make(map[string]*TrustAct),
253 }
254 }
255
256 // AddTrustAct adds a trust act to the filter.
257 func (rf *ReplicationFilter) AddTrustAct(act *TrustAct) {
258 rf.acts[act.GetTargetPubkey()] = act
259 }
260
261 // ShouldReplicate determines if an event should be replicated to a target relay.
262 func (rf *ReplicationFilter) ShouldReplicate(ev *event.E, targetPubkey string) bool {
263 act, exists := rf.acts[targetPubkey]
264 if !exists || act.IsExpired() {
265 return false
266 }
267
268 return act.ShouldReplicate(ev.Kind)
269 }
270
271 // GetReplicationTargets returns all target relays that should receive an event.
272 func (rf *ReplicationFilter) GetReplicationTargets(ev *event.E) []string {
273 var targets []string
274
275 for pubkey, act := range rf.acts {
276 if !act.IsExpired() && act.ShouldReplicate(ev.Kind) {
277 targets = append(targets, pubkey)
278 }
279 }
280
281 return targets
282 }
283
284 // EventBatcher helps batch events for efficient replication.
285 type EventBatcher struct {
286 maxBatchSize int
287 batches map[string][]*event.E
288 }
289
290 // NewEventBatcher creates a new event batcher.
291 func NewEventBatcher(maxBatchSize int) *EventBatcher {
292 if maxBatchSize <= 0 {
293 maxBatchSize = 100 // Default batch size
294 }
295
296 return &EventBatcher{
297 maxBatchSize: maxBatchSize,
298 batches: make(map[string][]*event.E),
299 }
300 }
301
302 // AddEvent adds an event to the batch for a target relay.
303 func (eb *EventBatcher) AddEvent(targetRelay string, ev *event.E) {
304 eb.batches[targetRelay] = append(eb.batches[targetRelay], ev)
305 }
306
307 // GetBatch returns the current batch for a target relay.
308 func (eb *EventBatcher) GetBatch(targetRelay string) []*event.E {
309 return eb.batches[targetRelay]
310 }
311
312 // IsBatchFull returns true if the batch for a target relay is full.
313 func (eb *EventBatcher) IsBatchFull(targetRelay string) bool {
314 return len(eb.batches[targetRelay]) >= eb.maxBatchSize
315 }
316
317 // FlushBatch returns and clears the batch for a target relay.
318 func (eb *EventBatcher) FlushBatch(targetRelay string) []*event.E {
319 batch := eb.batches[targetRelay]
320 eb.batches[targetRelay] = nil
321 return batch
322 }
323
324 // GetAllBatches returns all current batches.
325 func (eb *EventBatcher) GetAllBatches() map[string][]*event.E {
326 result := make(map[string][]*event.E)
327 for relay, batch := range eb.batches {
328 if len(batch) > 0 {
329 result[relay] = batch
330 }
331 }
332 return result
333 }
334
335 // FlushAllBatches returns and clears all batches.
336 func (eb *EventBatcher) FlushAllBatches() map[string][]*event.E {
337 result := eb.GetAllBatches()
338 eb.batches = make(map[string][]*event.E)
339 return result
340 }
341
342 // Utility functions
343
344 // ParseKindsList parses a comma-separated list of event kinds.
345 func ParseKindsList(kindsStr string) (kinds []uint16, err error) {
346 if kindsStr == "" {
347 return nil, nil
348 }
349
350 kindStrings := strings.Split(kindsStr, ",")
351 for _, kindStr := range kindStrings {
352 kindStr = strings.TrimSpace(kindStr)
353 if kindStr == "" {
354 continue
355 }
356
357 var kind uint64
358 if kind, err = strconv.ParseUint(kindStr, 10, 16); chk.E(err) {
359 return nil, errorf.E("invalid kind: %s", kindStr)
360 }
361
362 kinds = append(kinds, uint16(kind))
363 }
364
365 return
366 }
367
368 // FormatKindsList formats a list of event kinds as a comma-separated string.
369 func FormatKindsList(kinds []uint16) string {
370 if len(kinds) == 0 {
371 return ""
372 }
373
374 var kindStrings []string
375 for _, kind := range kinds {
376 kindStrings = append(kindStrings, strconv.FormatUint(uint64(kind), 10))
377 }
378
379 return strings.Join(kindStrings, ",")
380 }
381
382 // GenerateRequestID generates a unique request ID for replication requests.
383 func GenerateRequestID() (requestID string, err error) {
384 // Use timestamp + random nonce for uniqueness
385 timestamp := time.Now().Unix()
386 var nonce string
387 if nonce, err = GenerateNonceHex(8); chk.E(err) {
388 return
389 }
390
391 requestID = fmt.Sprintf("%d-%s", timestamp, nonce)
392 return
393 }
394
395 // CreateSuccessResponse creates a successful replication response.
396 func CreateSuccessResponse(
397 pubkey []byte,
398 requestID, sourceRelay string,
399 eventResults []*EventResult,
400 ) (response *DirectoryEventReplicationResponse, err error) {
401 return NewDirectoryEventReplicationResponse(
402 pubkey,
403 requestID,
404 ReplicationStatusSuccess,
405 "",
406 sourceRelay,
407 eventResults,
408 )
409 }
410
411 // CreateErrorResponse creates an error replication response.
412 func CreateErrorResponse(
413 pubkey []byte,
414 requestID, sourceRelay, errorMsg string,
415 ) (response *DirectoryEventReplicationResponse, err error) {
416 return NewDirectoryEventReplicationResponse(
417 pubkey,
418 requestID,
419 ReplicationStatusError,
420 errorMsg,
421 sourceRelay,
422 nil,
423 )
424 }
425
426 // CreateEventResult creates an event result for a replication response.
427 func CreateEventResult(eventID string, success bool, errorMsg string) *EventResult {
428 status := ReplicationStatusSuccess
429 if !success {
430 status = ReplicationStatusError
431 }
432
433 return &EventResult{
434 EventID: eventID,
435 Status: status,
436 Error: errorMsg,
437 }
438 }
439