doc.go raw

   1  // Package directory implements the distributed directory consensus protocol
   2  // as defined in NIP-XX for Nostr relay operators.
   3  //
   4  // # Overview
   5  //
   6  // This package provides complete message encoding, validation, and helper
   7  // functions for implementing the distributed directory consensus protocol.
   8  // The protocol enables Nostr relay operators to form trusted consortiums
   9  // that automatically synchronize essential identity-related events while
  10  // maintaining decentralization and Byzantine fault tolerance.
  11  //
  12  // # Event Kinds
  13  //
  14  // The protocol defines six new event kinds:
  15  //
  16  //   - 39100: Relay Identity Announcement - Announces relay participation
  17  //   - 39101: Trust Act - Creates trust relationships between relays
  18  //   - 39102: Group Tag Act - Attests to arbitrary string values
  19  //   - 39103: Public Key Advertisement - Advertises HD-derived keys
  20  //   - 39104: Directory Event Replication Request - Requests event replication
  21  //   - 39105: Directory Event Replication Response - Responds to replication requests
  22  //
  23  // # Directory Events
  24  //
  25  // The following existing event kinds are considered "directory events" and
  26  // are automatically replicated among consortium members:
  27  //
  28  //   - Kind 0: User Metadata
  29  //   - Kind 3: Follow Lists
  30  //   - Kind 5: Event Deletion Requests
  31  //   - Kind 1984: Reporting
  32  //   - Kind 10002: Relay List Metadata
  33  //   - Kind 10000: Mute Lists
  34  //   - Kind 10050: DM Relay Lists
  35  //
  36  // # Basic Usage
  37  //
  38  // ## Creating a Relay Identity Announcement
  39  //
  40  //	pubkey := []byte{...} // 32-byte relay identity key
  41  //	announcement, err := directory.NewRelayIdentityAnnouncement(
  42  //		pubkey,
  43  //		"relay.example.com",           // name
  44  //		"A community relay",           // description
  45  //		"admin@example.com",           // contact
  46  //		"wss://relay.example.com",     // relay URL
  47  //		"abc123...",                   // signing key (hex)
  48  //		"def456...",                   // encryption key (hex)
  49  //		"1",                           // version
  50  //		"https://relay.example.com/.well-known/nostr.json", // NIP-11 URL
  51  //	)
  52  //	if err != nil {
  53  //		log.Fatal(err)
  54  //	}
  55  //
  56  // ## Creating a Trust Act
  57  //
  58  //	act, err := directory.NewTrustAct(
  59  //		pubkey,
  60  //		"target_relay_pubkey_hex",     // target relay
  61  //		directory.TrustLevelHigh,      // trust level
  62  //		"wss://target.relay.com",      // target URL
  63  //		nil,                           // no expiry
  64  //		directory.TrustReasonManual,   // manual trust
  65  //		[]uint16{1, 6, 7},            // additional kinds to replicate
  66  //		nil,                           // no identity tag
  67  //	)
  68  //
  69  // ## Creating a Public Key Advertisement
  70  //
  71  //	validFrom := time.Now()
  72  //	validUntil := validFrom.Add(30 * 24 * time.Hour) // 30 days
  73  //
  74  //	keyAd, err := directory.NewPublicKeyAdvertisement(
  75  //		pubkey,
  76  //		"signing-key-001",                    // key ID
  77  //		"fedcba9876543210...",               // public key (hex)
  78  //		directory.KeyPurposeSigning,         // purpose
  79  //		validFrom,                           // valid from
  80  //		validUntil,                          // valid until
  81  //		"secp256k1",                         // algorithm
  82  //		"m/39103'/1237'/0'/0/1",            // derivation path
  83  //		1,                                   // key index
  84  //		nil,                                 // no identity tag
  85  //	)
  86  //
  87  // # Identity Tags
  88  //
  89  // Identity tags (I tags) provide npub-encoded identities with proof-of-control
  90  // signatures. They bind an identity to a specific delegate key, preventing
  91  // unauthorized use.
  92  //
  93  // ## Creating Identity Tags
  94  //
  95  //	// Create identity tag builder with private key
  96  //	identityPrivkey := []byte{...} // 32-byte private key
  97  //	builder, err := directory.NewIdentityTagBuilder(identityPrivkey)
  98  //	if err != nil {
  99  //		log.Fatal(err)
 100  //	}
 101  //
 102  //	// Create signed identity tag for delegate key
 103  //	delegatePubkey := []byte{...} // 32-byte delegate public key
 104  //	identityTag, err := builder.CreateIdentityTag(delegatePubkey)
 105  //	if err != nil {
 106  //		log.Fatal(err)
 107  //	}
 108  //
 109  //	// Use in trust act
 110  //	act, err := directory.NewTrustAct(
 111  //		pubkey,
 112  //		"target_relay_pubkey_hex",
 113  //		directory.TrustLevelHigh,
 114  //		"wss://target.relay.com",
 115  //		nil,
 116  //		directory.TrustReasonManual,
 117  //		[]uint16{1, 6, 7},
 118  //		identityTag, // Include identity tag
 119  //	)
 120  //
 121  // # Validation
 122  //
 123  // All message types include comprehensive validation:
 124  //
 125  //	// Validate a parsed event
 126  //	if err := announcement.Validate(); err != nil {
 127  //		log.Printf("Invalid announcement: %v", err)
 128  //		return
 129  //	}
 130  //
 131  //	// Validate any consortium event
 132  //	if err := directory.ValidateConsortiumEvent(event); err != nil {
 133  //		log.Printf("Invalid consortium event: %v", err)
 134  //		return
 135  //	}
 136  //
 137  //	// Verify NIP-11 binding
 138  //	valid, err := directory.ValidateRelayIdentityBinding(
 139  //		announcement,
 140  //		nip11Pubkey,
 141  //		nip11Nonce,
 142  //		nip11Sig,
 143  //		relayAddress,
 144  //	)
 145  //	if err != nil || !valid {
 146  //		log.Printf("Invalid relay identity binding")
 147  //		return
 148  //	}
 149  //
 150  // # Trust Calculation
 151  //
 152  // The package provides utilities for calculating trust relationships:
 153  //
 154  //	// Create trust calculator
 155  //	calculator := directory.NewTrustCalculator()
 156  //
 157  //	// Add trust acts
 158  //	calculator.AddAct(act1)
 159  //	calculator.AddAct(act2)
 160  //
 161  //	// Get direct trust level
 162  //	level := calculator.GetTrustLevel("relay_pubkey_hex")
 163  //
 164  //	// Calculate inherited trust
 165  //	inheritedLevel := calculator.CalculateInheritedTrust(
 166  //		"from_relay_pubkey",
 167  //		"to_relay_pubkey",
 168  //	)
 169  //
 170  // # Replication Filtering
 171  //
 172  // Determine which events should be replicated to which relays:
 173  //
 174  //	// Create replication filter
 175  //	filter := directory.NewReplicationFilter(calculator)
 176  //	filter.AddTrustAct(act)
 177  //
 178  //	// Check if event should be replicated
 179  //	shouldReplicate := filter.ShouldReplicate(event, "target_relay_pubkey")
 180  //
 181  //	// Get all replication targets for an event
 182  //	targets := filter.GetReplicationTargets(event)
 183  //
 184  // # Event Batching
 185  //
 186  // Batch events for efficient replication:
 187  //
 188  //	// Create event batcher
 189  //	batcher := directory.NewEventBatcher(100) // max 100 events per batch
 190  //
 191  //	// Add events to batches
 192  //	batcher.AddEvent("wss://relay1.com", event1)
 193  //	batcher.AddEvent("wss://relay1.com", event2)
 194  //	batcher.AddEvent("wss://relay2.com", event3)
 195  //
 196  //	// Check if batch is full
 197  //	if batcher.IsBatchFull("wss://relay1.com") {
 198  //		batch := batcher.FlushBatch("wss://relay1.com")
 199  //		// Send batch for replication
 200  //	}
 201  //
 202  // # Replication Requests and Responses
 203  //
 204  // ## Creating Replication Requests
 205  //
 206  //	requestID, err := directory.GenerateRequestID()
 207  //	if err != nil {
 208  //		log.Fatal(err)
 209  //	}
 210  //
 211  //	request, err := directory.NewDirectoryEventReplicationRequest(
 212  //		pubkey,
 213  //		requestID,
 214  //		"wss://target.relay.com",
 215  //		[]*event.E{event1, event2, event3},
 216  //	)
 217  //
 218  // ## Creating Replication Responses
 219  //
 220  //	// Success response
 221  //	results := []*directory.EventResult{
 222  //		directory.CreateEventResult("event_id_1", true, ""),
 223  //		directory.CreateEventResult("event_id_2", false, "duplicate event"),
 224  //	}
 225  //
 226  //	response, err := directory.CreateSuccessResponse(
 227  //		pubkey,
 228  //		requestID,
 229  //		"wss://source.relay.com",
 230  //		results,
 231  //	)
 232  //
 233  //	// Error response
 234  //	errorResponse, err := directory.CreateErrorResponse(
 235  //		pubkey,
 236  //		requestID,
 237  //		"wss://source.relay.com",
 238  //		"relay temporarily unavailable",
 239  //	)
 240  //
 241  // # Key Management
 242  //
 243  // The protocol uses BIP32 HD key derivation for deterministic key generation:
 244  //
 245  //	// Create key pool manager
 246  //	masterSeed := []byte{...} // BIP39 seed
 247  //	manager := directory.NewKeyPoolManager(masterSeed, 0) // identity index 0
 248  //
 249  //	// Generate derivation paths
 250  //	signingPath := manager.GenerateDerivationPath(directory.KeyPurposeSigning, 5)
 251  //	// Returns: "m/39103'/1237'/0'/0/5"
 252  //
 253  //	encryptionPath := manager.GenerateDerivationPath(directory.KeyPurposeEncryption, 3)
 254  //	// Returns: "m/39103'/1237'/0'/1/3"
 255  //
 256  //	// Track key usage
 257  //	nextIndex := manager.GetNextKeyIndex(directory.KeyPurposeSigning)
 258  //	manager.SetKeyIndex(directory.KeyPurposeSigning, 10) // Skip to index 10
 259  //
 260  // # Error Handling
 261  //
 262  // All functions return detailed errors using the errorf package:
 263  //
 264  //	announcement, err := directory.ParseRelayIdentityAnnouncement(event)
 265  //	if err != nil {
 266  //		// Handle specific error types
 267  //		switch {
 268  //		case strings.Contains(err.Error(), "invalid event kind"):
 269  //			log.Printf("Wrong event kind: %v", err)
 270  //		case strings.Contains(err.Error(), "missing"):
 271  //			log.Printf("Missing required field: %v", err)
 272  //		default:
 273  //			log.Printf("Parse error: %v", err)
 274  //		}
 275  //		return
 276  //	}
 277  //
 278  // # Security Considerations
 279  //
 280  // The package implements several security measures:
 281  //
 282  //   - All events must have valid signatures
 283  //   - Identity tags prevent unauthorized identity use
 284  //   - NIP-11 binding prevents relay impersonation
 285  //   - Timestamp validation prevents replay attacks
 286  //   - Content size limits prevent DoS attacks
 287  //   - Nonce validation ensures cryptographic security
 288  //
 289  // # Protocol Constants
 290  //
 291  // Important protocol constants:
 292  //
 293  //   - MaxKeyDelegations: 512 unused key delegations per identity
 294  //   - KeyExpirationDays: 30 days for unused key delegations
 295  //   - MinNonceSize: 16 bytes minimum for nonces
 296  //   - MaxContentLength: 65536 bytes maximum for event content
 297  //
 298  // # Integration Example
 299  //
 300  // Complete example of implementing consortium membership:
 301  //
 302  //	package main
 303  //
 304  //	import (
 305  //		"log"
 306  //		"time"
 307  //
 308  //		"next.orly.dev/pkg/protocol/directory"
 309  //	)
 310  //
 311  //	func main() {
 312  //		// Generate relay identity key
 313  //		relayPrivkey := []byte{...} // 32 bytes
 314  //		relayPubkey := schnorr.PubkeyFromSeckey(relayPrivkey)
 315  //
 316  //		// Create relay identity announcement
 317  //		announcement, err := directory.NewRelayIdentityAnnouncement(
 318  //			relayPubkey,
 319  //			"my-relay.com",
 320  //			"My Community Relay",
 321  //			"admin@my-relay.com",
 322  //			"wss://my-relay.com",
 323  //			hex.EncodeToString(signingPubkey),
 324  //			hex.EncodeToString(encryptionPubkey),
 325  //			"1",
 326  //			"https://my-relay.com/.well-known/nostr.json",
 327  //		)
 328  //		if err != nil {
 329  //			log.Fatal(err)
 330  //		}
 331  //
 332  //		// Sign and publish announcement
 333  //		if err := announcement.Event.Sign(relayPrivkey); err != nil {
 334  //			log.Fatal(err)
 335  //		}
 336  //
 337  //		// Create trust act for another relay
 338  //		act, err := directory.NewTrustAct(
 339  //			relayPubkey,
 340  //			"trusted_relay_pubkey_hex",
 341  //			directory.TrustLevelHigh,
 342  //			"wss://trusted-relay.com",
 343  //			nil, // no expiry
 344  //			directory.TrustReasonManual,
 345  //			[]uint16{1, 6, 7}, // replicate text notes, reposts, reactions
 346  //			nil, // no identity tag
 347  //		)
 348  //		if err != nil {
 349  //			log.Fatal(err)
 350  //		}
 351  //
 352  //		// Sign and publish act
 353  //		if err := act.Event.Sign(relayPrivkey); err != nil {
 354  //			log.Fatal(err)
 355  //		}
 356  //
 357  //		// Set up replication filter
 358  //		calculator := directory.NewTrustCalculator()
 359  //		calculator.AddAct(act)
 360  //
 361  //		filter := directory.NewReplicationFilter(calculator)
 362  //		filter.AddTrustAct(act)
 363  //
 364  //		// When receiving events, check if they should be replicated
 365  //		for event := range eventChannel {
 366  //			targets := filter.GetReplicationTargets(event)
 367  //			for _, target := range targets {
 368  //				// Replicate event to target relay
 369  //				replicateEvent(event, target)
 370  //			}
 371  //		}
 372  //	}
 373  //
 374  // For more detailed examples and advanced usage patterns, see the test files
 375  // and the reference implementation in the main relay codebase.
 376  package directory
 377