names.md raw

NIP-XX ======

Free Internet Name Daemon (FIND): Decentralized Name Registry with Trust-Weighted Consensus


draft optional

Abstract

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.

Names follow standard Internet DNS conventions (lowercase, dot-separated hierarchical structure). TLDs are registerable by anyone - there is no ICANN equivalent, meaning valuable TLDs like com, org, net will see intense competition in a first-come-first-served race at network launch.

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 (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:

DNS Replacement:

TLS/CA Replacement:

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. No Central Naming Authority: Unlike traditional DNS with ICANN controlling TLDs, this protocol allows anyone to register TLDs. Names follow standard DNS conventions (lowercase, dot-separated) ensuring compatibility, but TLDs are first-come-first-served. Subdomain authority is enforced (must own parent domain), creating a hierarchical ownership model without centralized gatekeepers.
  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.
  1. 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.
  1. 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

Architecture Overview

The name registry protocol is implemented as an independent service that runs separately from Nostr relay software. The service architecture consists of:

Registry Service:

Communication Layer (Ephemeral Events):

Storage Layer (Persistent Events):

Relay Role:

Advantages of Independent Service Architecture:

Event Kinds

This NIP defines the following event kinds:

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:

Recommended Expiration Periods:

Event KindPurposeNetwork Consensus ExpirationRationale
30100Registration Proposalcreated_at + 300 (5 min)Proposals should be processed quickly; unprocessed proposals are stale
20100Attestationcreated_at + 180 (3 min)Attestations only needed during consensus window
30101Trust Graphcreated_at + 2592000 (30 days)Trust relationships should be actively maintained monthly
30102Name Stateregistered_at + 31536000 (1 year)Name ownership lasts exactly 1 year by network consensus
30103Name RecordsTied to name expirationRecords are valid while name registration is active; expire with name
30104Certificatevalid_until timestampCertificates expire per their validity period (recommended: 90 days)
30105Witness Servicecreated_at + 15552000 (180 days)Witness service info should be updated semi-annually

Name Format and Syntax

Names in this protocol MUST follow standard Internet domain name conventions to ensure compatibility with existing tools and mental models.

Name Format Rules:

  1. Case Normalization: All names MUST be lowercase ASCII only

- Example.Com → normalized to example.com - Names are case-insensitive for registration but stored in lowercase - Uppercase letters in proposals are automatically converted to lowercase

  1. Character Set: Names MUST only contain:

- Lowercase letters: a-z - Digits: 0-9 - Hyphens: - (not at start or end of labels) - Dots: . (label separator)

  1. Label Structure: Names are composed of dot-separated labels

- Each label: 1-63 characters - Total name: maximum 253 characters - Labels cannot start or end with hyphens - Labels cannot be all numeric

  1. Hierarchical Structure: Standard DNS hierarchy applies

- com - top-level domain (TLD) - example.com - second-level domain under com - www.example.com - third-level domain (subdomain) under example.com - api.services.example.com - fourth-level domain

Top-Level Domains (TLDs):

In this protocol, TLDs are just names like any other and can be registered by anyone. There is no reserved set of TLDs (no ICANN equivalent).

// Anyone can register a TLD
{"kind": 30100, "tags": [["d", "com"], ["action", "register"]]}
{"kind": 30100, "tags": [["d", "nostr"], ["action", "register"]]}
{"kind": 30100, "tags": [["d", "bitcoin"], ["action", "register"]]}

TLD Registration Race:

Since TLDs are registerable by anyone, valuable/generic TLDs will likely see intense competition:

The first proposal to achieve consensus wins the TLD. Expect significant competition for short, memorable TLDs in the early network.

Subdomain Authority:

Only the owner of a domain can register its direct subdomains:

This is enforced by the protocol: Registry services MUST verify that the registrant of sub.parent.tld also owns parent.tld.

Examples:

Valid names:

com
example.com
www.example.com
api-v2.services.example.com
my-site.nostr
user123.bitcoin

Invalid names:

Example.Com          # Not lowercase (will be normalized)
exam ple.com         # Contains space
-example.com         # Label starts with hyphen
example-.com         # Label ends with hyphen
exam_ple.com         # Contains underscore
123.456              # All-numeric labels
a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f  # > 253 chars

Name Validation:

Registry services and clients MUST validate names against these rules:

  1. Convert to lowercase (normalization)
  2. Check character set (a-z, 0-9, hyphen, dot only)
  3. Validate label lengths (1-63 chars per label)
  4. Validate total length (≤253 chars)
  5. Check hyphen placement (not at label start/end)
  6. Verify label is not all-numeric
  7. For subdomains: Verify parent domain ownership

Invalid names MUST be rejected before attestation.

Registration Proposal (Kind 30100)

A parameterized replaceable event where users propose to register or transfer a name:

{
  "kind": 30100,
  "pubkey": "<claimant_pubkey>",
  "created_at": <unix_timestamp>,
  "tags": [
    ["d", "<name>"],              // name being claimed (e.g., "foo.n")
    ["action", "register"],        // "register" or "transfer"
    ["prev_owner", "<pubkey>"],    // previous owner pubkey (for transfers only)
    ["prev_sig", "<signature>"],   // signature from prev_owner authorizing transfer
    ["expiration", "<unix_timestamp>"]  // proposal expires if not processed
  ],
  "content": "",
  "sig": "<signature>"
}

Field Specifications:

Subdomain Registration:

To register a subdomain (e.g., www.example.com), the proposer MUST own the parent domain (example.com). Registry services validate this during proposal processing:

  1. Proposer owns com TLD
  2. Proposer registers example.com (requires owning com)
  3. Proposer registers www.example.com (requires owning example.com)

This hierarchical ownership ensures that only domain owners can create subdomains, preventing unauthorized subdomain squatting.

Transfer Authorization:

For transfers, the prev_sig MUST be a signature over the following message:

transfer:<name>:<new_owner_pubkey>:<timestamp>

This prevents unauthorized transfers and ensures the current owner explicitly approves the transfer.

Attestation (Kind 20100)

An ephemeral event where registry service operators attest to their acceptance or rejection of a registration proposal. This event is published to Nostr relays for propagation to other registry services:

{
  "kind": 20100,
  "pubkey": "<registry_service_pubkey>",
  "created_at": <unix_timestamp>,
  "tags": [
    ["e", "<proposal_event_id>"],           // the registration proposal being attested
    ["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
    ["expiration", "<unix_timestamp>"]       // attestation expires after consensus window
  ],
  "content": "",
  "sig": "<signature>"
}

Field Specifications:

- approve: Registry service accepts this registration as valid - reject: Registry service rejects this registration (e.g., conflict detected) - abstain: Registry service acknowledges but doesn't vote

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:

{
  "kind": 30101,
  "pubkey": "<registry_service_pubkey>",
  "created_at": <unix_timestamp>,
  "tags": [
    ["d", "trust-graph"],  // identifier for replacement
    ["p", "<trusted_service1_pubkey>", "wss://service1.example.com", "0.9"],
    ["p", "<trusted_service2_pubkey>", "wss://service2.example.com", "0.7"],
    ["p", "<trusted_service3_pubkey>", "wss://service3.example.com", "0.5"],
    ["expiration", "<unix_timestamp>"]  // trust graph expires and requires renewal
  ],
  "content": "",
  "sig": "<signature>"
}

Field Specifications:

- 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)

Trust Score Guidelines:

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:

{
  "kind": 30102,
  "pubkey": "<registry_service_pubkey>",
  "created_at": <unix_timestamp>,
  "tags": [
    ["d", "<name>"],                    // the name
    ["owner", "<current_owner_pubkey>"],
    ["registered_at", "<timestamp>"],
    ["proposal", "<proposal_event_id>"],
    ["attestations", "<count>"],
    ["confidence", "0.87"],             // consensus confidence score
    ["expiration", "<unix_timestamp>"]  // name registration expires and requires renewal
  ],
  "content": "<optional_metadata_json>",
  "sig": "<signature>"
}

Field Specifications:

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.

{
  "kind": 30103,
  "pubkey": "<name_owner_pubkey>",
  "created_at": <unix_timestamp>,
  "tags": [
    ["d", "<name>:<record_type>"],     // e.g., "foo.n:A" or "foo.n:AAAA"
    ["name", "<name>"],                 // the registered name
    ["type", "<record_type>"],          // A, AAAA, CNAME, MX, TXT, NS, SRV
    ["value", "<record_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": "<signature>"
}

Field Specifications:

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:

TypePurposeValue FormatLimit per Name
AIPv4 addressDotted decimal (e.g., "192.0.2.1")Max 5 records
AAAAIPv6 addressColon-separated hex (e.g., "2001:db8::1")Max 5 records
CNAMECanonical name (alias)Fully qualified domain/name (e.g., "target.n")Max 1 record
MXMail exchange serverHostname (e.g., "mail.example.n") + priority tagMax 5 records
TXTText recordAny text string (max 1024 chars)Max 10 records
NSName server (delegation)Hostname of authoritative name serverMax 5 records
SRVService locationHostname + priority, weight, port tagsMax 10 records
GITGit repository locationRepository URL with protocol + optional metadataMax 10 records

Record Type Details:

A Record (IPv4 Address):

{
  "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):

{
  "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):

{
  "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):

{
  "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):

{
  "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):

{
  "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):

{
  "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 - GIT: Valid git-compatible URL (git://, https://, ssh://, file://) - Priority/weight/port: Valid integer ranges (0-65535)

  1. CNAME exclusivity: If CNAME record exists, A/AAAA records MUST NOT exist for the same name
  2. Owner authorization: Record pubkey MUST match current name owner
  3. GIT record validation:

- Protocol tag matches URL scheme in value tag - Description max 256 characters - Access level is "public", "private", or "restricted" (if specified) - Mirror flag is "true" or absent - SSH/HTTPS URLs are valid if provided

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):

GIT Record (Git Repository Location):

{
  "tags": [
    ["d", "example.n:GIT:1"],
    ["name", "example.n"],
    ["type", "GIT"],
    ["value", "git://git.example.n/repo.git"],
    ["ttl", "3600"],
    ["protocol", "git"],
    ["clone_url", "https://git.example.n/repo.git"],
    ["ssh_url", "ssh://git@git.example.n:repo.git"],
    ["description", "My awesome project"],
    ["default_branch", "main"]
  ]
}

Maps name to git repository location with access metadata. Supports multiple protocols (git://, https://, ssh://). Multiple GIT records can provide different repository locations (mirrors, forks).

GIT Record Field Specifications:

GIT Record Limit: Max 10 records per name (supporting multiple mirrors and access methods)

GIT Record Usage Examples:

Example 1: Public Repository with Multiple Access Methods

{
  "kind": 30103,
  "pubkey": "<name_owner_pubkey>",
  "tags": [
    ["d", "myproject.n:GIT:1"],
    ["name", "myproject.n"],
    ["type", "GIT"],
    ["value", "https://git.myproject.n/myproject.git"],
    ["ttl", "3600"],
    ["protocol", "https"],
    ["clone_url", "https://git.myproject.n/myproject.git"],
    ["ssh_url", "git@git.myproject.n:myproject.git"],
    ["description", "Decentralized social network built on Nostr"],
    ["default_branch", "main"],
    ["access", "public"]
  ]
}

Example 2: Repository with Multiple Mirrors

{
  "kind": 30103,
  "pubkey": "<name_owner_pubkey>",
  "tags": [
    ["d", "bitcoin.n:GIT:1"],
    ["name", "bitcoin.n"],
    ["type", "GIT"],
    ["value", "https://git.bitcoin.n/bitcoin.git"],
    ["protocol", "https"],
    ["description", "Bitcoin Core reference implementation"],
    ["default_branch", "master"]
  ]
}
{
  "kind": 30103,
  "pubkey": "<name_owner_pubkey>",
  "tags": [
    ["d", "bitcoin.n:GIT:2"],
    ["name", "bitcoin.n"],
    ["type", "GIT"],
    ["value", "https://mirror1.bitcoin.n/bitcoin.git"],
    ["protocol", "https"],
    ["mirror", "true"],
    ["description", "Bitcoin Core reference implementation (Mirror 1)"]
  ]
}
{
  "kind": 30103,
  "pubkey": "<name_owner_pubkey>",
  "tags": [
    ["d", "bitcoin.n:GIT:3"],
    ["name", "bitcoin.n"],
    ["type", "GIT"],
    ["value", "git://mirror2.bitcoin.n/bitcoin.git"],
    ["protocol", "git"],
    ["mirror", "true"],
    ["description", "Bitcoin Core reference implementation (Mirror 2)"]
  ]
}

Example 3: Subdomain for Repository Organization

{
  "kind": 30103,
  "tags": [
    ["d", "orly.mleku.dev:GIT:1"],
    ["name", "orly.mleku.dev"],
    ["type", "GIT"],
    ["value", "https://git.nostrdev.com/mleku/orly.git"],
    ["protocol", "https"],
    ["clone_url", "https://git.nostrdev.com/mleku/orly.git"],
    ["ssh_url", "git@git.nostrdev.com:mleku/orly.git"],
    ["description", "ORLY - High-performance Nostr relay in Go"],
    ["default_branch", "main"],
    ["access", "public"]
  ]
}

Example 4: Private Repository with SSH-Only Access

{
  "kind": 30103,
  "tags": [
    ["d", "private.company.n:GIT:1"],
    ["name", "private.company.n"],
    ["type", "GIT"],
    ["value", "ssh://git@git.company.n:internal/project.git"],
    ["protocol", "ssh"],
    ["ssh_url", "git@git.company.n:internal/project.git"],
    ["description", "Internal company project (authorized access only)"],
    ["default_branch", "develop"],
    ["access", "private"]
  ]
}

GIT Record Client Resolution:

When resolving a git repository name, clients should:

  1. Verify name ownership:

- Query kind 30102 for name state - Check name is not expired - Note the owner pubkey

  1. Query GIT records:

- Subscribe to kind 30103 events with name and type=GIT tags - Filter to only records where record.pubkey == name_state.owner

  1. Select repository URL:

- Primary: Use value tag as main repository URL - HTTPS fallback: Use clone_url if available - SSH access: Use ssh_url for authenticated operations - Mirror selection: If multiple records exist with mirror=true, randomly select or prefer by latency

  1. Display metadata:

- Show description to users - Indicate access level (public/private/restricted) - Note if repository is a mirror

  1. Clone operation:

`bash # Automatic resolution via client tool git clone myproject.n

# Explicit protocol selection git clone https://myproject.n git clone git@myproject.n `

Integration with Existing Git Tools:

Git clients can be extended to resolve names via a custom URL handler or Git credential helper:

# Example: Custom git-remote-n helper
# Resolves myproject.n by querying Nostr relays
git clone n://myproject.n

# Or via DNS-style integration (if local resolver configured)
git clone https://myproject.n/repo.git

GIT Record Validation:

Registry services and clients SHOULD validate GIT records:

  1. URL format: Valid git-compatible URL (git://, https://, ssh://, file://)
  2. Protocol consistency: protocol tag matches URL scheme in value
  3. Description length: Max 256 characters
  4. Access level: One of "public", "private", "restricted" (if specified)
  5. Mirror flag: Must be "true" or absent
  6. SSH URL format: Valid SSH URL if ssh_url provided
  7. HTTPS URL format: Valid HTTPS URL if clone_url provided
  8. Per-name limit: Maximum 10 GIT records per name
  9. Owner authorization: Record pubkey matches current name owner

Subdomain Delegation:

Subdomains can be delegated using NS records:

{
  "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

  1. 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

  1. 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)

  1. 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

  1. Consensus: Registry services attest to the renewal following normal consensus rules.
  1. 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:

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. Proposal Validation

When a registry service receives a registration proposal for name N (via subscription to kind 30100 events on relays):

  1. Check proposal expiration - if expired, ignore the proposal
  1. Validate name format:

- Convert name to lowercase (normalization) - Verify character set (a-z, 0-9, hyphen, dot only) - Validate label lengths (1-63 chars per label) - Validate total length (≤253 chars) - Check hyphen placement (not at label start/end) - Verify labels are not all-numeric - REJECT if validation fails

  1. Verify subdomain authority (if name contains dots):

- Extract parent domain (e.g., www.example.com → parent is example.com) - Query kind 30102 for parent domain name state - Verify proposal.pubkey == parent_name_state.owner - REJECT if proposer doesn't own parent domain - Note: TLDs (single labels like com) have no parent, always allowed

  1. Query current name state - fetch kind 30102 events for name N from relays
  1. Validate against name state:

- If name is NOT registered (no valid name state): - Accept proposal from any pubkey (or from parent owner for subdomains) - 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 (or from parent owner for subdomains)

  1. Start attestation timer for T seconds (recommended: 60-120s)
  1. Collect attestations (kind 20100 events from relays), filtering out expired ones
  1. Collect competing proposals for the same name N, validating each against the rules above

2. Trust-Weighted Scoring

For each competing proposal P, compute a weighted score:

score(P) = Σ (attestation_weight × trust_decay)

Where:

- Direct trust (0-hop): trust_score × 1.0 - 1-hop trust: trust_score × 0.8 - 2-hop trust: trust_score × 0.6 - 3-hop trust: trust_score × 0.4 - 4+ hops: 0.0 (not counted)

3. Trust Distance Calculation

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. 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:

Service A → Service B (0.9) → Service C (0.8)
Effective trust from A to C: 0.9 × 0.8 × 0.8 (1-hop decay) = 0.576

4. Consensus Decision

After the attestation window expires:

  1. Compute total trust weight: Sum of all attestation scores across all proposals
  2. Compute proposal score: Each proposal's weighted attestation sum
  3. Accept proposal `P` if:

- score(P) / total_trust_weight > threshold (recommended: 0.51) - No competing proposal has higher score - Valid transfer authorization (if action is "transfer")

  1. Defer decision if:

- No proposal reaches threshold - Multiple proposals exceed threshold with similar scores (within 5%) - Insufficient attestations received (coverage < 30% of trust graph)

  1. Publish name state (kind 30102) to Nostr relays once consensus is reached

Sparse Attestation Optimization

To reduce network load, registry services MAY use sparse attestation where they only publish attestations when:

  1. Local interest: One of the service's clients queries this name
  2. Duty rotation: hash(proposal_event_id || service_pubkey) % K == 0 (for sampling rate 1/K)
  3. Conflict detected: Multiple competing proposals received for the same name
  4. Direct request: Client explicitly requests attestation

Recommended sampling rates:

This optimization reduces attestation volume by 80-95% while maintaining consensus reliability through randomized duty rotation.

Handling Conflicts

Simultaneous Registration

When multiple proposals for the same name arrive at similar timestamps:

  1. Primary resolution: Weighted consensus (highest score wins)
  2. Tiebreaker: If scores are within 5%, use lexicographic ordering of proposal event IDs
  3. Client notification: Registry services SHOULD publish notices about conflicts for transparency

Competing Transfers

When multiple transfer proposals claim to originate from the same owner:

  1. Verify prev_sig authenticity for each proposal
  2. Accept the earliest valid transfer (by created_at)
  3. If timestamps are identical (within 1 second), use event ID tiebreaker

Trust Graph Divergence

Different registry services may reach different consensus due to divergent trust graphs. This is acceptable and provides censorship resistance:

Finality Timeline

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 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) 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.

Implementation Guidelines

Client Implementation

Name Ownership Queries:

Clients querying name ownership should:

  1. Subscribe to kind 30102 events for the name from multiple relays
  1. Check expiration tag on each name state event - treat expired names as unregistered
  1. Use majority consensus among responses from different registry services
  1. Warn users if registry services disagree on ownership
  1. 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"

  1. 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

  1. Cache results with TTL of 5-10 minutes
  1. 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

  1. 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.)

  1. 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

  1. 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

  1. 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

  1. 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

  1. 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 MUST:

  1. Subscribe to kind 30100, 20100, 30101, and 30102 events from configured Nostr relays
  1. Maintain local trust graph (derived from kind 30101 events fetched from relays)
  1. 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

  1. 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

  1. Set mandatory registration period:

- All name state events MUST have expiration = registered_at + 31536000 (exactly 1 year) - Services MUST NOT accept custom expiration periods

  1. Implement the consensus algorithm described above with proposal validation
  1. Publish attestations (kind 20100) as ephemeral events to relays for valid proposals, with appropriate expiration tags
  1. Publish name state (kind 30102) as parameterized replaceable events to relays after reaching consensus
  1. Maintain local cache of active proposals, attestations, and name states (ephemeral data can be pruned after consensus)
  1. Periodically renew own trust graph (kind 30101) before expiration to remain in the network

Storage requirements (per registry service):

Note: Registry services can prune ephemeral attestations after consensus is reached. Persistent data (proposals, trust graphs, name state) is stored on Nostr relays, not in the registry service.

Bootstrap Service Discovery

New registry services should bootstrap their trust graph by:

  1. Connecting to well-known Nostr relays to fetch kind 30101 events
  2. Importing trust graphs from 3-5 reputable registry services
  3. Using weighted average of imported trust scores as initial state
  4. Gradually adjusting trust based on observed service behavior over 30 days
  5. Publishing their own trust graph (kind 30101) to relays once established

Security Considerations

Attack Vectors and Mitigations

1. Sybil Attack

Attack: Attacker creates thousands of registry service identities to dominate attestations.

Mitigation:

2. Trust Graph Manipulation

Attack: Attacker gradually builds trust relationships then exploits them.

Mitigation:

3. Censorship

Attack: Powerful registry services refuse to attest certain names (e.g., dissidents, competitors).

Mitigation:

4. Name Squatting

Attack: Registering valuable names without use.

Mitigation:

The mandatory expiration and renewal window mechanism ensures that:

TLD Squatting:

Since TLDs are registerable by anyone (no reserved set), expect immediate competition for valuable TLDs at network launch:

The first proposal to achieve consensus wins the TLD. Early adopters who successfully claim popular TLDs control all second-level registrations under those TLDs (e.g., owner of com controls *.com registrations).

Subdomain Squatting Prevention:

Subdomain authority (parent domain ownership requirement) prevents unauthorized subdomain squatting, but TLD owners could potentially:

This is mitigated by:

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:

Attack: Attacker observes owner's renewal proposal and attempts to register name immediately after expiration in case renewal fails.

Mitigation:

6. Transfer Fraud

Attack: Forged transfer without owner's consent.

Mitigation:

Privacy Considerations

- Mitigation: Use private relays or Tor for sensitive queries

- 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:

{
  "kind": 30104,
  "pubkey": "<name_owner_pubkey>",
  "created_at": <unix_timestamp>,
  "tags": [
    ["d", "<name>"],
    ["name", "<name>"],
    ["cert_pubkey", "<service_public_key>"],   // public key for this name's service
    ["valid_from", "<unix_timestamp>"],
    ["valid_until", "<unix_timestamp>"],
    ["challenge", "<challenge_token>"],         // proof of ownership challenge
    ["challenge_proof", "<signature>"],         // signature over challenge by name owner
    ["witness", "<witness1_pubkey>", "<witness1_sig>"],  // witness signatures
    ["witness", "<witness2_pubkey>", "<witness2_sig>"],
    ["witness", "<witness3_pubkey>", "<witness3_sig>"]
  ],
  "content": "{\"algorithm\":\"secp256k1-schnorr\",\"usage\":\"tls-replacement\"}",
  "sig": "<signature>"
}

Field Specifications:

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:

{
  "kind": 30103,
  "pubkey": "<name_owner_pubkey>",
  "tags": [
    ["d", "<name>:TXT:_challenge"],
    ["name", "<name>"],
    ["type", "TXT"],
    ["value", "_nostr-challenge=<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:

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:

Witness Discovery:

Witnesses can publish their service information:

{
  "kind": 30105,
  "pubkey": "<witness_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", "<reputation_event_id>"]
  ],
  "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:

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(serverephemeralpriv, clientephemeralpub)
  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(clientephemeralpriv, serverstaticpub)
  4. Computes se = ECDH(clientstaticpriv, serverephemeralpub)
  5. Computes ss = ECDH(clientstaticpriv, serverstaticpub) (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:

Encryption:

Uses ChaCha20-Poly1305 AEAD with:

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:

Limitations:

Event Kind Summary

KindPurposePersistence
30104CertificatesParameterized replaceable (by name)
30105Witness Service InfoParameterized replaceable (by witness)

Discussion

Trust Graph Bootstrapping

The effectiveness of trust-weighted consensus depends on establishing a healthy trust graph. This presents a chicken-and-egg problem: new registry services need trust to participate, but must participate to earn trust.

Proposed bootstrapping strategies:

1. Seed Service Trust Inheritance

New registry services can inherit trust graphs from established "seed" services by fetching their kind 30101 events from relays:

{
  "kind": 30101,
  "tags": [
    ["inherit", "<seed_service_pubkey>", "0.8"],
    ["inherit", "<seed_service_pubkey2>", "0.6"]
  ]
}

The new service computes its initial trust graph as a weighted average of the seed services' graphs. Over time (30-90 days), the service adjusts trust based on observed behavior and publishes updated trust graphs to relays.

Trade-offs:

2. Web of Trust Endorsements

Established registry service operators can endorse new services through signed endorsements (stored on relays):

{
  "kind": 1100,
  "pubkey": "<established_service_pubkey>",
  "tags": [
    ["p", "<new_service_pubkey>"],
    ["endorsement", "verified"],
    ["duration", "30d"],
    ["initial_trust", "0.3"]
  ]
}

Other services consuming this endorsement (from relays) automatically grant the new service temporary trust (e.g., 0.3 for 30 days), after which it decays to 0 unless organically reinforced.

Trade-offs:

3. Proof-of-Stake Bootstrap

New registry services can stake economic value (e.g., lock tokens in a Bitcoin Lightning channel) to gain initial trust. Stake proof events are published to relays:

{
  "kind": 30103,
  "pubkey": "<new_service_pubkey>",
  "tags": [
    ["stake", "<lightning_invoice>", "1000000"],  // 1M sats
    ["stake_duration", "180d"],
    ["stake_proof", "<proof_of_lock>"]
  ]
}

Other services fetching these events from relays treat staked services as having trust proportional to stake amount. Dishonest behavior results in slashing (stake destruction).

Trade-offs:

4. Proof-of-Work Bootstrap

New registry services solve computational puzzles to earn temporary trust. PoW proof events are published to relays:

{
  "kind": 30104,
  "pubkey": "<new_service_pubkey>",
  "tags": [
    ["pow", "<nonce>", "24"],  // 24-bit difficulty
    ["pow_created", "<timestamp>"]
  ]
}

Higher difficulty equals higher initial trust. PoW expires after 90 days unless organic trust replaces it.

Trade-offs:

Recommendation: Implement hybrid approach:

Economic Incentives

Why would registry service operators honestly attest to name registrations? Without proper incentives, rational operators might:

Proposed incentive mechanisms:

1. Registration Fees

Name registration proposals include optional tips to registry services that attest. Proposals are published to relays with fee information:

{
  "kind": 30100,
  "tags": [
    ["d", "foo.n"],
    ["fee", "<lightning_invoice>", "10000"],  // 10k sats
    ["fee_distribution", "attestors"]  // or "weighted" or "threshold"
  ]
}

Distribution strategies:

Trade-offs:

2. Reputation Markets

Registry service operators earn reputation scores based on attestation quality. Reputation events are published to relays:

{
  "kind": 30105,
  "tags": [
    ["p", "<service_pubkey>"],
    ["metric", "accuracy", "0.97"],      // % of attestations matching consensus
    ["metric", "uptime", "0.99"],        // % of proposals attested
    ["metric", "latency", "15"],         // avg attestation time (seconds)
    ["period", "<start>", "<end>"]
  ]
}

High-reputation services gain:

Trade-offs:

3. Mutual Benefit Networks

Registry services form explicit cooperation agreements (published to relays):

{
  "kind": 30106,
  "tags": [
    ["p", "<partner_service1>", "wss://service1.example.com"],
    ["p", "<partner_service2>", "wss://service2.example.com"],
    ["agreement", "reciprocal_attestation"],
    ["sla", "95_percent_uptime"]
  ]
}

Services attest to partners' proposals in exchange for reciprocal service. Violating agreements results in removal from the network.

Trade-offs:

4. Altruism + Low Operational Cost

If attestation cost is sufficiently low, registry service operators may participate altruistically:

At 1000 registrations/day with 10% sparse attestation:

Many operators already run services without direct compensation, motivated by:

Trade-offs:

Recommendation: Start with altruism (#4) supplemented by reputation (#2):

Client Conflict Resolution

Clients querying name ownership may receive conflicting responses from different registry services due to:

  1. Trust graph divergence (different services trust different attestors)
  2. Network partitions (some services missed attestations from relays)
  3. Byzantine services (malicious responses)

Conflict resolution strategies:

1. Majority Consensus

Client queries kind 30102 events from N relays (recommended N=5) to get name state from different registry services, then accepts the majority response:

def resolve_name(name, relays):
    responses = []
    for relay in relays:
        # Query kind 30102 events with d tag = name
        # Each event is from a different registry service
        name_states = query_relay(relay, kind=30102, filter={"#d": [name]})
        for state in name_states:
            owner = state.tags["owner"]
            responses.append(owner)

    # Count occurrences
    counts = {}
    for owner in responses:
        counts[owner] = counts.get(owner, 0) + 1

    # Return majority (>50%)
    for owner, count in counts.items():
        if count > len(responses) / 2:
            return owner

    return None  # No majority

Trade-offs:

2. Trust-Weighted Voting

Client maintains its own trust graph and weights responses by registry service pubkey:

def resolve_name_weighted(name, relays, trust_scores):
    responses = {}
    for relay in relays:
        # Query kind 30102 events
        name_states = query_relay(relay, kind=30102, filter={"#d": [name]})
        for state in name_states:
            service_pubkey = state.pubkey
            owner = state.tags["owner"]
            weight = trust_scores.get(service_pubkey, 0.5)  # default 0.5
            responses[owner] = responses.get(owner, 0) + weight

    # Return highest weighted response
    return max(responses, key=responses.get)

Trade-offs:

3. Consensus Confidence Scoring

Registry services include confidence scores in name state events (kind 30102):

{
  "kind": 30102,
  "pubkey": "<registry_service_pubkey>",
  "tags": [
    ["d", "foo.n"],
    ["owner", "<pubkey>"],
    ["confidence", "0.87"],  // 87% of trust weight agreed
    ["attestations", "42"]   // 42 services attested
  ]
}

Client queries kind 30102 events from multiple relays and uses the response with highest confidence:

def resolve_name_confidence(name, relays):
    best_response = None
    best_confidence = 0

    for relay in relays:
        # Query kind 30102 events
        name_states = query_relay(relay, kind=30102, filter={"#d": [name]})
        for state in name_states:
            confidence = float(state.tags["confidence"])

            if confidence > best_confidence:
                best_confidence = confidence
                best_response = state.tags["owner"]

    # Warn if low confidence
    if best_confidence < 0.6:
        warn_user("Low consensus confidence")

    return best_response

Trade-offs:

4. Recursive Attestation Verification

Client fetches attestations (kind 20100) from relays and recomputes consensus locally:

def resolve_name_verify(name, relays):
    # 1. Get all proposals for this name
    proposals = []
    for relay in relays:
        props = query_relay(relay, kind=30100, filter={"d": name})
        proposals.extend(props)

    # 2. Get all attestations for these proposals
    attestations = []
    for relay in relays:
        atts = query_relay(relay, kind=20100, filter={"e": [p.id for p in proposals]})
        attestations.extend(atts)

    # 3. Get trust graphs
    trust_graphs = []
    for relay in relays:
        graphs = query_relay(relay, kind=30101)
        trust_graphs.extend(graphs)

    # 4. Recompute consensus locally
    return compute_consensus(proposals, attestations, trust_graphs)

Trade-offs:

5. Hybrid: Quick Query with Dispute Resolution

Normal case: Query 3 relays, accept majority (fast path) Dispute case: If no majority, fetch attestations and recompute (slow path)

def resolve_name_hybrid(name, relays):
    # Fast path: majority consensus
    responses = [query_relay(r, name) for r in relays]
    majority = get_majority(responses)

    if majority:
        return majority

    # Slow path: fetch attestations and verify
    return resolve_name_verify(name, relays)

Trade-offs:

Recommendation: Implement hybrid approach (#5):

This provides good UX for most users while maintaining trustless properties for disputed or high-value names.

References

Changelog

- Independent service architecture (services separate from relays) - Trust-weighted consensus with Byzantine fault tolerance - Standard DNS naming conventions (lowercase, dot-separated, hierarchical) - TLDs registerable by anyone (no ICANN) - expect TLD registration race - Subdomain authority enforced (must own parent domain) - 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