// Package directory implements the distributed directory consensus protocol // as defined in NIP-XX for Nostr relay operators. // // # Overview // // This package provides complete message encoding, validation, and helper // functions for implementing the distributed directory consensus protocol. // The protocol enables Nostr relay operators to form trusted consortiums // that automatically synchronize essential identity-related events while // maintaining decentralization and Byzantine fault tolerance. // // # Event Kinds // // The protocol defines six new event kinds: // // - 39100: Relay Identity Announcement - Announces relay participation // - 39101: Trust Act - Creates trust relationships between relays // - 39102: Group Tag Act - Attests to arbitrary string values // - 39103: Public Key Advertisement - Advertises HD-derived keys // - 39104: Directory Event Replication Request - Requests event replication // - 39105: Directory Event Replication Response - Responds to replication requests // // # Directory Events // // The following existing event kinds are considered "directory events" and // are automatically replicated among consortium members: // // - Kind 0: User Metadata // - Kind 3: Follow Lists // - Kind 5: Event Deletion Requests // - Kind 1984: Reporting // - Kind 10002: Relay List Metadata // - Kind 10000: Mute Lists // - Kind 10050: DM Relay Lists // // # Basic Usage // // ## Creating a Relay Identity Announcement // // pubkey := []byte{...} // 32-byte relay identity key // announcement, err := directory.NewRelayIdentityAnnouncement( // pubkey, // "relay.example.com", // name // "A community relay", // description // "admin@example.com", // contact // "wss://relay.example.com", // relay URL // "abc123...", // signing key (hex) // "def456...", // encryption key (hex) // "1", // version // "https://relay.example.com/.well-known/nostr.json", // NIP-11 URL // ) // if err != nil { // log.Fatal(err) // } // // ## Creating a Trust Act // // act, err := directory.NewTrustAct( // pubkey, // "target_relay_pubkey_hex", // target relay // directory.TrustLevelHigh, // trust level // "wss://target.relay.com", // target URL // nil, // no expiry // directory.TrustReasonManual, // manual trust // []uint16{1, 6, 7}, // additional kinds to replicate // nil, // no identity tag // ) // // ## Creating a Public Key Advertisement // // validFrom := time.Now() // validUntil := validFrom.Add(30 * 24 * time.Hour) // 30 days // // keyAd, err := directory.NewPublicKeyAdvertisement( // pubkey, // "signing-key-001", // key ID // "fedcba9876543210...", // public key (hex) // directory.KeyPurposeSigning, // purpose // validFrom, // valid from // validUntil, // valid until // "secp256k1", // algorithm // "m/39103'/1237'/0'/0/1", // derivation path // 1, // key index // nil, // no identity tag // ) // // # Identity Tags // // Identity tags (I tags) provide npub-encoded identities with proof-of-control // signatures. They bind an identity to a specific delegate key, preventing // unauthorized use. // // ## Creating Identity Tags // // // Create identity tag builder with private key // identityPrivkey := []byte{...} // 32-byte private key // builder, err := directory.NewIdentityTagBuilder(identityPrivkey) // if err != nil { // log.Fatal(err) // } // // // Create signed identity tag for delegate key // delegatePubkey := []byte{...} // 32-byte delegate public key // identityTag, err := builder.CreateIdentityTag(delegatePubkey) // if err != nil { // log.Fatal(err) // } // // // Use in trust act // act, err := directory.NewTrustAct( // pubkey, // "target_relay_pubkey_hex", // directory.TrustLevelHigh, // "wss://target.relay.com", // nil, // directory.TrustReasonManual, // []uint16{1, 6, 7}, // identityTag, // Include identity tag // ) // // # Validation // // All message types include comprehensive validation: // // // Validate a parsed event // if err := announcement.Validate(); err != nil { // log.Printf("Invalid announcement: %v", err) // return // } // // // Validate any consortium event // if err := directory.ValidateConsortiumEvent(event); err != nil { // log.Printf("Invalid consortium event: %v", err) // return // } // // // Verify NIP-11 binding // valid, err := directory.ValidateRelayIdentityBinding( // announcement, // nip11Pubkey, // nip11Nonce, // nip11Sig, // relayAddress, // ) // if err != nil || !valid { // log.Printf("Invalid relay identity binding") // return // } // // # Trust Calculation // // The package provides utilities for calculating trust relationships: // // // Create trust calculator // calculator := directory.NewTrustCalculator() // // // Add trust acts // calculator.AddAct(act1) // calculator.AddAct(act2) // // // Get direct trust level // level := calculator.GetTrustLevel("relay_pubkey_hex") // // // Calculate inherited trust // inheritedLevel := calculator.CalculateInheritedTrust( // "from_relay_pubkey", // "to_relay_pubkey", // ) // // # Replication Filtering // // Determine which events should be replicated to which relays: // // // Create replication filter // filter := directory.NewReplicationFilter(calculator) // filter.AddTrustAct(act) // // // Check if event should be replicated // shouldReplicate := filter.ShouldReplicate(event, "target_relay_pubkey") // // // Get all replication targets for an event // targets := filter.GetReplicationTargets(event) // // # Event Batching // // Batch events for efficient replication: // // // Create event batcher // batcher := directory.NewEventBatcher(100) // max 100 events per batch // // // Add events to batches // batcher.AddEvent("wss://relay1.com", event1) // batcher.AddEvent("wss://relay1.com", event2) // batcher.AddEvent("wss://relay2.com", event3) // // // Check if batch is full // if batcher.IsBatchFull("wss://relay1.com") { // batch := batcher.FlushBatch("wss://relay1.com") // // Send batch for replication // } // // # Replication Requests and Responses // // ## Creating Replication Requests // // requestID, err := directory.GenerateRequestID() // if err != nil { // log.Fatal(err) // } // // request, err := directory.NewDirectoryEventReplicationRequest( // pubkey, // requestID, // "wss://target.relay.com", // []*event.E{event1, event2, event3}, // ) // // ## Creating Replication Responses // // // Success response // results := []*directory.EventResult{ // directory.CreateEventResult("event_id_1", true, ""), // directory.CreateEventResult("event_id_2", false, "duplicate event"), // } // // response, err := directory.CreateSuccessResponse( // pubkey, // requestID, // "wss://source.relay.com", // results, // ) // // // Error response // errorResponse, err := directory.CreateErrorResponse( // pubkey, // requestID, // "wss://source.relay.com", // "relay temporarily unavailable", // ) // // # Key Management // // The protocol uses BIP32 HD key derivation for deterministic key generation: // // // Create key pool manager // masterSeed := []byte{...} // BIP39 seed // manager := directory.NewKeyPoolManager(masterSeed, 0) // identity index 0 // // // Generate derivation paths // signingPath := manager.GenerateDerivationPath(directory.KeyPurposeSigning, 5) // // Returns: "m/39103'/1237'/0'/0/5" // // encryptionPath := manager.GenerateDerivationPath(directory.KeyPurposeEncryption, 3) // // Returns: "m/39103'/1237'/0'/1/3" // // // Track key usage // nextIndex := manager.GetNextKeyIndex(directory.KeyPurposeSigning) // manager.SetKeyIndex(directory.KeyPurposeSigning, 10) // Skip to index 10 // // # Error Handling // // All functions return detailed errors using the errorf package: // // announcement, err := directory.ParseRelayIdentityAnnouncement(event) // if err != nil { // // Handle specific error types // switch { // case strings.Contains(err.Error(), "invalid event kind"): // log.Printf("Wrong event kind: %v", err) // case strings.Contains(err.Error(), "missing"): // log.Printf("Missing required field: %v", err) // default: // log.Printf("Parse error: %v", err) // } // return // } // // # Security Considerations // // The package implements several security measures: // // - All events must have valid signatures // - Identity tags prevent unauthorized identity use // - NIP-11 binding prevents relay impersonation // - Timestamp validation prevents replay attacks // - Content size limits prevent DoS attacks // - Nonce validation ensures cryptographic security // // # Protocol Constants // // Important protocol constants: // // - MaxKeyDelegations: 512 unused key delegations per identity // - KeyExpirationDays: 30 days for unused key delegations // - MinNonceSize: 16 bytes minimum for nonces // - MaxContentLength: 65536 bytes maximum for event content // // # Integration Example // // Complete example of implementing consortium membership: // // package main // // import ( // "log" // "time" // // "next.orly.dev/pkg/protocol/directory" // ) // // func main() { // // Generate relay identity key // relayPrivkey := []byte{...} // 32 bytes // relayPubkey := schnorr.PubkeyFromSeckey(relayPrivkey) // // // Create relay identity announcement // announcement, err := directory.NewRelayIdentityAnnouncement( // relayPubkey, // "my-relay.com", // "My Community Relay", // "admin@my-relay.com", // "wss://my-relay.com", // hex.EncodeToString(signingPubkey), // hex.EncodeToString(encryptionPubkey), // "1", // "https://my-relay.com/.well-known/nostr.json", // ) // if err != nil { // log.Fatal(err) // } // // // Sign and publish announcement // if err := announcement.Event.Sign(relayPrivkey); err != nil { // log.Fatal(err) // } // // // Create trust act for another relay // act, err := directory.NewTrustAct( // relayPubkey, // "trusted_relay_pubkey_hex", // directory.TrustLevelHigh, // "wss://trusted-relay.com", // nil, // no expiry // directory.TrustReasonManual, // []uint16{1, 6, 7}, // replicate text notes, reposts, reactions // nil, // no identity tag // ) // if err != nil { // log.Fatal(err) // } // // // Sign and publish act // if err := act.Event.Sign(relayPrivkey); err != nil { // log.Fatal(err) // } // // // Set up replication filter // calculator := directory.NewTrustCalculator() // calculator.AddAct(act) // // filter := directory.NewReplicationFilter(calculator) // filter.AddTrustAct(act) // // // When receiving events, check if they should be replicated // for event := range eventChannel { // targets := filter.GetReplicationTargets(event) // for _, target := range targets { // // Replicate event to target relay // replicateEvent(event, target) // } // } // } // // For more detailed examples and advanced usage patterns, see the test files // and the reference implementation in the main relay codebase. package directory