Files
next.orly.dev/pkg/neo4j/WOT_SPEC.md

16 KiB

Web of Trust (WoT) Data Model Specification for Neo4j

This document describes the Web of Trust graph data model extensions for the ORLY Neo4j backend, based on the Brainstorm prototype.

Overview

The WoT data model extends the base Nostr relay functionality with trust metrics computation using graph algorithms (GrapeRank, Personalized PageRank) to enable:

  • Social graph-based filtering: Filter events based on web of trust relationships
  • Personalized trust scores: Compute trust metrics personalized to each user/customer
  • Multi-tenant support: Track separate trust metrics for multiple customers/observers
  • Spam and moderation: Use social graph signals (follows, mutes, reports) for content filtering

Reference Implementation

Data Model Architecture

The WoT model adds specialized nodes and relationships to track social graph structure and compute trust metrics.

Node Labels

1. NostrUser

Represents a Nostr user (identified by pubkey) with computed trust metrics.

Properties:

  • pubkey (string, unique) - Hex-encoded public key
  • npub (string) - Bech32-encoded npub

Trust Metrics (Owner-Personalized):

  • hops (integer) - Distance from owner node via FOLLOWS relationships
  • personalizedPageRank (float) - PageRank score personalized to owner
  • influence (float) - GrapeRank influence score
  • average (float) - GrapeRank average score
  • input (float) - GrapeRank input score
  • confidence (float) - GrapeRank confidence score

Social Graph Counts:

  • followingCount (integer) - Total number of users this user follows
  • followedByCount (integer) - Total number of followers
  • mutingCount (integer) - Total number of users this user mutes
  • mutedByCount (integer) - Total number of users who mute this user
  • reportingCount (integer) - Total number of reports filed by this user
  • reportedByCount (integer) - Total number of reports filed against this user

Verified Counts (GrapeRank-weighted):

  • verifiedFollowerCount (integer) - Count of followers with influence above threshold
  • verifiedMuterCount (integer) - Count of muters with influence above threshold
  • verifiedReporterCount (integer) - Count of reporters with influence above threshold

Input Scores (Sum of Influence):

  • followerInput (float) - Sum of influence scores of all followers
  • muterInput (float) - Sum of influence scores of all muters
  • reporterInput (float) - Sum of influence scores of all reporters

NIP-56 Report Types:

For each report type (impersonator, spam, illegal, malware, nsfw, etc.), the following metrics are tracked:

  • {reportType}Count (integer) - Total count of this report type
  • {reportType}VerifiedCount (integer) - Count from verified reporters
  • {reportType}Input (float) - Sum of influence scores of reporters

Note: NIP-56 metrics may be better modeled as separate nodes to avoid property explosion.

Indexes:

  • Unique constraint on pubkey
  • Index on hops
  • Index on personalizedPageRank
  • Index on influence
  • Index on verifiedFollowerCount
  • Index on verifiedMuterCount
  • Index on verifiedReporterCount
  • Index on followerInput

2. SetOfNostrUserWotMetricsCards

Organizational node that groups all WoT metric cards for a single observee (user being scored). This design pattern keeps WoT metric cards partitioned from other NostrUser relationships.

Properties:

  • observee_pubkey (string, unique) - Pubkey of the user being scored

Purpose: Acts as an intermediary to minimize direct relationships on NostrUser nodes, which may have many other relationships in a full relay implementation.

Indexes:

  • Unique constraint on observee_pubkey

3. NostrUserWotMetricsCard

Stores personalized trust metrics for a specific (observer, observee) pair. Each card corresponds to a NIP-85 Trusted Assertion (kind 30382) event.

Properties:

  • customer_id (string) - Identifier for the customer/service instance
  • observer_pubkey (string) - Pubkey of the observer (the customer)
  • observee_pubkey (string) - Pubkey of the user being scored

Trust Metrics (Observer-Personalized): All the same metrics as NostrUser node, but personalized to the observer:

  • hops, personalizedPageRank
  • influence, average, input, confidence
  • verifiedFollowerCount, verifiedMuterCount, verifiedReporterCount
  • followerInput, muterInput, reporterInput

