Universal Handle Addressing and Resolution for the Metanet
Reaching someone online means knowing which service they are on, which stops being true the moment they move. This gives each person a readable handle that resolves wherever they actually are.
Summary
- Why
- Wallet ecosystems on BSV each run their own contact lists, payment addressing, and messaging, so a handle from one is meaningless to another and cross-ecosystem reachability requires bespoke integration.
- What
- BRC-169 defines a universal `@handle@ecosystem` addressing scheme where each ecosystem's own domain resolves its handles to an identity key, a certificate, and a messagebox, with no central registry.
- How
- A client parses a handle, queries the named domain's resolution endpoint to get the identity key, a BRC-52 certificate attesting the binding, and a messagebox URL, then derives a payment key per BRC-42/BRC-43 to send a BRC-29 payment or drops a message into the recipient's BRC-33 messagebox.
What this lets you do
- Resolve a handle like @deggen@lkup.net to an identity key and messagebox
- Pay a handle directly using BRC-29 payments derived via BRC-42/BRC-43
- Message a handle through a store-and-forward messagebox without exposing a direct address
- Hand out subhandles for tagged contexts without registering anything new
- Delegate authority to another identity with a scoped, revocable certificate
Written by claude-sonnet-5 from the specification text. Where the two differ, the original is correct.
Reference for an AI
Everything an assistant needs to answer questions about BRC-169 accurately, including what it depends on.
The specification
Abstract
This document specifies a universal addressing scheme for the Metanet, in which every person, organisation, machine, and agent is reachable by a human-readable handle of the form @handle@ecosystem, regardless of which wallet ecosystem they belong to.
Name collisions are avoided without any central registry through domain separation. Every ecosystem is an internet domain, and handles are unique only within their ecosystem. The domain named in the handle is the authority for that handle: it publishes its trust anchor per BRC-68, attests the binding between a handle and an identity key with a BRC-52 certificate, and answers a resolution endpoint that returns the identity key, the certificate, and a messagebox URL.
From a verified resolution, a client can pay the recipient with a BRC-29 transaction derived per BRC-42 and BRC-43 and serialized as Atomic ,[object Object] (BRC-95), and can message them through a store-and-forward messagebox with BRC-33 semantics. Recipients control who may reach them, and at what price, through policy enforced at their own messagebox rather than by their ecosystem.
This document also specifies delegation, so that an organisation, an employee, or an autonomous agent can act for another identity under a scope, a spend cap, and an expiry that any counterparty can verify and the principal can revoke in one action.
The chat command grammar that drives this stack from a conversational interface is specified separately in BRC-218.
Motivation
Every wallet ecosystem on BSV today ships its own contact list, its own payment addressing, and its own messaging silo. A user of one ecosystem cannot naturally pay or message a user of another by name, and an application that wants to be reachable from everywhere must integrate with each ecosystem separately.
Paymail (BRC-28) solved this problem a generation ago with user@domain: one syntax, resolved at the domain, working everywhere. That deployment model was correct. What has not aged well is the stack beneath it, which predates BRC-29 payments, BRC-42 key derivation, BRC-52 certificates, and BRC-100 wallets.
Three failures motivate a standard:
- The silo. Users are addressable only inside their own ecosystem, so network effects accrue to individual vendors rather than to the Metanet.
- The indirection. Current implementations resolve a handle through a global identity overlay to an identity key, then perform a second lookup from the identity key to a messagebox URL. This works, but it centralizes discovery in an overlay and displaces the natural authority, which is the ecosystem the user actually belongs to. It is simpler for the handle's own domain to answer for it: register
deggenatlkup.net, hand out@deggen@lkup.net, and any counterparty knows exactly which domain to ask for the identity key, the attestation, and the messagebox. - The spam asymmetry. In a free-to-send messaging system the cost of attention falls entirely on the recipient. The Metanet has native micropayments, so a recipient should be able to price access to their attention and scope who may reach them at all, portably across ecosystems and without any vendor's permission.
The intended result is a single addressing surface across independently operated domains, with no registry to capture, no pairwise integration between ecosystems, and no vendor in a position to decide who is reachable.
Specification
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in RFC 2119.
1. Terminology
- Ecosystem: an independently operated wallet or client environment, identified by an internet domain it controls.
- Ecosystem host: the operator of an ecosystem's domain. It registers handles, attests handle to identity key bindings, and operates or designates the resolution endpoint and default messagebox.
- Handle: a human-readable name unique within its ecosystem only. Global uniqueness is neither required nor assumed; global unambiguity comes from domain separation.
- Subhandle: a handle extended with a
+tagsuffix that resolves to the same identity (section 3). - Alias: a short ecosystem name such as
handcashthat maps to a domain such ashandcash.io(section 5.5). - Messagebox: a store-and-forward relay with BRC-33 semantics that holds envelopes until the recipient's wallet collects them.
- Envelope: the delivery unit accepted by a messagebox (section 7.2). Its metadata is readable by the messagebox operator; its content is not.
- Principal and delegate: in a delegation, the identity being acted for and the identity acting (section 9).
- Value-moving action: any action that transfers satoshis, issues or revokes a certificate, or adds or updates an address book entry. These actions carry the strictest verification requirements throughout this document.
2. Addressing Grammar
2.1 Syntax
recipient = "@" handle [ "+" tag ] [ "@" ecosystem ]
handle = alnum [ *62( alnum / punct ) alnum ] ; 1 to 64 characters
tag = alnum [ *30( alnum / punct ) alnum ] ; 1 to 32 characters
ecosystem = domain / alias
alias = alnum [ *30( alnum / "-" ) alnum ] ; 1 to 32 characters
domain = <lowercase FQDN per RFC 1123 section 2.1>
alnum = lower / DIGIT
punct = "." / "_" / "-"
lower = %x61-7A ; a-z
- The grammar is normative. A handle, tag, or alias begins and ends with a letter or digit; punctuation may appear only internally.
- Handles, tags, aliases, and domains are case-insensitive. Clients MUST normalize to lowercase before resolution, comparison, or display.
- Handles are restricted to ASCII. This deliberately excludes non-Latin scripts, in exchange for eliminating the internationalized-domain homograph attacks that would otherwise make a handle's appearance an unreliable guide to its identity. Section 2.3 addresses the confusable characters that remain within ASCII.
- Handles are unique within their ecosystem. The ecosystem host is the sole authority for handle assignment within its domain and MUST NOT assign the same handle to two identities simultaneously.
- Alias and domain are disambiguated by the presence of a dot. An
ecosystemsegment containing at least one.MUST be parsed as a domain; one containing none MUST be parsed as an alias and resolved per section 5.5. A dotless segment MUST NOT be treated as a hostname, even where the client's network would resolve it as one. - The fully-qualified form
@handle@domain.tldis always valid and always unambiguous. The alias form is an input and display convenience. - Paymail-style input in the form
handle@domain.tldSHOULD be accepted wherever a recipient is expected, and MUST be normalized to@handle@domain.tldinternally. - Parsing a token that contains two
@. A recipient begins at a whitespace boundary, not at an@. A client MUST find the start of a recipient by scanning back to the preceding whitespace or the start of input; scanning back to the nearest@truncates the token as soon as the ecosystem separator is typed. - Handle and paymail are disambiguated by the leading
@. A token beginning with@MUST be parsed by this grammar. A token that does not MAY be treated as paymail per rule 7. This ordering matters:@alice@example.comis a fully-qualified handle whose ecosystem part is a domain, and matching it against the paymail form first mis-parses it. Both forms name the same identity, so the distinction is one of parse order rather than of meaning.
2.2 Same-ecosystem shorthand
A user addressing another user in the same ecosystem MAY omit the @ecosystem suffix. Clients MUST interpret a bare @handle as local to the sender's own ecosystem. The suffix is required only when crossing ecosystems.
2.3 Confusable handles
ASCII restriction removes cross-script homographs but not same-script confusables. @brand0n and @brandon, or @ac.me and @ac_me, are distinct identities that may be visually indistinguishable at a glance.
- Clients MUST compute a confusability skeleton for each handle by lowercasing, removing all
punctcharacters, and folding the sets{0, o},{1, l, i},{5, s},{2, z},{8, b},{rn, m}, and{vv, w}to a single representative. The set is normative: two clients folding differently disagree about what is a spoofing risk, which is worse than having no rule. - Before a value-moving action addressed to a handle not already in the user's address book, clients SHOULD warn when that handle's skeleton collides with an address book entry whose full handle differs, and MUST make the difference visible.
- Clients MUST NOT silently substitute a similar handle for the one the user typed.
2.4 Display rules
- Wherever a handle is surfaced, clients SHOULD render an avatar, or a generative identicon derived from the identity key, followed by the handle, followed by the ecosystem's icon and name when the handle is foreign or the context is a mixed result list.
- In mixed result lists the ecosystem segment MUST be shown for every result.
- When only an unverified alias is known for a domain, clients MUST display the fully-qualified domain instead of the alias.
- Clients MUST NOT display a handle as verified unless a certificate has been checked per section 4.
- Where the suffix may be omitted. In conversational context a client MAY show the bare handle where the ecosystem is unambiguous — a single-ecosystem thread, or a rendering that already carries the ecosystem's mark. In any surface a user consults in order to decide whether to trust an identity, including an identity card, a payment confirmation, and a delegation certificate, the fully-qualified form MUST be shown. Repeating an identical suffix on every line of a single-ecosystem thread is noise that readers stop seeing, which defeats the purpose of showing it at all.
- Rendering MUST NOT rewrite. A client that decorates a handle MUST render the form the user wrote. Where an ecosystem assigns account numbers,
@23@treechatand@thoth@treechatname one identity; silently redrawing one as the other edits the text. Resolution is canonical, display is not. - Both forms of a numeric handle. Where an ecosystem assigns an account number and also carries a chosen name, a profile surface MUST show both forms. Showing only the number makes the name look like an unverified alias; showing only the name hides the identifier other members of that ecosystem actually use.
- Host-supplied attributes. Display name, avatar, and any contact metadata the host serves — email address, telephone number, code-forge username — are unattested and MUST be labelled as such wherever they are shown. Contact details warrant this more than an avatar does, because a reader is more likely to act on them.
- Registration age. Where a host publishes the date a handle was registered, a client SHOULD display it with its age. A handle registered four years ago and one registered last week resolve identically and merit different amounts of trust.
2.5 Organisations, machines, and agents
Organisations, devices, and autonomous agents are addressed with the identical grammar. Nothing in resolution, attestation, payment, or messaging distinguishes them from human users. What distinguishes them is who controls the keys, which for organisations means members holding delegation certificates per section 9.
3. Subhandles
Any handle owner MAY hand out subhandles of the form @handle+tag@ecosystem, analogous to user+tag@example.com, without registering anything.
3.1 Resolution and identity
- Resolvers and messageboxes MUST strip the
+tagbefore handle lookup.@deggen+conf2036@lkup.netresolves to exactly the same identity key, certificate, and messagebox as@deggen@lkup.net. - Clients MUST NOT treat two subhandles of the same handle as distinct identities.
- The tag MUST be preserved verbatim, end to end, in the envelope metadata of every message and payment sent to a subhandle.
3.2 Recipient-side behavior
A conforming recipient client SHOULD offer, per tag, the organisational features that made plus-addressing useful in email:
- Labeling: envelopes arriving on a subhandle SHOULD be displayed with the tag as a visible label.
- Filtering and routing: users SHOULD be able to define client-side rules keyed on the tag.
- Separate activity views: clients SHOULD be able to present per-tag feeds, unread counts, and notification settings.
- Separate accounting: payments arriving on a subhandle internalize into the same wallet, but wallets SHOULD record the tag as a label or basket association per BRC-46 and BRC-65 conventions.
- Leak detection: a user who mints a unique tag per counterparty can identify who shared or sold the address from the tag on which unsolicited traffic arrives.
- Per-tag reachability: the policies of section 8 MAY be configured per tag.
3.3 Limits of tags
Tags are an organisational tool and, except where per-tag policy is enforced at the messagebox, not a security boundary. Two distinct weaknesses apply.
- Stripping. A sender can omit the
+tagand address the base handle. Users relying on tags for reachability control MUST configure the bare handle's policy to be at least as restrictive as their most restrictive tag, so that stripping never grants more access than the tag would. - Forgery. The tag is chosen by the sender and is not covered by any certificate, so a sender can assert a tag they were never given in order to appear to arrive through a trusted channel or to satisfy a filter rule. Recipients MUST NOT treat the presence of a tag as evidence of how the sender obtained the address, and SHOULD NOT grant a tag more privilege than the sender's identity key alone would earn. Leak detection under section 3.2.5 is reliable only for tags the user has actually issued.
4. Handle Attestation
4.1 The handle certificate
The binding between a handle and an identity key is attested by the ecosystem host using a BRC-52 identity certificate.
| Certificate element | Value |
|---|---|
type | The BRC-169 handle-certificate type identifier (32 bytes, base64; see section 4.5) |
subject | The user's identity public key |
certifier | The ecosystem host's certifier public key, which MUST equal metanet.trust.publicKey in the domain's manifest (section 5.1) |
fields.handle | The normalized handle, without tag and without domain |
fields.domain | The ecosystem's domain |
revocationOutpoint | An outpoint controlled by the certifier, spent when the handle is released, reassigned, or otherwise revoked |
A verifier MUST check all of the following, and MUST treat failure of any one as a resolution failure:
- The certificate signature is valid.
certifierequals the certifier key published atfields.domainper section 5.1.fields.handleandfields.domainmatch the handle being resolved, after normalization.subjectequals theidentityKeyreturned by the resolution endpoint.- The
revocationOutpointis unspent, per section 4.2.
Certificates are exchanged and selectively revealed over BRC-103 and BRC-104.
4.2 Verifying revocation status
Simplified Payment Verification proves that a transaction was included in a block. It cannot prove that an output has not been spent. Every "unspent" check in this document therefore depends on a source that indexes spends, and inherits that source's honesty and freshness.
- A verifier MUST obtain revocation status from at least one of: an overlay lookup service tracking the relevant topic; a transaction or UTXO index service; or an index the verifier maintains itself.
- Implementations MUST make the chosen source configurable, and SHOULD consult more than one for value-moving actions.
- A verifier MUST record, alongside any cached verification result, the time at which the check was performed.
- Before a value-moving action, a verifier MUST perform a check whose result is no older than 60 seconds by default. This bound MUST be configurable and MUST NOT exceed the
ttlof the cached resolution. - For actions that neither move value nor establish trust, such as rendering historical conversation, a cached result within
ttlis sufficient. - If no revocation source is reachable, a verifier MUST NOT treat the certificate as valid for a value-moving action. It MAY continue to render existing content, clearly marked as unverified.
Revocation is therefore detectable, with a delay bounded by the indexing lag of the source consulted. It is not instantaneous, and it is not verifiable without a trusted index.
4.3 Release, reassignment, and forwarding
When a handle is released or reassigned, the host MUST spend the old certificate's revocation outpoint before, or atomically with, issuing a certificate for a new binding. Messages signed under a revoked binding remain verifiable as having been valid at the time, by reference to the spend height of the revocation outpoint.
A departing user MAY publish a forwarding record so that counterparties can follow them to a new handle, at a new domain or the same one. The record is signed by the departing subject's identity key, never by the host, so that a host cannot fabricate a redirect for a handle it controls.
| Field | Value |
|---|---|
from | The released handle and domain |
toIdentityKey | The subject's identity key at the new location |
toHandle | The new handle and domain |
created | ISO-8601 UTC timestamp |
signature | DER-encoded ECDSA signature by the identity key that was the subject of the released handle's certificate, over SHA-256 of the UTF-8 encoding of from, toIdentityKey, toHandle, and created, in that order, each terminated by a single LF (%x0A) |
- A host that has been given a forwarding record SHOULD include it in the resolution response for the released handle, alongside
revoked: true. - A client MUST verify the forwarding signature against the identity key of the previous binding, which it can only do if it holds or can obtain that prior certificate. A forwarding record whose signature cannot be verified MUST be ignored.
- Clients MUST NOT follow a forwarding record automatically for a value-moving action. They MUST present the change and require explicit user confirmation.
- Clients MUST NOT follow a forwarding record more than once in a single resolution. Chains MUST be resolved by repeated, individually confirmed steps.
4.4 Key-change detection
A handle can be released and reassigned to a different person. Address book entries that survive that change would otherwise resolve, silently, to a stranger.
- On each fresh resolution, a client MUST compare the returned
identityKeywith the one it holds for that handle. - If they differ, the client MUST NOT silently update the stored entry, MUST mark the contact as changed, and MUST warn the user before any value-moving action addressed to that handle.
- The warning MUST distinguish a key change on an existing contact from a first-time resolution.
4.5 Certificate type identifiers
A BRC-52 CertificateTypeID is an opaque 32-byte value, and no BRC defines an authority that allocates one. Rather than wait for an allocation that has no allocator, this document derives its two type values from a name, so that any implementation reproduces them from this section alone:
type = base64( SHA-256( ASCII derivation string ) )
| Certificate | Derivation string | type |
|---|---|---|
| Handle certificate (section 4.1) | metanet-handles handle certificate v1 | XgCFdUfxEcI+3xtDjsIuSAjMl5EwzCUjsQc45ds1lC8= |
| Delegation certificate (section 9.1) | metanet-handles delegation certificate v1 | 30kchAJIGfLxzCNloCJNLI3AtkgA8UbkxlXU2Cj4PpA= |
- Both values are normative. An implementation MUST use them, and MUST NOT substitute a locally chosen constant.
- The derivation strings carry no BRC number. Nothing else in this document carries one in a wire name either — the manifest key is
metanet.handles, the alias protocol field isecosystem-alias— so that a later revision does not leave the deployed vocabulary naming a superseded document. - The
v1suffix versions the type rather than the document. A future revision that changes the field names or meanings of either certificate MUST mint a new type by changing the derivation string, so that certificates issued under the two revisions remain distinguishable and a verifier is never required to guess which set of field semantics applies. This is what BRC-52 means when it says certificates of the same type are expected to use the same field names and meanings. - The handle-certificate value above is the one used throughout Appendix A, and is therefore covered by the worked signature in A.3.
Publishing type metadata is an open consideration. Deployed overlay infrastructure includes a certificate-type directory, reachable as the topic tm_certmap and the lookup service ls_certmap and wrapped by a registry client in several BSV SDKs, through which an operator publishes a type identifier together with a human-readable name, an icon, a description, and per-field descriptors. It is not specified by any BRC, it does not allocate or reserve a type, and its records are attributable to the operator that published them rather than to any authority over the type. Whether this document should recommend publishing there, and if so what an operator should publish and how a client should weigh a record it finds, is left open for review. Nothing in this section depends on it: the type values above are fixed by their derivation.
4.6 Historical note
The Authrite-era certificate documents (BRC-31, BRC-53, BRC-56) are cited for historical context only. Per BRC-52's own status notes they are not normative for BRC-100 certificate behavior, and this document does not depend on them.
5. Resolution and Discovery
5.1 Trust anchor and manifest entry
An ecosystem host MUST publish trust anchor details at its domain per BRC-68, in /manifest.json under the metanet.trust key. The metanet.trust.publicKey value is the ecosystem's certifier key for the purposes of section 4.1. No separate certifier key is defined by this document.
A host MUST additionally publish a metanet.handles object alongside metanet.trust:
{
"name": "LkUp",
"metanet": {
"trust": {
"name": "LkUp",
"note": "Handle registry and messagebox for the LkUp ecosystem",
"icon": "https://lkup.net/icon.png",
"publicKey": "0371f0ec5992a9d38e09fe528e367890969c66eaebdb01b4d35a2fc0d61251b3f9"
},
"handles": {
"version": "1.0",
"resolve": "https://lkup.net/.well-known/metanet-handles/resolve",
"search": "https://lkup.net/.well-known/metanet-handles/search",
"messagebox": "https://messagebox.lkup.net",
"aliases": ["lkup"],
"commands": []
}
}
}
| Field | Requirement | Meaning |
|---|---|---|
version | MUST | The BRC-169 version implemented. "1.0" for this document. |
resolve | SHOULD | Absolute HTTPS URL of the resolution endpoint. When absent, clients MUST use https://<domain>/.well-known/metanet-handles/resolve. |
search | MAY | Absolute HTTPS URL of the search endpoint (section 5.6). When absent, search is not offered and clients MUST NOT probe the well-known path. |
messagebox | SHOULD | Default messagebox URL for handles in this ecosystem. Per-handle values in the resolution response override it. |
aliases | MAY | Aliases this domain claims, used for the bidirectional check of section 5.5. |
commands | MAY | Ecosystem-custom command descriptors. The contents are specified by BRC-218; hosts not implementing BRC-218 MUST publish an empty array or omit the field. |
Absence and versioning.
- A domain that publishes
metanet.trustbut nometanet.handlesobject is a valid BRC-68 trust anchor that does not offer handle resolution. Clients MUST treat handles at that domain as unresolvable, and MUST NOT fall back to probing the well-known path. - Clients MUST reject a
metanet.handlesobject whoseversionhas a major component they do not implement, and MUST treat the domain as non-conforming rather than guessing. - Clients MUST ignore fields they do not recognize, in this object and in every response defined by this document, and MUST NOT fail on a minor version they do not implement. New fields may be added in a minor revision; existing field meanings will not change within a major version.
5.2 The handle-resolution endpoint
GET <resolve>?handle=<handle>
The handle parameter MUST be the normalized handle with any +tag already stripped. For a registered handle a conforming endpoint MUST respond 200 OK with:
{
"metanetHandles": "1.0",
"handle": "deggen",
"domain": "lkup.net",
"identityKey": "02a1b2c3...",
"certificate": { "...": "the BRC-52 handle certificate of section 4.1" },
"messagebox": "https://messagebox.lkup.net",
"ttl": 3600,
"revoked": false
}
| Field | Requirement | Meaning |
|---|---|---|
metanetHandles | MUST | Response format version. |
handle, domain | MUST | Echo of the resolved handle, normalized. |
identityKey | MUST | 66-character compressed secp256k1 public key, hexadecimal. |
certificate | MUST | The BRC-52 handle certificate. Its subject MUST equal identityKey. |
messagebox | MUST | HTTPS URL implementing the semantics of section 7. |
ttl | MUST | Seconds for which this response may be cached. See section 5.4. |
revoked | MUST | false in a 200 response. |
forwarding | MAY | A forwarding record per section 4.3, permitted only with 410 Gone. |
Responses SHOULD be served over BRC-104, so that the resolving client obtains mutual authentication and can request selective certificate revelation in the same exchange. Endpoints MUST also accept unauthenticated requests, since first contact cannot presume an existing relationship.
5.3 Errors
An endpoint MUST signal failure with an HTTP status code and MUST NOT return a 200 response carrying an error, nor a 200 response carrying a stale or partial binding.
| Status | error.code | Meaning |
|---|---|---|
400 | malformed-handle | The handle parameter is absent or violates section 2.1. |
404 | handle-not-found | No such handle is registered at this domain. |
410 | handle-revoked | The handle was registered and has been released or revoked. MAY carry forwarding. |
429 | rate-limited | The client should retry after the interval in the Retry-After header. |
503 | unavailable | The endpoint cannot answer authoritatively at this time. |
The error body MUST be:
{
"metanetHandles": "1.0",
"error": {
"code": "handle-not-found",
"message": "No handle 'deggen' is registered at lkup.net."
}
}
messageis human-readable, is not machine-parsed, and MUST NOT be relied upon by clients.- Clients encountering an unrecognized
error.codeMUST treat it asunavailable. - An error is not permission to fall back to a cached binding for a value-moving action. On
404or410a client MUST treat the handle as unresolvable and MUST invalidate any cached entry. On429or503a client MAY continue to render existing content from cache but MUST NOT initiate a value-moving action against a cached binding whose revocation check has expired under section 4.2.
5.4 Caching
ttlis advisory downward and binding upward: a client MUST NOT cache a resolution for longer thanttl, and MAY cache it for less.- A
ttlof0means the response MUST NOT be cached. - Hosts SHOULD set
ttlno higher than86400, and SHOULD lower it in advance of a planned reassignment. - Caching a resolution does not cache its revocation status. Section 4.2 governs revocation freshness independently and is the stricter of the two for value-moving actions.
5.5 Alias advertisement
Aliases map to domains without a central registry. An ecosystem MAY publish an alias advertisement as a BRC-48 PushDrop token submitted to the overlay topic tm_ecosystemalias and discoverable through the lookup service ls_ecosystemalias, in the manner of the [object Object], and ,[object Object] advertisements of BRC-101.
On the names. They follow the conventions of BRC-87, and match deployed practice in running the descriptor together rather than splitting it with an underscore, as tm_certmap and tm_basketmap do. BRC-87 establishes no registry and leaves naming to agreement between operators, so this document claims the pair rather than reserving it, and a claim can only be checked against what is deployed at the time of checking. The check is reproducible: GET /listTopicManagers and GET /listLookupServiceProviders on each of the SLAP trackers an implementation is configured with will enumerate the names those hosts serve, and a BRC-24 query to ls_slap with findAll enumerates advertised services more broadly. An operator finding either name already serving something else SHOULD raise it against this document rather than silently choosing a variant, since two ecosystems advertising aliases under different topics cannot see each other's advertisements, which defeats the purpose of publishing them.
The token's pushed fields MUST appear in this order:
| Position | Field | Value |
|---|---|---|
| 1 | protocol | The ASCII string ecosystem-alias |
| 2 | version | The ASCII string 1 |
| 3 | alias | The normalized alias, per the alias rule of section 2.1 |
| 4 | domain | The normalized FQDN being claimed |
| 5 | certifier | The domain's certifier key, 33 bytes |
| 6 | signature | DER-encoded ECDSA signature, by the key in field 5, over SHA-256 of the concatenation of the raw bytes of fields 1 to 5 in order, with no separators and no length prefixes |
An advertisement is valid when its signature verifies, field 5 equals the current metanet.trust.publicKey at the domain in field 4, and its token output is unspent. An ecosystem withdraws an alias claim by spending the token.
Bidirectional confirmation. A client MUST NOT accept an alias unless the domain also lists it in metanet.handles.aliases (section 5.1). The advertisement asserts alias to domain; the manifest confirms domain to alias. Both are required, so that no party can bind an alias to a domain that has not consented.
Conflict resolution. When more than one valid advertisement claims the same alias:
- The client MUST surface the conflict rather than resolving it silently.
- The client SHOULD prefer the advertisement whose token was confirmed in the earliest block, and MUST break a remaining tie by the lexicographically lowest transaction identifier. This makes aliases first-come, first-served. The alternative, treating any conflict as fatal, would let anyone permanently disable any alias for the cost of one transaction.
- The client MUST allow the user to pin an alias to a specific domain, and a pin MUST override the ordering rule.
- Where an alias remains ambiguous or unpinned and the client is unwilling to apply the ordering rule, the fully-qualified domain MUST be displayed and used instead.
Clients SHOULD pin alias mappings for their contacts on first use, so that a later advertisement cannot redirect an established relationship. The fully-qualified form always works, and no client is ever obliged to support aliases at all.
5.6 The search endpoint
Search is OPTIONAL. A host offering it MUST advertise it in metanet.handles.search, and clients MUST NOT probe for it otherwise.
GET <search>?q=<query>&limit=<n>
q is a free-text fragment. limit is a client hint; the host chooses the actual bound and MUST NOT exceed 100.
{
"metanetHandles": "1.0",
"results": [
{
"handle": "brandon",
"identityKey": "02a1b2c3...",
"displayName": "Brandon C",
"avatarURL": "https://lkup.net/avatars/brandon.png"
}
],
"truncated": false
}
- Search results are a hint, not an attestation. No certificate is returned and none is implied. A client MUST resolve any selected result per section 5.7 and verify its certificate before displaying it as verified, before adding it to an address book, and before any value-moving action.
displayNameandavatarURLare host-supplied, unattested, and attacker-influenced wherever the host permits self-service registration. Clients MUST NOT present them as verified attributes and SHOULD apply the confusability check of section 2.3 to results.- Hosts MAY require BRC-104 authentication, apply rate limits, and monetize search per BRC-105. Clients MUST handle
429and503per section 5.3. - Cross-ecosystem search is performed by the client querying several domains and merging the results. There is no federated search protocol and no global index, by design.
- Search queries disclose intent. A client MUST NOT broadcast keystrokes to foreign domains, SHOULD debounce and require an explicit action before querying a domain the user has not previously transacted with, and SHOULD disclose which domains are being queried.
5.7 Client resolution algorithm
Given a recipient string, a conforming client MUST:
- Normalize the string: lowercase it, convert paymail-style input, strip and retain any
+tag, and apply the same-ecosystem default of section 2.2. Reject input that does not satisfy section 2.1. - Resolve the ecosystem segment to a domain per section 2.1.5, using section 5.5 for aliases.
- Fetch the domain's
/manifest.json, obtainingmetanet.trust.publicKeyand themetanet.handlesobject. Reject unsupported major versions. - Query the resolution endpoint. Handle errors per section 5.3.
- Verify the certificate per section 4.1, including the revocation check of section 4.2 at the freshness required by the pending action.
- Apply the key-change check of section 4.4.
- Only then treat the identity key as the key for that handle, and the messagebox as the delivery target.
6. Payments
6.1 Payment flow
A payment to a handle MUST be executed as follows:
- Resolve the recipient per section 5.7, obtaining a verified identity key and a messagebox URL.
- Construct a BRC-29 payment, deriving the recipient-specific locking key from the recipient's identity key per BRC-42 and BRC-43 with a fresh
derivationPrefixandderivationSuffix. - Serialize the transaction with its SPV data as Atomic BEEF (BRC-95, building on BRC-62).
- Deliver the payment inside an envelope to the recipient's messagebox, per section 7.
- The recipient's wallet collects the envelope and internalizes the payment using the BRC-100
internalizeActionwallet-payment protocol.
The sender MUST NOT broadcast the payment transaction. It travels inside the envelope, and the recipient broadcasts it on internalization. This is what makes the delivery invariant of section 8.3 enforceable, and it is what allows a sender whose envelope was rejected to reclaim the inputs by spending them elsewhere.
BRC-125 addresses the same underlying payment, to an identity key rather than an address, from a URI rather than a resolved handle. A client implementing both will share its BRC-29 construction and messagebox delivery between them.
6.2 Amounts and fiat conversion
Two notations MUST be supported wherever an amount is accepted: an integer number of satoshis, and a fiat amount converted at send time.
The exchange-rate oracle interface. An oracle is an HTTPS endpoint returning at least:
{ "rate": 43.21, "time": "2026-07-29T12:00:00Z", "currency": "USD" }
where rate is units of currency per one BSV and time is the moment the rate was observed.
- The WhatsOnChain exchange rate endpoint is a conforming public instance and a reasonable default. It is not normative, and implementations MUST allow the oracle to be configured. No conforming implementation may depend on a single provider for correctness.
- Staleness. A rate whose
timeis more than 600 seconds old MUST NOT be used for send-time conversion. Clients SHOULD prefer a rate under 60 seconds old and MUST refresh, or raise a visible error, rather than convert from a stale rate. - Currencies. USD is denoted by
$. Other currencies MAY be supported with an ISO-4217 prefix. Cross-rate sources are at the client's discretion and MUST be disclosed to the user. - Signed rates. Oracles SHOULD sign their responses. When a signed rate is available the signature and the oracle's identity key MUST be recorded with the payment.
- Audit trail. The client MUST record, in the envelope metadata: the fiat amount as typed, the currency, the rate, the rate's timestamp, an identifier for the oracle, and the resulting satoshi amount. Where the rate is unsigned, this record is the sender's own assertion and MUST be labeled as such. An unsigned rate is not third-party-verifiable evidence of the exchange rate at the time of sending, and clients MUST NOT present it as though it were.
- Clients MUST display the satoshi amount alongside the fiat amount before the user confirms a fiat-denominated send.
7. Messaging and the Messagebox
7.1 Semantics
Envelopes are delivered to the messagebox resolved in section 5.2, which implements the store-and-forward semantics of BRC-33: a sender submits into a named box, and the recipient lists, collects, and acknowledges. Implementations MAY vary in transport and internals provided those semantics and the enforcement duties of section 8 hold.
Ecosystems interoperate by each operating a messagebox and resolving each other's per section 5. No pairwise integration between ecosystems is required.
7.2 The envelope
The messagebox operator must be able to enforce policy without reading content. The envelope therefore separates metadata from an encrypted payload.
{
"metanetHandles": "1.0",
"recipient": { "handle": "deggen", "tag": "conf2036", "domain": "lkup.net" },
"sender": { "identityKey": "02a1b2c3...", "handle": "crumbs", "domain": "nexus.example" },
"created": "2026-07-29T12:00:00Z",
"quoteId": "9f2c...",
"payment": { "...": "BRC-29 payment as Atomic BEEF, or null" },
"content": { "...": "BRC-78 encrypted payload" },
"signature": "3045..."
}
contentMUST be encrypted to the recipient, using keys derived per BRC-42 and BRC-43 or a BRC-78 portable encrypted envelope. The messagebox operator MUST NOT be able to read it.signatureMUST be a DER-encoded ECDSA signature by the key insender.identityKey, overSHA-256of the envelope canonicalized per RFC 8785 (JSON Canonicalization Scheme) with thecontentandsignaturemembers removed. Canonicalizing rather than signing the transmitted bytes means a messagebox may re-serialize the envelope without invalidating it, while any change to a metadata value is detected. A messagebox MUST reject an envelope whose signature does not verify.sender.handleandsender.domainare a claim. A messagebox MAY resolve them, and a recipient client MUST resolve and verify them per section 5.7 before displaying the sender as a named, verified identity. Until then the sender is identified only byidentityKey.recipient.tagcarries the subhandle tag verbatim, subject to section 3.3.paymentisnullwhere no payment is attached.
8. Reachability Policy
Reachability is the recipient's property. It is configured in the recipient's own client, enforced at the recipient's own messagebox, and is never a decision of the ecosystem host. The chat syntax for configuring these policies is specified in BRC-218; the policies and their enforcement are specified here.
8.1 Scope
A scope determines who may deliver to a handle. The defined values are:
| Scope | Admits |
|---|---|
everyone | Any sender. |
contacts | Only senders whose identity key appears in the recipient's attested address book (section 10). |
ecosystem | Only senders whose verified domain equals the recipient's domain. |
toll | Any sender whose envelope carries a sufficient toll payment (section 8.2). |
- Scopes MAY be set per tag. Where a tag has no explicit scope, the bare handle's scope applies.
- The messagebox MUST reject an envelope from outside the active scope, naming the policy that rejected it but not the internals of its evaluation.
contactsandecosystemrequire the messagebox to verifysender.identityKeyagainst the recipient's contact list or against a resolution of the sender's claimed domain. A messagebox that cannot perform that verification MUST reject rather than admit.
8.2 Tolls
A toll requires a sender to attach a payment to every envelope. It is paid to the recipient, it is due for every message each time rather than as a one-time unlock, it is not refunded on reply, and it remains in force until the recipient lifts it. A toll MAY be set for all senders in scope or for a named sender, and MAY be set per tag.
A general toll and a per-sender toll are independent settings, and a client MUST treat them as such. Lifting the general toll does not lift any per-sender toll, and lifting a per-sender toll does not affect the general one. Because the natural reading of "the toll is off" is that no toll applies, a client that lifts one MUST state what has happened to the other; a client that conflates them will surprise its user in one direction or the other, and the direction that costs money is the one where a toll the user believed lifted is still charged.
The recipient's messagebox is the enforcement point. The sender's client only surfaces the requirement.
8.3 Toll quotes and the delivery invariant
A price that can change between quotation and delivery creates a race in which a sender pays and is rejected. The following rules close it.
Quotation.
GET <messagebox>/toll?handle=<handle>[&tag=<tag>]
{
"metanetHandles": "1.0",
"quoteId": "9f2c...",
"satoshis": 21545,
"fiat": { "amount": 0.218, "currency": "USD" },
"validUntil": "2026-07-29T12:10:00Z"
}
validUntilMUST be at least 60 seconds in the future.- The messagebox MUST honour a quote presented in an envelope whose
quoteIdmatches and whosecreatedtime precedesvalidUntil, even if the toll has since been raised. A quote is a binding offer for its stated window. - A quote MUST be single-use. The messagebox MUST reject a second envelope bearing the same
quoteId. - Toll changes apply to envelopes whose quotes were issued after the change. A client SHOULD warn the sender when a quoted toll differs from the last one it observed for that recipient.
- An envelope MAY omit
quoteIdand attach payment at the currently advertised rate, accepting the risk of a race. A messagebox MUST NOT require quotation.
The delivery invariant. The payment travels unbroadcast inside the envelope (section 6.1), so acceptance and settlement can be bound together:
- A messagebox MUST evaluate scope and toll and reject or accept the envelope before the recipient's wallet is offered the payment for internalization.
- If the payment attached to an envelope is internalized, that envelope MUST be delivered. A recipient MUST NOT retain the payment of an envelope it has rejected.
- A recipient client that internalizes a payment from an envelope it subsequently discards, filters, or deletes has still satisfied the invariant, provided the envelope was delivered to the user. Silent capture without delivery is a violation of this specification.
- A rejection MUST carry the required amount and a fresh quote, so that the sender's client can retry with payment attached in one action.
- Because the payment was never broadcast, a sender whose envelope was rejected reclaims the funds by spending the same inputs elsewhere. Sending clients SHOULD do so automatically once a rejection is received or a delivery timeout elapses, and MUST NOT treat the outputs of an undelivered payment as spent.
9. Delegation
Delegation lets one identity act for another under authority that any counterparty can verify and the principal can revoke in one action. A delegate always acts with its own identity key. Authority travels in a certificate, never in shared key material.
9.1 The delegation certificate
The principal's wallet issues a BRC-52 certificate:
| Certificate element | Value |
|---|---|
type | The BRC-169 delegation-certificate type identifier (see section 4.5) |
subject | The delegate's identity public key |
certifier | The principal's identity public key |
fields.delegator | The principal's handle, in the form @handle@domain |
fields.scope | Permitted actions, per section 9.2 |
fields.perActionCap | Maximum satoshis per delegated action, omitted for non-financial scopes |
fields.totalCap | OPTIONAL cumulative satoshi limit, subject to section 9.3 |
fields.expiry | ISO-8601 UTC expiry timestamp |
fields.expiryHeight | OPTIONAL block height at which authority lapses |
fields.delegable | OPTIONAL boolean, default false, per section 9.4 |
fields.maxDepth | OPTIONAL integer, default 1, per section 9.4 |
revocationOutpoint | An outpoint controlled by the principal's wallet |
9.2 Scope
scope = "*" / scope-token *( "," scope-token )
scope-token = verb [ ":" qualifier ]
verb = 1*32( lower / DIGIT / "-" )
qualifier = 1*64( lower / DIGIT / "-" / "_" / "." )
- A
verbis a command verb as defined in BRC-218, or an application-defined action name. - A
qualifiernarrows a verb. The qualifierthread:<id>binds an action to a single conversation and is used by section 9.6. *grants every action within the other constraints of the certificate. Clients MUST present*as unrestricted authority and SHOULD require additional confirmation to issue it.- Unknown scope tokens fail closed. A verifier that does not recognize a
verborqualifierMUST deny the action rather than ignore the token.
9.3 Spend caps
fields.perActionCap is a per-action limit. Any counterparty can verify it against the action in front of them, and MUST do so.
fields.totalCap is a cumulative limit across all actions taken under the certificate. It is not counterparty-verifiable. No counterparty can see the delegate's other transactions, and this specification deliberately does not ask them to track global state.
totalCapis enforceable only where the principal funds each delegated action, through a metered allowance, a payment channel, or per-action funding requests to the principal's wallet. In that arrangement the principal's wallet is the enforcement point and the limit is real.- Where the delegate spends funds it independently controls,
totalCapis advisory only and the effective exposure is bounded byperActionCapmultiplied by the number of actions before expiry. - A client MUST NOT describe
totalCapas a hard, guaranteed, or enforced limit unless a principal-funded arrangement under 9.3.1 is in place, and MUST state which of the two situations applies when displaying a delegation for approval. - Where
perActionCapis absent buttotalCapis present,perActionCapdefaults tototalCap.
9.4 Chaining
- A delegate MUST NOT issue further delegations under the principal's authority unless
fields.delegableistrue. - Where chaining is permitted, the chain length MUST NOT exceed
fields.maxDepth, counting the principal's own certificate as depth 1. - A verifier presented with a chain MUST validate every certificate in it, and MUST apply the most restrictive value found anywhere in the chain for scope,
perActionCap, and expiry. - Revoking any certificate in a chain invalidates every certificate beneath it.
9.5 Acting and verifying
A delegate signs with its own key and presents the certificate, or chain, over BRC-103. The counterparty MUST verify:
- Each certificate signature in the chain.
- That the root
certifierequals the identity key to whichfields.delegatorresolves per section 5.7. This chains delegation to host attestation. - That no certificate has expired, per section 9.6.
- That every revocation outpoint in the chain is unspent, per section 4.2.
- That the action is within the effective
scope, and that its value is within the effectiveperActionCap.
Clients MUST display delegated actions as delegated, for example as acme (via brandon), and never as the principal acting directly.
9.6 Expiry and clocks
fields.expiry is wall-clock time, verified against the verifier's own clock. This is the one place in this document where a verifier's conclusion depends on something other than a signature or the chain.
- A verifier MUST reject a certificate whose
expiryprecedes its current time, allowing a skew tolerance that MUST NOT exceed 300 seconds. - A principal that requires clock-independent expiry SHOULD also set
fields.expiryHeight. Where both are present, authority lapses at whichever comes first. - Verifiers MUST NOT extend an expiry on the basis of a counterparty-supplied timestamp.
9.7 Thread-scoped agency
A delegation whose scope carries a thread:<id> qualifier binds the delegate to a single conversation: it may read, reply, negotiate, and pay within that thread only, under the caps and expiry of its certificate. The counterparty can verify the constraint exists without trusting any server.
The limits of section 9.3 apply in full. A thread-scoped delegation with a perActionCap and no principal-funded arrangement does not bound total spend, and MUST NOT be presented to the user as though it does.
9.8 Revocation and reassignment
Spending the certificate's revocation outpoint revokes it. Any counterparty checking the outpoint will see it spent, subject to the indexing lag and trust assumptions of section 4.2. There are no revocation lists to distribute.
Reassigning a role is a revocation followed by a fresh delegation. Organisation handles are expected to be operated this way: the organisation issues each member a delegation certificate and reassigns access by revoke-and-reissue as people change roles, so that access is scoped, individually attributable, and revocable without moving keys. Issuance and revocation can plausibly be driven by existing SSO and directory systems, so that offboarding an employee spends the revocation outpoint as a side effect. The governance of how members are chosen is out of scope; only the addressing and certificate mechanics are specified here.
10. The Address Book and Web-of-Trust
- Clients SHOULD maintain an address book of resolved, certificate-verified contacts, both people and organisations. Entries are portable across ecosystems because each is anchored to an identity key and a domain rather than to a vendor's user table.
- An address book entry MUST record the identity key, the handle and domain, the certificate, and the time of last verification. Entries are subject to the key-change rule of section 4.4.
- A user MAY publish a peer attestation of a contact's handle to key binding, as a lightweight BRC-52 certificate with the user as certifier. Host attestation answers whether the domain vouches for a binding; peer attestation answers whether people the user already trusts vouch for it. Clients SHOULD surface both, and MAY weight peer attestations when ranking mixed search results.
- A peer attestation is evidence, not proof. It is exactly as trustworthy as the attesting key, and clients MUST NOT present peer attestation as equivalent to host attestation.
- A reputation signal MUST identify who signed it. Where a client surfaces peer statements of any kind, each MUST be attributable to the key that made it, and that key MUST be resolvable to a handle the reader can look up in turn. An unattributed count is not evidence: "eight people vouch for this handle" is worth exactly as much as the eight, and a reader who cannot see who they are cannot weigh it. Counts of different claims MUST NOT be summed — attestations of a binding and statements about a person are different things, and one total covering both is a number that means nothing.
- Attestation is not reputation. A peer attestation is a claim about a binding: this key is this handle. It is not a claim about the person, and it says nothing about whether they are worth transacting with. Ecosystems will want the second thing, and a client that renders the two identically lets a statement of regard be read as verification. Where a client surfaces both, it MUST label and group them separately, and a reputation signal MUST NOT contribute to any indication that an identity is verified. A reputation mechanism is out of scope here; an ecosystem defining one SHOULD do so as a custom command under BRC-218 section 8 rather than by overloading this section.
- Statements against a handle. A reputation mechanism that only records regard records half of what people know. An ecosystem MAY define a negative counterpart — Nexus's is
/renounce— subject to the rules below, because the failure modes are not the same as a vouch's and a symmetrical design gets them wrong.- A negative statement SHOULD be unattributed by default, with attribution offered as a deliberate choice by its author. Speaking against someone carries a risk that speaking for them does not, and a mechanism that names the author by default collects only the statements of people with nothing to lose — which is the opposite of the sample worth having.
- Where it is unattributed, the claim itself MUST still be shown. An anonymous count is a rumour with a number on it; an anonymous reason is something the subject can answer and a reader can weigh.
- A client MUST NOT let a negative statement contribute to any indication that an identity is unverified, exactly as rule 6 forbids the positive case. Both are opinions about a person; verification is arithmetic about a key.
- Attributed and unattributed statements MUST be visually distinguishable, and a client MUST NOT imply that an unattributed one is attributable on request when it is not.
- Attestations MUST be discoverable, not merely publishable. Rules 3 to 7 say how a peer statement is made and what it means, and say nothing about how a third party finds the statements made about a handle. That is the half a relying party actually needs: a client asking "who has vouched for this identity" has no endpoint to ask. An ecosystem SHOULD answer that question for its own handles, at the resolution endpoint of section 5.7, as a list of attestation outpoints with their certifiers. Until an ecosystem does, any mechanism built on this section — including the access gates of BRC-190 — can be evaluated only by a party that already holds the attestations, which is to say by nobody who needed to ask.
- Because organisations resolve identically to users, the address book doubles as a business directory.
Security Considerations
The ecosystem host is fully trusted within its namespace. A host can assign, reassign, or withhold any handle in its domain, and can issue a certificate binding one of its handles to any key it chooses. The revocation outpoint makes reassignment detectable to a counterparty who was watching, and section 4.4 turns that into a user-visible warning, but nothing here prevents a malicious or compromised host from impersonating its own users. This is the same trust model as paymail, and adopting the domain-hosted resolution of section 5 means accepting it. Peer attestation (section 10) is the only mechanism in this document that lets a relying party form a view independent of the host, and it is optional. Users with adversarial threat models should prefer ecosystems whose hosts they control, or rely on the identity key rather than the handle.
Handle recycling is an impersonation vector. A released handle may be reassigned to an unrelated person. Any counterparty holding a stale address book entry would otherwise transact with a stranger under a familiar name. Section 4.4 makes key-change detection mandatory for this reason, and it is the single most important client-side control in this specification. Implementations that skip it are not conforming.
Revocation depends on a trusted index. SPV cannot prove non-spend. Section 4.2 bounds the exposure by requiring recent checks before value-moving actions, but a verifier that consults a single dishonest or stale index can be induced to accept a revoked certificate. Consulting multiple independent sources for high-value actions is advisable.
The contacts scope discloses the address book to the messagebox. To enforce contacts (section 8.1), the messagebox must be able to test a sender's identity key against the recipient's contact list, which means holding that list or a derived form of it. A recipient who selects this scope is telling their messagebox operator who they know. Operators SHOULD accept a privacy-preserving form, such as a set of salted hashes of contact identity keys that the recipient updates, rather than plaintext handles, and recipients concerned about this should prefer toll or run their own messagebox. This specification does not mandate a particular representation, but a messagebox that cannot perform the test MUST reject rather than admit, per section 8.1.3.
The messagebox operator sees the social graph. Content is encrypted, but the envelope is not: the operator learns who contacts whom, when, how often, at what tags, and with what payment amounts. Recipients who require metadata privacy must operate their own messagebox, and even then the sender's messagebox and network path observe the other half. This specification does not provide metadata privacy and should not be described as though it does.
Resolution and search disclose intent. Querying lkup.net for deggen tells that host, and anyone observing the connection, that the querying IP is about to contact or pay that user. Search is worse, because the query itself is content. Section 5.6.5 constrains client behaviour, but the disclosure is inherent to asking a domain about its users. Clients concerned with this should resolve through a relay or cache aggressively within ttl.
Alias squatting is a denial of service on the ergonomic layer. Anyone can publish a signed advertisement claiming any alias. The bidirectional check of section 5.5 prevents binding an alias to a non-consenting domain, and the first-confirmed ordering rule prevents an attacker from permanently disabling an alias by manufacturing a conflict. What remains is a land grab: the first party to advertise a desirable alias holds it. Fully-qualified domains are unaffected, always work, and are what clients fall back to. No value should ever depend on an alias resolving a particular way.
Toll griefing. Without the quote mechanism of section 8.3 a recipient could raise a toll after a sender committed to paying it, and retain the payment of a rejected envelope. The binding-quote rule and the delivery invariant close this. Implementations that accept unbroadcast payments but reject the envelope while internalizing anyway are extracting funds for undelivered messages, and violate section 8.3.2. Senders should prefer quoted sends to unquoted ones with any counterparty they do not already trust.
Tags are sender-controlled. A tag asserts nothing. See section 3.3.2. Filter rules keyed on tags are a convenience and must not be load-bearing for security.
Confusable handles. ASCII-only addressing removes the worst homograph attacks but not 0 for o or 1 for l. Section 2.3 is a mitigation, not a solution. Address book entries and peer attestation are more reliable than reading a handle.
Delegation exposure is bounded per action, not in total. See section 9.3. A user who grants a delegation believing totalCap is enforced, when no principal-funded arrangement exists, has under-estimated their exposure by an unbounded factor. This is the most likely way for a user to lose more than they intended under this specification, which is why section 9.3.3 makes the disclosure mandatory.
Delegation expiry depends on the verifier's clock. A verifier with a badly wrong clock will accept expired authority or reject live authority. fields.expiryHeight avoids this for principals who need it.
Resolution endpoints are an unauthenticated DoS surface. Hosts must serve first-contact requests without authentication, which makes resolution cheap to flood. Rate limiting per section 5.3, and monetization per BRC-105 where appropriate, are expected. Clients MUST respect 429 and Retry-After.
Unsigned exchange rates prove nothing. Section 6.2.5 records the sender's assertion of the rate. Where a dispute over fiat-denominated value is foreseeable, use a signing oracle or denominate in satoshis.
Implementations
- At the time of writing the authors operate a resolution stack in which an identity overlay resolves a handle to an identity key and a second lookup resolves that key to a messagebox URL. This document restructures that flow around domain-hosted resolution: the domain named in the handle answers directly for the identity key, the attestation, and the messagebox, removing the overlay indirection for the common case. The overlay retains one narrow role, the registry-free alias advertisements of section 5.5.
- The payment and messaging legs are implementable today with the reference
@bsv/sdkand@bsv/wallet-toolboxstacks. BRC-42 and BRC-43 derivation, BRC-29 envelopes, Atomic BEEF serialization, BRC-33 messagebox relays, BRC-48 PushDrop tokens, overlay topic managers and lookup services, and BRC-100internalizeActionare all shipping components. What this document adds is the addressing grammar, the manifest and endpoint contracts, the attestation rules, and the enforcement invariants that bind them together. - The smallest useful conforming implementation is a host that publishes
metanet.handleswith aresolveendpoint and issues handle certificates, plus a client that performs section 5.7 and section 6.1. Aliases, search, tolls, and delegation are each independently optional and can be added later without breaking that core. - Appendix A is a complete worked example with real keys and real signatures, usable as a conformance test vector. An implementation that reproduces its certificate signature, alias signature, payment derivation, envelope signature, and forwarding signature has the cryptographic core of this document correct.
References
- BRC-28: Paymail Payment Destinations (historical context)
- BRC-29: Simple Authenticated BSV P2PKH Payment Protocol
- BRC-31: Authrite Mutual Authentication (historical context)
- BRC-33: PeerServ Message Relay Interface
- BRC-42: BSV Key Derivation Scheme (BKDS)
- BRC-43: Security Levels, Protocol IDs, Key IDs and Counterparties
- BRC-46: Wallet Transaction Output Tracking (Output Baskets)
- BRC-48: Pay to Push Drop
- BRC-52: Identity Certificates
- BRC-53: Certificate Creation and Revelation (historical context)
- BRC-56: Unified Abstract Wallet-to-Application Messaging Layer (historical context)
- BRC-62: Background Evaluation Extended Format (BEEF) Transactions
- BRC-65: Transaction Labels and List Actions
- BRC-68: Publishing Trust Anchor Details at an Internet Domain
- BRC-78: Serialization Format for Portable Encrypted Messages
- BRC-95: Atomic BEEF Transactions
- BRC-100: Unified, Vendor-Neutral, Unchanging, and Open BSV Blockchain Standard Wallet-to-Application Interface
- BRC-101: Diverse Facilitators and URL Protocols for SHIP and SLAP Overlay Advertisements
- BRC-103: Peer-to-Peer Mutual Authentication and Certificate Exchange Protocol
- BRC-104: HTTP Transport for BRC-103 Mutual Authentication
- BRC-105: HTTP Service Monetization Framework
- BRC-125: PeerPay URI Scheme for BRC-29 Payments
- BRC-190: Access Gates for Metanet Rooms
- BRC-218: Chat-Native Command Grammar for the Metanet
- WhatsOnChain Exchange Rate API
- RFC 1123: Requirements for Internet Hosts
- RFC 2119: Key words for use in RFCs to Indicate Requirement Levels
- RFC 6979: Deterministic Usage of DSA and ECDSA
- RFC 8785: JSON Canonicalization Scheme (JCS)
Appendix A: Worked Example
Every key, hash, and signature below is real and reproducible. Signatures were produced with deterministic ECDSA (RFC 6979) over secp256k1, DER-encoded with low-S normalization, and each verifies against the values as printed. The BRC-42 derivation used here is the same implementation that reproduces all seven official BRC-42 test vectors.
These keys are published in a public specification and are therefore compromised by construction. They are for conformance testing only and MUST NOT be used for anything else.
Private keys are SHA-256("BRC-169 EXAMPLE / " + label) reduced mod n, so an implementer can regenerate them.
A.1 Parties
| Party | Label | Private key | Identity / certifier public key |
|---|---|---|---|
lkup.net host | lkup.net certifier | 2641016ccb8e5602f53467fd6a8d91e2c58d44b8f727bd843da3f5f71e79e4c8 | 0371f0ec5992a9d38e09fe528e367890969c66eaebdb01b4d35a2fc0d61251b3f9 |
@deggen@lkup.net | deggen identity | 170605580e94d6f403468fdf9d2475e5b20379dc582f2ab9324932844be0b1aa | 0359c5f3bfe249f6c0ca99d0e9cc1517da51a511f3d04f18e47a5d7ae55f04008c |
nexus.example host | nexus.example certifier | bd60fdda070b1cd25813073615e2899a9a092ffb5ea87a0df1526bc89bcbc476 | 03df23a0a1bdf3135f9ec918bfc28e3d82b80ee7855e72a83c4b67a601270859d3 |
@crumbs@nexus.example | crumbs identity | cf94a849be462a110807900e45a909515c38a2d17b9a8e9753c9475bfd220848 | 0375b162a37d8794cfdcf72938d9467931e8586885b93c0da97051ecb512f46646 |
The scenario: @crumbs@nexus.example pays @deggen+conf2036@lkup.net 21,545 satoshis.
A.2 https://lkup.net/manifest.json
{
"name": "LkUp",
"metanet": {
"trust": {
"name": "LkUp",
"note": "Handle registry and messagebox for the LkUp ecosystem",
"icon": "https://lkup.net/icon.png",
"publicKey": "0371f0ec5992a9d38e09fe528e367890969c66eaebdb01b4d35a2fc0d61251b3f9"
},
"handles": {
"version": "1.0",
"resolve": "https://lkup.net/.well-known/metanet-handles/resolve",
"search": "https://lkup.net/.well-known/metanet-handles/search",
"messagebox": "https://messagebox.lkup.net",
"aliases": ["lkup"],
"commands": []
}
}
}
A.3 The handle certificate
Field values are shown as Base64 of the plaintext so that the example is readable. A production certificate encrypts them per BRC-52 field encryption. This does not affect verifiability: BRC-52 serializes the UTF-8 bytes of the Base64 string rather than the decoded bytes, so the signature below is computed over exactly the bytes shown and verifies as printed.
{
"type": "XgCFdUfxEcI+3xtDjsIuSAjMl5EwzCUjsQc45ds1lC8=",
"serialNumber": "JMNxKTvlkhOO88EJZRgnpTKL78dC1XwxQ9REUysjy08=",
"subject": "0359c5f3bfe249f6c0ca99d0e9cc1517da51a511f3d04f18e47a5d7ae55f04008c",
"certifier": "0371f0ec5992a9d38e09fe528e367890969c66eaebdb01b4d35a2fc0d61251b3f9",
"revocationOutpoint": {
"txid": "2b09f724127b5213ead87842deade00ef6cb1a834c951d1612e162f5891fb3cb",
"vout": 0
},
"fields": {
"domain": "bGt1cC5uZXQ=",
"handle": "ZGVnZ2Vu"
},
"signature": "30450221008becb25058954be7cf6f8c46d3a0411a85a9aed55ef90466651fc75374e2bdcf02205a232796a1c2dd0bd7096428a5e6fb766eee404f441a93a261986486ba3b553c"
}
bGt1cC5uZXQ= decodes to lkup.net and ZGVnZ2Vu to deggen.
Signature preimage. CertificateBinary with includeSignature = false, fields ordered lexicographically (domain before handle):
5e00857547f111c23edf1b438ec22e4808cc979130cc2523b10738e5db35942f
24c371293be592138ef3c109651827a5328befc742d57c3143d444532b23cb4f
0359c5f3bfe249f6c0ca99d0e9cc1517da51a511f3d04f18e47a5d7ae55f04008c
0371f0ec5992a9d38e09fe528e367890969c66eaebdb01b4d35a2fc0d61251b3f9
2b09f724127b5213ead87842deade00ef6cb1a834c951d1612e162f5891fb3cb
00
02
06 646f6d61696e 0c 62477431634335755a58513d
06 68616e646c65 08 5a47566e5a325675
(Line breaks and spacing are presentational; the preimage is the concatenation.)
Signing key. Per BRC-52 the certifier does not sign with its identity key directly. It signs with a BRC-42 key derived under protocol ID [2, "certificate signature"], key ID <type> <serialNumber>, counterparty anyone, giving the BRC-43 invoice number:
2-certificate signature-XgCFdUfxEcI+3xtDjsIuSAjMl5EwzCUjsQc45ds1lC8= JMNxKTvlkhOO88EJZRgnpTKL78dC1XwxQ9REUysjy08=
Because the counterparty is anyone (private key 1), both sides compute the same shared secret: the certifier computes certifierPriv * G, and any verifier computes 1 * certifierPub. Both equal the certifier's public key. The resulting signing public key, which a verifier derives and checks the signature against, is:
03c0d407698d43211eef3381e741a2b2a80840c655d0ac3880edfbcfe432f9c91c
A.4 Resolution response
GET https://lkup.net/.well-known/metanet-handles/resolve?handle=deggen
{
"metanetHandles": "1.0",
"handle": "deggen",
"domain": "lkup.net",
"identityKey": "0359c5f3bfe249f6c0ca99d0e9cc1517da51a511f3d04f18e47a5d7ae55f04008c",
"certificate": { "...": "exactly the object in A.3" },
"messagebox": "https://messagebox.lkup.net",
"ttl": 3600,
"revoked": false
}
Note that the +conf2036 tag was stripped before the query, per section 3.1, and that certificate.subject equals identityKey, per section 5.2.
A.5 Alias advertisement
The BRC-48 PushDrop fields claiming lkup for lkup.net, per section 5.5:
| Position | Field | Value (hex) |
|---|---|---|
| 1 | protocol | 65636f73797374656d2d616c696173 (ecosystem-alias) |
| 2 | version | 31 (1) |
| 3 | alias | 6c6b7570 (lkup) |
| 4 | domain | 6c6b75702e6e6574 (lkup.net) |
| 5 | certifier | 0371f0ec5992a9d38e09fe528e367890969c66eaebdb01b4d35a2fc0d61251b3f9 |
| 6 | signature | 304402207fbf1a6b98a115d3677bc25989aaaf625a56d0eb6410ac3ce2cb051976cd043b02204873d2a51cda0e13a9f8637bc75bfaedfbee03401dc002d82c6f3be7bc86896c |
The signature is over SHA-256 of the concatenation of fields 1 to 5:
65636f73797374656d2d616c696173316c6b75706c6b75702e6e65740371f0ec5992a9d38e09fe528e367890969c66eaebdb01b4d35a2fc0d61251b3f9
The bidirectional check of section 5.5 succeeds because metanet.handles.aliases in A.2 contains lkup.
A.6 The payment
With derivationPrefix = D1vMjXw0XVElldVZgrWk4w== and derivationSuffix = nEK2VVSJFSCulgp/tHd1ng==, the BRC-29 invoice number is:
2-3241645161d8-D1vMjXw0XVElldVZgrWk4w== nEK2VVSJFSCulgp/tHd1ng==
| Step | Value |
|---|---|
Sender computes shared secret crumbsPriv * deggenPub, HMAC-SHA256 over the invoice number, adds hash * G to deggenPub | |
| Derived locking public key | 029b113ca5cba4d7467cd2c4bc6dd8f13fc67c01ce5ac5de38af10e79ad9b6d17f |
| Locking script (P2PKH) | 76a914594afeccd8a693dc42bd2c40ac34237410a77d7888ac |
| Address | 1998yqe7nSNHhXrTddgHiohca5KfnhbarS |
Recipient independently derives deggenPriv + HMAC(deggenPriv * crumbsPub, invoice) | b731d94435f86af62b3e1513d6ab719f8c9cd227c72c5fbf3db944a768198e83 |
The recipient's derived private key corresponds to the sender's derived public key, which is the property BRC-42 provides and the one an implementation must reproduce.
A.7 The envelope
{
"metanetHandles": "1.0",
"recipient": {
"handle": "deggen",
"tag": "conf2036",
"domain": "lkup.net"
},
"sender": {
"identityKey": "0375b162a37d8794cfdcf72938d9467931e8586885b93c0da97051ecb512f46646",
"handle": "crumbs",
"domain": "nexus.example"
},
"created": "2026-07-30T09:15:00Z",
"quoteId": "0d34cb39c1c6fe06cd4a346867e1e334",
"payment": {
"derivationPrefix": "D1vMjXw0XVElldVZgrWk4w==",
"derivationSuffix": "nEK2VVSJFSCulgp/tHd1ng==",
"protocol": "3241645161d8",
"satoshis": 21545,
"beef": "<Atomic BEEF, BRC-95, elided>"
},
"content": "<BRC-78 encrypted payload, elided>",
"signature": "304402201ffa604f518febbc5dae08ce24b934943028663018fc69149dcf7c0fc6d016360220127dd6991f1a9797b7ad989cd56ae9c8d2b155e015f17f1c3a94e63ace9504b4"
}
The signature is over SHA-256 of the RFC 8785 canonicalization with content and signature removed, per section 7.2:
{"created":"2026-07-30T09:15:00Z","metanetHandles":"1.0","payment":{"beef":"<Atomic BEEF, BRC-95, elided>","derivationPrefix":"D1vMjXw0XVElldVZgrWk4w==","derivationSuffix":"nEK2VVSJFSCulgp/tHd1ng==","protocol":"3241645161d8","satoshis":21545},"quoteId":"0d34cb39c1c6fe06cd4a346867e1e334","recipient":{"domain":"lkup.net","handle":"deggen","tag":"conf2036"},"sender":{"domain":"nexus.example","handle":"crumbs","identityKey":"0375b162a37d8794cfdcf72938d9467931e8586885b93c0da97051ecb512f46646"}}
A.8 A forwarding record
@deggen@lkup.net later moves to nexus.example, with a new identity key derived from the label deggen identity at new domain:
{
"from": "@deggen@lkup.net",
"toIdentityKey": "033cef496cd596a9dab13b36335cf51383322056f118c91e71d80be69b87e5db66",
"toHandle": "@deggen@nexus.example",
"created": "2027-01-15T00:00:00Z",
"signature": "3045022100f4c27b2052e40565134c91e14e530fd0831c2043b5e698284f2a76d4383c509602201598a61ea25dd46f4e5f6f64d9ca54fdeac71bc34df048e685c5d8f77bf69d83"
}
The preimage is the four values, each followed by LF:
@deggen@lkup.net\n033cef496cd596a9dab13b36335cf51383322056f118c91e71d80be69b87e5db66\n@deggen@nexus.example\n2027-01-15T00:00:00Z\n
Critically, this verifies against 0359c5f3..., the old identity key from A.1, and not against the lkup.net certifier key. A host cannot forge a redirect for a handle it controls, which is the property section 4.3 requires. A client following this record must also apply section 4.4, since the identity key has changed.