HNS.ONE

Docs · Reference

Record conventions.

Every record this system writes or reads, with its exact owner name and value syntax. If a convention is designed but nothing publishes it, that is stated in the same row rather than left for you to discover.

At a glance

OwnerTypeValueStatus
_hns.<label>TXT v=hns1;pubkey=<64 hex>;epoch=<n>;receipt=<ts>.<b64url sig> written and read
_atproto.<label>TXT v=hns1;did=<did>;epoch=<n>;receipt=<ts>.<b64url sig> written and read
_nostr.<name>TXT v=nostr1;pubkey=<64 hex>;epoch=<n>;relay=wss://… read path only — nothing publishes it
_443._tcp[.<label>]TLSA 3 1 1 <64 hex>written and read
<56 hex>._openpgpkeyOPENPGPKEY base64 of the key packet mail flow, operator-run step
<56 hex>._smimecertSMIMEA 3 0 1 <64 hex> mail flow, operator-run step
<label> or @TXT ipfs=<cid> / ar=<txid> the site pointer
<label> or @TXT redirect=<https url>[;code=301|302|307|308][;path=drop|append] served by the gateway
_hnsone.<name>TXT verification=<token> ownership challenge
The identity record is _hns, not _nostr

Several comments in our own source still say _nostr where the code writes _hns. The constant is _hns, every writer and reader uses it, and querying _nostr under a live name today returns no data. If you are implementing against this, follow the table, not the stale comment.

_hns — the control record

One TXT record at _hns.<label>, or at bare _hns for a name's apex. There is exactly one per owner name; publishing replaces rather than appends.

_hns.matt.w3.  300  TXT  "v=hns1;pubkey=17bab1c31e04...9974;epoch=1;receipt=1787411566.xN8oG3z7...AoQ"
valways hns1; a reader that does not see this prefix must treat the record as absent
pubkeythe control key, 64 lowercase hex (x-only secp256k1)
epochincrements when the name changes hands; an older epoch is stale, not merely different
receipt<created_at>.<sig> — Unix seconds, a dot, then 64 signature bytes as unpadded base64url

The apex form of the record omits the receipt, because it is written when a control key is bound to a name we already know the owner of rather than by a claim. The whole value must fit in one 255-byte TXT string; publishing refuses rather than splitting it.

The identity receipt

The receipt is not a document served at a URL. It is a signature over a reconstructable event, so that anyone can verify a name's binding to a key without asking us anything.

The signed message is the NIP-01 event id of this event — rebuilt by the verifier, never trusted as transmitted:

{
  "pubkey":     "<64 hex control key>",
  "created_at": 1787411566,
  "kind":       30078,
  "tags":       [["v","hns1"], ["d","hns:matt.w3"], ["epoch","1"]],
  "content":    ""
}

The atproto receipt is the same shape with ["d","hns:atproto:<name>"] and an extra ["did", "<did>"] tag. The differing d prefix is what stops one receipt being replayed as the other.

Verifying one, with no trust in us

Read the record

Fetch _hns.<name> TXT and require the v=hns1; prefix. A duplicated field key means tampering — reject rather than take the first.

Decode the receipt

Split on the first dot. Left is created_at; right is base64url that must decode to exactly 64 bytes.

Rebuild and verify

Serialise the event as [0,pubkey,created_at,kind,tags,content] with no whitespace, SHA-256 it, and verify the signature as BIP-340 Schnorr over that hash with the record's own pubkey.

No relay, no DNS trust, no call to us. The registry's half of the attestation is separate: it is the DNSSEC signature over that TXT record.

The identity records are served but not signed

Today _hns and _atproto records are answered by our nameservers on the unsigned path and do not appear in the signed zone data. So the self-verifying half — the receipt — works fully, and the registry half does not yet: a validating resolver cannot prove the record came from the zone.

The signing queue for these exists and the operator-side drainer has never been run against the live edge. Until it is, verify the receipt yourself rather than relying on the DNSSEC chain for these two record types. Encryption-key records are signed today; these are not.

Nostr

