# Web of Trust (WoT) Implementation Plan

## Feature Description

When enabled, the relay restricts access to pubkeys that the admin follows. The admin's kind 3 (contact list) event is used as the source of truth — all `p` tags in that event become whitelisted pubkeys. The admin themselves is always whitelisted.

Single config variable `wot_enabled` with three levels:

| Value | Mode | Effect |
|-------|------|--------|
| `0` | Off | Open relay — anyone can read and write |
| `1` | Write-only | Only followed pubkeys can **publish** events. Anyone can read/subscribe. |
| `2` | Full | Only followed pubkeys can **publish AND subscribe**. Requires NIP-42 auth for subscriptions. Eliminates subscription churn from anonymous connections. |

## How It Works

### WoT Sync Flow
```mermaid
flowchart TD
    A["Admin publishes kind 3 event\n- contact list -"] --> B{wot_enabled > 0?}
    B -->|No| C["Store event normally"]
    B -->|Yes| D["Extract p tags from kind 3"]
    D --> E["Clear existing WoT whitelist rules"]
    E --> F["Insert new whitelist rules\nfor each followed pubkey"]
    F --> G["Enable auth_enabled"]
    G --> H{wot_enabled == 2?}
    H -->|Yes| I["Enable nip42_auth_required_subscriptions"]
    H -->|No| J["Done - write-only restriction"]
```

### Event Publishing Flow — Write Restriction (wot_enabled >= 1)
```mermaid
flowchart TD
    A["EVENT message arrives"] --> B{auth_enabled?}
    B -->|No| C["Accept event"]
    B -->|Yes| D["check_database_auth_rules - pubkey -"]
    D --> E{Pubkey in whitelist\nor wot_whitelist?}
    E -->|Yes| C
    E -->|No| F{Any whitelist rules exist?}
    F -->|Yes| G["REJECT: not whitelisted"]
    F -->|No| C
```

### Subscription Flow — Read Restriction (wot_enabled == 2)
```mermaid
flowchart TD
    A["REQ message arrives"] --> B{wot_enabled == 2?}
    B -->|No| C["Allow subscription"]
    B -->|Yes| D{NIP-42 authenticated?}
    D -->|No| E["Send AUTH challenge"]
    D -->|Yes| F["check_database_auth_rules\nusing authenticated_pubkey"]
    F --> G{Pubkey in whitelist\nor wot_whitelist?}
    G -->|Yes| C
    G -->|No| H["REJECT: not authorized\nfor subscriptions"]
```

## Design Decisions

### Leveraging Existing Infrastructure

The relay already has everything needed:

1. **`auth_rules` table** — stores whitelist/blacklist rules with `rule_type`, `pattern_type`, `pattern_value`
2. **`check_database_auth_rules()`** in [`request_validator.c:529`](src/request_validator.c:529) — already implements the logic: "if whitelist rules exist and pubkey is not whitelisted, deny"
3. **`add_auth_rule_from_config()`** / **`remove_auth_rule_from_config()`** in [`config.c`](src/config.c:2082) — already manage auth rules
4. **`event_tags` table** — can efficiently query `p` tags from kind 3 events
5. **Config system** — `auth_enabled` flag already controls whether auth rules are checked
6. **NIP-42 auth** — `nip42_auth_required_subscriptions` already gates REQ access, `pss->authenticated_pubkey` stores the authenticated pubkey

### WoT-Specific Whitelist Rules

To distinguish WoT-generated whitelist rules from manually-added ones, we use a new `rule_type` value: `wot_whitelist`. This allows:
- Clearing all WoT rules without affecting manual whitelist/blacklist rules
- Querying WoT status separately
- The existing `check_database_auth_rules()` function already checks for `rule_type = 'whitelist'` — we need to also match `wot_whitelist` in the whitelist check

### Trigger Mechanism

The WoT sync happens when:
1. **A kind 3 event from the admin is stored** — detected in `store_event()` after successful INSERT
2. **Startup** — if `wot_enabled > 0`, sync from the most recent admin kind 3 event in the database
3. **Admin DM command** — `wot sync` to force a manual resync

### What Gets Whitelisted

- All pubkeys in `p` tags of the admin's kind 3 event
- The admin pubkey itself (always whitelisted)
- The relay pubkey (always whitelisted — for admin DM responses)

### What Is NOT Blocked

Even with WoT enabled, these are always allowed:
- Admin events (kind 23456) — already bypassed in [`request_validator.c:303`](src/request_validator.c:303)
- NIP-42 auth events (kind 22242) — already bypassed in [`request_validator.c:318`](src/request_validator.c:318)
- Kind 3 events from the admin — needed to update the follow list itself
- Kind 1059 gift wraps addressed to the relay — needed for admin DMs

## Files to Modify

