diff --git a/docs/names.md b/docs/names.md index 2ec8e41..02530c2 100644 --- a/docs/names.md +++ b/docs/names.md @@ -1,7 +1,7 @@ NIP-XX ====== -Decentralized Name Registry with Trust-Weighted Consensus +Free Internet Name Daemon (FIND): Decentralized Name Registry with Trust-Weighted Consensus ---------------------------------------------------------- `draft` `optional` @@ -10,20 +10,49 @@ Decentralized Name Registry with Trust-Weighted Consensus This NIP defines a decentralized name registry protocol for human-readable resource naming with trustless title transfer. The protocol is implemented as an independent service that uses Nostr relays for communication (via ephemeral events) and persistent storage (for name state records). The service uses trust-weighted attestations from registry service operators to achieve Byzantine fault tolerance without requiring centralized coordination or proof-of-work. Consensus is reached through social mechanisms of association and affinity, enabling the network to scale to thousands of participating registry services while maintaining ~51% Byzantine fault tolerance against censorship. +All name registrations last exactly 1 year by network consensus. During the final 30 days before expiration, only the current owner can renew the name (preferential renewal window), preventing last-minute sniping while requiring active maintenance to prevent squatting. + +Name owners can publish DNS-style resource records (A, AAAA, CNAME, MX, TXT, NS, SRV) to define what their names resolve to, enabling use cases from IP address mapping to mail servers to service discovery. + +To complete the replacement of traditional DNS and TLS infrastructure, the protocol includes a decentralized certificate system using Let's Encrypt-style challenge-response verification and threshold witnessing, combined with a Noise protocol variant (Nostr Noise) for establishing secure authenticated connections without certificate authorities. + ## Motivation Many peer-to-peer applications require human-readable naming systems for locating network resources (analogous to DNS). Traditional approaches either rely on centralized authorities or blockchain-based systems with slow finality times and high resource requirements. -This proposal leverages Nostr's existing social graph and relay infrastructure to create a naming system that is: +This proposal (Free Internet Name Daemon - FIND) leverages Nostr's existing social graph and relay infrastructure to create a complete naming and security system that replaces both DNS and TLS: +**Name Registry Properties:** - **Trustless**: No single authority controls name registration - **Byzantine fault tolerant**: Resistant to malicious registry services (up to ~51% by trust weight) - **Scalable**: Supports thousands of participating registry services - **Censorship resistant**: Diverse trust graphs make network-wide censorship difficult - **Permissionless**: Anyone can operate a registry service and participate in consensus - **Relay-agnostic**: Uses standard Nostr relays for communication and storage without requiring relay modifications +- **Anti-squatting**: Mandatory 1-year registration period with preferential 30-day renewal window prevents indefinite name holding -The protocol is implemented as an independent service (separate from relay software) that communicates via ephemeral Nostr events and stores persistent state as parameterized replaceable events. This achieves finality in 1-2 minutes, which is acceptable for DNS-like use cases while avoiding the complexity and resource requirements of traditional blockchain consensus. +**DNS Replacement:** +- **Functionally complete**: DNS-style records (A, AAAA, CNAME, MX, TXT, NS, SRV) enable practical applications +- **Instant updates**: Record changes propagate in seconds via Nostr relays +- **Built-in auth**: Records cryptographically signed by name owners + +**TLS/CA Replacement:** +- **Decentralized witnessing**: Threshold signatures (3+ witnesses) replace certificate authorities +- **Challenge-response**: Let's Encrypt-style verification proves name ownership +- **Noise protocol**: Secure authenticated transport using Nostr's secp256k1 primitives +- **Perfect forward secrecy**: Ephemeral key exchanges protect past sessions + +The protocol is implemented as an independent service (separate from relay software) that communicates via ephemeral Nostr events and stores persistent state as parameterized replaceable events. + +**Key Innovations:** + +1. **Preferential Renewal Window:** All names expire after exactly 1 year by network consensus. During the final 30 days before expiration, only the current owner can renew the name, preventing last-minute sniping while ensuring names don't remain claimed indefinitely without active maintenance. After expiration, names become available to anyone on a first-come, first-served basis. + +2. **DNS-Compatible Records:** Owners publish standard DNS-style resource records (A, AAAA, CNAME, MX, TXT, NS, SRV) as Nostr events, enabling drop-in replacement for traditional DNS in many applications. Per-type limits prevent spam while supporting real-world use cases. + +3. **Decentralized Certificates & TLS Replacement:** Challenge-response verification (similar to Let's Encrypt) proves name ownership, threshold witnessing (3+ trusted parties) replaces certificate authorities, and a Noise protocol variant using Nostr's secp256k1 primitives provides secure authenticated transport with perfect forward secrecy. + +The protocol achieves registration finality in 1-2 minutes, record updates are instant (limited only by relay propagation), and certificate issuance takes minutes, making it suitable for replacing DNS and TLS infrastructure while avoiding the complexity and resource requirements of traditional blockchain consensus. ## Specification @@ -69,9 +98,38 @@ The name registry protocol is implemented as an independent service that runs se This NIP defines the following event kinds: - `30100`: Registration Proposal - Claim or transfer of a name (parameterized replaceable) -- `20100`: Attestation - Relay operator's vote on a registration proposal (ephemeral) -- `30101`: Trust Graph - Relay's trust relationships with other relays (parameterized replaceable) +- `20100`: Attestation - Registry service vote on a registration proposal (ephemeral) +- `30101`: Trust Graph - Service trust relationships with other services (parameterized replaceable) - `30102`: Name State - Current ownership state (parameterized replaceable) +- `30103`: Name Records - DNS-style resource records published by name owner (parameterized replaceable) +- `30104`: Certificate - Cryptographic certificate for secure connections (parameterized replaceable) +- `30105`: Witness Service - Certificate witness service information (parameterized replaceable) + +**Expiration Tags:** + +All event kinds in this protocol support expiration tags (NIP-40) which serve dual purposes: + +1. **Protocol Boundary**: Defines when an event is no longer valid for protocol operations. Registry services MUST ignore expired events when computing consensus. +2. **Relay Housekeeping**: Hints to relays when events can be deleted to reduce storage overhead. Relays MAY (but are not required to) delete expired events. + +Expiration ensures that: +- Ephemeral coordination data (attestations) is automatically pruned +- Trust relationships remain current through required renewal +- Names don't remain claimed indefinitely (anti-squatting) +- Abandoned services/names automatically exit the active network +- Relay storage requirements remain bounded + +**Recommended Expiration Periods:** + +| Event Kind | Purpose | Network Consensus Expiration | Rationale | +|------------|---------|-------------------------------|-----------| +| 30100 | Registration Proposal | `created_at + 300` (5 min) | Proposals should be processed quickly; unprocessed proposals are stale | +| 20100 | Attestation | `created_at + 180` (3 min) | Attestations only needed during consensus window | +| 30101 | Trust Graph | `created_at + 2592000` (30 days) | Trust relationships should be actively maintained monthly | +| 30102 | Name State | `registered_at + 31536000` (1 year) | Name ownership lasts exactly 1 year by network consensus | +| 30103 | Name Records | Tied to name expiration | Records are valid while name registration is active; expire with name | +| 30104 | Certificate | `valid_until` timestamp | Certificates expire per their validity period (recommended: 90 days) | +| 30105 | Witness Service | `created_at + 15552000` (180 days) | Witness service info should be updated semi-annually | ### Registration Proposal (Kind 30100) @@ -86,7 +144,8 @@ A parameterized replaceable event where users propose to register or transfer a ["d", ""], // name being claimed (e.g., "foo.n") ["action", "register"], // "register" or "transfer" ["prev_owner", ""], // previous owner pubkey (for transfers only) - ["prev_sig", ""] // signature from prev_owner authorizing transfer + ["prev_sig", ""], // signature from prev_owner authorizing transfer + ["expiration", ""] // proposal expires if not processed ], "content": "", "sig": "" @@ -99,6 +158,7 @@ A parameterized replaceable event where users propose to register or transfer a - `action` tag: Either `register` (initial claim) or `transfer` (ownership change) - `prev_owner` tag: Required for `transfer` actions. The pubkey of the current owner. - `prev_sig` tag: Required for `transfer` actions. Signature proving authorization from previous owner. +- `expiration` tag: Optional Unix timestamp when this proposal expires. Registry services MUST ignore proposals after expiration. Relays MAY delete expired proposals for housekeeping. Recommended: `created_at + 300` (5 minutes). **Transfer Authorization:** @@ -123,7 +183,8 @@ An ephemeral event where registry service operators attest to their acceptance o ["decision", "approve"], // "approve", "reject", or "abstain" ["weight", "100"], // optional stake/confidence weight ["reason", "first_valid"], // optional audit trail - ["service", "wss://registry.example.com"] // optional: the registry service endpoint + ["service", "wss://registry.example.com"], // optional: the registry service endpoint + ["expiration", ""] // attestation expires after consensus window ], "content": "", "sig": "" @@ -140,11 +201,20 @@ An ephemeral event where registry service operators attest to their acceptance o - `weight` tag: Optional numeric weight (default: 100). Higher weights indicate stronger confidence or stake. - `reason` tag: Optional human-readable justification for audit trails - `service` tag: Optional identifier for the registry service (URL or other identifier) +- `expiration` tag: Unix timestamp when this attestation expires. Registry services MUST ignore attestations after expiration. Relays SHOULD delete expired attestations for housekeeping. Recommended: `created_at + 180` (3 minutes after attestation, allowing for consensus window completion). **Attestation Window:** Registry services SHOULD publish attestations within 60-120 seconds of receiving a registration proposal. Attestations are posted to Nostr relays as ephemeral events, allowing them to propagate to other registry services via relay gossip. This allows sufficient time for event propagation while maintaining reasonable finality times. +**Expiration and Cleanup:** + +Attestations include an expiration tag that serves two purposes: +1. **Protocol validity**: Registry services MUST NOT count expired attestations in consensus calculations +2. **Relay housekeeping**: Relays SHOULD delete expired attestations to reduce storage overhead + +Once consensus is reached and name state (kind 30102) is published, attestations are no longer needed and relays can safely prune them. + ### Trust Graph (Kind 30101) A parameterized replaceable event where registry service operators declare their trust relationships. This event is stored on Nostr relays and used by other registry services to build trust graphs: @@ -158,7 +228,8 @@ A parameterized replaceable event where registry service operators declare their ["d", "trust-graph"], // identifier for replacement ["p", "", "wss://service1.example.com", "0.9"], ["p", "", "wss://service2.example.com", "0.7"], - ["p", "", "wss://service3.example.com", "0.5"] + ["p", "", "wss://service3.example.com", "0.5"], + ["expiration", ""] // trust graph expires and requires renewal ], "content": "", "sig": "" @@ -171,6 +242,7 @@ A parameterized replaceable event where registry service operators declare their - First parameter: Trusted registry service's pubkey - Second parameter: Optional service identifier (URL or other identifier) - Third parameter: Trust score (0.0 to 1.0, where 1.0 = complete trust) +- `expiration` tag: Unix timestamp when this trust graph expires. Registry services MUST NOT use expired trust graphs in consensus calculations. Requires periodic renewal to maintain active trust relationships. Recommended: `created_at + 2592000` (30 days). **Trust Score Guidelines:** @@ -180,10 +252,16 @@ A parameterized replaceable event where registry service operators declare their - `0.1-0.3`: Low trust (new or unknown operators) - `0.0`: No trust (excluded from consensus) -**Trust Graph Updates:** +**Trust Graph Updates and Renewal:** Registry service operators SHOULD update their trust graphs gradually. Rapid changes to trust relationships MAY be penalized in weighted consensus calculations to prevent manipulation. Trust graphs are stored as parameterized replaceable events on Nostr relays, making them publicly auditable. +Services MUST publish renewed trust graphs before expiration to maintain their trust relationships. An expired trust graph is treated as if the service has no trust relationships, effectively removing it from the consensus network until a new trust graph is published. This ensures that: +1. Only active, maintained services participate in consensus +2. Abandoned services automatically drop out of the network +3. Trust relationships remain current and intentional +4. Relays can optionally prune very old expired trust graphs + ### Name State (Kind 30102) A parameterized replaceable event representing the current ownership state of a name. This event is published by registry services after consensus is reached and stored on Nostr relays: @@ -199,26 +277,343 @@ A parameterized replaceable event representing the current ownership state of a ["registered_at", ""], ["proposal", ""], ["attestations", ""], - ["confidence", "0.87"] // consensus confidence score + ["confidence", "0.87"], // consensus confidence score + ["expiration", ""] // name registration expires and requires renewal ], "content": "", "sig": "" } ``` +**Field Specifications:** + +- `d` tag: The name being described +- `owner` tag: Current owner's pubkey +- `registered_at` tag: Unix timestamp when originally registered (or last renewed) +- `proposal` tag: Event ID of the registration proposal that established current ownership +- `attestations` tag: Number of services that attested to this registration +- `confidence` tag: Consensus confidence score (0.0 to 1.0) +- `expiration` tag: Unix timestamp when this name registration expires. By network consensus, MUST be exactly `registered_at + 31536000` (1 year). Owners can renew during the preferential renewal window (final 30 days). After expiration, anyone can register. + This event is published by registry services after consensus is reached and stored on Nostr relays as a parameterized replaceable event. This allows clients to quickly query current ownership state from relays without recomputing consensus or running their own registry service. +**Name Expiration and Renewal:** + +All name registrations last exactly **1 year** by network consensus. This is not configurable - registry services MUST enforce this standard registration period. + +**Registration Lifecycle:** + +``` +T=0: Name registered (registered_at timestamp) +T=0-335d: Active registration, owner has exclusive control +T=335-365d: Preferential renewal window (final 30 days) +T=365d: Expiration - name becomes available to anyone +``` + +**Preferential Renewal Window (Days 335-365):** + +During the final 30 days before expiration (starting at day 335, approximately 11 months), ONLY the current owner can successfully register the name. Registry services MUST reject registration proposals from other pubkeys during this window. This gives owners time to renew while preventing last-minute sniping. + +**After Expiration:** + +After the expiration timestamp passes, the name becomes available for registration by anyone. The original owner has no special claim and must compete with other claimants through the standard consensus process. + +The expiration mechanism serves multiple purposes: +1. **Anti-squatting**: Prevents indefinite holding of unused names +2. **Fair renewal**: Owners get 30 days exclusive renewal window +3. **Protocol boundary**: Clear definition of when ownership lapses +4. **Relay housekeeping**: Relays MAY delete expired name state events + +### Name Records (Kind 30103) + +Once a name is successfully registered, the owner can publish DNS-style resource records to define what the name resolves to. These records are parameterized replaceable events published by the name owner and stored on Nostr relays. + +```json +{ + "kind": 30103, + "pubkey": "", + "created_at": , + "tags": [ + ["d", ":"], // e.g., "foo.n:A" or "foo.n:AAAA" + ["name", ""], // the registered name + ["type", ""], // A, AAAA, CNAME, MX, TXT, NS, SRV + ["value", ""], // IP address, hostname, text, etc. + ["ttl", "3600"], // cache TTL in seconds + ["priority", "10"], // for MX and SRV records (optional) + ["weight", "50"], // for SRV records (optional) + ["port", "443"] // for SRV records (optional) + ], + "content": "", + "sig": "" +} +``` + +**Field Specifications:** + +- `d` tag: Unique identifier combining name and record type (e.g., "example.n:A", "example.n:MX:1") +- `name` tag: The registered name these records belong to +- `type` tag: DNS record type (see below) +- `value` tag: The record value (format depends on type) +- `ttl` tag: Time-to-live in seconds for client caching (recommended: 3600 = 1 hour) +- `priority` tag: For MX and SRV records, lower numbers = higher priority (0-65535) +- `weight` tag: For SRV records, relative weight for load balancing (0-65535) +- `port` tag: For SRV records, the service port number (0-65535) + +**Authorization:** + +Only the current owner of a name (as determined by kind 30102 name state events) can publish valid records for that name. Clients and services MUST verify that: +1. The name is currently registered (valid, non-expired kind 30102 exists) +2. `record.pubkey == name_state.owner` +3. If either check fails, ignore the record + +**Supported Record Types:** + +| Type | Purpose | Value Format | Limit per Name | +|------|---------|--------------|----------------| +| **A** | IPv4 address | Dotted decimal (e.g., "192.0.2.1") | Max 5 records | +| **AAAA** | IPv6 address | Colon-separated hex (e.g., "2001:db8::1") | Max 5 records | +| **CNAME** | Canonical name (alias) | Fully qualified domain/name (e.g., "target.n") | Max 1 record | +| **MX** | Mail exchange server | Hostname (e.g., "mail.example.n") + priority tag | Max 5 records | +| **TXT** | Text record | Any text string (max 1024 chars) | Max 10 records | +| **NS** | Name server (delegation) | Hostname of authoritative name server | Max 5 records | +| **SRV** | Service location | Hostname + priority, weight, port tags | Max 10 records | + +**Record Type Details:** + +**A Record (IPv4 Address):** +```json +{ + "tags": [ + ["d", "example.n:A:1"], + ["name", "example.n"], + ["type", "A"], + ["value", "192.0.2.1"], + ["ttl", "3600"] + ] +} +``` +Maps name to IPv4 address. Multiple A records enable round-robin load balancing. + +**AAAA Record (IPv6 Address):** +```json +{ + "tags": [ + ["d", "example.n:AAAA:1"], + ["name", "example.n"], + ["type", "AAAA"], + ["value", "2001:db8::1"], + ["ttl", "3600"] + ] +} +``` +Maps name to IPv6 address. + +**CNAME Record (Canonical Name):** +```json +{ + "tags": [ + ["d", "example.n:CNAME"], + ["name", "example.n"], + ["type", "CNAME"], + ["value", "target.n"], + ["ttl", "3600"] + ] +} +``` +Creates an alias pointing to another name. If CNAME exists, A/AAAA records SHOULD NOT exist for the same name (CNAME takes precedence). + +**MX Record (Mail Exchange):** +```json +{ + "tags": [ + ["d", "example.n:MX:1"], + ["name", "example.n"], + ["type", "MX"], + ["value", "mail.example.n"], + ["priority", "10"], + ["ttl", "3600"] + ] +} +``` +Specifies mail servers for the domain. Lower priority numbers = higher preference. + +**TXT Record (Text):** +```json +{ + "tags": [ + ["d", "example.n:TXT:1"], + ["name", "example.n"], + ["type", "TXT"], + ["value", "v=spf1 include:example.n ~all"], + ["ttl", "3600"] + ] +} +``` +Arbitrary text data. Commonly used for verification (SPF, DKIM, domain ownership), configuration, or metadata. Max 1024 characters per record. + +**NS Record (Name Server):** +```json +{ + "tags": [ + ["d", "example.n:NS:1"], + ["name", "example.n"], + ["type", "NS"], + ["value", "ns1.example.n"], + ["ttl", "3600"] + ] +} +``` +Delegates subdomain authority to specific name servers. Used for distributed name management. + +**SRV Record (Service):** +```json +{ + "tags": [ + ["d", "example.n:SRV:1"], + ["name", "_http._tcp.example.n"], + ["type", "SRV"], + ["value", "server.example.n"], + ["priority", "10"], + ["weight", "50"], + ["port", "443"], + ["ttl", "3600"] + ] +} +``` +Specifies service location (host and port). Name format: `_service._proto.name`. Priority determines preference order, weight enables load distribution. + +**Record Limits and Validation:** + +Registry services and clients SHOULD enforce the following limits: + +1. **Per-type limits**: Maximum records per type as specified in table above +2. **Total records**: Maximum 50 records per name across all types +3. **Value validation**: + - A: Valid IPv4 address format + - AAAA: Valid IPv6 address format + - CNAME: Valid name format, no circular references + - MX/NS/SRV: Valid hostname format + - TXT: Max 1024 characters + - Priority/weight/port: Valid integer ranges (0-65535) +4. **CNAME exclusivity**: If CNAME record exists, A/AAAA records MUST NOT exist for the same name +5. **Owner authorization**: Record pubkey MUST match current name owner + +**Record Expiration:** + +Name records do not have separate expiration. They are implicitly valid while the name registration is active. When a name expires (after 1 year without renewal): +- All associated records become invalid +- Clients MUST stop resolving records for expired names +- Relays MAY prune expired name records for housekeeping +- New owner must publish fresh records after re-registering + +**Subdomain Delegation:** + +Subdomains can be delegated using NS records: + +```json +{ + "tags": [ + ["d", "example.n:NS:1"], + ["name", "sub.example.n"], + ["type", "NS"], + ["value", "ns1.subdomain-operator.n"] + ] +} +``` + +The owner of `example.n` publishes NS records for `sub.example.n`, delegating authority to another operator's name server. The subdomain operator can then publish records for `*.sub.example.n` (if their system supports it). + +**Client Resolution Process:** + +1. Query relays for kind 30102 name state to verify ownership and expiration +2. If expired, return NXDOMAIN (name does not exist) +3. Query relays for kind 30103 records matching the name +4. Filter records by pubkey (MUST match name owner) +5. Apply record limits (reject excess records) +6. Check for CNAME - if exists, follow to target name (with loop detection) +7. Return A/AAAA records (or other requested type) +8. Cache result according to TTL + +### Name Renewal Process + +Owners can renew their name registration during the preferential renewal window (final 30 days before expiration) by submitting a new registration proposal (kind 30100) with `action: "register"` for the same name. + +**Preferential Renewal Window Enforcement:** + +Registry services MUST enforce the following rules: + +1. **Before Renewal Window** (Days 0-335): + - Name is owned and cannot be registered by anyone + - Registry services MUST reject all registration proposals for this name + +2. **During Renewal Window** (Days 335-365): + - ONLY the current owner's pubkey can successfully register the name + - Registry services MUST reject registration proposals from other pubkeys + - Owner's renewal proposals follow normal consensus process + +3. **After Expiration** (Day 365+): + - Name becomes available to anyone + - First valid registration to achieve consensus wins + - Former owner has no special privileges + +**Renewal Process:** + +1. **Renewal Proposal**: Owner publishes a kind 30100 event with: + - `action` tag: `"register"` (renewals use the register action) + - `d` tag: The name being renewed + - `expiration` tag: Proposal expiration (5 minutes) + - Must be published during the renewal window (days 335-365) + +2. **Validation**: Registry services validate: + - Current time is within renewal window (`expiration - 2592000` to `expiration`) + - Proposal pubkey matches current owner pubkey from name state + - If validation fails, reject the proposal + +3. **Consensus**: Registry services attest to the renewal following normal consensus rules. + +4. **New Name State**: After consensus, services publish updated kind 30102 events with: + - Updated `registered_at` timestamp (time of renewal consensus) + - New `expiration` tag: `registered_at + 31536000` (exactly 1 year) + - Same `owner` pubkey + - New `proposal` event ID pointing to renewal proposal + +**Renewal Timing Recommendations:** +- Owners SHOULD renew as soon as the renewal window opens (day 335) +- Clients MUST warn owners when renewal window opens (30 days until expiration) +- Early renewal reduces risk of missing the deadline +- If renewal consensus completes before expiration, ownership continues uninterrupted +- If owner misses the renewal window, they lose preferential status after expiration + ### Consensus Algorithm Each registry service independently computes consensus using the following algorithm. Registry services subscribe to events from Nostr relays to receive proposals and attestations: -#### 1. Attestation Window +#### 1. Proposal Validation When a registry service receives a registration proposal for name `N` (via subscription to kind 30100 events on relays): -1. Start a timer for `T` seconds (recommended: 60-120s) -2. Collect all attestations for this proposal (kind 20100 events from relays) -3. Collect competing proposals for the same name `N` (kind 30100 events from relays) +1. **Check proposal expiration** - if expired, ignore the proposal + +2. **Query current name state** - fetch kind 30102 events for name `N` from relays + +3. **Validate against name state:** + - If name is NOT registered (no valid name state): + - Accept proposal from any pubkey + - If name IS registered and NOT expired: + - Calculate: `current_time` vs `name_state.expiration` + - If `current_time < (expiration - 2592000)` (before renewal window): + - REJECT: Name is owned, not available + - If `current_time >= (expiration - 2592000)` AND `current_time < expiration` (renewal window): + - ACCEPT only if `proposal.pubkey == name_state.owner` + - REJECT if `proposal.pubkey != name_state.owner` + - If `current_time >= expiration` (after expiration): + - ACCEPT proposal from any pubkey + +4. **Start attestation timer** for `T` seconds (recommended: 60-120s) + +5. **Collect attestations** (kind 20100 events from relays), filtering out expired ones + +6. **Collect competing proposals** for the same name `N`, validating each against the rules above #### 2. Trust-Weighted Scoring @@ -242,9 +637,12 @@ Where: Trust distance is computed using the registry service's trust graph (assembled from kind 30101 events retrieved from relays): 1. Subscribe to kind 30101 events from relays to build a trust graph -2. Build a directed graph from all registry service trust declarations -3. Use Dijkstra's algorithm or breadth-first search to find shortest trust path -4. Multiply trust scores along the path for effective trust weight +2. Filter out expired trust graphs based on expiration tag - expired trust graphs are not used +3. Build a directed graph from all valid (non-expired) registry service trust declarations +4. Use Dijkstra's algorithm or breadth-first search to find shortest trust path +5. Multiply trust scores along the path for effective trust weight + +**Note:** Services with expired trust graphs are excluded from consensus calculations as if they published no trust relationships. This ensures only actively maintained services participate in the network. **Example:** ``` @@ -319,10 +717,20 @@ Expected timeline for name registration: ``` T=0s: Registration proposal published to relays (kind 30100) T=0-30s: Proposal propagates via relay gossip to registry services -T=30-90s: Registry services publish attestations (kind 20100) to relays +T=30-90s: Registry services validate proposal (ownership, renewal window) and publish attestations (kind 20100) T=90s: Most registry services compute weighted consensus T=120s: High confidence threshold (2-minute finality) -T=120s+: Registry services publish name state (kind 30102) to relays +T=120s+: Registry services publish name state (kind 30102) with expiration = registered_at + 31536000 +``` + +**Registration Lifecycle Timeline:** + +``` +T=0: Initial registration achieves consensus +T=0-28944000s: Name is owned and active (days 0-335, ~11 months) +T=28944000s: Preferential renewal window opens (day 335) +T=28944000-31536000s: Renewal window active - only owner can register (days 335-365) +T=31536000s: Name expires - available to anyone (day 365, exactly 1 year) ``` **Early finality**: If >70% of expected attestations arrive within 30s and reach consensus threshold, registry services MAY finalize earlier. @@ -331,26 +739,130 @@ T=120s+: Registry services publish name state (kind 30102) to relays ### Client Implementation +**Name Ownership Queries:** + Clients querying name ownership should: 1. Subscribe to kind 30102 events for the name from multiple relays -2. Use majority consensus among responses from different registry services -3. Warn users if registry services disagree on ownership -4. Cache results with TTL of 5-10 minutes -5. Re-query on cache expiry or when name is used in critical operations -Note: Clients do not need to run registry service software; they simply query kind 30102 events from standard Nostr relays. +2. Check expiration tag on each name state event - treat expired names as unregistered + +3. Use majority consensus among responses from different registry services + +4. Warn users if registry services disagree on ownership + +5. **Display renewal window status** for owned names: + - Calculate days until expiration: `(expiration - current_time) / 86400` + - If days remaining > 35: Show "Active until [date]" + - If days remaining 30-35: Show "Renewal window opens soon on [date]" + - If days remaining 0-30: Show **"Renewal window active - renew now!"** (prominent warning) + - If days remaining < 7: Show **"URGENT: Name expires in X days!"** (critical warning) + - If expired: Show "Expired - available for registration" + +6. For names owned by user's pubkey: + - Track expiration dates in wallet/profile + - Send notifications when renewal window opens (day 335) + - Send urgent notifications if approaching expiration (days 364-365) + - Provide easy renewal button/workflow in UI + +7. Cache results with TTL of 5-10 minutes + +8. Re-query on cache expiry or when name is used in critical operations + +Note: Clients do not need to run registry service software; they simply query kind 30102 events from standard Nostr relays. Clients MUST respect expiration tags and treat expired names as available for registration. + +**Name Record Resolution:** + +Clients resolving names to addresses/resources should: + +1. **Verify name ownership first:** + - Query kind 30102 for name state from multiple relays + - Check name is not expired + - Note the owner pubkey + +2. **Query records:** + - Subscribe to kind 30103 events with `name` tag matching the target name + - Filter to only records where `record.pubkey == name_state.owner` + - Apply record type filters based on query type (A, AAAA, MX, etc.) + +3. **Validate records:** + - Check record count limits per type + - Validate value formats (IP address syntax, hostname format, etc.) + - For CNAME: Check for circular references (max 10 hops) + - Reject records exceeding limits or with invalid formats + +4. **Handle CNAME:** + - If CNAME record exists, recursively resolve the target name + - Track visited names to detect loops + - Stop after 10 CNAME hops and return error + +5. **Sort and prioritize:** + - MX/SRV records: Sort by priority (ascending), then weight (descending) + - A/AAAA records: Randomize order for round-robin load balancing + - Return all valid records of the requested type + +6. **Cache results:** + - Use TTL from record tags (default: 3600 seconds) + - Cache negative results (NXDOMAIN) for short period (300 seconds) + - Invalidate cache if name ownership changes + +7. **Handle errors:** + - Name not registered: Return NXDOMAIN + - Name expired: Return NXDOMAIN + - No records of requested type: Return NODATA + - Record owner mismatch: Ignore invalid records + +**Example Resolution Flow:** + +``` +User queries: example.n → A record + +1. Query kind 30102 for "example.n" + → Found, owner=npub1abc..., expires=2026-01-15, not expired ✓ + +2. Query kind 30103 with name="example.n", type="A" + → Found 2 records from npub1abc... (matches owner ✓) + → Record 1: 192.0.2.1 + → Record 2: 192.0.2.2 + +3. Return both A records to application +4. Cache for 3600 seconds +``` ### Registry Service Implementation -Registry services participating in consensus should: +Registry services participating in consensus MUST: + +1. Subscribe to kind 30100, 20100, 30101, and 30102 events from configured Nostr relays -1. Subscribe to kind 30100, 20100, and 30101 events from configured Nostr relays 2. Maintain local trust graph (derived from kind 30101 events fetched from relays) -3. Implement the consensus algorithm described above -4. Publish attestations (kind 20100) as ephemeral events to relays for proposals of interest -5. Publish name state (kind 30102) as parameterized replaceable events to relays after reaching consensus -6. Maintain local cache of active proposals and attestations (ephemeral data can be pruned after consensus) + +3. **Enforce renewal window rules** (critical for network consensus): + - Query kind 30102 name state for each registration proposal + - Calculate current time vs name expiration + - Before renewal window (days 0-335): REJECT all registration proposals + - During renewal window (days 335-365): ACCEPT only if `proposal.pubkey == name_state.owner` + - After expiration (day 365+): ACCEPT proposals from any pubkey + +4. Filter out expired events when processing: + - Ignore proposals with expiration timestamps in the past + - Ignore attestations with expiration timestamps in the past + - Ignore trust graphs with expiration timestamps in the past + - Treat expired name states (>365 days old) as available for registration + +5. **Set mandatory registration period**: + - All name state events MUST have `expiration = registered_at + 31536000` (exactly 1 year) + - Services MUST NOT accept custom expiration periods + +6. Implement the consensus algorithm described above with proposal validation + +7. Publish attestations (kind 20100) as ephemeral events to relays for valid proposals, with appropriate expiration tags + +8. Publish name state (kind 30102) as parameterized replaceable events to relays after reaching consensus + +9. Maintain local cache of active proposals, attestations, and name states (ephemeral data can be pruned after consensus) + +10. Periodically renew own trust graph (kind 30101) before expiration to remain in the network **Storage requirements (per registry service):** - Trust graph cache: ~100 KB per 1000 registry services @@ -407,12 +919,40 @@ New registry services should bootstrap their trust graph by: **Attack**: Registering valuable names without use. -**Mitigation** (optional extensions): -- Name expiration: Require periodic renewal (NIP extension) -- Economic cost: Require payment or proof-of-burn for registration -- Proof-of-use: Require demonstrable resource at name (similar to DNS verification) +**Mitigation**: +- **Name expiration** (mandatory): All names expire after exactly 1 year by network consensus. Registry services MUST enforce this standard registration period. +- **Preferential renewal window** (mandatory): During the final 30 days before expiration, only the current owner can renew. This prevents sniping while requiring active maintenance. +- **No indefinite holding**: Owners must actively renew during their renewal window or lose the name. +- **Optional economic cost**: Registration fees (via Lightning) can be added to further discourage speculative squatting -#### 5. Transfer Fraud +The mandatory expiration and renewal window mechanism ensures that: +- Abandoned names automatically return to availability after 1 year +- Owners demonstrate continued interest through annual renewal +- Owners get fair notice (30 days) to renew before losing the name +- Snipers cannot steal names during the renewal window +- The protocol automatically handles inactive name holders +- Network consensus on registration duration prevents gaming the system + +#### 5. Renewal Window Denial of Service + +**Attack**: Attacker floods network with spam proposals during owner's renewal window to prevent owner's renewal from being processed, causing name expiration. + +**Mitigation**: +- Owner has 30 full days (720 hours) to submit renewal +- Proposals expire after 5 minutes, limiting spam effectiveness +- Owner can submit multiple renewal proposals if first ones fail +- Registry services filter proposals by pubkey during renewal window (non-owner proposals automatically rejected) +- Owner should renew early in renewal window (day 335, not day 364) + +**Attack**: Attacker observes owner's renewal proposal and attempts to register name immediately after expiration in case renewal fails. + +**Mitigation**: +- Renewal window gives owners 30 days exclusive opportunity +- After expiration, first proposal to achieve consensus wins (fair race) +- Owner can re-register immediately after expiration with no penalty +- Network propagation delays (~30s) give no particular advantage to any participant + +#### 6. Transfer Fraud **Attack**: Forged transfer without owner's consent. @@ -431,6 +971,353 @@ New registry services should bootstrap their trust graph by: - Registry service attestations are ephemeral but visible during attestation window - Attestations reveal which services are active and participating +## Cryptographic Transport Security + +To complete the infrastructure for replacing traditional DNS and TLS certificate authorities, this protocol includes a decentralized certificate system using Nostr cryptography and a Noise protocol variant for secure communications. + +### Certificate Events (Kind 30104) + +Name owners can generate certificates for their names and have them witnessed (signed) by trusted third parties. Certificates are published as parameterized replaceable events: + +```json +{ + "kind": 30104, + "pubkey": "", + "created_at": , + "tags": [ + ["d", ""], + ["name", ""], + ["cert_pubkey", ""], // public key for this name's service + ["valid_from", ""], + ["valid_until", ""], + ["challenge", ""], // proof of ownership challenge + ["challenge_proof", ""], // signature over challenge by name owner + ["witness", "", ""], // witness signatures + ["witness", "", ""], + ["witness", "", ""] + ], + "content": "{\"algorithm\":\"secp256k1-schnorr\",\"usage\":\"tls-replacement\"}", + "sig": "" +} +``` + +**Field Specifications:** + +- `d` tag: The name this certificate is for +- `name` tag: The registered name being certified +- `cert_pubkey` tag: The public key used for the service (may differ from owner's Nostr pubkey) +- `valid_from`/`valid_until` tags: Certificate validity period (recommended: 90 days) +- `challenge` tag: Random challenge token for proving name ownership +- `challenge_proof` tag: Signature over `challenge||name||cert_pubkey||valid_until` by name owner +- `witness` tags: Each witness (trusted third party) signs the entire certificate content +- `content`: JSON metadata about algorithm and usage + +**Certificate Validity:** + +A certificate is valid if: +1. Current time is between `valid_from` and `valid_until` +2. Certificate pubkey matches name owner pubkey (from kind 30102) +3. Challenge proof signature is valid +4. At least 3 witness signatures are valid (from trusted witnesses) +5. The associated name is not expired + +### Challenge-Response Protocol + +To prove ownership of a name when requesting certificate witnessing, name owners must complete a challenge-response similar to Let's Encrypt: + +**Step 1: Generate Challenge** + +Witness generates random challenge token: +``` +challenge = random_bytes(32) | hex +``` + +**Step 2: Publish Challenge Record** + +Name owner publishes a TXT record proving control of the name: +```json +{ + "kind": 30103, + "pubkey": "", + "tags": [ + ["d", ":TXT:_challenge"], + ["name", ""], + ["type", "TXT"], + ["value", "_nostr-challenge="], + ["ttl", "300"] + ] +} +``` + +**Step 3: Verify Challenge** + +Witness queries the name's TXT records: +1. Query kind 30102 to verify name ownership +2. Query kind 30103 for TXT records with `_nostr-challenge` prefix +3. Verify TXT record pubkey matches name owner +4. Confirm challenge token matches expected value +5. Validate within time window (5 minutes) + +**Step 4: Issue Witness Signature** + +If challenge succeeds, witness signs the certificate: +``` +witness_message = cert_pubkey || name || valid_from || valid_until || challenge +witness_sig = schnorr_sign(witness_privkey, sha256(witness_message)) +``` + +**Alternative Challenge Methods:** + +- **HTTP Challenge**: Place challenge token at `https://name/.well-known/nostr-challenge` +- **DNS Challenge**: TXT record at `_nostr-challenge.name` +- **Nostr Event Challenge**: Publish kind 1 event with challenge token and name tag + +### Certificate Witnesses + +Witnesses are trusted entities that verify name ownership and sign certificates. Anyone can become a witness, but clients must trust witness pubkeys. + +**Witness Trust Models:** + +1. **Web of Trust**: Users maintain list of trusted witness pubkeys +2. **Registry Service Witnesses**: Registry services that achieve consensus can act as witnesses +3. **Community Witnesses**: Well-known Nostr identities with established reputation +4. **Threshold Witnesses**: Require N-of-M witness signatures (e.g., 3-of-5) + +**Witness Responsibilities:** + +- Verify name ownership via challenge-response +- Check name is registered and not expired +- Verify certificate parameters are reasonable (validity period, pubkey format) +- Sign certificate if all checks pass +- Publish witness signature to relays +- Maintain witness service availability + +**Witness Discovery:** + +Witnesses can publish their service information: +```json +{ + "kind": 30105, + "pubkey": "", + "tags": [ + ["d", "witness-service"], + ["endpoint", "wss://witness.example.n"], + ["challenges", "txt", "http", "event"], + ["max_validity", "7776000"], // 90 days max + ["fee", "1000"], // sats per certificate + ["reputation", ""] + ], + "content": "{\"description\":\"Community certificate witness\",\"contact\":\"witness@example.n\"}" +} +``` + +### Noise Protocol Variant: Nostr Noise (NN) + +For establishing secure connections, we define a Noise protocol handshake using Nostr primitives (secp256k1 ECDH and Schnorr signatures). + +**Handshake Pattern: NNpsk0** + +Based on Noise protocol's `NNpsk0` pattern, adapted for Nostr: + +``` +-> e +<- e, ee, s, es, ss +-> s, se +``` + +Where: +- `e` = ephemeral key +- `s` = static key +- `ee` = ephemeral-ephemeral DH +- `es` = ephemeral-static DH +- `se` = static-ephemeral DH +- `ss` = static-static DH + +**Message 1: Client → Server (Initiator)** + +``` +message1: + ephemeral_pubkey (32 bytes) + encrypted_payload: + name_requested (variable) + supported_versions (variable) +``` + +Client generates ephemeral keypair and sends public key. + +**Message 2: Server → Client (Responder)** + +``` +message2: + ephemeral_pubkey (32 bytes) + encrypted_payload: + server_static_pubkey (32 bytes) // Server's certificate pubkey + certificate_event_id (32 bytes) // Reference to kind 30104 certificate + schnorr_signature (64 bytes) // Server signs (client_ephemeral || server_ephemeral || name) +``` + +Server: +1. Generates ephemeral keypair +2. Computes `ee` = ECDH(server_ephemeral_priv, client_ephemeral_pub) +3. Derives encryption key from `ee` +4. Encrypts payload containing static pubkey and certificate reference +5. Signs handshake with certificate private key + +**Message 3: Client → Server (Authentication)** + +``` +message3: + encrypted_payload: + client_static_pubkey (32 bytes) // Optional: client auth + schnorr_signature (64 bytes) // Client signature (optional) + application_data (variable) +``` + +Client: +1. Verifies server's certificate (query kind 30104, validate witnesses) +2. Verifies server's signature +3. Computes `es` = ECDH(client_ephemeral_priv, server_static_pub) +4. Computes `se` = ECDH(client_static_priv, server_ephemeral_pub) +5. Computes `ss` = ECDH(client_static_priv, server_static_pub) (if client auth) +6. Derives final encryption keys from `ee || es || se || ss` +7. Sends authenticated payload + +**Key Derivation:** + +After handshake completes: +``` +handshake_hash = SHA256(message1 || message2 || message3) +encryption_key = HKDF(chaining_key, handshake_hash, "nostr-noise-1.0") +``` + +Derives two keys: +- `initiator_key`: Client → Server encryption +- `responder_key`: Server → Client encryption + +**Encryption:** + +Uses ChaCha20-Poly1305 AEAD with: +- 96-bit nonce (counter-based) +- 256-bit key from key derivation +- Associated data: sequence number + +**Certificate Verification in Handshake:** + +Client must verify server certificate: +1. Fetch kind 30104 event by ID provided in message 2 +2. Verify certificate is not expired (`valid_from` to `valid_until`) +3. Verify `cert_pubkey` matches server's static pubkey +4. Verify certificate owner matches name being requested (query kind 30102) +5. Verify challenge proof signature +6. Verify at least 3 witness signatures from trusted witnesses +7. Verify name registration is not expired +8. Accept connection only if all checks pass + +**Perfect Forward Secrecy:** + +Ephemeral keys provide forward secrecy. Even if static keys are compromised later, past sessions remain secure due to ephemeral ECDH exchanges. + +### Integration with Name Records + +The complete flow for establishing a secure connection to a name: + +**Step 1: Name Resolution** + +``` +1. Client queries "example.n" for A/AAAA records +2. Client receives IP address(es) from kind 30103 records +3. Client verifies records are signed by current name owner +``` + +**Step 2: Certificate Discovery** + +``` +4. Client queries kind 30104 for "example.n" certificate +5. Client validates certificate: + - Check validity period + - Verify owner pubkey matches name owner (kind 30102) + - Verify challenge proof + - Verify witness signatures (at least 3 trusted) + - Confirm name not expired +``` + +**Step 3: Secure Connection** + +``` +6. Client initiates TCP connection to resolved IP +7. Client starts Nostr Noise handshake +8. Client sends ephemeral key (message 1) +9. Server responds with ephemeral key + certificate (message 2) +10. Client verifies certificate matches queried certificate +11. Client completes handshake (message 3) +12. Encrypted communication begins +``` + +**Step 4: Certificate Pinning** + +``` +13. Client caches certificate pubkey for this name +14. Future connections verify same pubkey (TOFU - Trust On First Use) +15. Client warns if certificate pubkey changes +``` + +### Certificate Renewal + +Certificates should be renewed before expiration (recommended: every 90 days): + +1. **Generate new challenge**: Request fresh challenge from witnesses +2. **Prove ownership**: Complete challenge-response (TXT record or HTTP) +3. **Request signatures**: Submit to witnesses for signature +4. **Publish certificate**: Publish new kind 30104 event with updated validity period +5. **Overlap period**: New certificate should overlap old by 7 days for smooth transition + +**Automated Renewal:** + +Services can automate certificate renewal: +``` +while true: + if certificate_expires_in < 30_days: + new_cert = request_certificate_renewal() + publish_certificate(new_cert) + sleep(7_days) + else: + sleep(1_day) +``` + +### Security Properties + +**Advantages over Traditional TLS/CA:** + +1. **No Certificate Authorities**: No centralized trust anchors to compromise +2. **Transparent Witnessing**: All certificate issuance is public and auditable +3. **Key Continuity**: Same Nostr keys used for name ownership and certificates +4. **Instant Revocation**: Delete kind 30104 event to revoke certificate +5. **Integrated with Names**: Certificate validity tied to name registration +6. **Threshold Trust**: Require multiple witnesses (3-of-5, 5-of-7, etc.) +7. **User-Selected Trust**: Clients choose which witnesses they trust + +**Threat Model:** + +- **Compromised Witness**: Requires 3+ witnesses, attacker must compromise majority +- **Name Hijacking**: Name ownership verified via registry consensus +- **MitM Attack**: Client verifies certificate ownership chain and witness signatures +- **Certificate Forgery**: Impossible without both name owner key and witness keys +- **Replay Attacks**: Prevented by ephemeral keys and sequence numbers in Noise protocol + +**Limitations:** + +- **Bootstrap Trust**: Users must initially trust some witnesses (similar to CA root trust) +- **Witness Availability**: Need responsive witnesses for certificate issuance +- **Certificate Size**: Larger than X.509 due to multiple witness signatures +- **Compatibility**: Not compatible with traditional TLS/X.509 infrastructure + +### Event Kind Summary + +| Kind | Purpose | Persistence | +|------|---------|-------------| +| 30104 | Certificates | Parameterized replaceable (by name) | +| 30105 | Witness Service Info | Parameterized replaceable (by witness) | + ## Discussion ### Trust Graph Bootstrapping @@ -860,10 +1747,26 @@ This provides good UX for most users while maintaining trustless properties for - [NIP-01: Basic protocol flow](https://github.com/nostr-protocol/nips/blob/master/01.md) - [NIP-33: Parameterized replaceable events](https://github.com/nostr-protocol/nips/blob/master/33.md) +- [NIP-40: Expiration timestamp](https://github.com/nostr-protocol/nips/blob/master/40.md) +- [NIP-44: Encrypted Direct Messages (ECDH)](https://github.com/nostr-protocol/nips/blob/master/44.md) - [NIP-65: Relay list metadata](https://github.com/nostr-protocol/nips/blob/master/65.md) +- [Noise Protocol Framework](http://www.noiseprotocol.org/noise.html) - Perrin, T. (2018) +- [Let's Encrypt ACME Protocol](https://datatracker.ietf.org/doc/html/rfc8555) - RFC 8555 - PageRank algorithm: Brin, S. and Page, L. (1998). "The anatomy of a large-scale hypertextual Web search engine" - Byzantine Generals Problem: Lamport, L., Shostak, R., and Pease, M. (1982) ## Changelog - 2025-01-XX: Initial draft + - Independent service architecture (services separate from relays) + - Trust-weighted consensus with Byzantine fault tolerance + - Expiration tags for protocol boundaries and relay housekeeping + - Mandatory 1-year registration period by network consensus + - Preferential 30-day renewal window (days 335-365) for current owners + - Name state, trust graphs, proposals, and attestations as Nostr events + - DNS-style name records (A, AAAA, CNAME, MX, TXT, NS, SRV) with per-type limits + - Subdomain delegation via NS records + - Decentralized certificate system with challenge-response verification + - Threshold witnessing (3+ signatures) replacing certificate authorities + - Nostr Noise protocol for secure authenticated transport (TLS replacement) + - Perfect forward secrecy via ephemeral key exchanges