- Updated the NIP-XX document to clarify terminology, replacing "attestations" with "acts" for consistency. - Enhanced the protocol by introducing new event kinds: Trust Act (Kind 39101) and Group Tag Act (Kind 39102), with detailed specifications for their structure and usage. - Modified the signature generation process to include the canonical WebSocket URL, ensuring proper binding and verification. - Improved validation mechanisms for identity tags and event replication requests, reinforcing security and integrity within the directory consensus protocol. - Added comprehensive documentation for new event types and their respective validation processes, ensuring clarity for developers and users. - Introduced new helper functions and structures to facilitate the creation and management of directory events and acts.
377 lines
11 KiB
Go
377 lines
11 KiB
Go
// 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
|