Indexes:

  • Unique constraint on (customer_id, observee_pubkey)
  • Unique constraint on (observer_pubkey, observee_pubkey)
  • Index on customer_id
  • Index on observer_pubkey
  • Index on observee_pubkey
  • Index on hops
  • Index on personalizedPageRank
  • Index on influence
  • Index on verifiedFollowerCount
  • Index on verifiedMuterCount
  • Index on verifiedReporterCount
  • Index on followerInput

4. Set (Deprecated)

Legacy node label that is redundant with SetOfNostrUserWotMetricsCards. Should be removed in new implementations.

Relationship Types

1. FOLLOWS

Represents a follow relationship between users (derived from kind 3 events).

Direction: (follower:NostrUser)-[:FOLLOWS]->(followed:NostrUser)

Properties: None (or optionally timestamp)

Source: Created/updated from kind 3 (contact list) events

2. MUTES

Represents a mute relationship between users (derived from kind 10000 events).

Direction: (muter:NostrUser)-[:MUTES]->(muted:NostrUser)

Properties: None (or optionally timestamp)

Source: Created/updated from kind 10000 (mute list) events

3. REPORTS

Represents a report filed against a user (derived from kind 1984 events).

Direction: (reporter:NostrUser)-[:REPORTS]->(reported:NostrUser)

Properties:

  • reportType (string) - NIP-56 report type (impersonation, spam, illegal, malware, nsfw, etc.)
  • timestamp (integer) - When the report was filed

Source: Created from kind 1984 (reporting) events

4. WOT_METRICS_CARDS

Links a NostrUser to their SetOfNostrUserWotMetricsCards organizational node.

Direction: (user:NostrUser)-[:WOT_METRICS_CARDS]->(set:SetOfNostrUserWotMetricsCards)

Properties: None

Cardinality: One-to-one (each NostrUser has at most one SetOfNostrUserWotMetricsCards)

5. SPECIFIC_INSTANCE

Links a SetOfNostrUserWotMetricsCards to individual NostrUserWotMetricsCard nodes for each observer.

Direction: (set:SetOfNostrUserWotMetricsCards)-[:SPECIFIC_INSTANCE]->(card:NostrUserWotMetricsCard)

Properties: None

Cardinality: One-to-many (one set has many cards, one per observer)

Note: May be renamed to WOT_METRICS_CARD for clarity.

Nostr Event Kinds

The WoT model processes the following Nostr event kinds:

Kind Name Purpose Graph Action
0 Profile Metadata User profile information Update NostrUser properties (npub, name, etc.)
3 Contact List Follow list Create/update FOLLOWS relationships
1984 Reporting Report users/content Create REPORTS relationships with reportType
10000 Mute List Mute list Create/update MUTES relationships
30382 Trusted Assertion (NIP-85) Published trust metrics Create/update NostrUserWotMetricsCard nodes

Trust Metrics Computation

User Tracking Criteria

Trust metrics are computed for users who meet any of these criteria:

  1. Connected to the owner/observer by a finite number of FOLLOWS relationships (e.g., within N hops)
  2. Muted by a trusted user (user with sufficient influence)
  3. Reported by a trusted user

This typically results in ~300k tracked users out of millions in the network.

GrapeRank Algorithm

GrapeRank is a trust scoring algorithm that computes:

  • Influence: Primary trust score based on social graph structure
  • Average: Average trust received from neighbors
  • Input: Total trust input from all connections
  • Confidence: Confidence level in the score

Note: Implementation details for GrapeRank are not included in the specification.

Personalized PageRank

Computes a personalized PageRank score for each user relative to an owner/observer, using the FOLLOWS graph as the link structure.

Note: Implementation details are not included in the specification.

Verified Counts

Users with influence above a configurable threshold are considered "verified" for counting purposes. This provides a quality-weighted count of followers/muters/reporters.

Input Scores

Alternative to verified counts: sum the influence scores of all followers/muters/reporters to get a weighted measure of social signals.

Deployment Modes

Lean Mode (Baseline)

Minimal WoT implementation suitable for resource-constrained deployments:

  • NostrUser, NostrUserWotMetricsCard, SetOfNostrUserWotMetricsCards nodes
  • FOLLOWS, MUTES, REPORTS, WOT_METRICS_CARDS, SPECIFIC_INSTANCE relationships
  • Process kinds: 0, 3, 1984, 10000
  • Compute baseline trust metrics