Two mechanisms, and only one of them is live.

  • NIP-05 over HTTPS — liveServed at https://<name>.hns.one/.well-known/nostr.json. Only the local part _ resolves; any other name returns an empty answer rather than an error, which is what NIP-05 clients expect.
  • _nostr in DNS — designed, parsed, never publishedThe resolver library can read a v=nostr1; record and validate it up to the on-chain DS. Nothing writes one. Querying it under a live name returns no data.
curl "https://matt.w3.hns.one/.well-known/nostr.json?name=_"
{"names": {"_": "<64 hex>"}, "relays": {"<64 hex>": ["wss://...", ...]}}

Up to four relay hints are returned. The route is served ahead of the site pointer deliberately, so a name's own published content can never answer for its identity.

The control key and the social key are not linked on the wire

The signed _hns record carries the control key, while NIP-05 serves the social key. Nothing published today proves the two belong to the same person. Closing that is known work, not something you should read into the current records.

Also: name@hns.one as a NIP-05 address does not resolve — hns.one itself serves no nostr.json, and its unknown paths answer 200 with an HTML page. The form that works is _@<name>.hns.one.

atproto

The handle binding that Bluesky and other atproto clients actually read is the HTTPS well-known, not a TXT record:

curl https://matt.w3.hns.one/.well-known/atproto-did
did:plc:uebb7mahovn3yxovcq7lyupq

Bare DID, text/plain, 404 when the name is unbound — never an empty 200. It is served only after the gateway has read the control key from the _hns record, read the DID and epoch from the _atproto record, and verified the receipt. Any failure means no answer.

The _atproto TXT we publish is not the atproto-spec did=<did> record. It is our own signed form. We deliberately do not publish the spec's DNS form under hns.one: doing so creates an empty non-terminal that stops the wildcard synthesising, which would take the parent name's own gateway address offline. That has happened here twice.

/.well-known/did.json is disabled and returns 404

A did:web document would have to carry a verification method and a service endpoint, and the receipt does not sign either of those — they would come from an unsigned local file, where a stray write is a full identity takeover. Rather than ship a document weaker than it looks, the route answers 404 unconditionally. Do not build against did:web here.

Fediverse handles

These use no DNS records at all — they are WebFinger and ActivityPub on the hns.one apex.

curl "https://hns.one/.well-known/webfinger?resource=acct:matt_w3@hns.one"

Note the underscore. The fediverse server we run accepts only [a-z0-9_] in a username, so dots in a name are mapped to underscores for the handle: matt.w3 becomes @matt_w3@hns.one. Interfaces still display the native dotted name; the underscored form is what other instances resolve.

Naming rules

A label is [a-z0-9]([a-z0-9-]{0,61}[a-z0-9])? — one label, lowercase, no leading or trailing hyphen. Input is normalised (trimmed, lowercased, stripped of dots) before it is verified, not after; the reverse order once let a name be verified in one spelling and stored in another.

Reserved labels, refused to everyone: the service set — www mail email ns ns1 ns2 ns3 ns4 mx smtp imap jmap pop pop3 webmail autoconfig autodiscover registry api app gateway dns ipfs arweave hns odoh query pds relay proof status docs ops social openpgpkey — plus the role set — hostmaster postmaster webmaster admin administrator root official support help abuse security. Anything beginning xn-- is also refused.

Addresses are dotted

A claimed label under a name is addressed as <label>.<name>.hns.one. A flat <label>-<name>.hns.one form also resolves and is still handed out for zones whose certificate wiring is not in place — because a TLS wildcard matches exactly one label, so the dotted URL would die in the handshake before any of this mattered. Once a zone is wired, the dotted form is the address.

Records you cannot edit

Locked to the flow that manages them, for everyone: owner names beginning _hns or _atproto; the types OPENPGPKEY and SMIMEA anywhere, which the mail encryption flow writes as an operator-run step; and _443._tcp* TLSA in zones with DANE enabled, where every certificate rollover would overwrite a manual edit.

The zone's apex NS and DS are not editable either — they live on-chain and are changed in your wallet. See Delegate.