From 69e55af3a16de5a34aff743c5e956252fd3fa610 Mon Sep 17 00:00:00 2001 From: Melvin Carvalho Date: Thu, 2 Jul 2026 14:15:02 +0200 Subject: [PATCH 1/4] NIP-134: define DNS record set format in event content Specifies a JSON record set for the content field of kind 31034 events, turning an event into a complete signed zone for .nostr: - Records as [type, name, ttl, data] arrays with data in DNS presentation format - MUST support A, AAAA, CNAME, TXT, SRV; unknown types ignored - Latest created_at wins via the parameterized replaceable range, mirroring PKARR's BEP44 sequence numbers - Freshness and caching guidance for resolvers --- README.md | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 909994f..2cb21a8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This is useful for services and individuals who wish to associate their Nostr pu A `kind 31034` event is used. -The `content` SHOULD be the empty string and is reserved for future use. It could contain strings similar to a DNS record. +The `content` MAY be the empty string, or MAY contain a DNS record set as described in [DNS record set](#dns-record-set) below. The following tags are defined as REQUIRED: @@ -46,6 +46,45 @@ Example event: Services and tools that wish to make use of this NIP SHOULD first verify the authenticity of the event by checking the signature and then map the domain name specified in the content to `npub.nostr` or `pubkey.nostr`. +## DNS record set + +The `content` field MAY contain a DNS record set, making the event a complete signed zone for `.nostr` (or `.nostr`). Because Nostr events are signed, this gives signed record sets in the manner of PKARR's `SignedPacket` without any additional machinery. + +The record set is encoded as a JSON object with a `records` array. Each record is an array of four elements: + +``` +[type, name, ttl, data] +``` + +* `type` - the DNS record type as a string. Implementations MUST support `A`, `AAAA`, `CNAME`, `TXT` and `SRV`. Records with unknown types MUST be ignored, allowing future extension. +* `name` - the record name, relative to the `.nostr` zone. `@` denotes the zone apex. Names MUST NOT be fully qualified. +* `ttl` - time to live in seconds, as a number. Resolvers MAY cap TTLs at a policy-defined maximum. +* `data` - the record data in DNS presentation format, as it would appear in a zone file (e.g. `203.0.113.7` for `A`, `10 5 443 relay.example.com.` for `SRV`). + +Example event: + +```json +{ + "kind": 31034, + "pubkey": "de7ecd1e2976a6adb2ffa5f4db81a7d812c8bb6698aa00dcf1e76adb55efd645", + "created_at": 1751462400, + "content": "{\"records\":[[\"A\",\"@\",3600,\"203.0.113.7\"],[\"AAAA\",\"@\",3600,\"2001:db8::7\"],[\"CNAME\",\"www\",3600,\"example.com.\"],[\"TXT\",\"@\",3600,\"hello nostr\"],[\"SRV\",\"_relay._tcp\",3600,\"10 5 443 relay.example.com.\"]]}", + "tags": [ + ["d", ""], + ["u", "https://example.com"] + ], + "sig": "exampleSignature" +} +``` + +A `content` that is empty or does not parse as a JSON object with a `records` array MUST be treated as an empty record set; the `u` tag mapping continues to apply regardless. + +### Freshness and conflict resolution + +Kind `31034` falls in the parameterized replaceable range, so relays retain only the newest event per `pubkey` and `d` tag. The record set with the highest `created_at` wins, serving the same role as PKARR's BEP44 sequence numbers. + +Resolvers SHOULD cache verified record sets for the record TTL, and MAY apply a freshness policy that treats record sets as stale when `created_at` is older than a policy-defined age, prompting a re-query of relays. + ## Implementation In order to lookup a .nostr domain you simply query the pubkey with kind=31034 and use the field in the content as the domain. Some lookup services may choose to fallback to profile web page or nip-05 origin, as desired. From ceb5dca82b91460ad745abe69a4e16ed10f9299d Mon Sep 17 00:00:00 2001 From: Melvin Carvalho Date: Thu, 2 Jul 2026 14:46:23 +0200 Subject: [PATCH 2/4] Address Copilot review - Domain mapping comes from the u tag, not content; fix the two sentences that still said otherwise - TTL is a non-negative integer - Quote the TXT RDATA in the example per DNS presentation format --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2cb21a8..ea257d0 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Example event: } ``` -Services and tools that wish to make use of this NIP SHOULD first verify the authenticity of the event by checking the signature and then map the domain name specified in the content to `npub.nostr` or `pubkey.nostr`. +Services and tools that wish to make use of this NIP SHOULD first verify the authenticity of the event by checking the signature and then map the domain name specified in the `u` tag to `npub.nostr` or `pubkey.nostr`. ## DNS record set @@ -58,7 +58,7 @@ The record set is encoded as a JSON object with a `records` array. Each record * `type` - the DNS record type as a string. Implementations MUST support `A`, `AAAA`, `CNAME`, `TXT` and `SRV`. Records with unknown types MUST be ignored, allowing future extension. * `name` - the record name, relative to the `.nostr` zone. `@` denotes the zone apex. Names MUST NOT be fully qualified. -* `ttl` - time to live in seconds, as a number. Resolvers MAY cap TTLs at a policy-defined maximum. +* `ttl` - time to live in seconds, as a non-negative integer. Resolvers MAY cap TTLs at a policy-defined maximum. * `data` - the record data in DNS presentation format, as it would appear in a zone file (e.g. `203.0.113.7` for `A`, `10 5 443 relay.example.com.` for `SRV`). Example event: @@ -68,7 +68,7 @@ Example event: "kind": 31034, "pubkey": "de7ecd1e2976a6adb2ffa5f4db81a7d812c8bb6698aa00dcf1e76adb55efd645", "created_at": 1751462400, - "content": "{\"records\":[[\"A\",\"@\",3600,\"203.0.113.7\"],[\"AAAA\",\"@\",3600,\"2001:db8::7\"],[\"CNAME\",\"www\",3600,\"example.com.\"],[\"TXT\",\"@\",3600,\"hello nostr\"],[\"SRV\",\"_relay._tcp\",3600,\"10 5 443 relay.example.com.\"]]}", + "content": "{\"records\":[[\"A\",\"@\",3600,\"203.0.113.7\"],[\"AAAA\",\"@\",3600,\"2001:db8::7\"],[\"CNAME\",\"www\",3600,\"example.com.\"],[\"TXT\",\"@\",3600,\"\\\"hello nostr\\\"\"],[\"SRV\",\"_relay._tcp\",3600,\"10 5 443 relay.example.com.\"]]}", "tags": [ ["d", ""], ["u", "https://example.com"] @@ -87,7 +87,7 @@ Resolvers SHOULD cache verified record sets for the record TTL, and MAY apply a ## Implementation -In order to lookup a .nostr domain you simply query the pubkey with kind=31034 and use the field in the content as the domain. Some lookup services may choose to fallback to profile web page or nip-05 origin, as desired. +In order to lookup a .nostr domain you simply query the pubkey with kind=31034 and use the `u` tag as the domain, treating the `content` (if present) as the DNS record set. Some lookup services may choose to fallback to profile web page or nip-05 origin, as desired. ### did:nostr resolution From 401f31b204b2c206ac0bd83095de3e5092cec587 Mon Sep 17 00:00:00 2001 From: Melvin Carvalho Date: Thu, 2 Jul 2026 15:13:06 +0200 Subject: [PATCH 3/4] Address second Copilot review - Fix mapping direction: .nostr name resolves to the u tag domain - Lookup queries filter on d tag (empty string) - Caching honours each record's own ttl, not one TTL for the set --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ea257d0..1de2239 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Example event: } ``` -Services and tools that wish to make use of this NIP SHOULD first verify the authenticity of the event by checking the signature and then map the domain name specified in the `u` tag to `npub.nostr` or `pubkey.nostr`. +Services and tools that wish to make use of this NIP SHOULD first verify the authenticity of the event by checking the signature and then resolve `npub.nostr` or `pubkey.nostr` to the domain name specified in the `u` tag. ## DNS record set @@ -83,11 +83,11 @@ A `content` that is empty or does not parse as a JSON object with a `records` ar Kind `31034` falls in the parameterized replaceable range, so relays retain only the newest event per `pubkey` and `d` tag. The record set with the highest `created_at` wins, serving the same role as PKARR's BEP44 sequence numbers. -Resolvers SHOULD cache verified record sets for the record TTL, and MAY apply a freshness policy that treats record sets as stale when `created_at` is older than a policy-defined age, prompting a re-query of relays. +Resolvers SHOULD cache each verified record for its own `ttl` (optionally capped), and MAY apply a freshness policy that treats record sets as stale when `created_at` is older than a policy-defined age, prompting a re-query of relays. ## Implementation -In order to lookup a .nostr domain you simply query the pubkey with kind=31034 and use the `u` tag as the domain, treating the `content` (if present) as the DNS record set. Some lookup services may choose to fallback to profile web page or nip-05 origin, as desired. +In order to lookup a .nostr domain you simply query the pubkey with kind=31034 and the `d` tag set to the empty string, then use the `u` tag as the domain, treating the `content` (if present) as the DNS record set. Some lookup services may choose to fallback to profile web page or nip-05 origin, as desired. ### did:nostr resolution From 48b5446276c4a97312a90ebed0b461bf23522a06 Mon Sep 17 00:00:00 2001 From: Melvin Carvalho Date: Thu, 2 Jul 2026 17:48:59 +0200 Subject: [PATCH 4/4] Address third Copilot review - Use .nostr / .nostr placeholders so they don't read as literal hostnames - State replaceable semantics at SHOULD level with an explicit client selection rule - Fix the Use Case section to verify the u tag, not content --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1de2239..65c2a32 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Example event: } ``` -Services and tools that wish to make use of this NIP SHOULD first verify the authenticity of the event by checking the signature and then resolve `npub.nostr` or `pubkey.nostr` to the domain name specified in the `u` tag. +Services and tools that wish to make use of this NIP SHOULD first verify the authenticity of the event by checking the signature and then resolve `.nostr` or `.nostr` to the domain name specified in the `u` tag. ## DNS record set @@ -81,7 +81,7 @@ A `content` that is empty or does not parse as a JSON object with a `records` ar ### Freshness and conflict resolution -Kind `31034` falls in the parameterized replaceable range, so relays retain only the newest event per `pubkey` and `d` tag. The record set with the highest `created_at` wins, serving the same role as PKARR's BEP44 sequence numbers. +Kind `31034` falls in the parameterized replaceable range: relays SHOULD retain only the newest event per `pubkey` and `d` tag, and clients SHOULD select the event with the highest `created_at` when more than one is returned. This serves the same role as PKARR's BEP44 sequence numbers. Resolvers SHOULD cache each verified record for its own `ttl` (optionally capped), and MAY apply a freshness policy that treats record sets as stale when `created_at` is older than a policy-defined age, prompting a re-query of relays. @@ -119,7 +119,7 @@ The default resolver is `https://nostr.social` and can be overridden with the `D By having a Nostr public key mapped to a domain name, users and services can easily share, reference, or verify the authenticity of a domain based on its associated Nostr public key. -For instance, if Alice wants to verify that `example.com` is genuinely associated with a specific Nostr public key, she can look up the Nostr event with `kind 31034` and verify the domain name in the content. +For instance, if Alice wants to verify that `example.com` is genuinely associated with a specific Nostr public key, she can look up the Nostr event with `kind 31034` and verify the domain name in the `u` tag (and, optionally, any DNS records in the `content`). ## Related Work