| File | Changes |
|------|---------|
| `src/default_config_event.h` | Add `wot_enabled` config key (default: `0`) |
| `src/config.c` | Add `wot_sync_from_admin_kind3()` function |
| `src/main.c` | Add WoT trigger in `store_event()` when admin kind 3 is stored, add startup WoT sync |
| `src/request_validator.c` | Update whitelist SQL to also match `wot_whitelist` rule type |
| `src/sql_schema.h` | Update `auth_rules` CHECK constraint to include `wot_whitelist` |
| `src/websockets.c` | Add WoT pubkey check after NIP-42 auth check in REQ handler |
| `src/dm_admin.c` | Add `wot 0`, `wot 1`, `wot 2`, `wot sync`, `wot status` DM commands |

## Detailed Changes

### 1. Schema: `src/sql_schema.h`

Update the `auth_rules` table CHECK constraint to allow `wot_whitelist`:

```sql
-- Before:
rule_type TEXT NOT NULL CHECK (rule_type IN ('whitelist', 'blacklist', 'rate_limit', 'auth_required'))

-- After:
rule_type TEXT NOT NULL CHECK (rule_type IN ('whitelist', 'blacklist', 'rate_limit', 'auth_required', 'wot_whitelist'))
```

### 2. Config: `src/default_config_event.h`

Add WoT configuration:

```c
// Web of Trust Settings
// 0 = off, 1 = write-only (followed pubkeys can publish), 2 = full (followed pubkeys can publish AND subscribe)
{"wot_enabled", "0"},
```

### 3. Core WoT Sync Function: `src/config.c`

New function `wot_sync_from_admin_kind3()`:

```c
int wot_sync_from_admin_kind3(void) {
    int wot_level = get_config_int("wot_enabled", 0);
    if (wot_level <= 0) return 0; // WoT disabled

    // 1. Get admin pubkey from config
    // 2. Query event_tags for p tags from admin's latest kind 3 event:
    //    SELECT DISTINCT et.tag_value FROM event_tags et
    //    JOIN events e ON et.event_id = e.id
    //    WHERE e.kind = 3 AND e.pubkey = ? AND et.tag_name = 'p'
    //    ORDER BY e.created_at DESC
    // 3. BEGIN TRANSACTION
    // 4. DELETE FROM auth_rules WHERE rule_type = 'wot_whitelist'
    // 5. INSERT wot_whitelist for admin pubkey
    // 6. INSERT wot_whitelist for relay pubkey
    // 7. For each p tag: INSERT INTO auth_rules (rule_type, pattern_type, pattern_value)
    //    VALUES ('wot_whitelist', 'pubkey', ?)
    // 8. COMMIT
    // 9. Set auth_enabled = true
    // 10. If wot_level == 2: set nip42_auth_required_subscriptions = true
    // 11. Log count of whitelisted pubkeys
    return 0;
}
```

### 4. Trigger on Kind 3 Store: `src/main.c`

In `store_event()`, after successful INSERT and after `store_event_tags()`:

```c
// Check if this is a kind 3 event from the admin — trigger WoT sync
if ((int)cJSON_GetNumberValue(kind) == 3) {
    int wot_level = get_config_int("wot_enabled", 0);
    if (wot_level > 0) {
        const char* admin_pubkey = get_config_value("admin_pubkey");
        if (admin_pubkey && strcmp(cJSON_GetStringValue(pubkey), admin_pubkey) == 0) {
            DEBUG_INFO("Admin kind 3 event stored — triggering WoT sync");
            wot_sync_from_admin_kind3();
        }
        if (admin_pubkey) free((char*)admin_pubkey);
    }
}
```

### 5. Startup WoT Sync: `src/main.c`

In `main()`, after `populate_event_tags_from_existing()`:

```c
// Sync Web of Trust whitelist if enabled
int wot_level = get_config_int("wot_enabled", 0);
if (wot_level > 0) {
    wot_sync_from_admin_kind3();
}
```

### 6. Update Whitelist Check: `src/request_validator.c`

Update the whitelist SQL queries to also match `wot_whitelist`:

```c
// Before:
"SELECT rule_type FROM auth_rules WHERE rule_type = 'whitelist' AND pattern_type = 'pubkey' AND pattern_value = ? AND active = 1 LIMIT 1"

// After:
"SELECT rule_type FROM auth_rules WHERE rule_type IN ('whitelist', 'wot_whitelist') AND pattern_type = 'pubkey' AND pattern_value = ? AND active = 1 LIMIT 1"
```

And the whitelist-exists check:
```c
// Before:
"SELECT COUNT(*) FROM auth_rules WHERE rule_type = 'whitelist' AND pattern_type = 'pubkey' AND active = 1 LIMIT 1"

// After:
"SELECT COUNT(*) FROM auth_rules WHERE rule_type IN ('whitelist', 'wot_whitelist') AND pattern_type = 'pubkey' AND active = 1 LIMIT 1"
```

