trust.go raw
1 package find
2
3 import (
4 "fmt"
5 "sync"
6 "time"
7
8 "next.orly.dev/pkg/nostr/encoders/hex"
9 )
10
11 // TrustGraph manages trust relationships between registry services
12 type TrustGraph struct {
13 mu sync.RWMutex
14 entries map[string][]TrustEntry // pubkey -> trust entries
15 selfPubkey []byte // This registry service's pubkey
16 lastUpdated map[string]time.Time // pubkey -> last update time
17 decayFactors map[int]float64 // hop distance -> decay factor
18 }
19
20 // NewTrustGraph creates a new trust graph
21 func NewTrustGraph(selfPubkey []byte) *TrustGraph {
22 return &TrustGraph{
23 entries: make(map[string][]TrustEntry),
24 selfPubkey: selfPubkey,
25 lastUpdated: make(map[string]time.Time),
26 decayFactors: map[int]float64{
27 0: 1.0, // Direct trust (0-hop)
28 1: 0.8, // 1-hop trust
29 2: 0.6, // 2-hop trust
30 3: 0.4, // 3-hop trust
31 4: 0.0, // 4+ hops not counted
32 },
33 }
34 }
35
36 // AddTrustGraph adds a trust graph from another registry service
37 func (tg *TrustGraph) AddTrustGraph(graph *TrustGraph) error {
38 tg.mu.Lock()
39 defer tg.mu.Unlock()
40
41 sourcePubkey := hex.Enc(graph.selfPubkey)
42
43 // Copy entries from the source graph
44 for pubkey, entries := range graph.entries {
45 // Store the trust entries
46 tg.entries[pubkey] = make([]TrustEntry, len(entries))
47 copy(tg.entries[pubkey], entries)
48 }
49
50 // Update last modified time
51 tg.lastUpdated[sourcePubkey] = time.Now()
52
53 return nil
54 }
55
56 // AddEntry adds a trust entry to the graph
57 func (tg *TrustGraph) AddEntry(entry TrustEntry) error {
58 if err := ValidateTrustScore(entry.TrustScore); err != nil {
59 return err
60 }
61
62 tg.mu.Lock()
63 defer tg.mu.Unlock()
64
65 selfPubkey := hex.Enc(tg.selfPubkey)
66 tg.entries[selfPubkey] = append(tg.entries[selfPubkey], entry)
67 tg.lastUpdated[selfPubkey] = time.Now()
68
69 return nil
70 }
71
72 // GetTrustLevel returns the trust level for a given pubkey (0.0 to 1.0)
73 // This computes both direct trust and inherited trust through the web of trust
74 func (tg *TrustGraph) GetTrustLevel(pubkey []byte) float64 {
75 tg.mu.RLock()
76 defer tg.mu.RUnlock()
77
78 pubkeyStr := hex.Enc(pubkey)
79 selfPubkeyStr := hex.Enc(tg.selfPubkey)
80
81 // Check for direct trust first (0-hop)
82 if entries, ok := tg.entries[selfPubkeyStr]; ok {
83 for _, entry := range entries {
84 if entry.Pubkey == pubkeyStr {
85 return entry.TrustScore
86 }
87 }
88 }
89
90 // Compute inherited trust through web of trust
91 // Use breadth-first search to find shortest trust path
92 maxHops := 3 // Maximum path length (configurable)
93 visited := make(map[string]bool)
94 queue := []trustPath{{pubkey: selfPubkeyStr, trust: 1.0, hops: 0}}
95 visited[selfPubkeyStr] = true
96
97 for len(queue) > 0 {
98 current := queue[0]
99 queue = queue[1:]
100
101 // Stop if we've exceeded max hops
102 if current.hops > maxHops {
103 continue
104 }
105
106 // Check if we found the target
107 if current.pubkey == pubkeyStr {
108 // Apply hop-based decay
109 decayFactor := tg.decayFactors[current.hops]
110 return current.trust * decayFactor
111 }
112
113 // Expand to neighbors
114 if entries, ok := tg.entries[current.pubkey]; ok {
115 for _, entry := range entries {
116 if !visited[entry.Pubkey] {
117 visited[entry.Pubkey] = true
118 queue = append(queue, trustPath{
119 pubkey: entry.Pubkey,
120 trust: current.trust * entry.TrustScore,
121 hops: current.hops + 1,
122 })
123 }
124 }
125 }
126 }
127
128 // No trust path found - return default minimal trust for unknown services
129 return 0.0
130 }
131
132 // trustPath represents a path in the trust graph during BFS
133 type trustPath struct {
134 pubkey string
135 trust float64
136 hops int
137 }
138
139 // GetDirectTrust returns direct trust relationships (0-hop only)
140 func (tg *TrustGraph) GetDirectTrust() []TrustEntry {
141 tg.mu.RLock()
142 defer tg.mu.RUnlock()
143
144 selfPubkeyStr := hex.Enc(tg.selfPubkey)
145 if entries, ok := tg.entries[selfPubkeyStr]; ok {
146 result := make([]TrustEntry, len(entries))
147 copy(result, entries)
148 return result
149 }
150
151 return []TrustEntry{}
152 }
153
154 // RemoveEntry removes a trust entry for a given pubkey
155 func (tg *TrustGraph) RemoveEntry(pubkey string) {
156 tg.mu.Lock()
157 defer tg.mu.Unlock()
158
159 selfPubkeyStr := hex.Enc(tg.selfPubkey)
160 if entries, ok := tg.entries[selfPubkeyStr]; ok {
161 filtered := make([]TrustEntry, 0, len(entries))
162 for _, entry := range entries {
163 if entry.Pubkey != pubkey {
164 filtered = append(filtered, entry)
165 }
166 }
167 tg.entries[selfPubkeyStr] = filtered
168 tg.lastUpdated[selfPubkeyStr] = time.Now()
169 }
170 }
171
172 // UpdateEntry updates an existing trust entry
173 func (tg *TrustGraph) UpdateEntry(pubkey string, newScore float64) error {
174 if err := ValidateTrustScore(newScore); err != nil {
175 return err
176 }
177
178 tg.mu.Lock()
179 defer tg.mu.Unlock()
180
181 selfPubkeyStr := hex.Enc(tg.selfPubkey)
182 if entries, ok := tg.entries[selfPubkeyStr]; ok {
183 for i, entry := range entries {
184 if entry.Pubkey == pubkey {
185 tg.entries[selfPubkeyStr][i].TrustScore = newScore
186 tg.lastUpdated[selfPubkeyStr] = time.Now()
187 return nil
188 }
189 }
190 }
191
192 return fmt.Errorf("trust entry not found for pubkey: %s", pubkey)
193 }
194
195 // GetAllEntries returns all trust entries in the graph (for debugging/export)
196 func (tg *TrustGraph) GetAllEntries() map[string][]TrustEntry {
197 tg.mu.RLock()
198 defer tg.mu.RUnlock()
199
200 result := make(map[string][]TrustEntry)
201 for pubkey, entries := range tg.entries {
202 result[pubkey] = make([]TrustEntry, len(entries))
203 copy(result[pubkey], entries)
204 }
205
206 return result
207 }
208
209 // GetTrustedServices returns a list of all directly trusted service pubkeys
210 func (tg *TrustGraph) GetTrustedServices() []string {
211 tg.mu.RLock()
212 defer tg.mu.RUnlock()
213
214 selfPubkeyStr := hex.Enc(tg.selfPubkey)
215 if entries, ok := tg.entries[selfPubkeyStr]; ok {
216 pubkeys := make([]string, 0, len(entries))
217 for _, entry := range entries {
218 pubkeys = append(pubkeys, entry.Pubkey)
219 }
220 return pubkeys
221 }
222
223 return []string{}
224 }
225
226 // GetInheritedTrust computes inherited trust from one service to another
227 // This is useful for debugging and understanding trust propagation
228 func (tg *TrustGraph) GetInheritedTrust(fromPubkey, toPubkey string) (float64, []string) {
229 tg.mu.RLock()
230 defer tg.mu.RUnlock()
231
232 // BFS to find shortest path and trust level
233 type pathNode struct {
234 pubkey string
235 trust float64
236 hops int
237 path []string
238 }
239
240 visited := make(map[string]bool)
241 queue := []pathNode{{pubkey: fromPubkey, trust: 1.0, hops: 0, path: []string{fromPubkey}}}
242 visited[fromPubkey] = true
243
244 maxHops := 3
245
246 for len(queue) > 0 {
247 current := queue[0]
248 queue = queue[1:]
249
250 if current.hops > maxHops {
251 continue
252 }
253
254 // Found target
255 if current.pubkey == toPubkey {
256 decayFactor := tg.decayFactors[current.hops]
257 return current.trust * decayFactor, current.path
258 }
259
260 // Expand neighbors
261 if entries, ok := tg.entries[current.pubkey]; ok {
262 for _, entry := range entries {
263 if !visited[entry.Pubkey] {
264 visited[entry.Pubkey] = true
265 newPath := make([]string, len(current.path))
266 copy(newPath, current.path)
267 newPath = append(newPath, entry.Pubkey)
268
269 queue = append(queue, pathNode{
270 pubkey: entry.Pubkey,
271 trust: current.trust * entry.TrustScore,
272 hops: current.hops + 1,
273 path: newPath,
274 })
275 }
276 }
277 }
278 }
279
280 // No path found
281 return 0.0, nil
282 }
283
284 // ExportTrustGraph exports the trust graph for this service as a TrustGraphEvent
285 func (tg *TrustGraph) ExportTrustGraph() *TrustGraphEvent {
286 tg.mu.RLock()
287 defer tg.mu.RUnlock()
288
289 selfPubkeyStr := hex.Enc(tg.selfPubkey)
290 entries := tg.entries[selfPubkeyStr]
291
292 exported := &TrustGraphEvent{
293 Event: nil, // TODO: Create event
294 Entries: make([]TrustEntry, len(entries)),
295 Expiration: time.Now().Add(TrustGraphExpiry),
296 }
297
298 copy(exported.Entries, entries)
299
300 return exported
301 }
302
303 // CalculateTrustMetrics computes metrics about the trust graph
304 func (tg *TrustGraph) CalculateTrustMetrics() *TrustMetrics {
305 tg.mu.RLock()
306 defer tg.mu.RUnlock()
307
308 metrics := &TrustMetrics{
309 TotalServices: len(tg.entries),
310 DirectTrust: 0,
311 IndirectTrust: 0,
312 AverageTrust: 0.0,
313 TrustDistribution: make(map[string]int),
314 }
315
316 selfPubkeyStr := hex.Enc(tg.selfPubkey)
317 if entries, ok := tg.entries[selfPubkeyStr]; ok {
318 metrics.DirectTrust = len(entries)
319
320 var trustSum float64
321 for _, entry := range entries {
322 trustSum += entry.TrustScore
323
324 // Categorize trust level
325 if entry.TrustScore >= 0.8 {
326 metrics.TrustDistribution["high"]++
327 } else if entry.TrustScore >= 0.5 {
328 metrics.TrustDistribution["medium"]++
329 } else if entry.TrustScore >= 0.2 {
330 metrics.TrustDistribution["low"]++
331 } else {
332 metrics.TrustDistribution["minimal"]++
333 }
334 }
335
336 if len(entries) > 0 {
337 metrics.AverageTrust = trustSum / float64(len(entries))
338 }
339 }
340
341 // Calculate indirect trust (services reachable via multi-hop)
342 // This is approximate - counts unique services reachable within 3 hops
343 reachable := make(map[string]bool)
344 queue := []string{selfPubkeyStr}
345 visited := make(map[string]int) // pubkey -> hop count
346 visited[selfPubkeyStr] = 0
347
348 for len(queue) > 0 {
349 current := queue[0]
350 queue = queue[1:]
351
352 currentHops := visited[current]
353 if currentHops >= 3 {
354 continue
355 }
356
357 if entries, ok := tg.entries[current]; ok {
358 for _, entry := range entries {
359 if _, seen := visited[entry.Pubkey]; !seen {
360 visited[entry.Pubkey] = currentHops + 1
361 queue = append(queue, entry.Pubkey)
362 reachable[entry.Pubkey] = true
363 }
364 }
365 }
366 }
367
368 metrics.IndirectTrust = len(reachable) - metrics.DirectTrust
369 if metrics.IndirectTrust < 0 {
370 metrics.IndirectTrust = 0
371 }
372
373 return metrics
374 }
375
376 // TrustMetrics holds metrics about the trust graph
377 type TrustMetrics struct {
378 TotalServices int
379 DirectTrust int
380 IndirectTrust int
381 AverageTrust float64
382 TrustDistribution map[string]int // high/medium/low/minimal counts
383 }
384