// Package grapevine computes Web-of-Trust scores by BFS traversal // of the follow graph (kind 3 events). package grapevine import ( "bytes" "sort" "smesh.lol/pkg/nostr/filter" "smesh.lol/pkg/nostr/kind" "smesh.lol/pkg/nostr/tag" "smesh.lol/pkg/store" ) // Score is a WoT score for a pubkey. type Score struct { Pubkey []byte Value float64 Depth int } // WoT computes Web-of-Trust scores from the follow graph. type WoT struct { store *store.Engine } // New creates a WoT scorer. func New(s *store.Engine) *WoT { return &WoT{store: s} } // Compute runs BFS from seed to maxDepth, returning scored pubkeys // sorted by score descending. func (w *WoT) Compute(seed []byte, maxDepth int) []Score { scores := map[string]*Score{} visited := map[string]bool{string(seed): true} queue := [][]byte{seed} for depth := 1; depth <= maxDepth && len(queue) > 0; depth++ { decay := 1.0 / float64(int(1)< out[j].Value }) return out } // IsTrusted returns true if pubkey has score >= threshold. func IsTrusted(scores []Score, pubkey []byte, threshold float64) bool { for i := range scores { if bytes.Equal(scores[i].Pubkey, pubkey) { return scores[i].Value >= threshold } } return false } func (w *WoT) getFollows(pubkey []byte) [][]byte { f := &filter.F{ Kinds: kind.NewS(kind.FollowList), Authors: tag.NewFromBytesSlice(pubkey), } limit := uint(1) f.Limit = &limit events, err := w.store.QueryEvents(f) if err != nil || len(events) == 0 { return nil } ev := events[0] if ev.Tags == nil { return nil } pTags := ev.Tags.GetAll([]byte("p")) out := [][]byte{:0:len(pTags)} for _, pt := range pTags { val := pt.ValueBinary() if val != nil && len(val) == 32 { pk := []byte{:32} copy(pk, val) out = append(out, pk) } } return out }