### 7. REQ Handler WoT Check: `src/websockets.c`

When `wot_enabled == 2`, add a pubkey whitelist check **after** the existing NIP-42 auth check in the REQ handler. The existing code at line 903 already requires NIP-42 auth when `nip42_auth_required_subscriptions` is set. We add a WoT check right after:

```c
// Existing NIP-42 auth check (line 903):
if (pss && pss->nip42_auth_required_subscriptions && !pss->authenticated) {
    // ... send AUTH challenge or NOTICE ...
    return 0;
}

// NEW: WoT read restriction check (wot_enabled == 2)
if (pss && pss->authenticated && get_config_int("wot_enabled", 0) == 2) {
    // Client is authenticated — check if their pubkey is in the WoT whitelist
    int wot_result = check_database_auth_rules(pss->authenticated_pubkey, "subscription", NULL);
    if (wot_result != NOSTR_SUCCESS) {
        send_notice_message(wsi, pss, "restricted: your pubkey is not in this relay's web of trust");
        DEBUG_INFO("REQ rejected: pubkey %s not in WoT whitelist", pss->authenticated_pubkey);
        cJSON_Delete(json);
        return 0;
    }
}
```

### 8. Admin DM Commands: `src/dm_admin.c`

Add plain text DM commands:

| Command | Action |
|---------|--------|
| `wot 0` or `wot off` | Disable WoT, delete all `wot_whitelist` rules, reset `nip42_auth_required_subscriptions` |
| `wot 1` or `wot write` | Write-only WoT — sync follow list, set `auth_enabled=true` |
| `wot 2` or `wot full` | Full WoT — sync follow list, set `auth_enabled=true`, set `nip42_auth_required_subscriptions=true` |
| `wot sync` | Force resync from admin's kind 3 event |
| `wot status` | Show WoT level (0/1/2), count of whitelisted pubkeys |

### 9. NIP-11 Update

When WoT is enabled (level 1 or 2), the relay should indicate this in NIP-11 relay info. Add to the `limitation` object:
```json
"auth_required": true
```

## Edge Cases

1. **Admin has no kind 3 event in database**: WoT sync does nothing, logs a warning. No whitelist rules created = open relay.

2. **Admin updates follow list**: New kind 3 event triggers full resync — old WoT rules are cleared, new ones inserted. This is atomic (transaction).

3. **WoT disabled (set to 0)**: Clears all `wot_whitelist` rules. Manual `whitelist` rules are preserved. `nip42_auth_required_subscriptions` is reset to false.

4. **Admin unfollows someone**: Next kind 3 event triggers resync, the unfollowed pubkey's `wot_whitelist` rule is removed (full clear + reinsert).

5. **Kind 1059 gift wraps**: These need special handling — the relay needs to accept gift wraps addressed to it even from non-whitelisted pubkeys (for admin DMs). The `is_nip17_gift_wrap_for_relay()` check should bypass WoT.

6. **Read restriction without NIP-42 support (level 2)**: If a client doesn't support NIP-42, they cannot authenticate and therefore cannot subscribe. This is by design — it's the most effective way to reduce subscription churn from anonymous connections.

7. **NIP-11 discovery**: Clients can check NIP-11 to see if `auth_required` is true before connecting, avoiding wasted connections.

8. **Changing level from 2 to 1**: `nip42_auth_required_subscriptions` is reset to false, allowing anonymous subscriptions again. WoT whitelist rules remain for write restriction.

## Testing Strategy

1. Enable write-only WoT via DM: `wot 1`
2. Verify admin can still publish events
3. Verify followed pubkeys can publish events
4. Verify non-followed pubkeys are rejected for EVENT
5. Verify non-followed pubkeys can still subscribe (REQ)
6. Enable full WoT via DM: `wot 2`
7. Verify unauthenticated clients get AUTH challenge on REQ
8. Verify authenticated non-followed pubkeys are rejected for REQ
9. Verify authenticated followed pubkeys can subscribe
10. Verify `wot status` shows correct level and count
11. Publish new kind 3 event, verify auto-resync
12. `wot 0` — verify all pubkeys can publish and subscribe again
13. Verify admin DMs still work when WoT is enabled

## Performance Considerations

- WoT sync is O(n) where n = number of follows (typically 100-2000)
- Uses a transaction for bulk insert — fast even for large follow lists
- Auth rule check is already indexed: `idx_auth_rules_pattern ON auth_rules(pattern_type, pattern_value)`
- No additional per-event overhead — the existing `check_database_auth_rules()` already runs on every event when auth is enabled
- **Level 2 directly addresses CPU load**: With `wot_enabled=2`, unauthenticated connections cannot create subscriptions, eliminating the ~120 REQ/minute churn from anonymous connections that was causing 99% CPU