Hardware: Can run on smaller instances (e.g., 8 GB RAM, 2 vCPU)

Full Relay Mode (Extended)

Comprehensive implementation with additional features:

  • All lean mode features
  • NostrEvent nodes with full event storage
  • Additional relationships:
    • IS_A_REACTION_TO (kind 7 reactions)
    • IS_A_RESPONSE_TO (kind 1 replies)
    • IS_A_REPOST_OF (kind 6, kind 16 reposts)
    • P_TAGGED (p-tag mentions from events to users)
    • E_TAGGED (e-tag references from events to events)
  • NostrRelay, CashuMint nodes for ecosystem mapping
  • Enhanced GrapeRank incorporating zaps, replies, reactions

Hardware: Requires larger instances (e.g., 32 GB RAM, 8 vCPU, 100+ GB SSD)

Cypher Schema Definitions

-- NostrUser node constraint and indexes
CREATE CONSTRAINT nostrUser_pubkey IF NOT EXISTS
  FOR (n:NostrUser) REQUIRE n.pubkey IS UNIQUE;

CREATE INDEX nostrUser_hops IF NOT EXISTS
  FOR (n:NostrUser) ON (n.hops);

CREATE INDEX nostrUser_personalizedPageRank IF NOT EXISTS
  FOR (n:NostrUser) ON (n.personalizedPageRank);

CREATE INDEX nostrUser_influence IF NOT EXISTS
  FOR (n:NostrUser) ON (n.influence);

CREATE INDEX nostrUser_verifiedFollowerCount IF NOT EXISTS
  FOR (n:NostrUser) ON (n.verifiedFollowerCount);

CREATE INDEX nostrUser_verifiedMuterCount IF NOT EXISTS
  FOR (n:NostrUser) ON (n.verifiedMuterCount);

CREATE INDEX nostrUser_verifiedReporterCount IF NOT EXISTS
  FOR (n:NostrUser) ON (n.verifiedReporterCount);

CREATE INDEX nostrUser_followerInput IF NOT EXISTS
  FOR (n:NostrUser) ON (n.followerInput);

-- SetOfNostrUserWotMetricsCards constraint
CREATE CONSTRAINT SetOfNostrUserWotMetricsCards_observee_pubkey IF NOT EXISTS
  FOR (n:SetOfNostrUserWotMetricsCards) REQUIRE n.observee_pubkey IS UNIQUE;

-- NostrUserWotMetricsCard constraints and indexes
CREATE CONSTRAINT nostrUserWotMetricsCard_unique_combination_1 IF NOT EXISTS
  FOR (n:NostrUserWotMetricsCard) REQUIRE (n.customer_id, n.observee_pubkey) IS UNIQUE;

CREATE CONSTRAINT nostrUserWotMetricsCard_unique_combination_2 IF NOT EXISTS
  FOR (n:NostrUserWotMetricsCard) REQUIRE (n.observer_pubkey, n.observee_pubkey) IS UNIQUE;

CREATE INDEX nostrUserWotMetricsCard_customer_id IF NOT EXISTS
  FOR (n:NostrUserWotMetricsCard) ON (n.customer_id);

CREATE INDEX nostrUserWotMetricsCard_observer_pubkey IF NOT EXISTS
  FOR (n:NostrUserWotMetricsCard) ON (n.observer_pubkey);

CREATE INDEX nostrUserWotMetricsCard_observee_pubkey IF NOT EXISTS
  FOR (n:NostrUserWotMetricsCard) ON (n.observee_pubkey);

CREATE INDEX nostrUserWotMetricsCard_hops IF NOT EXISTS
  FOR (n:NostrUserWotMetricsCard) ON (n.hops);

CREATE INDEX nostrUserWotMetricsCard_personalizedPageRank IF NOT EXISTS
  FOR (n:NostrUserWotMetricsCard) ON (n.personalizedPageRank);

CREATE INDEX nostrUserWotMetricsCard_influence IF NOT EXISTS
  FOR (n:NostrUserWotMetricsCard) ON (n.influence);

