> For the complete documentation index, see [llms.txt](https://hub.bsvblockchain.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hub.bsvblockchain.org/brc/overlays/0211.md).

# Signed On-Chain Reachability Manifest

RexStarBSV (<RexStarBSV@protonmail.com>)

## Abstract

This BRC defines a reachability manifest: a record, signed by a service's issuer key and published on the BSV chain, that lists the transports at which the service can be reached (an inscribed application shell, Tor onion services, clearnet mirrors, IP addresses, IPFS content, Nostr relays), each with a priority, under one sequence number. A client holds the issuer key as a constant compiled into its build, takes candidate manifests from any source, accepts only those whose ECDSA signature over a domain-separated canonical JSON encoding verifies under that key, keeps the one with the highest sequence number at or above its stored floor, and tries its endpoints in order. It can therefore still find the service after a domain suspension, a host seizure or an app-store removal. Presence on chain confers no trust, and the signature proves who listed the endpoints, not that the list is the newest or that each endpoint is still the service's. Overlay discovery ([BRC-88](/brc/overlays/0088.md)) takes over once a connection exists.

## Motivation

[BRC-22](/brc/overlays/0022.md), [BRC-24](/brc/overlays/0024.md) and BRC-88 assume that the client can already reach an overlay host. That host, like a service's own front end, is found through a domain name, an API origin or an app-store listing, each of which a third party can remove while the service keeps running: a registry can put a domain on `serverHold`, which under RFC 5731 stops its delegation being published in DNS, a store can delist an app, and a provider can null-route an address. What is missing is a bootstrap step whose trust root and record both survive those actions. No registrar or store can withdraw a mined transaction, so this BRC uses the chain as the place where a service publishes and the client build as the place where trust is fixed. A client needs one SHA-256 hash and one ECDSA verification per candidate, and one stored integer.

## Scope and conformance

§§2 to 15 specify the manifest and its verification, the on-chain forms, endpoint ordering, the build pin, the HTTP binding of a token source and versioning; §16 lists every requirement by role. Out of scope are how a source finds tokens on chain, the protocol spoken to an endpoint, authentication of the service once connected, issuer key generation, and any overlay topic manager or lookup service.

A deployment that follows this BRC defends against the removal of a name, host, address or store listing (provided the client can still reach a token source and one listed endpoint), against forged manifests, against replay of a genuine manifest older than one the client has accepted while its storage keeps the floor, and against a party that writes client storage to replace the issuer key. It does not defend against staleness, theft of the issuer key, a listed endpoint that is dishonest or has changed hands, tampering with the client's own code, a censor that blocks the listed endpoints, or disclosure of the manifest, which is public.

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when, they appear in capitals. They describe conformance to this BRC. Implementations and compatibility notes records how the listed implementations behave where this BRC leaves behaviour open.

## Relationship to existing standards

This standard answers one question: given only a public key compiled into a client, where can that client reach the service the key speaks for?

### Overlay discovery: BRC-22, BRC-24, BRC-88 and BRC-101

BRC-22 and BRC-24 define how an overlay host admits transactions and answers lookups, BRC-88's SHIP and SLAP advertisements are resolved through SLAP trackers at HTTPS base URLs, and [BRC-101](/brc/overlays/0101.md) describes richer advertisement URLs and calls itself aspirational. All of them begin once the client reaches an overlay host, the step a manifest supplies. Trust differs too: an advertisement is attributable to whichever key publishes it, and BRC-88 does not limit who may advertise a topic, while a manifest is accepted only under a key the client held before it ran.

### A signed host list shipped with the client: BRC-178

[BRC-178](/brc/overlays/0178.md), on bootstrapping and default SLAP trackers, recommends that clients ship a list of overlay host URLs and requires that the list be signed by a publisher key shipped with the client or retrieved over a pinned channel, that it carry a version and a fetched-at time, and that the user can replace it, leaving format, transport and verification open. A manifest meets the first of these requirements as written and half of the second: `seq` is a version, and `updated_at` is informational (§2), not a fetched-at time. A tracker list could be carried as a manifest under its own service id. A user override chooses where to connect, which this standard leaves alone; §11 forbids only a stored value that changes which signatures the client believes.

### Discovery through a domain: BRC-68, BRC-180 and BRC-169

[BRC-68](/brc/peer-to-peer/0068.md) publishes an entity's key in a domain's `/manifest.json`, [BRC-180](/brc/overlays/0180.md) adds overlay service URLs there and calls the declaration secured by control of the domain, and [BRC-169](/brc/peer-to-peer/0169.md) resolves `@handle@domain` to an identity key and messagebox URL at the handle's domain. Each takes its trust root, and BRC-180 and BRC-169 their endpoints, through DNS, which lets the domain holder change keys by editing a file and which a `serverHold` or a host seizure removes. This standard compiles the key into the client (§11) and takes endpoints from any source, at the price of rotation by new build; a client can follow BRC-180 while the domain resolves and fall back to a manifest when it does not. BRC-169's Reachability Policy governs who may message a handle; here the word means only where a service can be contacted.

### The same pin discipline: BRC-179

[BRC-179](/brc/wallet/0179.md) pins a capability manifest's fingerprint into the build and, in its section 9, forbids reading the pin from runtime-mutable state in a production build. §11 applies that discipline to a signing key, and BRC-179's stated limit holds here too: a pin gives no protection against an attacker who already controls the host or the build.

### Other signed overlay records: BRC-77, BRC-192, BRC-194 and BRC-183

[BRC-192](/brc/apps/0192.md) signs an ASCII type prefix, a `0x00` byte and the RFC 8785 (JCS) serialization of a body under [BRC-77](/brc/peer-to-peer/0077.md), with a key derived under [BRC-42](/brc/key-derivation/0042.md) and BRC-43, as [BRC-194](/brc/overlays/0194.md) does for a host's expiring capability manifest. §§5 and 6 have the same shape (Claim 2), but sign with the pinned key directly, so a reader needs only SHA-256 and secp256k1, and carry no expiry, which during a takedown would leave every client without a record. [BRC-183](/brc/overlays/0183.md) observes that a web endpoint is not automatically an overlay; this standard defines no topic manager and is filed under Overlays because it supplies the step before overlay discovery.

### Prior art outside the BRC series

**The Update Framework (TUF)** keeps root keys offline, refuses older metadata and expires every file. The floor of §8 answers its rollback attack for a client that has seen the newer manifest; its freeze attack, which TUF answers with expiry, is not answered here; and its fast-forward attack applies, since a raised floor cannot be lowered. TUF rotates its root in band, where this standard rotates by new build.

**Certificate Transparency** (RFC 9162) makes misissuance detectable. The chain gives published manifests the same public record, though §7 never asks whether a token was published.

**DNSSEC** (RFC 4033) authenticates what a name says, and cannot keep a name in existence.

**IPNS** records carry a sequence number, an expiry and a TTL under the signed prefix `ipns-signature:`, and lapse from the DHT within 48 hours unless republished. This standard shares the sequence rule and the signed prefix, keeps the key in the build instead of the name, needs no republishing, and has no expiry.

**Nostr relay lists (NIP-65)** are the nearest prior art for publishing a key's endpoints through untrusted servers. NIP-01 keeps the latest replaceable event by `created_at`, ties going to the lowest id. A manifest is ordered by an issuer-assigned `seq`, so no clock can reorder it, and no rule chooses between two valid manifests with one `seq` (§8).

## Specification

### 1. Terminology and roles

* **Issuer**: the identity that signs a service's manifests and holds the issuer private key, distinct from any wallet key that pays for on-chain outputs.
* **Issuer key**: the issuer's secp256k1 public key, written here in compressed form as 66 lowercase hex characters; the client's trust root.
* **Service id**: a short ASCII label naming the service, such as `"messaging"`. One issuer key MAY sign for several services, and a client always verifies against the service id it expects.
* **Manifest**: the signed record of a service's endpoints at one sequence number. **Token**: its string form (§4).
* **Endpoint**: one `(kind, url, priority)` entry; its `url` is its **locator**.
* **Candidate**: any string presented to selection (§8) as a possible token.
* **Floor**: the highest sequence number a client has accepted (§8).

Requirements are stated for five roles, and one party may play several.

| Role         | What it does                                                                                                                                        | Sections        |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| Signer       | builds a payload and signs it under the issuer key, normally on an offline machine                                                                  | §§2 to 6, 8, 11 |
| Publisher    | writes a token on chain, paying with a funding key of its own                                                                                       | §9              |
| Token source | supplies candidates: a node that serves the tokens it holds (§13), an index that records the outputs of §9, or a client reading transactions itself | §§9, 13         |
| Verifier     | checks one token under a given issuer key                                                                                                           | §§4 to 7        |
| Client       | holds the pinned key and its floor, selects the current manifest, orders its endpoints and connects                                                 | §§8, 10 to 12   |

### 2. Payload object

The payload is a JSON object. A signer following this BRC emits exactly these members:

| Member       | JSON type | Required | Meaning                                                                                         |
| ------------ | --------- | -------- | ----------------------------------------------------------------------------------------------- |
| `v`          | integer   | yes      | Format version. A signer following this BRC writes `1`.                                         |
| `service`    | string    | yes      | Service id. MUST be non-empty.                                                                  |
| `seq`        | integer   | yes      | Sequence number, `>= 0`. The ordering authority (§8).                                           |
| `updated_at` | integer   | yes      | Publication time in epoch seconds. Informational only: ordering uses `seq`, never `updated_at`. |
| `endpoints`  | array     | yes      | One or more endpoint objects.                                                                   |

Each endpoint object has these members:

| Member     | JSON type | Required           | Meaning                                                                                                     |
| ---------- | --------- | ------------------ | ----------------------------------------------------------------------------------------------------------- |
| `kind`     | string    | yes                | One of the kinds in §3.                                                                                     |
| `url`      | string    | yes                | Transport-specific locator (§3). MUST be non-empty.                                                         |
| `priority` | integer   | no (default `100`) | Try-order hint. Lower is tried first. Any integer within the bound below is valid, a negative one included. |

A later version MAY add members. It MAY also add a top-level integer `min_reader`, the lowest reader version that is allowed to use the manifest (§7, step 4). A verifier MUST ignore members it does not recognise; the signature still covers them, because canonicalization (§5) takes in every member of the decoded object.

A signer MUST reject a payload that contains a non-integer number anywhere, since floating-point values serialize differently across languages and would break byte parity between signer and verifier. A signer MUST also reject a payload that contains an integer whose absolute value exceeds 9007199254740991 (2^53 - 1), beyond which a JavaScript number no longer holds every integer exactly.

Strings SHOULD be printable ASCII, so that an operator approving a manifest sees exactly what each locator names (*Locator confusion*). Canonicalization handles any other character.

A conforming signer never produces a payload whose required members are missing or carry another JSON type. This BRC does not define how a verifier treats such a payload when it is correctly signed, except for `v` and `min_reader` (§7, step 4); the listed readers refuse it (compatibility note 1). Nor does a verifier check the signer's other rules: it accepts a negative `seq`, an empty `endpoints` array, which leaves the resolver nothing to try, and an empty `url`.

### 3. Endpoint kinds and preference order

A signer MUST NOT emit any `kind` other than the six below. Each has a fixed rank, which breaks ties between endpoints of equal `priority` (§10).

| `kind`  | Rank | What `url` names                                                                                                                | Takedown posture                                                                                                                                                                                  |
| ------- | ---- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bsv`   | 0    | The client application inscribed on chain as a 1-sat ordinal: an inscription id (`<txid>_<vout>`) or a txid. Never an HTTP URL. | Immutable and outside every registry. A gateway that serves it can be seized, or can serve other bytes to a client that does not check them. Usable only by a client that can fetch inscriptions. |
| `onion` | 1    | A Tor v3 onion service.                                                                                                         | Self-authenticating, since the address encodes the service's key, and outside every registry.                                                                                                     |
| `https` | 2    | A clearnet host, such as a hardened domain or an alternate-TLD mirror.                                                          | Exposed to registry and DNS action, and to anyone who obtains a certificate for the name after it changes hands.                                                                                  |
| `ip`    | 3    | A raw IP address.                                                                                                               | Needs no DNS. The manifest binds no key to it, and its address can be null-routed or reassigned.                                                                                                  |
| `ipfs`  | 4    | A content identifier.                                                                                                           | Content-addressed: the identifier authenticates the content only if the client checks it.                                                                                                         |
| `nostr` | 5    | A Nostr relay.                                                                                                                  | Usually named by a domain, with the exposure of `https`.                                                                                                                                          |

A verifier does not reject a manifest for an unknown kind: it ranks the endpoint 99 (§10), and a client that passes `allowed_kinds` never tries it.

A signer SHOULD include at least one `onion` endpoint, the only live kind both outside every registry and self-authenticating. Publishing without one is permitted, because some services are reachable only by IP, but the signer SHOULD warn the operator when it happens. An `ip` endpoint SHOULD be treated as lower assurance: the manifest binds no key to it, and over plain HTTP or WebSocket it belongs to anyone on the path.

**Locators.** `kind` names the network, and locator syntax is left to the publisher and its clients. In the listed applications a locator is a host with an optional `:port` and path and no scheme, to which the client adds `https` or `wss` for `https`, `http` or `ws` for `onion` and `ip`, and `wss` for `nostr`. Nothing ties a locator to its `kind` at verification (*Locator confusion*). The vectors' `example.onion` is a placeholder, not a version 3 address.

### 4. Token serialization

A token is an ASCII string with two parts separated by a dot:

```
token = BASE64URL_NOPAD(wire_json) "." DER_SIG_HEX
```

* `wire_json` is the payload as UTF-8 JSON, whose member order and whitespace do not affect verification (§5). The reference signer writes compact JSON with members in the order `v, service, seq, updated_at, endpoints` and `kind, url, priority`, and every non-ASCII character as a `\u` escape; a signer that wants to reproduce the vector tokens uses the same layout.
* `BASE64URL_NOPAD` is the URL-safe base64 alphabet of RFC 4648 §5 with the trailing `=` padding removed. A decoder MUST accept the unpadded form, restoring the padding first (`"=" * (-len % 4)`) if its library needs it.
* `DER_SIG_HEX` is the DER-encoded ECDSA signature of §6 in lowercase hex.

A token is split at its last `.`, and a string with no `.` is not a token. The listed readers accept different supersets of this grammar, each variant decoding to the same wire JSON and signature or failing, so that a token stored with a trailing line ending, for example, verifies in some readers and not in others (compatibility note 3).

### 5. Canonicalization

The signed bytes are computed from the decoded payload object, so they do not depend on the layout of the wire JSON. For a payload object `P`:

```
canonical(P) = UTF-8 bytes of the JSON serialization of P, with
  - object keys sorted in ascending order, recursively (inside endpoint objects too)
  - no whitespace: "," between items, ":" between key and value
  - non-ASCII characters written as UTF-8, not escaped
  - no NaN or Infinity, and no non-integer numbers
```

The rule is Python's `json.dumps(P, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False).encode("utf-8")`. Strings are escaped as JSON requires: `"` and `\` are escaped, U+0000 to U+001F are written as `\b`, `\t`, `\n`, `\f` and `\r` where those exist and as `\u00xx` with lower-case hex digits otherwise, and every other character, U+007F, U+2028 and U+2029 among them, is written as its UTF-8 bytes. Canonical bytes therefore never contain a raw `0x00`.

Member names MUST be ASCII, in this and every later version, because Python sorts by code point and JavaScript by UTF-16 code unit, and the two orders can differ outside ASCII. A JavaScript implementation MUST sort recursively; a shallow sort leaves endpoint members in wire order, and its digests do not interoperate. On every payload that meets §2 and whose strings are sequences of Unicode scalar values, `canonical(P)` is byte for byte the output of the RFC 8785 serialization steps, and it is the RFC 8785 canonical form itself when no string holds a Unicode noncharacter, which the I-JSON input rule of RFC 8785 excludes (Claim 2).

A verifier MUST canonicalize the same decoded object that it then reads fields from. Whatever the wire spelling, the decoded object then canonicalizes to the bytes the issuer signed or fails verification, barring a forgery, and for a payload a conforming signer produces the fields read mean what the issuer signed (Claim 4). A verifier is not required to check the integer bound or the ASCII rule; a payload that breaks either, which only a holder of the issuer key can produce, can verify in one reader and fail in another. Vector 1 gives a worked example.

### 6. Signature scheme

Let `DOMAIN` be the 19 ASCII bytes `bsv-reachability-v1`. The signed bytes and the digest are:

```
signed = DOMAIN || 0x00 || canonical(P)
digest = SHA256(signed)
sig    = ECDSA_sign(issuer_priv, digest)     secp256k1, DER-encoded, low-S
```

Neither the domain nor canonical bytes contain `0x00`, so the separator is the only zero byte in `signed`, and the domain confines a signature to this protocol (Claim 5). The digest is a single SHA-256 of `signed`, not a double SHA-256 and not the Bitcoin Signed Message construction.

A signer MUST produce low-S signatures, that is, with `s <= (n - 1) / 2` where `n` is the order of the secp256k1 group. The reference signer derives its nonces by RFC 6979 §3.2 (HMAC-SHA-256, no additional data) and normalizes `s` to low-S, so a signer built the same way reproduces the vector signatures exactly. A verifier MAY reject a high-S signature, and the listed readers differ on this. A high-S signature is the malleated twin of a low-S one over the same payload, so when the low-S original is also presented it cannot change which payload is selected; a source that serves only the twin withholds the manifest from a reader that refuses high-S (compatibility note 3).

Verification is:

```
digest = SHA256(DOMAIN || 0x00 || canonical(P))
valid  = ECDSA_verify(issuer_key, DER_decode(DER_SIG_HEX), digest)
```

### 7. Verification algorithm

```
verify_manifest(token, issuer_key_hex, expected_service?, min_seq?) -> (ok, manifest?, reason)
```

Verification MUST NOT throw or raise. Every failure returns `ok = false` with a reason. The steps, in order:

1. If `token` is absent, empty or contains no `.`, return `(false, none, "no manifest presented")`.
2. Split at the last `.`, base64url-decode the first part, parse it as JSON, require an object, and read the payload members. On any failure return `(false, none, "malformed manifest")`.
3. Parse `issuer_key_hex` as a public key (§1), compute `digest` as in §6, decode the signature from its hex and DER form, and verify it against the key. If the key is not a curve point, the signature cannot be decoded, or verification fails, return `(false, none, "manifest signature does not verify")`.
4. Let `V` be the highest format version the verifier implements, which is 1 for this BRC. If `v` is missing, is not an integer or is below 1, return `(false, none, "unsupported manifest version")`. If `min_reader` is present and is not an integer or is greater than `V`, return `(false, none, reason)` with a reason saying that a newer reader is required. Otherwise continue, reading only the members this BRC defines, whatever the value of `v`.
5. If `expected_service` is given and `service` differs from it, return `(false, manifest, "manifest is for a different service")`.
6. If `min_seq` is given and `seq >= min_seq` does not hold, return `(false, manifest, "manifest is older than the last-seen (rollback rejected)")`. A `seq` equal to `min_seq` passes, and against a `min_seq` that is not a number, such as `NaN`, none passes.
7. Otherwise return `(true, manifest, "valid")`.

Step 3 precedes every semantic check, so every field inspected after it is authenticated. Steps 5 and 6 return the parsed manifest for diagnosis; steps 1 to 4 return none. In step 4 a JSON `true`, a numeric string and `null` are not integers, and a present `min_reader` of `null` is refused, not treated as absent.

Step 4 keeps an upgrade from stranding installed clients. A version 2 manifest MAY add members, and a version 1 reader that accepts it reads the members it knows, which stays correct while the change is additive; for a change that is not, the publisher sets `min_reader` inside the signed payload and older readers refuse the manifest.

### 8. Sequence floor and highest-valid-seq selection

`seq` is the only ordering authority. A client persists the highest `seq` it has accepted and passes it as `min_seq`, and on accepting a manifest with sequence `s` it sets `min_seq = max(min_seq, s)`, so the floor only rises and a genuine manifest older than one the client has accepted is never selected again while its storage keeps the floor. A client with no floor, such as a fresh install, accepts any genuine manifest ever signed for the service. A `seq` equal to the floor is accepted, so that a restarting client can read the current manifest again.

`select_current(tokens, issuer_key_hex, expected_service, min_seq?)` verifies each candidate with the given `expected_service` and `min_seq` and returns the valid manifest with the greatest `seq`, or none. `expected_service` is a required argument in every listed implementation, since a verifier called without it would accept another service's manifest signed under a shared key. A forged manifest fails its signature, and an older genuine one loses to any newer genuine candidate presented; selection cannot prefer a manifest that no source presents.

A signer MUST increase `seq` on every change. Reusing a `seq` lets both manifests pass the floor, and when two valid candidates share the greatest `seq` the listed implementations keep the first they encounter, so discovery order decides. Whether two candidates are different manifests is a question about their canonical payloads (§5), never about token text: a high-S twin and a re-encoded wire JSON are the same manifest.

### 9. On-chain publishing and discovery

A token is written on chain in one of two forms. Either form MAY be accompanied, in the same transaction, by a constant marker output.

**Carrier form (default).** An `OP_RETURN` output:

```
OP_FALSE OP_RETURN "TKRM1" <token>
bytes: 0x00 0x6a PUSH("TKRM1") PUSH(token as ASCII)
```

**Ordinal form (permanent).** A 1-satoshi output whose script is a P2PKH lock followed by a 1Sat Ordinals inscription envelope with content type `text/plain` and the token as its body:

```
<P2PKH lock> OP_FALSE OP_IF PUSH("ord") OP_1 PUSH("text/plain") OP_0 PUSH(token as ASCII) OP_ENDIF
```

A publisher MUST put the whole token in that single body push. A 1Sat Ordinals decoder takes the one push after `OP_0` as the whole body, so a split token reaches it truncated and never verifies, and since the Genesis upgrade no 520-byte limit requires splitting. A reader MAY still accept a body split across several pushes, concatenating them in order. The output stays spendable by its owner.

**Discovery marker (optional).** A constant output that a publisher MAY add in either form:

```
OP_FALSE OP_RETURN "TKRMREG1"
bytes: 0x00 0x6a 0x08 54 4b 52 4d 52 45 47 31  =  006a08544b524d52454731
```

`PUSH(data)` writes the length of `data` using the first of these encodings that fits:

| Length `n`             | Encoding                                                         |
| ---------------------- | ---------------------------------------------------------------- |
| `n < 0x4c`             | `[n]` then `data`                                                |
| `0x4c <= n <= 0xff`    | `0x4c [n]` then `data` (PUSHDATA1)                               |
| `0x100 <= n <= 0xffff` | `0x4d [n_lo n_hi]` then `data` (PUSHDATA2, little-endian length) |
| `n > 0xffff`           | `0x4e [4-byte little-endian length]` then `data` (PUSHDATA4)     |

A token is normally a few hundred bytes; the Vector 1 token is 613 bytes and goes in a PUSHDATA2.

**Marker script hash.** The lookup key is `SHA256(marker_script)` with its 32 bytes reversed, in hex, the script-hash convention of the ElectrumX protocol. It is the same for every service and issuer, and a client MAY hard-code it.

```
marker_script      = 006a08544b524d52454731
marker_script_hash = 6f33820d5cb3f85c23bdd46daeea10bc197c3ef2c93f9a51b6e2e5bdedab49ce
```

A marker lookup needs an index that keeps data-carrier outputs, and this BRC assumes none: an index of spendable outputs, such as the original ElectrumX server, which skips every output beginning `OP_FALSE OP_RETURN`, does not hold the marker. A client MAY query an index that records those outputs under their script hash, but MUST NOT depend on the marker as its only source of candidates (Security Considerations, *Adversaries*).

**Extracting candidate tokens.** An implementation that reads a transaction collects candidates from every output:

* an output that begins `OP_FALSE OP_RETURN` and whose first push is `TKRM1`: the push that follows, decoded as ASCII;
* an output carrying an inscription whose content type begins with `text/plain`: the body, its pushes concatenated if split, decoded as ASCII.

A candidate that is not valid ASCII is not a token. It MUST be skipped, never accepted, and it is never fatal. Every candidate collected goes to `select_current` (§8). Compatibility note 8 records how the reference parses these forms.

**Trust model.** Anyone can publish stale or forged manifests in either form, with or without the marker. A client MUST NOT treat "found on chain" as evidence of anything; a manifest is trusted only if it verifies under the pinned key (§7) and wins selection (§8). The issuer key, which signs, and the funding key, which pays for and owns the output, are deliberately different keys.

**Broadcast uncertainty.** A publisher that does not know whether its broadcast succeeded MUST NOT simply broadcast again, since a resend can inscribe the ordinal twice. It MUST report the transaction id it built, so that an operator can check the chain first. Readers are unaffected by duplicates.

### 10. Endpoint ordering and the fallback resolver

Given a verified manifest and an optional set `allowed_kinds` of the transports the client can use (a client without Tor omits `onion`; one that cannot fetch inscriptions omits `bsv`), the try-order is:

```
ordered = [ e for e in endpoints if allowed_kinds is None or e.kind in allowed_kinds ]
          sorted by (e.priority ascending, RANK(e.kind) ascending, e.url ascending)
```

`RANK` is the rank of §3, and 99 for an unknown kind. Ties left after `priority` and rank are broken by `url`, compared by code point in the reference and by UTF-16 code unit in the JavaScript readers, which agree on ASCII. Endpoints that tie on all three keys keep their emit order, since the listed sorts are stable.

The filter runs before any probe, and nothing adds a kind to `allowed_kinds`, so failures cannot move a client onto a kind it excluded. A privacy-sensitive client, for example one in a Tor-only mode, MUST leave `bsv` and every clearnet kind out of `allowed_kinds` and keep to `onion`; fetching an inscribed application usually goes through a clearnet gateway.

A `FallbackResolver` is built from a verified manifest and `allowed_kinds` and holds the ordered list. `resolve(probe)` returns the first endpoint for which `probe(endpoint)` is true. An exception thrown by `probe` MUST be treated as that endpoint failing; the resolver catches it and moves on. If every endpoint fails, `resolve` returns none and never substitutes an endpoint of its own. That is a total failure, and the caller MUST handle it; both listed applications keep their default connection.

### 11. Issuer key pinning: the build-pin rule (normative)

The pinned issuer key is the whole trust root, so this BRC requires a build pin that configuration cannot override.

* The issuer key MUST be shipped as a constant compiled into the distributed bundle or binary, inlined at build time. It comes from the offline ceremony described below.
* A client MAY honour a locally stored key (a configuration or `localStorage` value) only in a development build that carries no pin. Whether a build is a development build MUST be fixed when it is built, by a flag that no runtime state can change. A production build MUST NOT honour a stored key, with or without a pin.
* When a build pin is present, a stored value MUST NOT be able to substitute a different key, in a development build or any other. The resolution rule, where `dev_build` is that build-time flag, is:

```
resolve_issuer_key(pinned, stored, dev_build):
    p = trim(pinned)
    s = trim(stored)
    if p != "":
        if s != "" and lowercase(s) != lowercase(p):
            return { key: "", conflict: true }     # the stored value tried to replace the pin: refuse
        return { key: p, conflict: false }         # the build pin is authoritative
    if dev_build:
        return { key: s, conflict: false }         # development build with no pin: the override is allowed
    return { key: "", conflict: false }            # production build with no pin: inert, whatever is stored
```

The comparison ignores hex case. When `conflict` is true the client MUST refuse discovery, MUST log the refusal as possible tampering, and MUST keep its default connection (its default relay or same-origin API). It MUST NOT go on with the stored key. Without this rule, anything able to write client storage, such as an information stealer or an injected script, could point discovery at its own issuer key, and the redirection would outlast the foothold that wrote it; a production build that honoured a stored key while its pin was empty would open the same door. An empty pin in a production build makes discovery inert instead, the safe shipped state until an operator pins the real key. Rotating the issuer key means shipping a new signed build.

The rule protects the key and nothing else: the floor, the token source and a privacy mode are ordinary stored settings (Security Considerations, *Adversaries*), and a tampered build, or a web client loaded from a seized origin, carries whatever pin the attacker chose.

**Issuer key custody.** The private issuer key MUST be generated and kept offline, in a ceremony, and MUST be independent of any wallet or funding key. It SHOULD sign nothing except records that carry their own domain separator: a tag that contains no `0x00` byte followed by a `0x00` byte, as in §6, or the Bitcoin Signed Message construction, whose signatures, made over a double SHA-256, cannot be replayed as manifests short of a SHA-256 collision or an event rarer still (Claim 5). A key that hashes and signs bytes chosen by someone else could be handed `DOMAIN || 0x00 || canonical(P)` and would return a valid manifest.

### 12. Client bootstrap flow (informative)

1. Resolve the effective issuer key with the build-pin rule (§11). If no key is usable, stay on the default connection and stop.
2. Obtain candidate tokens from a token source (§13), normally a node or overlay host that serves them. A client MAY also query the marker script hash (§9) at an index that records data-carrier outputs; this BRC names no such index, and the marker is never the only source. A client in a Tor-only mode fetches candidates only from an onion source, judged by the host of the parsed URL.
3. Call `select_current(candidates, issuer_key, expected_service, min_seq = stored floor)` (§§7 and 8).
4. If a manifest is selected, raise the stored floor to its `seq`. Raising it at selection, before any probe, keeps the floor's protection in a session in which no endpoint answers (Claim 6).
5. Build a `FallbackResolver(manifest, allowed_kinds)`, with `allowed_kinds` set by what the client can do and its privacy mode (§10).
6. Call `resolve(probe)` and connect to the first working endpoint, or take the first endpoint of the ordered list if the connection layer already retries. If every endpoint fails, stay on the default connection.

### 13. Token sources and the HTTP binding

A token source is any party that supplies candidates, such as a node that holds the tokens its operator has published, a node that scans blocks for the forms of §9, or an index that answers the marker script hash. Every candidate is verified under the client's pin, so a source can withhold or reorder manifests and cannot forge one.

A host that serves candidates over HTTP answers `GET <base>/reachability`, where `<base>` is the configured URL with one trailing `/` removed. A successful answer has status 200 and a body that is a JSON object whose member `tokens` is an array of token strings, not filtered by service or issuer. A host MAY add other members; the listed host adds `markerScriptHash`, the §9 value. A browser client on another origin can read the answer only if the host sends `Access-Control-Allow-Origin`.

A client treats a status outside 200 to 299, a body that is not a JSON object, or a missing or non-array `tokens` member as an empty list. It drops entries that are not strings and passes the rest to `select_current` unchanged, without trimming. A failure to connect, or any other error while fetching, also gives an empty list, and the client keeps its default connection.

### 14. Versioning and extensibility

This BRC has three version markers, each changing for a different reason.

* **The signing domain**, `bsv-reachability-v1`, names the construction of §§5 and 6 and changes only if the construction does, a new domain then keeping old and new signatures apart (Claim 5). Payload versions do not change it: the version 2 tokens of Vector 4 use the same domain.
* **The payload version**, `v`, with `min_reader`, governs what the members mean (§7, step 4). An additive change raises `v`; a breaking change also sets `min_reader`.
* **The on-chain tags**, `TKRM1` and `TKRMREG1`, are opaque byte strings compared byte for byte, and change only if an on-chain form changes in a way an existing reader would misread.

A later version can add payload members, which version 1 readers ignore, and endpoint kinds: the kind list is closed for version 1 signers (§3), but verifiers rank an unknown kind 99, so a manifest that lists a new kind beside the existing ones still serves version 1 clients.

### 15. Reasons and error handling

Conformance is decided by `ok`, by the step that produced it and by whether a manifest is returned (§7); reasons are diagnostic. The listed implementations return these strings, and the vectors match the substrings shown.

| Step | `ok`  | Manifest returned | Reason                                                     |
| ---- | ----- | ----------------- | ---------------------------------------------------------- |
| 1    | false | no                | `no manifest presented`                                    |
| 2    | false | no                | `malformed manifest`                                       |
| 3    | false | no                | `manifest signature does not verify`                       |
| 4    | false | no                | `unsupported manifest version`                             |
| 4    | false | no                | a reason containing `requires a newer`                     |
| 5    | false | yes               | `manifest is for a different service`                      |
| 6    | false | yes               | `manifest is older than the last-seen (rollback rejected)` |
| 7    | true  | yes               | `valid`                                                    |

`select_current` returns a manifest or none and reports no reason; a client that wants to log why nothing was selected calls `verify_manifest` on each candidate. `resolve` returns an endpoint or none. None of these calls throws.

### 16. Summary of requirements

Every requirement, by the roles of §1, followed by what each role is permitted to do. The issuer's key custody is listed under the signer, which holds the key.

**Signer**

* The private issuer key MUST be generated and kept offline, in a ceremony, and MUST be independent of any wallet or funding key. It SHOULD sign nothing except records that carry their own domain separator, or messages under the Bitcoin Signed Message construction (§11).
* `service` MUST be non-empty, and every endpoint `url` MUST be non-empty (§2).
* A signer MUST reject a payload that contains a non-integer number anywhere, and MUST reject one that contains an integer whose absolute value exceeds 2^53 - 1 (§2).
* A signer MUST NOT emit any `kind` other than the six of §3. It SHOULD include an `onion` endpoint, and SHOULD warn the operator when it does not (§3).
* Member names MUST be ASCII, in this and every later version (§5). Strings SHOULD be printable ASCII (§2).
* A signer MUST produce low-S signatures (§6).
* A signer MUST increase `seq` on every change (§8).
* Permitted: one issuer key MAY sign for several services (§1).

**Publisher**

* In the ordinal form, a publisher MUST put the whole token in the single body push after `OP_0` (§9).
* A publisher that does not know whether its broadcast succeeded MUST NOT simply broadcast again, and MUST report the transaction id it built (§9).
* Permitted: a publisher MAY add the constant marker output beside either form (§9).

**Token source**

* A candidate that is not valid ASCII MUST be skipped, never accepted, and is never fatal. This binds whoever extracts candidates from transactions, a token source or a client that reads transactions itself (§9).
* The HTTP binding of §13 is stated without keywords. Permitted: a host that serves it MAY add other members to its answer (§13), and a party that extracts candidates MAY accept an ordinal body split across several pushes, concatenating them in order (§9).

**Verifier**

* A decoder MUST accept the unpadded base64url form (§4).
* A verifier MUST canonicalize the same decoded object that it then reads fields from, and a JavaScript implementation MUST sort keys recursively (§5).
* A verifier MUST ignore members it does not recognise (§2).
* Verification MUST NOT throw or raise, and runs the steps of §7 in order, the signature check before every semantic check (§7).
* Changing any byte of token A's decoded payload, or of its decoded signature, MUST make verification fail (Vector 2).
* Against a floor of 5, a manifest with `seq = 3` MUST be rejected and one with `seq = 5` MUST be accepted (Vector 7).
* Permitted: a verifier MAY reject a high-S signature (§6), and MAY reject an edit to a token's text that decodes to the same bytes, such as non-zero unused base64url bits (Vector 2).

**Client**

* A client MUST NOT treat "found on chain" as evidence of anything (§9).
* A client MUST NOT depend on the marker as its only source of candidates (§9).
* A privacy-sensitive client, for example one in a Tor-only mode, MUST leave `bsv` and every clearnet kind out of `allowed_kinds` (§10).
* An exception thrown by a probe MUST be treated as that endpoint failing, and the caller MUST handle a total failure (§10).
* An `ip` endpoint SHOULD be treated as lower assurance (§3).
* The issuer key MUST be a constant compiled into the build. Whether a build is a development build MUST be fixed when it is built. A production build MUST NOT honour a stored key, and while a build pin is present a stored value MUST NOT be able to substitute a different key (§11).
* On a conflict between a stored key and the pin, the client MUST refuse discovery, MUST log the refusal as possible tampering, MUST keep its default connection, and MUST NOT go on with the stored key (§11).
* Permitted: a client MAY honour a stored key only in a development build that carries no pin (§11), MAY hard-code the marker script hash (§9), and MAY query the marker at an index that records data-carrier outputs, never as its only source (§§9 and 12).

**Later versions**

* A later version MAY add payload members, among them a top-level integer `min_reader`, and a version 2 manifest MAY add members that a version 1 reader ignores (§§2 and 7). Member names stay ASCII (§5).

## Test Vectors

The vectors use two published test keys, fabricated for this document. They must never be used for anything else.

```
issuer private scalar : 0x11 repeated 32 times
issuer_key            : 034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa

other private scalar  : 0x22 repeated 32 times
other_key             : 02466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27
```

Every signature below was produced by the reference signer with RFC 6979 nonces and low-S normalization (§6), so a signer built the same way reproduces each DER signature exactly.

### Vector 1: sign, canonicalize, verify

Payload: `service = "messaging"`, `seq = 5`, `updated_at = 1700000000`, and these endpoints in emit order:

| kind    | url                                                                  | priority |
| ------- | -------------------------------------------------------------------- | -------- |
| `bsv`   | `abababababababababababababababababababababababababababababababab_0` | 100      |
| `onion` | `example.onion`                                                      | 100      |
| `https` | `mirror.example.com`                                                 | 100      |
| `https` | `mirror.example.net`                                                 | 200      |

Wire JSON (compact, emit order):

```
{"v":1,"service":"messaging","seq":5,"updated_at":1700000000,"endpoints":[{"kind":"bsv","url":"abababababababababababababababababababababababababababababababab_0","priority":100},{"kind":"onion","url":"example.onion","priority":100},{"kind":"https","url":"mirror.example.com","priority":100},{"kind":"https","url":"mirror.example.net","priority":200}]}
```

Canonical JSON (keys sorted recursively; inside each endpoint the order becomes `kind, priority, url`):

```
{"endpoints":[{"kind":"bsv","priority":100,"url":"abababababababababababababababababababababababababababababababab_0"},{"kind":"onion","priority":100,"url":"example.onion"},{"kind":"https","priority":100,"url":"mirror.example.com"},{"kind":"https","priority":200,"url":"mirror.example.net"}],"seq":5,"service":"messaging","updated_at":1700000000,"v":1}
```

Digest:

```
SHA256("bsv-reachability-v1" || 0x00 || canonical)
  = a43f614861d62aa7eacf3e2bf199297c9ee215c6f826e17f763088890497791d
```

Token A (`base64url(wire_json)`, a dot, the DER signature in hex):

```
eyJ2IjoxLCJzZXJ2aWNlIjoibWVzc2FnaW5nIiwic2VxIjo1LCJ1cGRhdGVkX2F0IjoxNzAwMDAwMDAwLCJlbmRwb2ludHMiOlt7ImtpbmQiOiJic3YiLCJ1cmwiOiJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiXzAiLCJwcmlvcml0eSI6MTAwfSx7ImtpbmQiOiJvbmlvbiIsInVybCI6ImV4YW1wbGUub25pb24iLCJwcmlvcml0eSI6MTAwfSx7ImtpbmQiOiJodHRwcyIsInVybCI6Im1pcnJvci5leGFtcGxlLmNvbSIsInByaW9yaXR5IjoxMDB9LHsia2luZCI6Imh0dHBzIiwidXJsIjoibWlycm9yLmV4YW1wbGUubmV0IiwicHJpb3JpdHkiOjIwMH1dfQ.3045022100bacc832b3a9cef05392b4ea7eca4d842be84b3e59130a0ce95a864a2afee8a7c02202eec6b482a900a1d1fa7796c82a2b096333b57309c0b133b2b2c9c8344498dbb
```

Expected results:

* `verify_manifest(A, issuer_key)` gives `ok = true`, reason `"valid"`, `service = "messaging"`, `seq = 5` and 4 endpoints.
* `verify_manifest(A, issuer_key, expected_service = "marketplace")` gives `ok = false`, reason containing `"different service"`.
* `verify_manifest(A, issuer_key, min_seq = 5)` gives `ok = true`. A `seq` equal to the floor passes.
* `verify_manifest(A, issuer_key, min_seq = 6)` gives `ok = false`, reason containing `"rollback"`.
* `verify_manifest(A, other_key)` gives `ok = false`, reason `"manifest signature does not verify"`.
* `verify_manifest(A, "02" + "11" * 32)` gives `ok = false`, reason `"manifest signature does not verify"`. That string is not the encoding of a curve point.
* `verify_manifest(x, issuer_key)` for each `x` in `null`, `""`, `"."`, `"a.b"`, `"no-dot"` and `"x" * 4000` gives `ok = false` and does not raise.
* `select_current([A], issuer_key, "messaging")` gives the manifest with `seq = 5`.

Ordering:

* `ordered_endpoints(A)` gives `bsv abab…ab_0`, `onion example.onion`, `https mirror.example.com`, `https mirror.example.net`. The first two share priority 100 and are ordered by rank; `mirror.example.net` comes last on priority 200.
* `ordered_endpoints(A, allowed_kinds = {"onion", "https"})` drops the `bsv` endpoint and gives `onion example.onion`, `https mirror.example.com`, `https mirror.example.net`.
* `FallbackResolver(A, {"onion", "https"}).resolve(e => e.url == "mirror.example.net")` returns the `https mirror.example.net` endpoint, after the probes of `example.onion` and `mirror.example.com` return false.

### Vector 2: one-byte tampering (negative)

Token T1 is token A with one byte of its decoded payload changed: `"seq":5` becomes `"seq":9`, with the signature left as it was. Its wire JSON is:

```
{"v":1,"service":"messaging","seq":9,"updated_at":1700000000,"endpoints":[{"kind":"bsv","url":"abababababababababababababababababababababababababababababababab_0","priority":100},{"kind":"onion","url":"example.onion","priority":100},{"kind":"https","url":"mirror.example.com","priority":100},{"kind":"https","url":"mirror.example.net","priority":200}]}
```

and the token is:

```
eyJ2IjoxLCJzZXJ2aWNlIjoibWVzc2FnaW5nIiwic2VxIjo5LCJ1cGRhdGVkX2F0IjoxNzAwMDAwMDAwLCJlbmRwb2ludHMiOlt7ImtpbmQiOiJic3YiLCJ1cmwiOiJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiYWJhYmFiXzAiLCJwcmlvcml0eSI6MTAwfSx7ImtpbmQiOiJvbmlvbiIsInVybCI6ImV4YW1wbGUub25pb24iLCJwcmlvcml0eSI6MTAwfSx7ImtpbmQiOiJodHRwcyIsInVybCI6Im1pcnJvci5leGFtcGxlLmNvbSIsInByaW9yaXR5IjoxMDB9LHsia2luZCI6Imh0dHBzIiwidXJsIjoibWlycm9yLmV4YW1wbGUubmV0IiwicHJpb3JpdHkiOjIwMH1dfQ.3045022100bacc832b3a9cef05392b4ea7eca4d842be84b3e59130a0ce95a864a2afee8a7c02202eec6b482a900a1d1fa7796c82a2b096333b57309c0b133b2b2c9c8344498dbb
```

Token T2 is token A with the last byte of its DER signature changed from `bb` to `ba`, the payload left as it was.

* `verify_manifest(T1, issuer_key)` gives `ok = false`, reason `"manifest signature does not verify"`.
* `verify_manifest(T2, issuer_key)` gives `ok = false`, reason `"manifest signature does not verify"`.

More generally, changing any byte of token A's decoded payload, or of its decoded signature, MUST make verification fail; the rule is stated for token A because its wire JSON has no insignificant whitespace. Some edits to the token text decode to the same bytes, such as the unused low bits of the final base64url character or the case of a hex digit. Those are not forgeries, and a verifier MAY reject them (compatibility note 3).

### Vector 3: the highest valid sequence wins

Token B is a genuine manifest at `seq = 7`, signed by the issuer key. Its wire JSON is:

```
{"v":1,"service":"messaging","seq":7,"updated_at":1700000600,"endpoints":[{"kind":"onion","url":"example.onion","priority":100},{"kind":"https","url":"mirror.example.org","priority":100},{"kind":"https","url":"mirror.example.com","priority":100}]}
```

Token B:

```
eyJ2IjoxLCJzZXJ2aWNlIjoibWVzc2FnaW5nIiwic2VxIjo3LCJ1cGRhdGVkX2F0IjoxNzAwMDAwNjAwLCJlbmRwb2ludHMiOlt7ImtpbmQiOiJvbmlvbiIsInVybCI6ImV4YW1wbGUub25pb24iLCJwcmlvcml0eSI6MTAwfSx7ImtpbmQiOiJodHRwcyIsInVybCI6Im1pcnJvci5leGFtcGxlLm9yZyIsInByaW9yaXR5IjoxMDB9LHsia2luZCI6Imh0dHBzIiwidXJsIjoibWlycm9yLmV4YW1wbGUuY29tIiwicHJpb3JpdHkiOjEwMH1dfQ.3045022100d0db7912eaea0ba4745662540ebd166a836e29d39f0d7353bfa4163e08a341590220659b34996cc50f4dac30a3af8c414c9ebf7ea02e55e7ca1bf8b1af15ad0b8956
```

Token C claims `seq = 9` for the same service and points it at `attacker.example`. It carries a real signature, made with the other key:

```
{"v":1,"service":"messaging","seq":9,"updated_at":1700001200,"endpoints":[{"kind":"https","url":"attacker.example","priority":100}]}
```

Token C:

```
eyJ2IjoxLCJzZXJ2aWNlIjoibWVzc2FnaW5nIiwic2VxIjo5LCJ1cGRhdGVkX2F0IjoxNzAwMDAxMjAwLCJlbmRwb2ludHMiOlt7ImtpbmQiOiJodHRwcyIsInVybCI6ImF0dGFja2VyLmV4YW1wbGUiLCJwcmlvcml0eSI6MTAwfV19.3044022057c5d7813c9704fc8ca267c3f118665403e5f8e9d7d8625fb2ca96d6235e79d602202f9ff08a4416e241a2c64ec476b81f04dd44b779ea5f70a68f1d7125e00d7d4f
```

Expected results:

* `verify_manifest(C, other_key)` gives `ok = true`, and `verify_manifest(C, issuer_key)` gives `ok = false`, reason `"manifest signature does not verify"`.
* `select_current([A, B, C], issuer_key, "messaging")` gives the manifest with `seq = 7`, which is token B. Token C is rejected however high its `seq`, and token A loses to B.
* `select_current([A, B, C], issuer_key, "messaging", min_seq = 8)` gives none.
* `ordered_endpoints(B)` gives `onion example.onion`, `https mirror.example.com`, `https mirror.example.org`. The two `https` endpoints tie on priority and rank, so they are ordered by `url`, against their emit order.

### Vector 4: the version rule (§7, step 4)

All three tokens are signed by the issuer key, have `service = "messaging"` and `seq = 6`, and list a single endpoint.

Token D has `v = 2` and a member this BRC does not define:

```
{"v":2,"service":"messaging","seq":6,"updated_at":1700000300,"endpoints":[{"kind":"onion","url":"example.onion","priority":100}],"future":["ignored"]}
```

Token D:

```
eyJ2IjoyLCJzZXJ2aWNlIjoibWVzc2FnaW5nIiwic2VxIjo2LCJ1cGRhdGVkX2F0IjoxNzAwMDAwMzAwLCJlbmRwb2ludHMiOlt7ImtpbmQiOiJvbmlvbiIsInVybCI6ImV4YW1wbGUub25pb24iLCJwcmlvcml0eSI6MTAwfV0sImZ1dHVyZSI6WyJpZ25vcmVkIl19.30440220026f1dd794a25cbf8404c10338e8ca97ad531b50a41b5ac3ff91963f2e0a8aa902207293236df0f25967ca5731a5b1bd95e7f48837bd9d555fe4befbed40b67ac6f3
```

Token E has `v = 2` and `min_reader = 2`:

```
{"v":2,"service":"messaging","seq":6,"updated_at":1700000300,"endpoints":[{"kind":"onion","url":"example.onion","priority":100}],"min_reader":2}
```

Token E:

```
eyJ2IjoyLCJzZXJ2aWNlIjoibWVzc2FnaW5nIiwic2VxIjo2LCJ1cGRhdGVkX2F0IjoxNzAwMDAwMzAwLCJlbmRwb2ludHMiOlt7ImtpbmQiOiJvbmlvbiIsInVybCI6ImV4YW1wbGUub25pb24iLCJwcmlvcml0eSI6MTAwfV0sIm1pbl9yZWFkZXIiOjJ9.3045022100bab2863b50c5870fb8830b6aa193d135a956b3a3e0370b0ef899ecea287be22302202dd34f27d88913f080c15ef49d4c9b780ae7391a13d809e237f1e67fa7741e41
```

Token F has `v = 0`:

```
{"v":0,"service":"messaging","seq":6,"updated_at":1700000300,"endpoints":[{"kind":"onion","url":"example.onion","priority":100}]}
```

Token F:

```
eyJ2IjowLCJzZXJ2aWNlIjoibWVzc2FnaW5nIiwic2VxIjo2LCJ1cGRhdGVkX2F0IjoxNzAwMDAwMzAwLCJlbmRwb2ludHMiOlt7ImtpbmQiOiJvbmlvbiIsInVybCI6ImV4YW1wbGUub25pb24iLCJwcmlvcml0eSI6MTAwfV19.304402200b617d1280afc72f0b4a489121a901e8f53207ca306f6e4d83d28bc319b606b302205f71cf0576b8a96406896ac2bf833f26f92ca030c085e700960e3ecbf92c70eb
```

A version 1 verifier gives:

* `verify_manifest(D, issuer_key, expected_service = "messaging")`: `ok = true`, `seq = 6`, one endpoint. The unknown member is ignored, and it is still covered by the signature.
* `verify_manifest(E, issuer_key, expected_service = "messaging")`: `ok = false`, reason containing `"requires a newer"`.
* `verify_manifest(F, issuer_key, expected_service = "messaging")`: `ok = false`, reason `"unsupported manifest version"`.

### Vector 5: on-chain scripts (§9)

```
marker_script      = OP_FALSE OP_RETURN "TKRMREG1" = 006a08544b524d52454731
marker_script_hash = SHA256(marker_script), reversed, hex
                   = 6f33820d5cb3f85c23bdd46daeea10bc197c3ef2c93f9a51b6e2e5bdedab49ce
```

The carrier output for token A is 624 bytes. It is these 11 bytes followed by the 613 ASCII bytes of token A:

```
006a 05 544b524d31 4d 6502
```

That is `OP_FALSE OP_RETURN`, a 5-byte push of `TKRM1`, then PUSHDATA2 with length `0x0265` (613).

The ordinal output for token A, locked to a P2PKH whose 20-byte hash is `0x33` repeated, is 661 bytes, with the whole token in one push:

```
76a914 3333333333333333333333333333333333333333 88ac     P2PKH lock
00 63 03 6f7264 51 0a 746578742f706c61696e 00             OP_FALSE OP_IF "ord" OP_1 "text/plain" OP_0
4d 6502 <the 613 bytes of token A>                         PUSHDATA2, 613 bytes
68                                                         OP_ENDIF
```

Reading either output back as in §9 recovers token A exactly.

### Vector 6: build-pin resolution (§11)

`P` is the pinned build key, `S` the stored key and `dev_build` the build-time flag. `K` is the issuer key above and `K2` is the other key.

| Pinned `P` | Stored `S`            | `dev_build` | Result `key` | `conflict` | Meaning                                                                                            |
| ---------- | --------------------- | ----------- | ------------ | ---------- | -------------------------------------------------------------------------------------------------- |
| `K`        | `""`                  | false       | `K`          | false      | The build pin is authoritative.                                                                    |
| `K`        | `K` in upper-case hex | false       | `K`          | false      | Case-insensitive match, accepted; the pinned value is returned.                                    |
| `K`        | `K2`                  | false       | `""`         | **true**   | The stored key tried to replace the pin. Refused; discovery stays inert on the default connection. |
| `K`        | `K2`                  | true        | `""`         | **true**   | A development build cannot relax a pin either.                                                     |
| `""`       | `K`                   | true        | `K`          | false      | Development build with no pin: the stored override is allowed.                                     |
| `""`       | `K`                   | false       | `""`         | false      | Production build with no pin: the stored key is ignored and discovery is inert.                    |
| `""`       | `""`                  | false       | `""`         | false      | Nothing pinned, nothing stored: discovery is inert (fail-closed).                                  |

### Vector 7: the sequence floor (§8)

`update_floor(current, seq) = max(current, seq)`, with an unset floor treated as lower than every `seq`:

| Current floor | Incoming `seq` | New floor                                    |
| ------------- | -------------- | -------------------------------------------- |
| unset         | 5              | 5                                            |
| 5             | 3              | 5 (an older manifest never lowers the floor) |
| 5             | 5              | 5 (the same manifest read again)             |
| 5             | 9              | 9                                            |

Against a floor of 5, a manifest with `seq = 3` MUST be rejected with a reason containing `"rollback"`, and one with `seq = 5` MUST be accepted.

## Implementations and compatibility notes

Four implementations exist. None is public yet, so they are described here rather than linked.

* **A Python reference** signer, verifier and publisher on bitcoinx over libsecp256k1, with RFC 6979 nonces and low-S signatures, refusing high-S ones; it produced every vector signature. Its publisher builds the outputs of §9, reads candidates back out of a transaction, and stops with the transaction id it built when a broadcast outcome is unknown. Its mint tool signs a token for an external broadcaster without a wallet or the network, reading the issuer key only from a key file or a hidden prompt.
* **Two TypeScript readers**, one on `@noble/curves`, which refuses high-S signatures, and one on `@bsv/sdk`, which accepts them. Each sits in an application with the §11 resolver behind a build-time development flag, a persisted floor and a token-source reader, and both ship the pin empty. The `@noble/curves` application runs discovery at start-up and maps `onion`, `https` and `nostr` endpoints to relay URLs. The `@bsv/sdk` application probes `onion` and `https` endpoints, and its start-up hook is present but not yet called.
* **A relay node in JavaScript**, which wraps a token minted elsewhere in the carrier and marker of §9, byte for byte as the reference does, broadcasts it under its own funding key, and records it once handed to the network, without learning whether it was mined. It serves what it has published at `GET /reachability`, unverified, deduplicated, oldest first and at most 50, exactly as received, with `Access-Control-Allow-Origin: *` and the marker script hash.

Vectors 1 to 4 pass in all three readers' tests, Vector 5 in the Python tests and Vector 6 in both applications' resolver tests. A separate program on the Python standard library and OpenSSL checked every vector value as printed, an independent RFC 6979 signer reproduces the Vector 1 signature, and on 20,000 random payloads within §2 the reference's canonical bytes equalled the recursive-sort serialization of the JavaScript readers. The reader behaviour in the notes below was observed by running the three readers on the same tokens. No manifest has yet been published on chain.

### Compatibility notes

1. **Wrongly typed members (§2).** All three readers refuse at step 2 a payload whose `service`, `kind` or `url` is not a string, whose `seq`, `updated_at` or present `priority` is not an integer within ±(2^53 - 1), or whose `endpoints` is not an array of objects, including a signed `null` rewritten as `1e400` (Claim 1). A `v` or `min_reader` that is not such an integer is refused at step 2, 3 or 4, depending on the reader and the spelling.
2. **Float spellings and unread members (§§2 and 5).** A signed integer written on the wire as `5.0` or `5e0` is refused by the reference, whose decoder returns a float, and read as the integer by the JavaScript readers. In a member no reader reads, the reference accepts an integer beyond the bound and refuses any float, and the JavaScript readers accept any value whose canonical form reproduces the signed bytes, `1e400` for a signed `null` included, and refuse an integer a double cannot hold, such as 2^53 + 1.
3. **Encodings of one token (§§4 and 6).** None of these is a forgery: each decodes to the signed bytes or fails.

   | Variation of a genuine token                                 | Reference | `@noble/curves` | `@bsv/sdk` |
   | ------------------------------------------------------------ | --------- | --------------- | ---------- |
   | High-S twin of the signature                                 | refuses   | refuses         | accepts    |
   | DER `r` without its `0x00` sign byte                         | refuses   | refuses         | accepts    |
   | Other malformed DER (padding, length, trailing byte, tag)    | refuses   | refuses         | refuses    |
   | Whitespace inside or after the signature hex                 | accepts   | refuses         | refuses    |
   | Whitespace before or inside the base64url part               | refuses   | refuses         | accepts    |
   | The standard base64 alphabet                                 | accepts   | refuses         | accepts    |
   | Non-zero unused bits in the last base64url character         | accepts   | refuses         | accepts    |
   | Wire JSON in UTF-16                                          | accepts   | refuses         | refuses    |
   | An integer written `5.0` or `5e0` (note 2)                   | refuses   | accepts         | accepts    |
   | A signed `null` written `1e400` in an unread member (note 2) | refuses   | accepts         | accepts    |

   All three accept upper-case signature hex, restored `=` padding, a UTF-8 byte order mark, and wire JSON re-spelled by whitespace, member order, `\u` escapes or a repeated member ending in the signed value. A source that serves only a variant some readers refuse withholds the manifest from them; the relay node serves tokens as received, so one stored with a trailing line ending verifies only in the reference.
4. **Non-ASCII names and strings (§§2, 5 and 10).** With member names U+FFFF and U+1F600 the reference verifies and the readers, which sort by UTF-16 code unit, fail. A signed lone surrogate verifies only in the readers, and locators that differ above U+FFFF tie-break differently.
5. **Locators and kinds (§§3 and 10).** No reader checks a locator against its kind, and all three rank 99 a kind named after an inherited JavaScript property, such as `constructor`. The `@bsv/sdk` application keeps an explicit `http://` or `https://` scheme, so an `onion` endpoint written as a clearnet URL is fetched over the clearnet and an `https` endpoint written `http://` without TLS. The `@noble/curves` application keeps an explicit `ws://` or `wss://` scheme, and in its Tor-only mode refuses every host that is not an onion, whatever the label.
6. **The floor and the token source (§§8 and 12).** The `@noble/curves` application raises its floor at selection, the `@bsv/sdk` application only after an endpoint answers (Claim 6). Each keeps one floor, not keyed by issuer key, in browser storage with no integrity check, beside the token-source URL, the Tor-only setting and an opt-out flag; after a key rotation the new key's manifests are refused below the old floor. Both refuse to record a `seq` that is not an integer within the bound and treat such a stored floor as none, logging an error, and in a Tor-only mode both refuse a token source whose parsed host is not an onion.
7. **Probing and `bsv` (§§10 and 12).** On total failure both applications keep their default connection without telling the user. The `@noble/curves` application does not probe; it takes the first relay URL. The `@bsv/sdk` application has an unchecked path for fetching `bsv` content through a clearnet gateway, which never runs because its only caller excludes `bsv`.
8. **Reading transactions (§9).** The reference, the only listed parser of transactions, reads a carrier only when it begins `00 6a`, accepts every push encoding, stops at the first opcode that is not a push, takes only the push after the tag, and does not search a carrier for an inscription. It reads the first inscription envelope anywhere in the script, matching `text/plain` as a case-sensitive prefix.
9. **Signer checks (§§2, 3 and 8).** The reference's builder and signing call both refuse every payload §§2 and 3 forbid a signer to write, and a negative `seq`, truncating nothing; the builder warns when no `onion` endpoint is listed. Nothing refuses a reused or lower `seq`.
10. **Reason strings (§15).** The reference words its `min_reader` reason differently from §15, but it contains `requires a newer`.

## Design rationale and alternatives considered

* **The chain**, because a mined output stays in every unpruned copy of its block without anyone's cooperation, where an IPNS record lapses unless republished and a Nostr relay keeps what it chooses.
* **A pinned key**, because a key fetched at run time or published at a domain arrives through the channel a takedown attacks.
* **`seq`, rather than a timestamp**, because an offline signer may lack a trustworthy clock, and time ordering lets one future-dated record hold off every later one. 2^53 values last about 285 million years at one per second.
* **An equal `seq` passes the floor**, so that a restart is not mistaken for a rollback.
* **A bare ECDSA signature**, because Bitcoin Signed Message shares one prefix among all its applications and wallets sign user-supplied text under it on request, while BRC-3 and BRC-77 would put BRC-42 key derivation into every reader and the offline ceremony.
* **This canonical JSON**, one standard-library call in Python and a short function in JavaScript (Claim 2); BRC-192's stricter rules would change what existing readers accept without changing what an honest signer writes.
* **Closed kinds with fixed ranks**, so that the try-order is a function of the manifest and encodes each kind's takedown posture.
* **No expiry**, because with an offline key, expiry would let anyone who can prevent the next publication end discovery for every client.
* **Two on-chain forms**, because pruning nodes and indexes of spendable outputs drop the carrier, while the ordinal stays in every node's unspent set until it is spent.
* **No overlay topic**, because a topic manager runs on an overlay host, which the client cannot yet reach; a federated topic could later be one token source among several.
* **One issuer key**, for one verification per candidate and the simplest ceremony; a threshold would need a new payload version and signing domain (§14).
* **A floor kept by the client**, one integer, where order on chain would need proofs of inclusion against a chain of headers.

## Mathematical and cryptographic basis

`||` is concatenation and `|X|` a length in bytes. `n` and `G` are the order and base point of secp256k1 (SEC 2), `d` the issuer private scalar, `Q = d*G`, `H` SHA-256 and `D` the 19 bytes `bsv-reachability-v1`. `C` is `canonical` of §5 and `E(P) = D || 0x00 || C(P)`; `C_W` and `E_W` are the same computed by a reader `W` from the value its decoder returns. ECDSA verification of `(r, s)` over a digest `e` under `Q` (SEC 1, §4.1.4) checks `1 <= r, s < n`, computes `R = (e * s^-1)*G + (r * s^-1)*Q`, and accepts if `R` is not the point at infinity and its x-coordinate modulo `n` is `r`. The payload space is:

```
V    = null | true | false | Int | Str | Arr | Obj
Int  = the integers i with -(2^53 - 1) <= i <= 2^53 - 1
Str  = finite sequences of Unicode scalar values (U+0000 to U+D7FF and U+E000 to U+10FFFF)
Arr  = finite sequences of elements of V
Obj  = finite maps from member names to elements of V, where a member name is an
       element of Str whose characters are all below U+0080
```

A payload is an element of `Obj` with the members of §2; the bounds come from §2, §5 and the reference signer's UTF-8 step, which refuses a lone surrogate. All the listed verifiers resolve a member repeated on the wire to its last occurrence.

### Claim 1: the encoding is injective

**Claim 1.** `C` is injective on `V`, and so is `E`.

*Proof sketch.* A JSON parser (RFC 8259) that returns integers exactly and strings as scalar values inverts `C`, by induction on `P`. Literals are written as themselves. An integer in decimal without fraction or exponent is read back exactly, in JavaScript too, since every element of `Int` is a distinct double. A string's escapes and UTF-8 are uniquely decodable, with no unescaped `"` inside. Arrays and objects are recovered element by element, and a map has distinct names. The fixed prefix `D || 0x00` keeps the encoding injective. ∎

Sorting, the fixed separators, the ASCII names and the integer bound play no part in the proof; they make implementations agree on `C`. Each reader's `C_W` is injective with two exceptions, both in the JavaScript readers: a `-0` encodes as `0`, which nothing in §§7, 8 or 10 distinguishes, and a number too large for a double, such as `1e400`, decodes to an infinity, which `JSON.stringify` writes as `null`. Anyone can therefore write `1e400` where the issuer signed `null` and the signature still verifies, but the listed readers refuse an infinity, as they refuse `null`, in every member they read (compatibility note 1). Security does not depend on the rules that define `V`: a payload outside `V` can only make readers disagree on whether a genuine token verifies.

### Claim 2: the implementations compute the same encoding

**Claim 2.** On `V`, the Python rule of §5 and the recursive sort of the TypeScript readers produce the same bytes, equal to the output of the RFC 8785 serialization steps (its §3.2), and to the RFC 8785 canonical form when no string holds a Unicode noncharacter.

*Argument.* Separators, literals and string escapes coincide, and every other scalar value, U+007F, U+2028 and U+2029 included, is written as UTF-8. RFC 8785 writes numbers as `Number.prototype.toString`, which gives every element of `Int` as plain digits, as Python does. RFC 8785 and the readers sort names by UTF-16 code unit and Python by code point, which agree on ASCII. RFC 8785 requires I-JSON input, which forbids noncharacters (RFC 7493 §2.1); `Str` admits them, and §5 writes them as the RFC 8785 serialization steps would. Outside `V` the rules part on floats (`5.0`, `1e+16`), on integers beyond 2^53, on lone surrogates, which Python and RFC 8785 refuse and the readers escape, and on the infinities, which the readers write as `null`.

### Claim 3: unforgeability

The adversary knows `Q`, may obtain issuer tokens for payloads of its choice, controls every token source and may submit any string. It wins if a conforming reader `W` accepts a token whose signed bytes `E_W(P*)` differ from `E(P_i)` for every payload `P_i` the issuer signed. The assumption is that ECDSA over secp256k1, signing `M` by signing `H(M)`, is existentially unforgeable under an adaptive chosen-message attack (EUF-CMA).

**Claim 3.** For every such adversary `A` that obtains `q` issuer tokens, there is an adversary `B` against ECDSA with SHA-256 that makes `q` signing queries, runs in the time of `A` plus `q + 1` canonical encodings, and forges with probability at least `Pr[A wins]`.

*Proof.* `B` runs `A` on `Q`, answering each query `P_i` by asking its oracle to sign `M_i = E(P_i)`. When `A` outputs a token that `W` accepts, `B` decodes it as `W` does into `P*` and `sigma*` and outputs `(E_W(P*), sigma*)`. `W` accepted at step 3 of §7, so `sigma*` verifies over `H(E_W(P*))`, and `A` won, so `E_W(P*)` is no `M_i`. ∎

The reduction is tight, and a SHA-256 collision, or two digests congruent modulo `n`, is already a forgery for `B`. The build pin (§11) is what fixes `Q` in this game.

**Strength of the assumption.** EUF-CMA security of ECDSA is proved only in idealized models (Brown, 2005; Fersch, Kiltz and Poettering, 2016), and the best known attacks are generic: Pollard's rho needs about 2^127 group operations, a SHA-256 collision about 2^128 evaluations, and two uniform 256-bit digests are congruent modulo `n` without being equal with probability about 2^-382.7. An attacker with 2^80 operations succeeds with probability below about 2^-94. A quantum computer running Shor's algorithm at this size recovers `d` from the public `Q`, and this BRC claims nothing against one.

### Claim 4: one token, one meaning

**Claim 4.** (1) If two conforming readers both accept the same token, they verified the same signed bytes, unless one accepted a forgery in the sense of Claim 3. (2) If a reader accepts a token whose signed bytes are `E(P)`, where every member of `P` that this BRC defines has the type §2 gives it, as in every payload a conforming signer produces, it reads the manifest it reads from the issuer's own token for `P`, except that a `-0` may stand for a `0`.

*Proof.* (1) The three DER parsers read the same `(r, s)` from every encoding more than one of them accepts. Suppose the readers verified different messages with digests `e1` and `e2` modulo `n`. Both were issued, or one reader accepted a forgery (Claim 3), and the token's `(r, s)` is the issuer's signature over one of them, say the first, or its twin. Outside a fraction of about 2^-127 of signatures, `(r, s)` verifies over exactly two digests, `e1` and `e1' = -e1 - 2*r*d mod n`. If `e2 = e1`, two issued messages have congruent digests (Claim 3). If `e2 = e1'`, then since `d = -(e1 + e1') / (2r) mod n`, a payload chosen without `d` lands there with probability about `1/n`. (2) The decoded value `P*` has `C_W(P*) = C(P)`. `C_W` is injective except that `-0` shares the encoding of `0` and the infinities that of `null` (Claim 1), and `null` occurs in `C(P)` only where `P` holds it, never in a defined member, so `P*` agrees with `P` on every member the reader reads, up to `-0`. ∎

The listed readers refuse every token in which a member they read lacks its §2 type (compatibility note 1), so for them part (2) holds without its condition. A token's text does not identify a manifest, since anyone can derive many accepted variants of one genuine token (compatibility note 3), the high-S twin `(r, n - s)` among them. Strong unforgeability is not needed, because every check after step 3, selection and the endpoint order depend only on the decoded payload; deduplicating candidates by canonical bytes bounds them, and deduplicating by token text does not.

### Claim 5: domain separation

**Claim 5.** If `D1` and `D2` are distinct non-empty byte strings that contain no `0x00`, no byte string begins with both `D1 || 0x00` and `D2 || 0x00`.

*Proof.* If `|D1| = |D2|`, the prefixes differ within their first `|D1|` bytes. If `|D1| < |D2|`, byte `|D1|` (counting from zero) is `0x00` in the first and a byte of `D2`, which is not `0x00`, in the second. The case `|D1| > |D2|` is symmetric. ∎

A protocol that signs under its own such tag therefore has messages disjoint from this BRC's, and a signature valid in both needs congruent digests or the second digest of Claim 4. Transactions and Bitcoin Signed Message sign `H(z)` for a 32-byte `z`, while every object that passes step 2 in the listed verifiers contains `endpoints`, so `|E(P)| >= 36` and the same holds. A BRC-77 signature is made under a child key `Q + h*G` and verifies under `Q` only if `h` is congruent to 0 modulo `n`. The separator does not stop a signing oracle, which is why §11 confines the issuer key to tagged records, nor separate services under one key, which the signed `service` and step 5 do. The signed bytes name no network or output, so a copied token is the same manifest.

### Nonces

ECDSA sets `r` to the x-coordinate of `k*G` modulo `n` and `s = k^-1 * (e + r*d) mod n` for a nonce `k` that must be secret, uniform and never reused. Two signatures that share `r` over digests that differ modulo `n` give up `d` whatever low-S normalization did, since an attacker can solve `k = (e1 - e2) / (s1 - eps*s2) mod n` for `eps` equal to 1 and -1 and keep the `d = (s1*k - e1) / r mod n` with `d*G = Q`; a few biased nonce bits across many signatures let lattice reduction do the same. The reference derives `k` by RFC 6979 before normalizing to low-S, so signing is deterministic, its nonces are pseudorandom when HMAC-SHA-256 is modelled as a random oracle, and signing needs no random number generator, though generating `d` does. RFC 6979 is not required. Deterministic signing is exposed to fault injection, and the reference does not verify its own output, so an issuer on untrusted hardware can verify each token under `Q` before publishing it.

### Claim 6: rollback

`G_S` is the set of payloads the issuer has signed for a service `S`, each with the members and types of §2, and the issuer discipline is that distinct payloads in `G_S` have distinct `seq` values (§8). The floor `F` is an integer or unset, and unset is below every integer. In round `t` the adversary supplies any finite list of strings, and `A_t` is the set of payloads decoded from them that pass `verify_manifest` with `expected_service = S` and `min_seq = F_(t-1)`. If `A_t` is not empty the client selects the element `m_t` with the greatest `seq` and sets `F_t = max(F_(t-1), seq(m_t))`; otherwise `F_t = F_(t-1)`.

**Claim 6.** Unless a forgery occurs (Claim 3), in every round:

1. every selected `m_t` is in `G_S`;
2. `F_t >= F_(t-1)`, and `seq(m_t) >= F_(t-1)`;
3. if rounds `t < t'` both select, then `seq(m_t') >= seq(m_t)`;
4. if the list in round `t` contains an accepted encoding of some `g` in `G_S` with `seq(g) >= F_(t-1)`, then `seq(m_t) >= seq(g)`, and under the issuer discipline `m_t` is `g` or a later genuine manifest;
5. under the issuer discipline, a manifest whose `seq` equals the floor is the manifest that set the floor.

*Proof.* (1) follows from step 3 of §7, Claims 3 and 4, and step 5. (2) follows from the definition of `F_t` and from step 6, since by Claim 4 the `seq` read from a genuine payload is the integer signed. (3) `seq(m_t') >= F_(t'-1) >= F_t >= seq(m_t)`, by (2). (4) holds because `g` is in `A_t`. (5) The floor equals `seq(m_u)` for the round `u` that last raised it, and under the discipline only `m_u` has that `seq`. ∎

So the adversary can choose, in each round, any genuine manifest for `S` at or above the floor, or prevent selection, and nothing more. The claim rests on four conditions. **The floor is raised on selection** (§12, step 4); a client that raises it only after an endpoint answers gets (3) only for rounds in which it connected. **The issuer discipline holds**; otherwise two genuine payloads tie, and the order of the adversary's list decides. **The floor survives in client storage**; a party that can write storage can lower it, though it cannot introduce a forgery or, by §11, replace the key. **Every genuine payload has the types of §2**; a reader that read a wrongly typed `seq` loosely could break (2) and (3), as JavaScript reads a non-numeric `seq` as `NaN`, which passes a `<` test against any floor, and a signed `null` rewritten as `1e400` as an infinity, which wins selection. The listed readers refuse both.

None of the claims concerns freshness, control of endpoints, recovery from key theft, confidentiality or quantum attack.

## Security Considerations

### What a verified manifest proves

A manifest that passes §7 and wins selection under §8 proves that the holder of the pinned issuer key signed this list of endpoints, for this service, at this sequence number. It does not show that the manifest is the newest (a source can withhold, and a fresh install accepts any genuine manifest ever signed), that it is the only one at its `seq`, that it was ever published on chain, that any endpoint is honest, still the service's, or reachable, or, for a payload no conforming signer produces, what its values mean. Discovery therefore also rests on the build that carries the pin (§11), on the token sources (§13), and on the kind of locator each endpoint uses.

A **self-certifying** locator commits to a key or to content: an onion address encodes the service's key, a `bsv` inscription id names a transaction whose hash commits to the bytes, and an IPFS identifier hashes its content. A **delegated** locator, such as an `https` name, an `ip` address or a relay's domain, is assigned by a third party and can change hands while a manifest that lists it stays valid; `bsv` and `ipfs` become delegated to any gateway the client lets check the content. Every attack that makes a client act on an older list does its harm through delegated locators, or through self-certifying ones whose keys have been taken.

### Adversaries

| Adversary                         | Can                                                                                                                                       | Mitigation                                                                                                                       | Residual risk                                                                                                     |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Registrar or DNS provider         | suspend (RFC 5731 `serverHold`, `clientHold`), transfer or redirect names and obtain certificates for them, which DNSSEC does not prevent | the pin never passes through DNS; `onion` and `bsv` have no registry; a new manifest without the name                            | a client shown an older manifest reaches the wrong party over valid TLS; a web client loaded from a seized origin |
| Host or cloud provider            | stop, image or impersonate hosted endpoints with their TLS and onion keys; run a lying token source                                       | the issuer key is offline (§11); a new manifest, with a new onion address, since none can be revoked; endpoints across providers | seized endpoints, until a client sees the new manifest                                                            |
| App store or other code channel   | delist the app, block updates, ship its own build if it holds the signing key                                                             | installed builds keep their pin; the `bsv` kind; other channels                                                                  | a blocked rotation build; a replaced pin; trust on first install                                                  |
| Network observer or censor        | see connections; block listed endpoints, the token source or Tor                                                                          | `onion` over Tor with a pluggable transport; mirrors handed out by other means                                                   | listed mirrors; Tor itself blocked                                                                                |
| Malicious or lagging token source | withhold, freeze, choose among genuine manifests, serve variants, flood, log clients                                                      | verification; the floor; selection over several sources, one honest and current                                                  | staleness for a client with one source, as in each listed application                                             |
| Chain spammer                     | publish junk and replayed tokens without limit                                                                                            | verification never raises; the marker is optional (§9)                                                                           | an index that caps or truncates the marker's history can return nothing usable, or omit the newest manifest       |
| Miner                             | decline to mine a publication                                                                                                             | other miners; tokens served off chain stay valid                                                                                 | miners acting together                                                                                            |
| Ordinals indexer or gateway       | withhold inscriptions; serve other bytes for `bsv`; serve all inscriptions from one shared web origin                                     | a client that checks the transaction; an origin of the shell's own                                                               | a client that runs unchecked bytes (no listed one fetches `bsv`)                                                  |
| Issuer-key thief                  | anything a manifest can express, for every client                                                                                         | offline custody (§11); monitoring the chain                                                                                      | full trust in the stolen key until a new build arrives                                                            |
| Insider at the service            | sign, if at the ceremony; republish, withhold or serve selectively                                                                        | separate funding and signing keys; a signing record                                                                              | an insider with the key is a thief                                                                                |
| Client-storage attacker           | rewrite the floor, token source, privacy mode and opt-out flag                                                                            | §11 stops it substituting the key                                                                                                | rollback, freezing and loss of Tor-only mode                                                                      |

**Issuer-key thief.** A compromised issuer key is a total compromise. A thief can list endpoints it controls at any priority, an onion address included; put a clearnet locator under the `onion` label (*Locator confusion*); serve its own application through `bsv`, which makes the issuer key a code-signing key; list a victim's URL, or loopback and private addresses, for every client to probe; exhaust the floor; sign a second manifest at the current `seq`, which moves clients without raising any floor (*Equivocation*); sign wrongly typed members, which a loosely typed reader would let past its floor; and serve tokens privately, leaving no public trace. An issuer that records every token it signs can watch the chain for others that verify under its key, as a domain owner watches Certificate Transparency, but that catches only a thief who publishes, and floors the thief raised stay raised after rotation. A quantum computer running Shor's algorithm would make anyone a thief; a new domain string (§14) gives a later version a path to another signature scheme.

**Client-storage attacker.** An information stealer, an injected script, or any inscription sharing a gateway origin with the shell can write client storage, and relaxing §11 so that a stored key overrides the pin is a critical vulnerability. The listed applications keep the other settings in browser storage with no integrity check (compatibility note 6), so an attacker can lower the floor, opening rollback to any genuine manifest; raise it past any future `seq`; point the client at its own token source, which freezes manifests and logs the client's address long after the foothold is gone; or turn Tor-only mode or discovery off.

### Freshness and the freeze attack

A freeze attack, in TUF's terms, withholds newer metadata without presenting anything invalid. Here it needs only control of a client's token sources, or one lagging source, and the floor does not help. If endpoints have only died, the client falls back to its default connection; if a delegated locator in the stale list has changed hands, or a self-certifying one has lost its key, the client connects to the new holder. There is no expiry (Design rationale), and `updated_at`, which no listed implementation reads, is the offline signer's own claim; a later freshness rule would need a time the client can trust, such as the mining block's. A publisher limits the damage by preferring self-certifying kinds and by keeping control, for good, of every delegated locator any acceptable manifest has listed.

### Equivocation

Two different valid manifests at one `seq` come from an operator's mistake, which §8 forbids, or from a key thief. The listed implementations keep whichever they meet first, and a client whose floor equals that `seq` accepts either, so a thief can move clients between them without raising any floor. A deterministic rule such as Nostr's lowest id would make clients agree, though a key holder can grind payloads until its own wins, and treating two manifests at the highest `seq` as evidence of compromise would defeat the attack. This version does neither.

### Floor exhaustion and recovery

A signed `seq` cannot be withdrawn, so one signed in error, such as a test manifest under the production key, moves the floor of every client that reads it, permanently. A thief who signs at `seq = 2^53 - 1` leaves the issuer able to publish only at that `seq`, with selection following discovery order: TUF's fast-forward attack. The listed readers refuse a larger `seq` (compatibility note 1), though §2 binds only signers. A rotation build stops the thief's manifests verifying, but a floor stored without the key it was earned under, as in the listed applications, refuses the new key's manifests below it, and this BRC does not specify how a new build resets it.

### Key rotation, revocation and compromise recovery

The pin changes only with a new build (§11), the price of keeping runtime state, DNS and manifests from moving the trust root. There is no revocation, so a client trusts a stolen key until it installs a build without it; recovery has no time bound if the channel that would deliver that build is under attack; and a key that signs for several services (§1) exposes all of them. DNSSEC's in-band rollover (RFC 5011), TUF's chained root metadata and KERI's pre-rotation each move a trust root under rules fixed beforehand, and each could be added in a later version without weakening the pin.

### Downgrade of endpoint kinds

A party that makes preferred endpoints fail, for example by blocking Tor, moves a client down the order of §10 onto delegated and clearnet kinds if its `allowed_kinds` admits them. Failures never add a kind, which is why §10 requires a privacy-sensitive client to keep to `onion`. Two downgrades lie outside `allowed_kinds`: the publisher sets `priority` and can place `https` ahead of `onion`, and `allowed_kinds` filters on the signed label, so it is only as strong as the client's check that each locator matches its label.

### Parser differentials

Because a verifier canonicalizes the very object it then reads (§5), a parser differential can make verifiers disagree about validity, and cannot, without the key, make them accept different signed bytes or, for a payload a conforming signer produces, different contents (Claim 4). Each differential in compatibility notes 1 to 4 changes only whether a token is accepted, except the tie-break of note 4, which changes only the order in which endpoints are tried.

### Resource exhaustion

Everything before step 3 of §7 runs on unauthenticated input, and this version bounds none of it; the listed readers set no timeout or size limit on the fetch of §13. In testing every reader refused a 20 MB token, a 5,000-digit number and 200,000 levels of nesting cleanly, but a well-formed junk candidate costs one verification, 2 to 3 ms in each JavaScript reader, and a manifest with thousands of endpoints can keep a resolver probing for hours. A source can withhold anyway, so a client loses nothing by bounding response size and duration, token length, candidates verified and endpoints probed.

### Locator confusion

The issuer signs each locator, so confusion is first a hazard at signing time, which the printable-ASCII recommendation of §2 addresses by forcing an internationalized name into its `xn--` form. Each kind has confusions of its own:

| Kind    | Confusions                                                                                                                                                                       | The check that resolves them                                                                                |
| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `onion` | a vanity address sharing a prefix with the real one; `.onion` in a clearnet path or label, as in `a.onion.example.com`; a clearnet URL under the `onion` label                   | compare all 56 characters; take the host from a parsed URL; require a version 3 address                     |
| `https` | an explicit `http://` or `ws://` scheme; user information, as in `mirror.example.com@attacker.example`; a numeric host that parsers rewrite, as `2130706433` becomes `127.0.0.1` | parse the URL; require a TLS scheme; refuse user information and loopback, link-local and private addresses |
| `ip`    | the same numeric forms; no key bound to the endpoint                                                                                                                             | treat the endpoint as lower assurance (§3)                                                                  |
| `bsv`   | a bare txid, which leaves the output to the gateway; another ordinal scheme's `i` separator; a txid's display byte order                                                         | use `<txid>_<vout>`, then fetch and check the transaction                                                   |
| `ipfs`  | a mutable `/ipns/` or DNSLink name                                                                                                                                               | require an immutable content identifier                                                                     |
| `nostr` | a public-key, profile or NIP-05 identifier where a relay URL is meant; `ws://` without TLS                                                                                       | require a `wss://` relay URL                                                                                |

This version requires none of these checks, and the listed applications differ in which they apply (compatibility note 5).

### Summary of residual risks in this version

These limitations are stated so that a deployment can plan for them; none is a requirement.

* No manifest expires, and nothing bounds how stale a client's view can be (*Freshness and the freeze attack*).
* There is no revocation, successor key or threshold of keys (*Key rotation, revocation and compromise recovery*).
* The floor is not bound to the issuer key, and nothing resets it on rotation (*Floor exhaustion and recovery*).
* Two manifests at one `seq` are resolved by discovery order (*Equivocation*).
* A verifier is not required to check the types of a correctly signed payload, and one that converted them loosely could be steered past its floor (compatibility note 1).
* Nothing bounds what a client accepts from a source (*Resource exhaustion*).
* Locator syntax is unchecked, and nothing ties a locator to its kind (*Locator confusion*).
* A client does not learn whether a token was ever published, and each listed application reads one token source.

## Privacy Considerations

A manifest provides authenticity and integrity, and no confidentiality: its endpoints are meant to be found. What matters is who learns what when a client uses one, and what an operator reveals by publishing one.

| A client's contact                      | Who learns its network address                                         | What else they learn                                                         |
| --------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| A token source over the clearnet        | the operator and the network path                                      | that the client uses discovery, and perhaps for which service                |
| A token source that is an onion service | nobody on the path                                                     | that someone uses discovery                                                  |
| A marker lookup at an index             | the index operator                                                     | that the client seeks manifests, and from its next fetches for which issuers |
| A `bsv` or `ipfs` endpoint              | the gateway operator, or for native IPFS the peers asked for providers | an identifier that names the service                                         |
| An `onion` endpoint                     | nobody, given Tor                                                      |                                                                              |
| An `https` endpoint                     | the server, the DNS resolver and the path                              | the host name, unless Encrypted Client Hello hides it                        |
| An `ip` or `nostr` endpoint             | the server or relay and the path                                       | the service's address, or the client's subscriptions                         |

Every endpoint probed learns of the client, including those that fail. The token source is contacted first, so a Tor-only client that fetches tokens over the clearnet gains nothing from an onion-only `allowed_kinds`; a source's onion status is judged from the parsed host, since a clearnet URL can carry `.onion` in its path.

Publishing on chain publishes for good. The sequence of manifests is a public, dated map of every mirror, address and onion service the service has listed, so a home address or staging host has no place in one, and an onion address listed beside a clearnet endpoint on the same machine reveals where the onion service runs. The issuer key ships in every build and can be tested against every manifest, so services that share a key are publicly linked; the funding transaction ties the publisher's coins to the service; and `updated_at` and mining times show when the operator works.

## Deployment and operational guidance

These practices follow from the analysis above and are not requirements.

* **The ceremony.** Generate the issuer key on a machine that never connects to a network, sign there, and carry out only the finished token. Pass the key to the signing tool through a file or a prompt, never on a command line or in an environment variable, where shell history and process listings keep it, as the listed mint tool requires. Keep a record of every token signed, apart from the key.
* **Sequence numbers.** Take each new `seq` as the next integer after the last one signed for the service, check it against the record, and sign test manifests with a test key.
* **Endpoints.** Prefer self-certifying kinds, include an `onion` endpoint, spread clearnet endpoints across providers, and keep every listed domain registered indefinitely.
* **Publication.** Republish when an endpoint changes, use more than one token source, and strip line endings from a token before publishing it.
* **Monitoring.** Watch the chain for any token that verifies under the issuer key and is missing from the signing record, and for two manifests at one `seq`.
* **Clients and rotation.** Ship the pin empty until the real key exists, use an onion token source in a Tor-only mode, and raise the floor at selection (§12). Plan a rotation build, including how it treats a floor raised under the old key, before it is needed.

## Backwards compatibility

No manifest has been published on chain under this or any earlier form of the scheme, and the listed implementations ship with discovery inert, so this version has no predecessor to stay compatible with. Its forward compatibility rests on §§7 and 14: a version 1 reader accepts later additive versions, and a publisher marks a breaking one with `min_reader`.

## References

* [BRC-3](/brc/wallet/0003.md): Digital Signature Creation and Verification.
* [BRC-22](/brc/overlays/0022.md): Overlay Network Data Synchronization.
* [BRC-24](/brc/overlays/0024.md): Overlay Network Lookup Services.
* [BRC-42](/brc/key-derivation/0042.md): BSV Key Derivation Scheme (BKDS).
* [BRC-43](/brc/key-derivation/0043.md): Security Levels, Protocol IDs, Key IDs and Counterparties.
* [BRC-68](/brc/peer-to-peer/0068.md): Publishing Trust Anchor Details at an Internet Domain.
* [BRC-77](/brc/peer-to-peer/0077.md): Message Signature Creation and Verification.
* [BRC-88](/brc/overlays/0088.md): Overlay Services Synchronization Architecture (SHIP, the Services Host Interconnect Protocol, and SLAP, the Services Lookup Availability Protocol).
* [BRC-101](/brc/overlays/0101.md): Diverse Facilitators and URL Protocols for SHIP and SLAP Overlay Advertisements.
* [BRC-169](/brc/peer-to-peer/0169.md): Universal Handle Addressing and Resolution for the Metanet.
* [BRC-178](/brc/overlays/0178.md): Race-Settled Collection Markets for Overlay Lookups and Message Boxes (its section on bootstrapping and default SLAP trackers).
* [BRC-179](/brc/wallet/0179.md): Signed Capability Manifest for Wallet and Tool-Server Method Exposure (section 9, the build pin and its limit).
* [BRC-180](/brc/overlays/0180.md): Overlay Service Discovery at an Internet Domain.
* [BRC-183](/brc/overlays/0183.md): A Framework for Strict and Federated Overlays.
* [BRC-192](/brc/apps/0192.md): Application Output Knowledge and Source Adapters (section 1, shared representation rules).
* [BRC-194](/brc/overlays/0194.md): Overlay Capabilities and Non-Final Proposal Sessions.
* RFC 2119: Key words for use in RFCs to Indicate Requirement Levels, and RFC 8174: Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words.
* RFC 4033, RFC 4034 and RFC 4035: DNS Security Introduction and Requirements, Resource Records, and Protocol Modifications.
* RFC 4648: The Base16, Base32, and Base64 Data Encodings (§5, base64url).
* RFC 5011: Automated Updates of DNS Security (DNSSEC) Trust Anchors.
* RFC 5731: Extensible Provisioning Protocol (EPP) Domain Name Mapping (the `serverHold` and `clientHold` statuses).
* RFC 6979: Deterministic Usage of the Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA).
* RFC 7493: The I-JSON Message Format (§2.1, surrogates and noncharacters).
* RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format.
* RFC 8785: JSON Canonicalization Scheme (JCS).
* RFC 9162: Certificate Transparency Version 2.0 (obsoletes RFC 6962).
* SEC 1: Elliptic Curve Cryptography, and SEC 2: Recommended Elliptic Curve Domain Parameters (ECDSA, secp256k1).
* D. R. L. Brown, "Generic Groups, Collision Resistance, and ECDSA", Designs, Codes and Cryptography 35, 2005.
* M. Fersch, E. Kiltz and B. Poettering, "On the Provable Security of (EC)DSA Signatures", ACM CCS 2016.
* The Update Framework (TUF) specification, version 1.0: roles, the rollback, freeze and fast-forward attacks, and root rotation.
* IPNS Record and Protocol specification: the sequence number, validity and DHT record lifetime.
* Nostr NIP-01 (replaceable events and the lowest-id rule), NIP-05 and NIP-65 (relay list metadata).
* KERI, Key Event Receipt Infrastructure: pre-rotation.
* ElectrumX protocol documentation, "Script Hashes": `SHA256(scriptPubKey)`, byte-reversed, in hex.
* 1Sat Ordinals inscription envelope (content type `text/plain`, the body in one push after `OP_0`), used by the ordinal form.
* Tor Rendezvous Specification, version 3: self-authenticating onion service addresses.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://hub.bsvblockchain.org/brc/overlays/0211.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
