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