384 lines
9.5 KiB
Go
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
|
|
}
|