FIND_IMPLEMENTATION_PLAN.md raw

FIND Name Binding Implementation Plan

Overview

This document outlines the implementation plan for integrating the Free Internet Name Daemon (FIND) protocol with the ORLY relay. The FIND protocol provides decentralized name-to-npub bindings that are discoverable by any client using standard Nostr queries.

Architecture

System Components

┌─────────────────────────────────────────────────────────────┐
│                        ORLY Relay                            │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │   WebSocket  │  │  FIND Daemon │  │   HTTP API       │  │
│  │   Handler    │  │  (Registry   │  │   (NIP-11, Web)  │  │
│  │              │  │   Service)   │  │                  │  │
│  └──────┬───────┘  └──────┬───────┘  └────────┬─────────┘  │
│         │                 │                    │             │
│         └─────────────────┼────────────────────┘             │
│                          │                                   │
│                  ┌───────▼────────┐                          │
│                  │   Database     │                          │
│                  │   (Badger/     │                          │
│                  │    DGraph)     │                          │
│                  └────────────────┘                          │
└─────────────────────────────────────────────────────────────┘
         │                                        ▲
         │  Publish FIND events                   │  Query FIND events
         │  (kinds 30100-30105)                   │  (kinds 30102, 30103)
         ▼                                        │
┌─────────────────────────────────────────────────────────────┐
│                     Nostr Network                            │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │   Other      │  │   Other      │  │    Clients       │  │
│  │   Relays     │  │   Registry   │  │                  │  │
│  │              │  │   Services   │  │                  │  │
│  └──────────────┘  └──────────────┘  └──────────────────┘  │
└─────────────────────────────────────────────────────────────┘

Event Flow

  1. Name Registration:

` User → FIND CLI → Registration Proposal (kind 30100) → Relay → Database ↓ Registry Service (attestation) ↓ Attestation (kind 20100) → Other Registry Services ↓ Consensus → Name State (kind 30102) `

  1. Name Resolution:

` Client → Query kind 30102 (name state) → Relay → Database → Response Client → Query kind 30103 (records) → Relay → Database → Response `

Implementation Phases

Phase 1: Database Storage for FIND Events ✓ (Already Supported)

The relay already stores all parameterized replaceable events (kind 30xxx) and ephemeral events (kind 20xxx), which includes all FIND event types:

Status: No changes needed. The existing event storage system handles these automatically.

Phase 2: Registry Service Implementation

Create a new registry service that runs within the ORLY relay process (optional, can be enabled via config).

New Files:

Key Components:

// Registry service that monitors proposals and computes consensus
type RegistryService struct {
    db              database.Database
    pubkey          []byte // Registry service identity
    trustGraph      *TrustGraph
    pendingProposals map[string]*ProposalState
    config          *RegistryConfig
}

type RegistryConfig struct {
    Enabled             bool
    ServicePubkey       string
    AttestationDelay    time.Duration // Default: 60s
    SparseAttestation   bool
    SamplingRate        int // For sparse attestation
}

// Proposal state tracking during attestation window
type ProposalState struct {
    Proposal      *RegistrationProposal
    Attestations  []*Attestation
    ReceivedAt    time.Time
    ProcessedAt   *time.Time
}

Responsibilities:

  1. Subscribe to kind 30100 (registration proposals) from database
  2. Validate proposals (name format, ownership, renewal window)
  3. Check for conflicts (competing proposals)
  4. After attestation window (60-120s):

- Fetch attestations (kind 20100) from other registry services - Compute trust-weighted consensus - Publish name state (kind 30102) if consensus reached

Phase 3: Client Query Handlers

Enhance existing query handlers to optimize FIND event queries.

Enhancements:

New Helper Functions:

// Query name state for a given name
func (d *Database) QueryNameState(name string) (*find.NameState, error)

// Query all records for a name
func (d *Database) QueryNameRecords(name string, recordType string) ([]*find.NameRecord, error)

// Check if name is available for registration
func (d *Database) IsNameAvailable(name string) (bool, error)

// Get parent domain owner (for subdomain validation)
func (d *Database) GetParentDomainOwner(name string) (string, error)

Phase 4: Configuration Integration

Add FIND-specific configuration options to app/config/config.go:

type C struct {
    // ... existing fields ...

    // FIND registry service settings
    FindEnabled           bool     `env:"ORLY_FIND_ENABLED" default:"false" usage:"enable FIND registry service for name consensus"`
    FindServicePubkey     string   `env:"ORLY_FIND_SERVICE_PUBKEY" usage:"public key for this registry service (hex)"`
    FindServicePrivkey    string   `env:"ORLY_FIND_SERVICE_PRIVKEY" usage:"private key for signing attestations (hex)"`
    FindAttestationDelay  string   `env:"ORLY_FIND_ATTESTATION_DELAY" default:"60s" usage:"delay before publishing attestations"`
    FindSparseEnabled     bool     `env:"ORLY_FIND_SPARSE_ENABLED" default:"false" usage:"use sparse attestation (probabilistic)"`
    FindSamplingRate      int      `env:"ORLY_FIND_SAMPLING_RATE" default:"10" usage:"sampling rate for sparse attestation (1/K)"`
    FindBootstrapServices []string `env:"ORLY_FIND_BOOTSTRAP_SERVICES" usage:"comma-separated list of bootstrap registry service pubkeys"`
}

Phase 5: FIND Daemon HTTP API

Add HTTP API endpoints for FIND operations (optional, for user convenience):

New Endpoints:

Implementation:

// app/handle-find-api.go
func (s *Server) handleFindNameQuery(w http.ResponseWriter, r *http.Request) {
    name := r.PathValue("name")

    // Validate name format
    if err := find.ValidateName(name); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    // Query name state from database
    nameState, err := s.DB.QueryNameState(name)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    if nameState == nil {
        http.Error(w, "name not found", http.StatusNotFound)
        return
    }

    // Return as JSON
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(nameState)
}

Phase 6: Client Integration Examples

Provide example code for clients to use FIND:

Example: Query name ownership

// JavaScript/TypeScript example using nostr-tools
import { SimplePool } from 'nostr-tools'

async function queryNameOwner(relays, name) {
  const pool = new SimplePool()

  // Query kind 30102 events with d tag = name
  const events = await pool.list(relays, [{
    kinds: [30102],
    '#d': [name],
    limit: 5
  }])

  if (events.length === 0) {
    return null // Name not registered
  }

  // Check for majority consensus among registry services
  const ownerCounts = {}
  for (const event of events) {
    const ownerTag = event.tags.find(t => t[0] === 'owner')
    if (ownerTag) {
      const owner = ownerTag[1]
      ownerCounts[owner] = (ownerCounts[owner] || 0) + 1
    }
  }

  // Return owner with most attestations
  let maxCount = 0
  let consensusOwner = null
  for (const [owner, count] of Object.entries(ownerCounts)) {
    if (count > maxCount) {
      maxCount = count
      consensusOwner = owner
    }
  }

  return consensusOwner
}

// Example: Resolve name to IP address
async function resolveNameToIP(relays, name) {
  const owner = await queryNameOwner(relays, name)
  if (!owner) {
    throw new Error('Name not registered')
  }

  // Query kind 30103 events for A records
  const pool = new SimplePool()
  const records = await pool.list(relays, [{
    kinds: [30103],
    '#name': [name],
    '#type': ['A'],
    authors: [owner], // Only records from name owner are valid
    limit: 5
  }])

  if (records.length === 0) {
    throw new Error('No A records found')
  }

  // Extract IP addresses from value tags
  const ips = records.map(event => {
    const valueTag = event.tags.find(t => t[0] === 'value')
    return valueTag ? valueTag[1] : null
  }).filter(Boolean)

  return ips
}

Example: Register a name

import { finalizeEvent, getPublicKey } from 'nostr-tools'
import { find } from './find-helpers'

async function registerName(relays, privkey, name) {
  // Validate name format
  if (!find.validateName(name)) {
    throw new Error('Invalid name format')
  }

  const pubkey = getPublicKey(privkey)

  // Create registration proposal (kind 30100)
  const event = {
    kind: 30100,
    created_at: Math.floor(Date.now() / 1000),
    tags: [
      ['d', name],
      ['action', 'register'],
      ['expiration', String(Math.floor(Date.now() / 1000) + 300)] // 5 min expiry
    ],
    content: ''
  }

  const signedEvent = finalizeEvent(event, privkey)

  // Publish to relays
  const pool = new SimplePool()
  await Promise.all(relays.map(relay => pool.publish(relay, signedEvent)))

  // Wait for consensus (typically 1-2 minutes)
  console.log('Registration proposal submitted. Waiting for consensus...')
  await new Promise(resolve => setTimeout(resolve, 120000))

  // Check if registration succeeded
  const owner = await queryNameOwner(relays, name)
  if (owner === pubkey) {
    console.log('Registration successful!')
    return true
  } else {
    console.log('Registration failed - another proposal may have won consensus')
    return false
  }
}

