From 7cc5d37cbf4f02f0bb7eee6342dc1ede5a841a7b Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Fri, 9 Jan 2026 07:57:54 +0000 Subject: feat: replace owner-npub with relay-owner-nsec for persistent operator identity Replace the owner-npub configuration option with relay-owner-nsec to provide a persistent cryptographic identity for the relay operator. This addresses NIP-42 authentication requirements discovered during sync debugging. Motivation: - Some relays (e.g., relay.damus.io) require NIP-42 authentication for advanced features like NIP-77 negentropy sync - Previously used random ephemeral keys per connection, providing no persistent identity - Other relays can now recognize us by pubkey for reputation-based rate limiting - Ensures consistency between NIP-11 pubkey and authentication key Changes: - Config: relay_owner_nsec with auto-load/generate from .relay-owner.nsec - NIP-11: Pubkey derived from nsec instead of separate npub field - Sync: RelayConnection now uses operator keys for NIP-42 auth - Docs: Updated README, .env.example, and added .relay-owner.nsec to gitignore Key Features: - Auto-generates key on first run and saves to .relay-owner.nsec - Loads existing key from file on subsequent runs - Can override via CLI flag or environment variable - Enables reputation building across relay network - Future-ready for event signing and WoT calculations Testing: - 225/232 tests passing (7 pre-existing purgatory failures unrelated) - Verified key generation, loading, and NIP-11 derivation - Release build successful Related: work/sync-debug-analysis.md, work/relay-owner-nsec-implementation.md --- src/http/nip11.rs | 53 +++++++++++++++-------------------------------------- 1 file changed, 15 insertions(+), 38 deletions(-) (limited to 'src/http') diff --git a/src/http/nip11.rs b/src/http/nip11.rs index 7df8306..cf31cf3 100644 --- a/src/http/nip11.rs +++ b/src/http/nip11.rs @@ -59,7 +59,7 @@ impl RelayInformationDocument { Self { name: config.relay_name(), description: config.relay_description.clone(), - pubkey: config.owner_npub.clone(), + pubkey: config.relay_owner_npub().ok(), contact: None, // Could be added to config if needed supported_nips: vec![ 1, // NIP-01: Basic protocol flow @@ -93,30 +93,21 @@ mod tests { #[test] fn test_relay_information_document_structure() { - let config = Config { - domain: "relay.example.com".to_string(), - owner_npub: Some("npub1test".to_string()), - relay_name_override: Some("Test Relay".to_string()), - relay_description: "A test relay".to_string(), - git_data_path: "./data/git".to_string(), - relay_data_path: "./data/relay".to_string(), - bind_address: "127.0.0.1:8080".to_string(), - database_backend: crate::config::DatabaseBackend::Memory, - metrics_enabled: true, - metrics_connection_per_ip_abuse_threshold: 10, - metrics_top_n_repos: 10, - sync_bootstrap_relay_url: None, - sync_max_backoff_secs: 3600, - sync_disconnect_check_interval_secs: 60, - sync_base_backoff_secs: 5, - sync_disable_negentropy: false, - }; + let mut config = Config::for_testing(); + config.domain = "relay.example.com".to_string(); + config.relay_name_override = Some("Test Relay".to_string()); + config.relay_description = "A test relay".to_string(); let doc = RelayInformationDocument::from_config(&config); assert_eq!(doc.name, "Test Relay"); assert_eq!(doc.description, "A test relay"); - assert_eq!(doc.pubkey, Some("npub1test".to_string())); + + // Verify pubkey is present and is a valid npub + assert!(doc.pubkey.is_some()); + let pubkey = doc.pubkey.unwrap(); + assert!(pubkey.starts_with("npub1")); + assert!(doc.supported_nips.contains(&1)); assert!(doc.supported_nips.contains(&11)); assert!(doc.supported_nips.contains(&34)); @@ -132,24 +123,10 @@ mod tests { #[test] fn test_relay_information_document_json() { - let config = Config { - domain: "relay.example.com".to_string(), - owner_npub: Some("npub1test".to_string()), - relay_name_override: Some("Test Relay".to_string()), - relay_description: "A test relay".to_string(), - git_data_path: "./data/git".to_string(), - relay_data_path: "./data/relay".to_string(), - bind_address: "127.0.0.1:8080".to_string(), - database_backend: crate::config::DatabaseBackend::Memory, - metrics_enabled: true, - metrics_connection_per_ip_abuse_threshold: 10, - metrics_top_n_repos: 10, - sync_bootstrap_relay_url: None, - sync_max_backoff_secs: 3600, - sync_disconnect_check_interval_secs: 60, - sync_base_backoff_secs: 5, - sync_disable_negentropy: false, - }; + let mut config = Config::for_testing(); + config.domain = "relay.example.com".to_string(); + config.relay_name_override = Some("Test Relay".to_string()); + config.relay_description = "A test relay".to_string(); let doc = RelayInformationDocument::from_config(&config); let json = doc.to_json().expect("Failed to serialize to JSON"); -- cgit v1.2.3