You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
376 lines
11 KiB
376 lines
11 KiB
// 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
|
|
|