Testing Plan

Unit Tests

  1. Name Validation Tests (pkg/find/validation_test.go - already exists)

- Valid names - Invalid names (too long, invalid characters, etc.) - Subdomain authority validation

  1. Consensus Algorithm Tests (pkg/find/consensus_test.go - new)

- Single proposal scenario - Competing proposals - Trust-weighted scoring - Attestation window expiry

  1. Trust Graph Tests (pkg/find/trust_test.go - new)

- Direct trust relationships - Multi-hop trust inheritance - Trust decay calculation

Integration Tests

  1. End-to-End Registration (pkg/find/integration_test.go - new)

- Submit proposal - Generate attestations - Compute consensus - Verify name state

  1. Name Renewal (pkg/find/renewal_test.go - new)

- Renewal during preferential window - Rejection outside renewal window - Expiration handling

  1. Record Management (pkg/find/records_test.go - new)

- Publish DNS-style records - Verify owner authorization - Query records by type

Performance Tests

  1. Concurrent Proposals - Benchmark handling 1000+ simultaneous proposals
  2. Trust Graph Calculation - Test with 10,000+ registry services
  3. Query Performance - Measure name resolution latency

Deployment Strategy

Development Phase

  1. Implement core registry service (Phase 2)
  2. Add unit tests
  3. Test with local relay and simulated registry services

Testnet Phase

  1. Deploy 5-10 test relays with FIND enabled
  2. Simulate various attack scenarios (Sybil, censorship, etc.)
  3. Tune consensus parameters based on results

Production Rollout

  1. Documentation and client libraries
  2. Enable FIND on select relays (opt-in)
  3. Monitor for issues and gather feedback
  4. Gradual adoption across relay network

Security Considerations

Attack Mitigations

  1. Sybil Attacks

- Trust-weighted consensus prevents new services from dominating - Age-weighted trust (new services have reduced influence)

  1. Censorship

- Diverse trust graphs make network-wide censorship difficult - Users can query different registry services aligned with their values

  1. Name Squatting

- Mandatory 1-year expiration - Preferential 30-day renewal window - No indefinite holding

  1. Renewal Window DoS

- 30-day window reduces attack surface - Owner can submit multiple renewal attempts - Registry services filter by pubkey during renewal window

Privacy Considerations

Integration Status

Completed

Remaining Integration Points

  1. Configuration fields in app/config/config.go
  2. Database query helpers (QueryNameState, QueryNameRecords, IsNameAvailable)
  3. Server integration in app/main.go
  4. HTTP API endpoints (optional)
  5. WebSocket event routing for FIND kinds

Client Usage Examples

Register a Name

import { finalizeEvent, getPublicKey } from 'nostr-tools'

async function registerName(relay, privkey, name) {
  const pubkey = getPublicKey(privkey)
  const event = {
    kind: 30100, pubkey,
    created_at: Math.floor(Date.now() / 1000),
    tags: [
      ['d', name],
      ['action', 'register'],
      ['expiration', String(Math.floor(Date.now() / 1000) + 300)]
    ],
    content: ''
  }
  const signedEvent = finalizeEvent(event, privkey)
  await relay.publish(signedEvent)
  // Wait ~2 min for consensus, then check kind 30102
}

Resolve Name

async function resolveNameToIP(relay, name) {
  const nameState = await relay.get({ kinds: [30102], '#d': [name] })
  if (!nameState) throw new Error('Name not registered')
  const owner = nameState.tags.find(t => t[0] === 'owner')[1]
  const records = await relay.list([{
    kinds: [30103], '#name': [name], '#type': ['A'], authors: [owner]
  }])
  return records.map(e => e.tags.find(t => t[0] === 'value')[1])
}

Future Enhancements

  1. Economic Incentives - Optional registration fees via Lightning
  2. Certificate System - Implement NIP-XX certificate witnessing
  3. Client Libraries - Official libraries for popular languages
  4. DNS Gateway - Traditional DNS server that queries FIND