Files
next.orly.dev/pkg/find/trust.go
2025-11-23 08:15:06 +00:00

384 lines
9.5 KiB
Go

package find
import (
"fmt"
"sync"
"time"
"git.mleku.dev/mleku/nostr/encoders/hex"
)
// TrustGraph manages trust relationships between registry services
type TrustGraph struct {
mu sync.RWMutex
entries map[string][]TrustEntry // pubkey -> trust entries
selfPubkey []byte // This registry service's pubkey
lastUpdated map[string]time.Time // pubkey -> last update time
decayFactors map[int]float64 // hop distance -> decay factor
}
// NewTrustGraph creates a new trust graph
func NewTrustGraph(selfPubkey []byte) *TrustGraph {
return &TrustGraph{
entries: make(map[string][]TrustEntry),
selfPubkey: selfPubkey,
lastUpdated: make(map[string]time.Time),
decayFactors: map[int]float64{
0: 1.0, // Direct trust (0-hop)
1: 0.8, // 1-hop trust
2: 0.6, // 2-hop trust
3: 0.4, // 3-hop trust
4: 0.0, // 4+ hops not counted
},
}
}
// AddTrustGraph adds a trust graph from another registry service
func (tg *TrustGraph) AddTrustGraph(graph *TrustGraph) error {
tg.mu.Lock()
defer tg.mu.Unlock()
sourcePubkey := hex.Enc(graph.selfPubkey)
// Copy entries from the source graph
for pubkey, entries := range graph.entries {
// Store the trust entries
tg.entries[pubkey] = make([]TrustEntry, len(entries))
copy(tg.entries[pubkey], entries)
}
// Update last modified time
tg.lastUpdated[sourcePubkey] = time.Now()
return nil
}
// AddEntry adds a trust entry to the graph
func (tg *TrustGraph) AddEntry(entry TrustEntry) error {
if err := ValidateTrustScore(entry.TrustScore); err != nil {
return err
}
tg.mu.Lock()
defer tg.mu.Unlock()
selfPubkey := hex.Enc(tg.selfPubkey)
tg.entries[selfPubkey] = append(tg.entries[selfPubkey], entry)
tg.lastUpdated[selfPubkey] = time.Now()
return nil
}
// GetTrustLevel returns the trust level for a given pubkey (0.0 to 1.0)
// This computes both direct trust and inherited trust through the web of trust
func (tg *TrustGraph) GetTrustLevel(pubkey []byte) float64 {
tg.mu.RLock()
defer tg.mu.RUnlock()
pubkeyStr := hex.Enc(pubkey)
selfPubkeyStr := hex.Enc(tg.selfPubkey)
// Check for direct trust first (0-hop)
if entries, ok := tg.entries[selfPubkeyStr]; ok {
for _, entry := range entries {
if entry.Pubkey == pubkeyStr {
return entry.TrustScore
}
}
}
// Compute inherited trust through web of trust
// Use breadth-first search to find shortest trust path
maxHops := 3 // Maximum path length (configurable)
visited := make(map[string]bool)
queue := []trustPath{{pubkey: selfPubkeyStr, trust: 1.0, hops: 0}}
visited[selfPubkeyStr] = true
for len(queue) > 0 {
current := queue[0]
queue = queue[1:]
// Stop if we've exceeded max hops
if current.hops > maxHops {
continue
}
// Check if we found the target
if current.pubkey == pubkeyStr {
// Apply hop-based decay
decayFactor := tg.decayFactors[current.hops]
return current.trust * decayFactor
}
// Expand to neighbors
if entries, ok := tg.entries[current.pubkey]; ok {
for _, entry := range entries {
if !visited[entry.Pubkey] {
visited[entry.Pubkey] = true
queue = append(queue, trustPath{
pubkey: entry.Pubkey,
trust: current.trust * entry.TrustScore,
hops: current.hops + 1,
})
}
}
}
}
// No trust path found - return default minimal trust for unknown services
return 0.0
}
// trustPath represents a path in the trust graph during BFS
type trustPath struct {
pubkey string
trust float64
hops int
}
// GetDirectTrust returns direct trust relationships (0-hop only)
func (tg *TrustGraph) GetDirectTrust() []TrustEntry {
tg.mu.RLock()
defer tg.mu.RUnlock()
selfPubkeyStr := hex.Enc(tg.selfPubkey)
if entries, ok := tg.entries[selfPubkeyStr]; ok {
result := make([]TrustEntry, len(entries))
copy(result, entries)
return result
}
return []TrustEntry{}
}
// RemoveEntry removes a trust entry for a given pubkey
func (tg *TrustGraph) RemoveEntry(pubkey string) {
tg.mu.Lock()
defer tg.mu.Unlock()
selfPubkeyStr := hex.Enc(tg.selfPubkey)
if entries, ok := tg.entries[selfPubkeyStr]; ok {
filtered := make([]TrustEntry, 0, len(entries))
for _, entry := range entries {
if entry.Pubkey != pubkey {
filtered = append(filtered, entry)
}
}
tg.entries[selfPubkeyStr] = filtered
tg.lastUpdated[selfPubkeyStr] = time.Now()
}
}
// UpdateEntry updates an existing trust entry
func (tg *TrustGraph) UpdateEntry(pubkey string, newScore float64) error {
if err := ValidateTrustScore(newScore); err != nil {
return err
}
tg.mu.Lock()
defer tg.mu.Unlock()
selfPubkeyStr := hex.Enc(tg.selfPubkey)
if entries, ok := tg.entries[selfPubkeyStr]; ok {
for i, entry := range entries {
if entry.Pubkey == pubkey {
tg.entries[selfPubkeyStr][i].TrustScore = newScore
tg.lastUpdated[selfPubkeyStr] = time.Now()
return nil
}
}
}
return fmt.Errorf("trust entry not found for pubkey: %s", pubkey)
}
// GetAllEntries returns all trust entries in the graph (for debugging/export)
func (tg *TrustGraph) GetAllEntries() map[string][]TrustEntry {
tg.mu.RLock()
defer tg.mu.RUnlock()
result := make(map[string][]TrustEntry)
for pubkey, entries := range tg.entries {
result[pubkey] = make([]TrustEntry, len(entries))
copy(result[pubkey], entries)
}
return result
}
// GetTrustedServices returns a list of all directly trusted service pubkeys
func (tg *TrustGraph) GetTrustedServices() []string {
tg.mu.RLock()
defer tg.mu.RUnlock()
selfPubkeyStr := hex.Enc(tg.selfPubkey)
if entries, ok := tg.entries[selfPubkeyStr]; ok {
pubkeys := make([]string, 0, len(entries))
for _, entry := range entries {
pubkeys = append(pubkeys, entry.Pubkey)
}
return pubkeys
}
return []string{}
}
// GetInheritedTrust computes inherited trust from one service to another
// This is useful for debugging and understanding trust propagation
func (tg *TrustGraph) GetInheritedTrust(fromPubkey, toPubkey string) (float64, []string) {
tg.mu.RLock()
defer tg.mu.RUnlock()
// BFS to find shortest path and trust level
type pathNode struct {
pubkey string
trust float64
hops int
path []string
}
visited := make(map[string]bool)
queue := []pathNode{{pubkey: fromPubkey, trust: 1.0, hops: 0, path: []string{fromPubkey}}}
visited[fromPubkey] = true
maxHops := 3
for len(queue) > 0 {
current := queue[0]
queue = queue[1:]
if current.hops > maxHops {
continue
}
// Found target
if current.pubkey == toPubkey {
decayFactor := tg.decayFactors[current.hops]
return current.trust * decayFactor, current.path
}
// Expand neighbors
if entries, ok := tg.entries[current.pubkey]; ok {
for _, entry := range entries {
if !visited[entry.Pubkey] {
visited[entry.Pubkey] = true
newPath := make([]string, len(current.path))
copy(newPath, current.path)
newPath = append(newPath, entry.Pubkey)
queue = append(queue, pathNode{
pubkey: entry.Pubkey,
trust: current.trust * entry.TrustScore,
hops: current.hops + 1,
path: newPath,
})
}
}
}
}
// No path found
return 0.0, nil
}
// ExportTrustGraph exports the trust graph for this service as a TrustGraphEvent
func (tg *TrustGraph) ExportTrustGraph() *TrustGraphEvent {
tg.mu.RLock()
defer tg.mu.RUnlock()
selfPubkeyStr := hex.Enc(tg.selfPubkey)
entries := tg.entries[selfPubkeyStr]
exported := &TrustGraphEvent{
Event: nil, // TODO: Create event
Entries: make([]TrustEntry, len(entries)),
Expiration: time.Now().Add(TrustGraphExpiry),
}
copy(exported.Entries, entries)
return exported
}
// CalculateTrustMetrics computes metrics about the trust graph
func (tg *TrustGraph) CalculateTrustMetrics() *TrustMetrics {
tg.mu.RLock()
defer tg.mu.RUnlock()
metrics := &TrustMetrics{
TotalServices: len(tg.entries),
DirectTrust: 0,
IndirectTrust: 0,
AverageTrust: 0.0,
TrustDistribution: make(map[string]int),
}
selfPubkeyStr := hex.Enc(tg.selfPubkey)
if entries, ok := tg.entries[selfPubkeyStr]; ok {
metrics.DirectTrust = len(entries)
var trustSum float64
for _, entry := range entries {
trustSum += entry.TrustScore
// Categorize trust level
if entry.TrustScore >= 0.8 {
metrics.TrustDistribution["high"]++
} else if entry.TrustScore >= 0.5 {
metrics.TrustDistribution["medium"]++
} else if entry.TrustScore >= 0.2 {
metrics.TrustDistribution["low"]++
} else {
metrics.TrustDistribution["minimal"]++
}
}
if len(entries) > 0 {
metrics.AverageTrust = trustSum / float64(len(entries))
}
}
// Calculate indirect trust (services reachable via multi-hop)
// This is approximate - counts unique services reachable within 3 hops
reachable := make(map[string]bool)
queue := []string{selfPubkeyStr}
visited := make(map[string]int) // pubkey -> hop count
visited[selfPubkeyStr] = 0
for len(queue) > 0 {
current := queue[0]
queue = queue[1:]
currentHops := visited[current]
if currentHops >= 3 {
continue
}
if entries, ok := tg.entries[current]; ok {
for _, entry := range entries {
if _, seen := visited[entry.Pubkey]; !seen {
visited[entry.Pubkey] = currentHops + 1
queue = append(queue, entry.Pubkey)
reachable[entry.Pubkey] = true
}
}
}
}
metrics.IndirectTrust = len(reachable) - metrics.DirectTrust
if metrics.IndirectTrust < 0 {
metrics.IndirectTrust = 0
}
return metrics
}
// TrustMetrics holds metrics about the trust graph
type TrustMetrics struct {
TotalServices int
DirectTrust int
IndirectTrust int
AverageTrust float64
TrustDistribution map[string]int // high/medium/low/minimal counts
}