CREATE INDEX nostrUserWotMetricsCard_verifiedFollowerCount IF NOT EXISTS
  FOR (n:NostrUserWotMetricsCard) ON (n.verifiedFollowerCount);

CREATE INDEX nostrUserWotMetricsCard_verifiedMuterCount IF NOT EXISTS
  FOR (n:NostrUserWotMetricsCard) ON (n.verifiedMuterCount);

CREATE INDEX nostrUserWotMetricsCard_verifiedReporterCount IF NOT EXISTS
  FOR (n:NostrUserWotMetricsCard) ON (n.verifiedReporterCount);

CREATE INDEX nostrUserWotMetricsCard_followerInput IF NOT EXISTS
  FOR (n:NostrUserWotMetricsCard) ON (n.followerInput);

Example Queries

Find users followed by owner within N hops

MATCH path = (owner:NostrUser {pubkey: $ownerPubkey})-[:FOLLOWS*1..3]->(user:NostrUser)
WHERE user.hops <= 3
RETURN user.pubkey, user.hops, user.influence
ORDER BY user.influence DESC
LIMIT 100

Get trust metrics for a specific observer-observee pair

MATCH (card:NostrUserWotMetricsCard {
  observer_pubkey: $observerPubkey,
  observee_pubkey: $observeePubkey
})
RETURN card.hops, card.influence, card.personalizedPageRank

Find highly trusted users (high influence, many verified followers)

MATCH (user:NostrUser)
WHERE user.influence > $threshold
  AND user.verifiedFollowerCount > $minFollowers
RETURN user.pubkey, user.influence, user.verifiedFollowerCount
ORDER BY user.influence DESC
LIMIT 50

Find reported users with high reporter influence

MATCH (reporter:NostrUser)-[r:REPORTS]->(reported:NostrUser)
WHERE reporter.influence > $threshold
RETURN reported.pubkey,
       r.reportType,
       COUNT(reporter) AS reportCount,
       SUM(reporter.influence) AS totalInfluence
ORDER BY totalInfluence DESC

Integration with ORLY Relay

Configuration

# Enable Neo4j backend
export ORLY_DB_TYPE=neo4j
export ORLY_NEO4J_URI=bolt://localhost:7687
export ORLY_NEO4J_USER=neo4j
export ORLY_NEO4J_PASSWORD=password

# Enable WoT processing
export ORLY_WOT_ENABLED=true
export ORLY_WOT_OWNER_PUBKEY=<hex-pubkey>
export ORLY_WOT_INFLUENCE_THRESHOLD=0.5
export ORLY_WOT_MAX_HOPS=3

# Enable multi-tenant support
export ORLY_WOT_MULTI_TENANT=true

Event Processing Flow

  1. Kind 0 (Profile): Update NostrUser node properties
  2. Kind 3 (Follows): Parse p-tags, create/update FOLLOWS relationships
  3. Kind 1984 (Reports): Parse p-tags and report type, create REPORTS relationships
  4. Kind 10000 (Mutes): Parse p-tags, create/update MUTES relationships
  5. Background Job: Periodically run GrapeRank and PageRank algorithms
  6. Kind 30382 (Trusted Assertion): Update NostrUserWotMetricsCard nodes

Query Filtering

Extend REQ filters with WoT parameters:

{
  "kinds": [1],
  "wot": {
    "max_hops": 2,
    "min_influence": 0.5,
    "observer": "<pubkey>"
  }
}

Performance Considerations

  • Index Strategy: Heavy indexing on trust metric fields for fast filtering
  • Batch Updates: Process social graph events in batches to minimize graph writes
  • Cached Metrics: Store computed trust metrics as node properties (denormalized)
  • Incremental Computation: Update metrics incrementally when graph changes
  • Query Optimization: Use Cypher query plans (EXPLAIN/PROFILE) to optimize complex traversals

Future Enhancements

  • NIP-56 report type nodes (separate from NostrUser properties)
  • Full relay mode with NostrEvent nodes
  • Zap-weighted trust metrics
  • Reply/reaction-weighted trust metrics
  • Distributed trust computation across multiple relay instances
  • Real-time trust metric updates (streaming)

References