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

# Registry-Free Typed Content Anchor with On-Chain Code Provenance

RexStarBSV (<294282606+RexStarBSV@users.noreply.github.com>)

## Abstract

This BRC specifies a **registry-free typed content anchor**: a deterministic scheme for committing to *typed* content with a single 32-byte anchor and no central registry. An anchor is `SHA-256(INKAN_DOMAIN || u8(len(kind)) || kind || data)`: a fixed domain string separates these digests from every other SHA-256 usage, a one-byte length prefix makes the `kind`/`data` boundary unambiguous for all inputs, and the `kind` string is *inside* the digest so two anchors of different types never collide even when the underlying `data` is identical. Because the type is carried in the digest and the type namespace is content-derived, third parties assign their own kinds and tiers (`[namespace:]name[@t1|t2|t3]`) without asking anyone, and the same kind renders and verifies identically on every device, forever. Anchors serialize to self-identifying **`ik1` codes** whose version prefix keeps the construction hash-agile. The same primitive produces **software/build provenance**: a 1-of-1 (1Sat) code-provenance ordinal carrying the seal of a released build's anchor, which any client re-verifies against the build it is executing, published beside a per-adopter tamper fingerprint over the same bytes; signed statements are published as immutable on-chain data records. This BRC defines the anchor construction, the code format, the open kind system, the tamper fingerprint, and the on-chain provenance record, in enough detail for an independent second implementation to interoperate byte-for-byte.

In plain terms: this standard describes a way to take any piece of digital content (an address, a document, a contract, a software release) and produce a short, permanent fingerprint for it that also records what kind of thing it is. Two different kinds of content can never share a fingerprint, even when their raw bytes are identical, and anyone can invent a new kind for their own application without asking a registry, a committee, or this document. The fingerprint writes out as a compact text code, renders as a deterministic visual seal that looks the same on every device forever, and can be published on the BSV chain as a permanent, timestamped record. The same mechanism lets a software project publish the fingerprint of a released build on chain, so any user can check that the code they are running is exactly the code that was released. The scheme is published as the **Inkan Standard v1.0** (frozen) by RexStarBSV, with a complete Python reference implementation and JavaScript and Kotlin ports proven byte for byte against a shared vector suite for the layers each implements.

## Motivation

Bitcoin SV applications routinely need to commit to a piece of content (an address, an identity key, an account, a contract, a build artifact) in a way that is (a) deterministic across languages and devices, (b) *typed*, so a commitment to one kind of thing cannot be confused with a commitment to another, and (c) coordinated without a registry, so any party can introduce a new type without a gatekeeper.

The existing BRC stack does not provide this primitive:

* **BRC-42 / BRC-43 (BSV key derivation, invoice numbering / protocol IDs)** namespace *keys* by protocol and key IDs that two parties must already share. They are a coordination convention for derivation, not a general typed content-commitment, and they presuppose the counterparties agree on the protocol string out of band.
* **BRC-100 (wallet-to-application interface)** and its `createAction` give an application a way to *write* arbitrary bytes to the chain, but say nothing about how to canonically commit to typed content before writing it. Two apps that both "hash the thing and put it in an `OP_RETURN`" produce incompatible, type-blind, domain-unseparated digests.
* **Overlay-network discovery (topic managers / lookup services)** solves coordination by introducing exactly the registry this scheme is designed to avoid: a topic string must be recognized by an overlay to be meaningful.
* **Visual-hash / identicon conventions** (LifeHash, jdenticon, blockies) are presentation-only. They render a digest but define neither a typed, domain-separated digest nor a self-describing, version-tagged code, and none binds the *type* of the content into the commitment.

No existing BRC covers a **registry-free typed content commitment** or **software/build provenance published on-chain**. A bare `SHA-256(content)` has three defects this BRC fixes: it is not domain-separated (the same digest can mean anything, inviting cross-protocol confusion), it is not type-bound (a hash of an "address" equals a hash of the same bytes labelled "contract"), and it has no in-band version of the hash function (a future migration off SHA-256 is silent and dangerous). The scheme defined here closes all three while remaining a plain SHA-256 that any language computes with its standard library.

## Relationship to related work

NotaryHash (Gregory Ward, SmartLedger; merged as BRC-220) and this specification overlap in two ways: both anchor a content commitment on chain, and both derive that commitment from a deterministic length-prefixed binary encoding, so any language reproduces identical bytes. They answer different questions. NotaryHash standardizes a notarization certificate: a signer proves, SPV-verifiable against block headers alone, that they signed one specific `SHA-256(content)` at the block where it was mined, and the document itself never leaves the client. That is the right tool when what matters is who signed a given hash and when. This specification standardizes a registry-free *typed* content commitment. The `kind` of the thing being committed to is folded into the digest by a fixed domain string and a one-byte length prefix, so a commitment to an address and a commitment to a contract over identical bytes produce different anchors that cannot collide, and no central authority allocates or arbitrates the type namespace. Around that anchor it adds a self-versioning code format that keeps the construction hash-agile, plus a per-adopter fingerprint used for running-code and build provenance. The two layers compose cleanly: a typed anchor computed here can itself be the hash a NotaryHash certificate signs. Cite NotaryHash for the bare signed-hash anchoring case; the contribution here is the typed-commitment and type-system layer above it.

## Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as in RFC 2119.

### 1. Constants

All multi-byte integers below are big-endian unless stated otherwise. `u8(n)` denotes the single unsigned byte `n` (`0 ≤ n ≤ 255`). `||` denotes byte concatenation. Text is encoded to bytes with UTF-8 (RFC 3629). "NFC" denotes Unicode Normalization Form C (UAX #15); text containing a code point that the normalizer's Unicode database leaves unassigned is rejected, not normalized (§2). `hex(x)` denotes lower-case, zero-padded hexadecimal.

| Name             | Value                            | Bytes (hex)                                  | Role                                     |
| ---------------- | -------------------------------- | -------------------------------------------- | ---------------------------------------- |
| `INKAN_DOMAIN`   | ASCII `"inkan/v1"` + `0x00`      | `69 6e 6b 61 6e 2f 76 31 00`                 | glyph/anchor domain (9 bytes)            |
| `CODE_PREFIX`    | `"ik1"`                          | `69 6b 31`                                   | self-identifying code prefix             |
| `SHORT_CODE_LEN` | `16`                             | n/a                                          | hex chars in a short code body (64 bits) |
| `KIND_DOMAIN`    | ASCII `"inkan/kind/v1"` + `0x00` | `69 6e 6b 61 6e 2f 6b 69 6e 64 2f 76 31 00`  | open-kind derivation domain (14 bytes)   |
| `FP_SUFFIX(v)`   | `"/inkan/v" + dec(v) + 0x00`     | e.g. `v=1` → `2f 69 6e 6b 61 6e 2f 76 31 00` | tamper-fingerprint domain suffix         |
| `MARKER`         | ASCII `"INKANC1"`                | `49 4e 4b 41 4e 43 31`                       | on-chain signed-record marker (7 bytes)  |

Open-kind resolution constants (§4):

* `TIER_CELLS = { "t1": 72, "t2": 48, "t3": 24 }`; default tier is `t2`.
* `KMAX = { 72: 2, 48: 4, 24: 7 }`.
* `ORBIT_REPS = (0, 1, 3, 5, 7, 9, 11, 13, 15, 21, 23, 27, 31, 63)`, 14 rotation-distinct 6-bit frame representatives.

`INKAN_DOMAIN`, `CODE_PREFIX`, and `KIND_DOMAIN` are permanent. An implementation MUST NOT alter any of them; changing `INKAN_DOMAIN` or `CODE_PREFIX` invalidates every anchor ever computed and is a new version namespace, not a revision (§9).

### 2. The anchor

Given `data` (an octet string, or text) and `kind` (a string):

1. If `data` is text, set `data_bytes = utf8(NFC(data))`. If `data` is raw octets, set `data_bytes = data` **verbatim** (raw bytes MUST NOT be normalized, they are not text).
2. Set `kind_bytes = utf8(NFC(kind))`. The implementation MUST reject `kind` with `len(kind_bytes) > 255`.
3. The **glyph seed** is:

```
seed = INKAN_DOMAIN || u8(len(kind_bytes)) || kind_bytes || data_bytes
```

4. The **anchor** is `SHA-256(seed)`, exactly 32 octets (FIPS 180-4).

The anchor is the sole basis for trust: it is what a receipt commits to, the input to every presentation, and the value re-derived during verification. Any visual or other presentation of an anchor is a convenience; the anchor, never the presentation, is trusted.

**Typed input is text.** What a person types or pastes is text; only a file or a binary payload is raw octets. An implementation that takes `data` from a person (a command-line argument, a form field, the string argument of an API or tool call) MUST pass it as text, so that it is normalized and the rule below applies. It passes raw octets only when it reads a file, or an input the person has explicitly marked as binary (for example hex or base64). The two readings give different anchors for what looks like the same input: `café` typed with a separate combining accent (`e` followed by U+0301) is `ik17288300319e48…` as text and `ik191d9326afef7a…` as octets (TV4).

**Unassigned code points MUST be rejected.** A text input (`data` when it is text, the `kind`, or an adopter name, §6) that contains a code point which is unassigned (General\_Category `Cn`) in the Unicode Character Database the implementation's NFC normalizer uses, other than the 66 noncharacters (U+FDD0 to U+FDEF, and the last two code points of each of the 17 planes, U+*n*FFFE and U+*n*FFFF), MUST be rejected. It is never normalized and never hashed. Each implementation checks against the same database, at the same Unicode version, that its normalizer uses, and its self-test reports that Unicode version. No minimum Unicode version is required. Raw octets are never checked.

NFC on its own does not make the anchor deterministic, because each Unicode version assigns new characters and normalizers ship with the tables of one version. A normalizer that does not know a code point treats it as a starter with no decomposition, so it can block a reordering or a composition that a newer normalizer performs, and two conforming implementations on different Unicode versions derive different anchors for the same text. Data `e` U+0897 U+0327 normalizes to U+0229 U+0897 under Unicode 16.0 or later, where U+0897 is assigned, and is left unchanged by an earlier database: two anchors for one input (TV4). This happens between current runtimes, and between two releases of the same runtime. Under Unicode's normalization stability policy, a string whose code points are all assigned in some version has the same NFC form in that version and in every later one, so any two implementations that accept a string derive the same anchor for it, now and later. An implementation whose database does not know a code point rejects the string instead of returning a different answer. Noncharacters are permanently unassigned and normalize to themselves in every version, so they are accepted.

**Why the length prefix (normative rationale).** Without `u8(len(kind_bytes))` between `kind_bytes` and `data_bytes`, the concatenation `kind_bytes || data_bytes` is ambiguous: `kind="address", data="X"` and `kind="", data="addressX"` produce the same trailing bytes and therefore the same anchor. The one-byte length prefix makes the boundary unambiguous for every possible `(kind, data)` pair (see Test Vector 5). An implementation MUST include it exactly as specified.

**Why the type is in the digest (registry-free typing).** Because `kind_bytes` is inside `seed`, an anchor for `(data, kindA)` and an anchor for the *same* `data` under `kindB` differ whenever `kindA ≠ kindB`. Type collisions are therefore cryptographically excluded rather than administratively prevented, so no allocation authority or registry is required to keep two adopters' types apart.

### 3. Codes (`ik1`)

An anchor serializes to a self-identifying ASCII string:

```
full = CODE_PREFIX || hex(anchor) # "ik1" + 64 hex chars = 67 chars
short = CODE_PREFIX || hex(anchor)[:16] # "ik1" + 16 hex chars = 19 chars
```

* The literal `ik` marks an anchor code; the trailing digit is the **code-format / hash version**. `1` denotes SHA-256 over glyph domain `inkan/v1`.
* The hex body of a code is a genuine prefix of the full anchor, so any code (including a short code) is auditable by recomputing the anchor and checking the prefix. The short code's 16 hex chars are 64 bits: collision-safe to billions of items and forgery-resistant, while remaining short enough for filenames and display. A short code is a **label only**; trust decisions MUST use the full 32-byte anchor.
* **Version handling (MUST).** A reader parsing a code MUST verify the version prefix. If a code begins with `ik` but not with a version this implementation supports (e.g. a future `ik2`), the reader MUST reject it rather than assume SHA-256. The reference parse is: reject if the string does not start with `ik`; reject if it does not start with the supported `CODE_PREFIX`; otherwise the digest hex is the remainder after `CODE_PREFIX`. This is what keeps the anchor hash-agile: a hash migration is a new prefix (`ik2…`), unambiguously distinguishable at parse time.

### 4. Kinds, open, tier-weighted, third-party assignable

A `kind` is any UTF-8 string of at most 255 bytes. By convention it is written `[namespace:]name[@tier]` (e.g. `address`, `acme:invoice@t3`, `acme:vault@t1`) but the anchor (§2) treats it as an opaque byte string, so any string that encodes as valid UTF-8 is a valid kind. Namespacing (`ns:`) lets adopters avoid clashing without coordination; because the whole string is inside the digest, `acme:invoice` and `beta:invoice` are distinct anchors even over identical data.

A value that cannot be encoded as valid UTF-8 is **not** a kind: an implementation MUST reject it and MUST NOT substitute replacement characters. Substitution is not a lenient reading of this rule, it is a collision. Every unpaired surrogate encodes to the same replacement bytes, so two distinct kinds that differ only in their ill-formed portion would derive one anchor, and a commitment to one would verify against the other.

A string that contains a code point unassigned in the implementation's Unicode database (§2) is likewise rejected as a kind, by the anchor and by the derivation in 4.2 alike.

Presentations (the visual seal, §5) require a `kind` to resolve to geometry parameters `(cells, k, motif)`, where `cells ∈ {24, 48, 72}` is the tier's cell count, `k ∈ 1..7` is a rotational-symmetry order, and `motif` is a 6-bit frame code. Resolution is:

**4.1 Reserved kinds.** If `kind` is a key of the reserved table below, use its curated entry directly:

| kind                   | cells | k | motif |
| ---------------------- | ----- | - | ----- |
| `""` (empty / default) | 48    | 2 | 63    |
| `identity`             | 48    | 4 | 56    |
| `account`              | 48    | 3 | 36    |
| `address`              | 72    | 1 | 51    |
| `vault`                | 72    | 1 | 61    |
| `signer`               | 72    | 2 | 54    |
| `contract`             | 72    | 2 | 60    |
| `code`                 | 24    | 7 | 42    |
| `report`               | 24    | 6 | 44    |
| `collection`           | 24    | 5 | 48    |
| `backup`               | 24    | 4 | 32    |
| `custom/24`            | 24    | 1 | 52    |
| `custom/48`            | 48    | 1 | 52    |
| `custom/72`            | 72    | 1 | 52    |

**4.2 Derivation (any other kind).** If `kind` is not reserved, derive `(cells, k, motif)` deterministically:

1. **Tier.** If `kind` ends with the literal suffix `@t1`, `@t2`, or `@t3`, that selects the tier; otherwise the tier defaults to `t2`. (The reference tests the suffixes in the order `t1, t2, t3` and takes the first match.) `cells = TIER_CELLS[tier]`.
2. **Kind hash.** `kh = SHA-256(KIND_DOMAIN || utf8(kind))`, where `kind` is the kind string **as provided** (see the normalization note below).
3. **Symmetry.** `k = 1 + (kh[0] mod KMAX[cells])`.
4. **Frame motif.** `motif = ORBIT_REPS[ kh[1] mod 14 ]`.

Because the tier is part of the kind string and the kind is part of the anchor, the same kind renders identically everywhere and namespaced kinds never collide between adopters, **no registry is needed** for either the anchor or the presentation.

> **Normalization note (MUST-for-interop).** The anchor (§2) NFC-normalizes `kind` before hashing; the reference presentation-derivation in §4.2 hashes the kind bytes **as provided** (no explicit NFC step) and performs the reserved-table lookup and suffix test on the string as provided. For an ASCII kind (all reserved kinds are ASCII) the two layers are identical. To keep the anchor and the presentation consistent for non-ASCII kinds, callers SHOULD pass kinds already in NFC. An implementation aiming for byte-exact parity with the reference presentation MUST NOT insert an NFC step into §4.2. The unassigned-code-point check of §2 does apply here: it rejects the kind, and it never alters the string that is hashed.

**Tiers and exposure.** Tiers `t1/t2/t3` map to cell profiles `72/48/24`. More cells means more visible entropy and a costlier forgery (§ Security Considerations); value-at-risk kinds (e.g. `address`, `vault`) SHOULD use `t1`. The resolution parameters affect only the presentation; **the anchor never depends on `(cells, k, motif)`**, it depends only on the exact `kind` string. Two distinct kind strings that happen to resolve to the same `(cells, k, motif)` still produce distinct anchors.

### 5. Deterministic presentation (the seal)

An anchor MAY be rendered to a deterministic visual **seal** so a human notices when a trusted value changes. The seal is optional to this BRC's interoperability core (anchor + code + kind, §2-§4). Its pixel-level construction is not reproduced here. It is the Seal v3 construction of the Inkan Standard (§3 of that document), driven by one data file, the Seal v3 tables, which holds every table and constant the renderer uses: the palette and its selection weights, the core patterns, the region maps, and the reserved-kind table and derivation constants of §4. The tables file is published under CC0 as `seal-v3-tables.json` and is identified by its SHA-256:

```
441881915ddf6c213dadac507d714b57068e0f6bb0a6f6070acd3fb924dcdf4b
```

The file is one JSON object with exactly fourteen members: the tables and constants under the names the Inkan Standard gives them (`CANVAS`, `SEAL_VERSION`, `STREAM_DOMAIN_HEX`, `KIND_DOMAIN_HEX`, `GOLAY_G`, `TIER_CELLS`, `KMAX`, `ORBIT_REPS`, `KINDS`, `PALETTE`, `SELECT_WEIGHTS`, `CORE_PATTERNS` and `REGION_RLE`), and `FORMAT`, whose value is the string `"inkan-seal-v3-tables"`. `FORMAT` names the file and is not read by the renderer, but it is part of the encoded object and therefore of the digest.

**The Seal v3 tables are normative.** An implementation that renders seals MUST use tables whose canonical encoding is exactly the file with this digest, and MUST reproduce the seal RGB digests in TV1, TV2 and TV8 and in the conformance vectors. The canonical encoding lets an implementation rebuild the exact bytes from its own copy of the tables and compare digests: one JSON object; members sorted by key in code-point order; no insignificant whitespace (separators `,` and `:`); integers in shortest decimal form; every string pure ASCII and written without escapes; the whole followed by a single line feed (`0x0A`). Matching a sample of vectors is not enough on its own. An engine with one palette value off by one unit can pass a sampled vector set and still draw different seals for ordinary inputs, which is why the tables themselves are pinned.

**RGB digest.** The canonical seal is 224 × 224 pixels. Pixel `(x, y)`, with column `x` and row `y` counted from 0 at the top-left corner, is pixel number `i = 224·y + x`. A seal's RGB888 string is, for `i = 0 … 50175` in order, the three bytes `R, G, B` of pixel `i`: 150,528 bytes in all. The seal's RGB digest, `rgb_sha256`, is the SHA-256 of that string, and it is the value every seal test vector pins.

The seal's normative contract is:

* **Deterministic and float-free.** Geometry and palette are integer tables, so all implementations agree byte-for-byte by construction, not by luck. Seal renderer version is `SEAL_VERSION = 3`.
* **The kind selects symmetry and frame.** `(cells, k, motif)` from §4 fix the rotational symmetry order and the 6-bit frame, so a seal of one kind cannot be confused with a seal of another.
* **A visible distance floor.** Interior cells carry 2 bits each from bit-interleaved extended Golay \[24,12,8] blocks; any two distinct seals of the same profile differ in at least 8 cells (Golay minimum weight), a floor verified tight over all 4096 code words.
* **Pixel-deterministic PNG.** The canonical seal is an 8-bit indexed PNG whose chunks are exactly `IHDR`, `PLTE`, `IDAT`, `IEND`, with **no ancillary chunks**. Every conforming encoder writes a valid PNG that decodes to exactly the seal's pixels. The cross-language invariant is the decoded RGB pixel content, not the compressed bytes: deflate output depends on the implementation and its settings, so compressed PNG bytes are not part of this standard, and no test vector, comparison or on-chain record may pin them (an inscribed seal is verified by its pixels, §7.4). A **tagged** export additionally embeds `iTXt` chunks (keyword `inkan-code` carrying the full `ik1` code, keyword `inkan-kind` carrying the kind) making the image self-describing. The embedded code is an **untrusted hint**: a verifier MUST recompute the anchor and compare pixels, treating the tag only as a cross-check. The keyword `inkan-seal` is reserved: a tagged PNG of a later `SEAL_VERSION` names its version in an `iTXt` chunk with that keyword (§9). A Seal v3 writer never emits it, and a seal without it is Seal v3.

`iTXt` body layout used by the reference tagged export, for a `keyword` and UTF-8 `text`:

```
keyword(latin-1) || 0x00 || 0x00(comp flag) || 0x00(comp method) || 0x00(empty lang) || 0x00(empty translated keyword) || utf8(text)
```

**Verification of a presented seal.** To verify a seal claimed to be `(data, kind)`: recompute the anchor and the canonical seal; decode the presented file to native RGB under the verifiable-PNG profile below; if it is an exact integer multiple of the canonical size, integer-downscale it, otherwise reject; compare pixel-for-pixel. Result is GENUINE iff equal. Re-encoding and integer upscales are tolerated; a resampled, cropped, or otherwise different image is rejected. Verification MUST NOT trust embedded metadata.

**Downscaling is a check, not a resize.** When the presented image is an integer upscale by factor *s*, the verifier MUST confirm that every pixel of each *s×s* block is identical before reducing that block to one pixel. Sampling one pixel per block and discarding the rest satisfies a pixel-for-pixel comparison of the reduced image while leaving *s²−1* of every *s²* pixels unexamined and under the presenter's control: at *s* = 8 that is 63 of every 64 pixels, 98.44% of what the viewer actually sees, free to carry a different mark entirely. A block whose pixels are not uniform MUST be reported as a verification failure in its own right, never as a size error and never as GENUINE.

**Rendering-affecting chunks MUST be rejected.** A presented PNG carrying `tRNS`, `gAMA`, `iCCP`, `sRGB`, `cHRM`, `sBIT`, `bKGD`, or the APNG chunks `acTL`, `fcTL`, `fdAT` MUST be rejected. These change what a conformant viewer draws without changing the stored pixel values the comparison reads, so an image that is byte-identical under comparison can present as a different mark. The constraint above on the canonical seal binds what a writer emits; this binds what a verifier accepts, and the two are not the same requirement. A list of forbidden chunks cannot keep up with PNG, which keeps adding chunk types that change rendering: a genuine seal carrying `cICP` (a colour space) or `eXIf` (an orientation) is drawn in other colours, or rotated, by a current browser while its stored pixels still compare equal. The profile below therefore lists the chunks that are allowed and rejects all others.

**Verifiable-PNG profile.** A verifier accepts a presented file only if it is a PNG that meets every rule below, and rejects it otherwise. The profile accepts every honest re-encoding of a seal (any deflate settings, any scanline filters, indexed or truecolour, benign metadata) and nothing whose appearance can differ from its decoded pixels. The verifier MUST decide on its own decode under these rules. It MAY use a platform image decoder to display the image, never to judge it: a browser's decoder will, for example, open an animated GIF named `.png` and show its frames.

1. **Signature and chunks.** The file begins with the PNG signature `89 50 4E 47 0D 0A 1A 0A` and continues as a sequence of chunks: a 4-byte big-endian length of at most 2^31 − 1, a 4-byte type, the data, and a CRC-32 over the type and the data as PNG defines it. Every CRC MUST be correct. The file ends exactly where the `IEND` chunk ends.
2. **Allowed chunks.** Only `IHDR`, `PLTE`, `IDAT`, `IEND`, `tEXt`, `zTXt`, `iTXt`, `pHYs` and `tIME` may appear. Any other chunk, critical or ancillary, known or unknown, MUST be rejected: among them every chunk named in the previous paragraph, and `hIST`, `sPLT`, `mDCV` and `cLLI`.
3. **Order.** `IHDR` comes first and appears once. `IEND` comes last and has no data. `PLTE` appears at most once, before the first `IDAT`. The `IDAT` chunks are consecutive. `pHYs` (before the first `IDAT`) and `tIME` appear at most once each. Text chunks may appear anywhere between `IHDR` and `IEND`.
4. **Header.** Bit depth 8; colour type 2 (truecolour) or 3 (indexed-colour); compression method 0; filter method 0; interlace method 0. Width and height are from 1 to 2^31 − 1.
5. **Palette.** Colour type 3 requires a `PLTE` whose length is a multiple of 3, with 1 to 256 entries, and every pixel's index MUST be less than the number of entries. With colour type 2, a `PLTE` (a suggested palette) is allowed and ignored.
6. **Image data.** The data of all `IDAT` chunks, concatenated, is one zlib stream (RFC 1950: compression method 8, no preset dictionary, a correct Adler-32) that ends exactly at the end of the last `IDAT` and inflates to exactly `height · (1 + width · channels)` bytes, `channels` being 3 for truecolour and 1 for indexed-colour. Each scanline begins with a filter type from 0 to 4 and is unfiltered as PNG defines. A verifier SHOULD stop inflating as soon as the output would exceed that size.
7. **Metadata.** `pHYs` has 9 bytes of data and `tIME` 7. `tEXt`, `zTXt`, `iTXt`, `pHYs` and `tIME` do not change the pixels and are not interpreted, except that a verifier MAY read the `inkan-code` and `inkan-kind` tags as an untrusted cross-check.

### 6. Tamper fingerprint (build / running-code provenance digest)

The per-adopter tamper fingerprint of an octet payload (a build's bytes, an asset, a config) is:

```
domain_prefix = u8(len(adopter_bytes)) || adopter_bytes || FP_SUFFIX(version)
 = u8(len(adopter_bytes)) || adopter_bytes || "/inkan/v" || dec(version) || 0x00
fingerprint = SHA-256(domain_prefix || payload)
```

where `adopter_bytes = utf8(NFC(adopter))`, `len(adopter_bytes) ≤ 255` (reject otherwise), and `version` defaults to `1`. `dec(version)` is the version written in ASCII decimal digits, with no sign and no leading zeros. The `payload` is raw octets and is never normalized (§2). `fingerprint_hex` is the lower-case hex of the 32-byte digest.

**Adopter-name profile.** An adopter name MUST be printable UTF-8, MUST NOT contain a NUL byte, and MUST NOT begin with `nkan/`. The last constraint closes a cross-context parse. `u8(len(adopter_bytes))` is a single length byte, and for an adopter name of the right length beginning `nkan/` that byte can equal `0x69`, ASCII `i`, so the fingerprint preimage would open with the exact nine bytes of `INKAN_DOMAIN` and would also parse as a glyph seed under section 2. One digest would then be simultaneously a valid fingerprint and a valid anchor.

Printable means exactly this: the name is well-formed Unicode text (as a kind must be, §4), it contains no code point unassigned in the implementation's Unicode database (§2), and it contains no control character, that is, no code point in U+0000 to U+001F or U+007F to U+009F (NUL included). The `nkan/` test applies to the name after NFC. These rules are fixed by code-point ranges rather than by Unicode general categories, so they do not change as Unicode assigns new characters. An implementation MUST reject a name that breaks any of them, before hashing.

**Fingerprint versions.** `version` is a positive integer: 1, 2, 3, and so on. Anything else (zero, a negative number, a fraction, a string, a boolean) MUST be rejected; a fraction such as `1.5`, left to the implementation, is truncated by one and written out in full by another, giving two digests for one input. Version *n* is SHA-256 over exactly this construction with `dec(n)` in the suffix, forever: no version ever changes its hash or its layout. A fingerprint over a different hash function is not a new version of this scheme. It gets its own domain and a text form that names its hash, so it can never be mistaken for, or compared with, a fingerprint defined here. A verifier takes the version as an explicit input and never infers it; the reference default is `1`.

The length-prefixed, per-adopter domain namespaces each project: identical payloads yield **distinct** fingerprints across adopters while every adopter shares the same universally verifiable scheme, and no adopter needs to register its name. A one-byte change in `payload` changes the whole digest (avalanche). This fingerprint is a build's per-adopter digest for **software/build provenance**. It is published beside the code-provenance ordinal of §7.4, never inscribed in it.

### 7. On-chain provenance

An anchor or a fingerprint (§2, §6) MAY be committed to the chain to obtain an immutable, timestamped, non-retractable record. This BRC defines two on-chain forms; both are self-verifying (a client re-derives everything offline and trusts the chain only for immutability and time-ordering).

**7.1 Signed-record data output (reference `OP_RETURN` form).** A signed statement (the reference case is a warrant/compromise **canary**, but the container is general) is carried as a data output whose locking script is:

```
OP_RETURN
push( MARKER) # "INKANC1", 7 bytes
push( canonical_record_bytes) # §7.2
push( der_signature) # secp256k1 ECDSA, DER
push( public_key) # 33-byte compressed secp256k1 point
```

Each `push(blob)` is minimal Bitcoin script pushdata: for `n = len(blob)`,

* `n < 0x4C` → `u8(n) || blob`
* `0x4C ≤ n ≤ 0xFF` → `0x4C || u8(n) || blob`
* `0x100 ≤ n ≤ 0xFFFF` → `0x4D || u16le(n) || blob`
* otherwise → `0x4E || u32le(n) || blob`

A reader parses the script by matching `OP_RETURN` then reading four pushes in order; it MUST reject the output if the first push is not exactly `MARKER` or if any push is truncated. Everything a verifier needs (the record, the signature, and the public key) is on-chain, so the output is checked entirely offline (§7.3). The reader MUST NOT trust the parsed bytes; it re-verifies them.

**7.2 Canonical record and signature.** A signed record is a JSON object serialized canonically: keys sorted lexicographically, no insignificant whitespace (`separators` `,` and `:`), UTF-8, non-ASCII left un-escaped. The reference canary record has fields:

| field          | type       | meaning                                         |
| -------------- | ---------- | ----------------------------------------------- |
| `v`            | int        | record version (`1`)                            |
| `domain`       | string     | operator/publisher identifier                   |
| `seq`          | int        | monotonic sequence number                       |
| `date`         | string     | `YYYY-MM-DDTHH:MM:SSZ` (UTC)                    |
| `block_height` | int        | a recent chain-tip height (anti-backdate proof) |
| `block_hash`   | hex string | that tip's block hash                           |
| `statement`    | string     | the human-readable assertion                    |

**Canonical bytes, exactly.** `canonical_record_bytes` is the record written as one JSON object with no insignificant whitespace, encoded in UTF-8: `{`, then the members in ascending order of key (for the reference record: `block_hash`, `block_height`, `date`, `domain`, `seq`, `statement`, `v`) separated by `,`, each written `"key":value`, then `}`. An integer is written in decimal digits with no sign, fraction, exponent or leading zero. A string is written between `"` characters with exactly these escapes: `"` as `\"`, `\` as `\\`, U+0008 as `\b`, U+000C as `\f`, U+000A as `\n`, U+000D as `\r`, U+0009 as `\t`, and every other code point below U+0020 as `\u00xx` with two lower-case hexadecimal digits. Every other code point, U+007F, U+2028, U+2029 and all non-ASCII characters included, is written as itself. This is the output of Python's `json.dumps(record, sort_keys=True, separators=(",", ":"), ensure_ascii=False)`, and of JavaScript's `JSON.stringify` over the record with its keys sorted, for every record that meets the input rules of 7.3.

The message to sign/verify is `SHA-256(canonical_record_bytes)`. The signature scheme is secp256k1 ECDSA, DER-encoded, deterministic per RFC 6979, low-S. A signature whose `S` is greater than half the group order `n` (the high-S twin `(r, n − S)` of a valid signature) is not one this scheme produces, and a verifier MUST report it `INVALID`, even where a generic ECDSA library would accept it. A verifier built on such a library would otherwise accept a second, different signature for every genuine one (TV7). A signature-algorithm identifier is reserved for a future post-quantum migration. The `block_height`/`block_hash` pair binds the record to a chain tip so it cannot have been pre-signed before that block existed.

**7.3 Status.** A verifier reports exactly one of:

* `MALFORMED`, the record, the public key, or the signature did not parse, so no signature check was performed. A verifier MUST report `MALFORMED` whenever its inputs did not parse, and MUST NOT report `INVALID` in that case. `INVALID` asserts that a check ran and the signature was wrong, which is an accusation of forgery; a truncated key or a DER blob that will not decode is a handling error, and reporting it at maximum severity trains an operator to discount the verdict that matters.
* `UNSUPPORTED`, the record parsed and carries a record version `v` this verifier does not implement. A verifier MUST check `v`. It MUST NOT report `OK` for an unknown version, which would assert a validity it is not able to judge, and MUST NOT report `INVALID`, which would assert a failure it did not observe. A well-formed, correctly signed, fresh `v=2` record read by a `v=1` verifier is the case this rule exists for.
* `INVALID`, the signature does not verify against the published key over `SHA-256(canonical_record_bytes)`.
* `STALE`, signature valid but the record is outside the freshness window: with `now`, `max_age_days` (reference default 35), and `date` parsed as UTC, the record is fresh iff `-86400 ≤ (now − date_seconds) ≤ max_age_days · 86400` (i.e. not expired and not implausibly future-dated by more than one day).
* `OK`, signature valid and fresh.

A record whose shape does not match the 7.2 table, exactly those fields with those types, is `MALFORMED`, and a verifier MUST validate the shape before it reports `OK`. The signature covers whatever fields are present, so a record of the wrong shape is not a forgery. It can still be published by mistake, and without this rule every verifier reports `OK` over it while downstream readers disagree about what they have read.

**Input rules.** After the version check (a record whose `v` is not the integer `1` is `UNSUPPORTED`), a verifier checks the record against these rules before it checks the signature, and reports `MALFORMED` for a record that breaks any of them:

1. `seq` and `block_height` are integers from 0 to 2^53 − 1 inclusive.
2. `date` is exactly 20 ASCII characters, `YYYY-MM-DDTHH:MM:SSZ`, each letter other than `T` and `Z` standing for an ASCII digit `0` to `9`, with `-`, `T`, `:` and an upper-case `Z` appearing literally, and it names a real UTC time: year 0001 to 9999, month 01 to 12, a day that exists in that month of that year (Gregorian calendar), hour 00 to 23, minute 00 to 59, second 00 to 59.
3. Every string is well-formed Unicode text: a JSON escape that leaves an unpaired surrogate is malformed.
4. A record read from bytes (for example from the §7.1 output) is malformed unless those bytes are exactly its canonical bytes (7.2).

These rules make every conforming verifier give the same verdict for every record. Without them, verifiers disagree over correctly signed records. A `seq` above 2^53 that a double cannot hold exactly (2^53 + 1, or a nanosecond timestamp such as 1760112000123456789) is read exactly by one JSON parser and rounded by another, so one verifier reports `OK` and the other `INVALID`. A date written `2026-6-30T0:0:0Z`, or one with hour 24 or second 60, is accepted by one date parser and refused by another. Rule 1 ends at 2^53 − 1, the largest integer that no other integer rounds onto in a double (2^53 + 1 rounds to 2^53). A `seq` or `block_height` of 2^53 or more is therefore `MALFORMED` even where a double holds it exactly, as it holds 2^53 and 10^18, and so is a negative one.

**Key and signature form.** The public key and the signature are each given to a verifier as hexadecimal text: an even number, at least two, of the ASCII digits `0` to `9`, `a` to `f` and `A` to `F`, with no prefix such as `0x` and nothing between the digits. Only these characters are ignored before and after the digits: U+0009 to U+000D, U+0020, U+00A0, U+1680, U+2000 to U+200A, U+2028, U+2029, U+202F, U+205F and U+3000. A key or signature in any other form, or one that is not text, is `MALFORMED`. A reader of a §7.1 output gives the verifier the pushed key and signature as their hexadecimal text (`hex()`, §1). Without this rule, one verifier removes a `0x` prefix that another refuses, and the second skips spaces between digit pairs that the first refuses, so a correctly signed record reads `OK` in one verifier and `MALFORMED` in the other.

For a canary, absence, `STALE`, `INVALID`, `MALFORMED`, or `UNSUPPORTED` is itself the warning.

**7.4 1-of-1 ordinal form (code-provenance ordinal).** A build's provenance MAY instead be published as a 1-of-1 (1Sat) ordinal. The provenance anchor of a released file (a build, an archive, a page) is computed from the file's bytes alone:

```
file_hash = SHA-256(file_bytes) # 32 octets
anchor_data = hex(file_hash) # text: 64 lower-case hex ASCII characters
anchor = SHA-256(INKAN_DOMAIN || u8(4) || "code" || anchor_data)
 # the §2 anchor of (data = anchor_data, kind = "code")
code = CODE_PREFIX || hex(anchor)
```

The inscription content is the canonical **tagged** seal PNG (§5) of `anchor` under kind `code`, which resolves to `(cells=24, k=7, motif=42)`: its `inkan-code` tag carries `code`, and its `inkan-kind` tag carries `code`. Any integer scale verifies (§5); the reference writes it at scale 8. The tags make the inscription self-describing; the ordinal's single-satoshi custody model conveys and transfers the mark. The inscription envelope and transfer follow the 1Sat Ordinals convention (referenced, not redefined here).

The publisher's tamper fingerprint (§6) of the same `file_bytes`, under its adopter name and fingerprint version, is published beside the ordinal as a second, independent check of the same bytes. It is **not the inscribed seal**, it is not rendered as one, and no `ik1` code is formed from it: an `ik1` code labels only an anchor (§3).

A client verifies a release by computing `file_hash` from the bytes it holds, recomputing `anchor`, `code` and the canonical seal, and comparing the inscribed image with that seal pixel for pixel (§5). Where the publisher's fingerprint is given, the client recomputes it as well and compares all 32 bytes. The displayed seal *is* the on-chain-committed mark, so a tampered build cannot reproduce it. TV8 is a worked example.

**7.5 Broadcast interoperability (BRC-100, informative).** The reference publishes §7.1 outputs through a BRC-100 wallet: it runs `getVersion → getNetwork → isAuthenticated → getPublicKey → createAction`, funding a single `OP_RETURN` output (`satoshis: 0`) carrying the §7.1 script. The wallet only funds and broadcasts; it never signs the record (the operator signs offline with the bare secp256k1 key), so verification is never coupled to a wallet. An implementation MAY use any broadcast path; the on-chain bytes (§7.1) are what matters. Implementations that broadcast SHOULD default to a test network and require explicit operator opt-in for mainnet, so real funds are the last step, never the first.

### 8. Verification requirements (summary)

* An anchor is re-derived, never received: a verifier presented with `(data, kind)` and a claimed code/seal MUST recompute `SHA-256(seed)` per §2 and compare, and MUST NOT trust any embedded code or metadata.
* A code MUST be version-checked before its body is interpreted as a SHA-256 digest (§3).
* A value that is not valid UTF-8 MUST be rejected as a kind, never substituted; substitution collapses distinct kinds onto one anchor (§4).
* Text that contains a code point unassigned in the implementation's Unicode database MUST be rejected, never normalized or hashed, whether it is `data`, a kind or an adopter name (§2).
* A canonical seal MUST be rendered from the Seal v3 tables whose canonical encoding has the SHA-256 given in §5; reproducing a sample of vectors does not establish that (§5).
* An integer upscale MUST be reduced only after confirming every pixel of each block is identical; sampling one pixel per block leaves the rest unexamined and presenter-controlled (§5).
* A presented image carrying a rendering-affecting ancillary chunk MUST be rejected, because it changes what is drawn without changing the pixels compared (§5).
* A presented image MUST be a verifiable PNG under the profile of §5, judged on the verifier's own decode; a platform decoder may display it, never judge it (§5).
* An adopter name MUST satisfy the §6 profile, and a fingerprint version MUST be a positive integer, before anything is hashed (§6).
* An on-chain record MUST be re-verified offline (signature + freshness) after parsing; the chain is trusted only for immutability and timestamp ordering (§7).
* A record that breaks the 7.3 input rules, or a key or signature that is not hexadecimal text in the form 7.3 fixes, is `MALFORMED`, and a high-S signature is `INVALID` (§7).

### 9. Versioning and parity

* **Anchor** (`inkan/v1`, SHA-256, code `ik1`): permanent. A new hash is a new version namespace (`ik2…`), not a revision. Changing the anchor domain invalidates every anchor ever computed.
* **Presentation** (`SEAL_VERSION`): versioned and additive. A new seal design is a new `SEAL_VERSION` added beside version 3, never a replacement: version 3 is never redefined, every version 3 seal keeps its pixels, and every conforming verifier keeps version 3. Adding a presentation never invalidates an anchor, the property that lets an on-chain-anchored digest outlive any renderer. Two surfaces that compare seals MUST share `SEAL_VERSION`, and a product that can show more than one labels which one it shows.
* **Fingerprint domain** (`FP_SUFFIX` version): per-adopter and independently versioned. Version *n* is fixed forever (§6).
* **Signed record** (`v`): version 1 is the format of §7.2. A new signature scheme is a new record version whose algorithm is named inside the signed bytes.

**Permanence.** For every input it accepts, this scheme has exactly one result, forever: the anchor and its `ik1` code; kind resolution under `SEAL_VERSION` 3 (the 14 reserved kinds and the suffixes `@t1`, `@t2`, `@t3`); the Seal v3 pixels, fixed by the tables file of §5 and the conformance vectors; each fingerprint version; and the version 1 record bytes and signature rule. A correction to this document MAY clarify wording, add test vectors, or make implementations reject an input that was malformed or on which conforming implementations already gave different results. It MUST NOT change the result of any input that the reference implementation accepts and on which conforming implementations agree. One correction is an exception, made in revision 1.0.1 of the Inkan Standard before any signed record had been published: under the input rules of §7.3, signed records on which the reference and the JavaScript verifier had agreed now read `MALFORMED`. These are a record whose `seq` or `block_height` is negative, or is 2^53 or more and held exactly by a double, and a record read from bytes that are not its canonical bytes, all of which both read `OK`; and a record whose `date` is not a real time in the §7.2 form, which both read `STALE`. Every later correction is bound by this paragraph without exception.

Every result belongs to a version, and no version name is ever reused or redefined. Where no marker is present, the result is `ik1`, `SEAL_VERSION` 3, the SHA-256 fingerprint and record version 1. New versions are added beside the old ones. A new hash is a new anchor version with its own domain. A new seal design is a new `SEAL_VERSION` whose tagged PNG names it under the reserved `inkan-seal` keyword (§5). The suffixes `@t0` and `@t4` to `@t9`, and any new reserved kind, can take effect only in a new `SEAL_VERSION`; under version 3 they are ordinary open kinds (§4.2). An implementation that adds a version keeps generating, verifying and self-testing every earlier version it claims. A verifier treats the version as an explicit part of what it checks, never infers it from an untrusted tag, and reports a version it does not implement as unsupported (for a code, the rejection of §3; for a record, `UNSUPPORTED` in §7.3), never as a pass or a failure. If SHA-256 or secp256k1 weakens, no version 1 result changes: verifiers keep computing version 1 results and add a warning, and a migration adds the new version beside the old one.

## Test Vectors

All values below are produced by the reference implementation and are reproducible with a SHA-256 and UTF-8 alone, apart from these: the ECDSA signatures in TV7 need secp256k1, the TV4 results need NFC and a Unicode database, and the seal RGB digests in TV1, TV2 and TV8 need the Seal v3 construction and its tables (§5).

### TV1, anchor of text, default kind

```
data = "Hello" (text) kind = ""
seed (hex) = 696e6b616e2f7631000048656c6c6f
 = INKAN_DOMAIN || 00 (len kind = 0) || "Hello"
anchor = ae63a28a7b3fbae640f24ab4003d285a453e5493b89741326a7fbcc36a2a6a44
code = ik1ae63a28a7b3fbae640f24ab4003d285a453e5493b89741326a7fbcc36a2a6a44
short code = ik1ae63a28a7b3fbae6
resolve_kind("") = (cells=48, k=2, motif=63) [reserved]
seal rgb_sha256 = 84d8d92167a91a139b00e804519d8e5ea3d0ad720394fc3aa5559ce3733a8a6b (§5)
```

### TV2, anchor of a typed address (reserved kind `address`)

```
data = "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2" kind = "address"
seed (hex) = 696e6b616e2f7631000761646472657373314276424d53455973745765747154466e354175346d3447466737784a614e564e32
 = INKAN_DOMAIN || 07 (len "address") || "address" || "1BvB…VN2"
anchor = 12cf1aaca7ef50793d990e94c9ae889af6f9e8fdfc28b2f9a72d4028bde05dcd
code = ik112cf1aaca7ef50793d990e94c9ae889af6f9e8fdfc28b2f9a72d4028bde05dcd
resolve_kind("address") = (cells=72, k=1, motif=51) [reserved, tier t1]
seal rgb_sha256 = e41a237845f172634c2a59846557aa45cfcff8c7d142cd5b2ac36d54b7e48596 (§5)
```

### TV3, open-kind derivation (no registry): `acme:invoice@t3`

```
kind = "acme:invoice@t3" (not reserved)
tier = t3 (suffix "@t3") -> cells = 24
kh = SHA-256( "inkan/kind/v1\0" || "acme:invoice@t3")
kh[0] = 137, kh[1] = 93
k = 1 + (137 mod KMAX[24]=7) = 1 + 4 = 5
motif = ORBIT_REPS[ 93 mod 14 = 9 ] = 21
resolve_kind("acme:invoice@t3") = (cells=24, k=5, motif=21)
```

This resolves identically on any device with no allocation authority, demonstrating the registry-free kind system. (`myapp:thing` → `(48, 4, 11)` and `acme:vault@t1` → `(72, 2, 27)` resolve the same way.)

### TV4, NFC normalization stability

```
kind entered as "café" with é = U+00E9 (NFC)
kind entered as "café" with e + U+0301 (NFD)
data = "x"
Both anchors = ik188858a890fa01bf5a463e0e4ca0181d18fe261f34799fe5c5d0ab76e0647f6af (equal)

Derived kind "café@t2" (NFC): utf8 = 63 61 66 c3 a9 40 74 32 (8 bytes)
resolve_kind("café@t2") = (cells=48, k=3, motif=1)

data entered as "café" with e + U+0301, passed as text, kind = ""
 anchor = ik17288300319e48fa900f4c3f938e938fa4a8c6966f33a8cc8cde58ddd498b3bd1
 (equal to "café" entered with U+00E9)
the same input passed as raw octets 63 61 66 65 cc 81 (not normalized)
 anchor = ik191d9326afef7acdb46684756ccb7db2ec0329cafd74a03a95c567a4aeba3dbe6
 (the anchor of those octets, as for a file; never the anchor of typed text, §2)

data = U+0065 U+0897 U+0327 (text), kind = "" (U+0897 is assigned from Unicode 16.0)
 database in which U+0897 is assigned: NFC = U+0229 U+0897
 anchor = ik174047d56c5a5fae88e083d5c5cd6d2d52c07991d79f10070a88381a492d86351
 database in which U+0897 is unassigned: rejected (§2)
 never: ik1883baf1d9618d2adecec5e4d8b5641edc22ff92568faa9050e00a6e109f282ef
 (the input left unchanged, as a normalizer without U+0897 would leave it)
```

An implementation returns the pinned anchor or rejects the input, according to its Unicode database, and never returns the value marked `never`.

### TV5, length-prefix collision resistance

```
kind = "address", data = "X" -> ik1b49a32c4d99c6b80b331223166711da0a91105d1aea989430b8dc22e073c97fd
kind = "", data = "addressX" -> ik13780ad6190697cc754df1159766f9c9781aa610529d0654a197a9a31c691a7f7
distinct: TRUE
```

Without the `u8(len(kind))` prefix both would hash `… "addressX"` and collide. The length prefix (`07` vs `00`) makes the boundary unambiguous.

### TV6, tamper fingerprint / build provenance (per-adopter namespacing)

```
domain_prefix("acme", v=1) = 04 61636d65 2f696e6b616e2f7631 00
 = 0461636d652f696e6b616e2f763100
fingerprint("build-bytes-v1", adopter="acme") = d88657e80e283d5a44bd29b83cf8a93ec60729ec635e642b5454b8829bd3e3c6
fingerprint("build-bytes-v1", adopter="otherapp") = 1847961553e04828204efb7322f1763b8ade6faa14f6f78e30b43957a0c24ab1
```

Same payload, different adopter → different digest, with no registration.

### TV7, on-chain signed record and `OP_RETURN` script (§7)

```
record (fields) = { v:1, domain:"acme", seq:7, date:"2026-06-30T00:00:00Z",
 block_height:870000,
 block_hash:"00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff",
 statement:"No warrant, gag, key compromise or coercion." }

canonical_record_bytes (222 bytes, keys sorted, no whitespace) =
 {"block_hash":"00ff...00ff","block_height":870000,"date":"2026-06-30T00:00:00Z",
 "domain":"acme","seq":7,"statement":"No warrant, gag, key compromise or coercion.","v":1}
message_hash = SHA-256(canonical_record_bytes)
 = 7b0991a1ab13b4b17384e1c6acd0017edb77bd563cdea59cdc776993d8c99362

pubkey = 031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f
der_sig = 3045022100d336085483c2dea86794ef3404d7dc7beef56368fd380000e4d0d8f3ac3fe50c
 02200a4fff49ba1173d1b66f8d4a335cb587c221db79280df23fc2b495b081c65e11
verify(record, der_sig, pubkey) = true (secp256k1 ECDSA over message_hash)

high-S twin of der_sig (S replaced by n − S, n the secp256k1 group order):
 3046022100d336085483c2dea86794ef3404d7dc7beef56368fd380000e4d0d8f3ac3fe50c
 022100f5b000b645ee8c2e499072b5cca34a76f88d016d873aadfbfd1dc8dc4e6fe330
status(record, high-S twin, pubkey) = INVALID (§7.2), although it satisfies the ECDSA equation

key and signature form (§7.3):
verify(record, der_sig in upper-case hex, pubkey in upper-case hex) = true
status(record, der_sig, "0x" || pubkey) = MALFORMED
status(record, der_sig with a space between each pair of digits, pubkey) = MALFORMED

status(record with block_height = 9007199254740992 (2^53), any signature, pubkey)
 = MALFORMED (§7.3 rule 1), before any signature check

OP_RETURN script (339 bytes) begins:
 6a OP_RETURN
 07 494e4b414e4331 push "INKANC1"
 4c de 7b22626c6f636b5f68617368... push canonical_record_bytes (0xde = 222 bytes)
... push der_sig, push pubkey
```

Round-tripping the script recovers `record`, `der_sig`, and `pubkey` for offline re-verification. The key in this vector is a deliberately public **test** key (private key `0x01` repeated 32 times) and must never sign a real record; live records are published by each publisher and never appear in test material.

### TV8, release provenance (code-provenance ordinal, §7.4)

```
file_bytes = "build-bytes-v1" (14 octets, the payload of TV6)
file_hash = 192b667e91b33c6b0a4477aae3daf7ff2586be329b8b0c36a2d03343ae993199
anchor_data = "192b667e91b33c6b0a4477aae3daf7ff2586be329b8b0c36a2d03343ae993199" (text)
kind = "code"
seed (hex) = 696e6b616e2f76310004636f6465
 3139326236363765393162333363366230613434373761616533646166376666
 3235383662653332396238623063333661326430333334336165393933313939
 = INKAN_DOMAIN || 04 (len "code") || "code" || anchor_data
anchor = 549f98f358b1ca9aa486a3fcbda7bd75bc19cf398b84028f71b25d8a3c797923
code = ik1549f98f358b1ca9aa486a3fcbda7bd75bc19cf398b84028f71b25d8a3c797923
resolve_kind("code") = (cells=24, k=7, motif=42) [reserved]
seal rgb_sha256 = 2ce37811263654414954449618db5c4fd6787c3320437ba53c3bd00cb6561184
inscription = tagged seal PNG of anchor under kind "code"
 iTXt inkan-code = ik1549f98f358b1ca9aa486a3fcbda7bd75bc19cf398b84028f71b25d8a3c797923
 iTXt inkan-kind = code

published beside the ordinal (not inscribed, no ik1 code formed from it):
fingerprint("build-bytes-v1", adopter="acme", v=1) = d88657e80e283d5a44bd29b83cf8a93ec60729ec635e642b5454b8829bd3e3c6 (TV6)
```

A client holding the file recomputes every line above from its bytes. A seal drawn from the fingerprint, or an `ik1` code formed from the fingerprint or directly from `file_hash`, is not the provenance mark.

## Implementations

The reference implementation is **Inkan**, an offering by RexStarBSV: a frozen v1.0 standard carrying three reference implementations and a pinned conformance-vector suite, from which every value in the Test Vectors above is reproduced.

* **Reference implementation** (Python): `seed`/anchor and code construction (`inkan_digest`, `inkan_code`, `inkan_short_code`, `code_to_digest_hex`), open-kind resolution and the Seal v3 renderer (`resolve_kind`, `seal_rgb`, `seal_png`, reserved `KINDS` table and derivation constants), the tamper fingerprint (`domain_prefix`, `fingerprint_digest`, `fingerprint_hex`), the signed-record/canary module (`canonical_bytes`, `message_hash`, `sign`, `verify`, `status`), and the on-chain anchoring module (`build_canary_script`, `parse_canary_script`, and a BRC-100 `createAction` broadcast path with a testnet default guard). Stdlib-only for everything except secp256k1 signing/verification.
* **JavaScript port**: a dependency-free, synchronous, browser-and-Node port of the anchor, code, kind resolution, seal renderer, and fingerprint, proven byte-for-byte against the Python reference by the shared vectors.
* **Kotlin port**: the mobile/desktop rendering path, likewise held to the shared vectors.
* **Conformance vectors**: a fingerprint/canary/glyph/release-provenance vector set, a Seal v3 vector set (sampled fields, RGB SHA-256s, and constructed minimum-distance pairs), and a Seal v3 coverage set that reaches every entry of every table and the second block of the field stream, published with the Seal v3 tables file (§5). An implementation conforms iff it reproduces these sets for the layers it implements and, if it renders seals, its Seal v3 tables encode to the file §5 pins. Further sets pin the outcome of each rejection rule (text inputs and the Unicode database, §2; adopter names and fingerprint versions, §6; signed records, §7.3; presented PNG files, §5), and each binds every implementation that performs that check.
* **Standalone verifier and creator**, offered by RexStarBSV: a command-line verifier with `seal`, `fingerprint`, `canary`, and `selftest` subcommands (the verifier reproduces the pinned vectors before it trusts itself), plus creation of anchors, codes, and seals from any `(data, kind)` pair through the same package.

The reference sources, the full conformance-vector suite and a single-file web verifier that runs offline in a browser are published at <https://github.com/RexStarBSV/Inkan> (release `inkan-standard-v1.0`); this BRC is written so that a second, independent implementation can be built from the Specification and Test Vectors above without access to them. The optional seal presentation layer (§5) additionally requires the Seal v3 construction of the Inkan Standard and its tables file, which the SHA-256 in §5 identifies exactly; the interoperability core (§2-§4, §6-§7) is fully specified here.

## Mathematical and cryptographic basis

The commitment folds the type into the digest. The anchor is SHA-256 over a fixed, versioned domain string terminated by a zero byte, then the length-prefixed kind, then the content (§2). Because the kind sits inside the preimage, two records of different kinds coincide only if SHA-256 itself collides. The length prefix makes the concatenation injective: given the fixed domain, there is exactly one way to split a preimage back into (kind, content), so no two distinct field assignments produce the same bytes. This is what removes the need for a central type registry, since type distinctness is a property of the hash rather than of a lookup table. Authenticity of the anchoring party, where a use requires it, is a separate signature over the same digest and does not change the commitment.

## Security Considerations

* **The anchor is the trust root; the image is not.** Every verifier MUST re-derive the anchor from `(data, kind)` and MUST NOT trust a presented code, seal, or embedded metadata. A phishing lookalike fails because it is not the byte-exact render of the recomputed anchor.
* **Domain separation and length prefixing.** The fixed `INKAN_DOMAIN` prevents an anchor digest from being mistaken for, or manufactured from, any other SHA-256 usage. The `u8(len(kind))` prefix (§2) and the `u8(len(adopter))` prefix (§6) each close a boundary-ambiguity collision class; both MUST be implemented exactly, as an implementation that drops either re-opens a real collision (TV5).
* **Type binding without a registry.** Because `kind` is inside the digest, cross-type confusion is cryptographically excluded, not administratively prevented. This removes the registry as both a coordination burden and a single point of failure/censorship, but it also means there is no authority to revoke or arbitrate a namespace; adopters MUST choose namespaces they control (e.g. a domain-like `ns:`).
* **Hash agility.** The `ik1` version prefix makes a future migration off SHA-256 explicit and non-silent. A reader that assumes SHA-256 for an unrecognized version (e.g. `ik2`) would mis-verify; readers MUST reject unknown versions (§3).
* **Seal forgery economics** (figures are 2026 classical compute; see the erosion note at the end of this list). Visible entropy scales with tier: a perfect visual collision costs on the order of `2^16 / 2^28 / 2^40` renders (birthday) for `t3 / t2 / t1`. Low-tier seals therefore have *findable* perfect collisions **by design** (value-at-risk kinds SHOULD use `t1`, and in all cases the 32-byte anchor, not the image, is the trust root). The 6-bit frame yields 14 distinct frames per configuration; beyond that, open kinds may share a frame. This is defence-in-depth, not a break, since different kinds over the same data always differ in the interior and always differ in the anchor.
* **Honest perceptual limits.** All perceptual-distance figures are model outputs, not measurements of people; no human study has been run, so no accessibility or "phishing-proof" claim is made. Implementers MUST NOT market a seal as human-proof.
* **On-chain records are self-verifying and non-retractable.** A signed record (§7) carries its own record, signature, and public key, so it is checked entirely offline; the chain is trusted only for immutability and timestamp ordering. The `block_height`/`block_hash` binding is an anti-backdate proof. A verifier MUST treat absence, `STALE`, or `INVALID` as the warning (fail-closed), and MUST NOT trust the parsed on-chain bytes without re-verifying the signature and freshness. `INVALID` means a signature check was performed and failed; a verifier whose signature backend is unavailable MUST raise an explicit error and MUST NOT report `INVALID` or `OK`, since a fabricated verdict is a silent false alarm in one direction and a silent pass in the other.
* **Key custody.** The signing key MUST be the operator's own key held offline; the broadcast wallet (§7.5) funds but never signs, so a compromised or coerced broadcast path cannot forge a record. The test-network default (§7.5) keeps real funds as the last step, never the first.
* **Metadata hygiene / privacy.** The canonical seal and the fingerprint image writers emit no ancillary PNG chunks (no author, software, timestamp, or physical-dimension metadata). Implementations MUST preserve this and MUST NOT embed identifying information (public key, fingerprint, name, paymail, timestamp) in a seal's filename; a seal image should reveal nothing about the machine, build, time, or subject beyond the anchor itself. The optional `iTXt` tags carry only the (already public) `ik1` code and kind, and remain untrusted hints.
* **Presentation/anchor consistency for non-ASCII kinds.** The anchor NFC-normalizes `kind` while the reference presentation derivation does not (§4.2 note); callers SHOULD pass NFC kinds so both layers agree.
* **Unicode version skew.** Text normalization depends on the Unicode database each runtime ships, and the database grows every year. Without the rule in §2, two conforming implementations, or two releases of one runtime, derive different anchors for the same text containing a recently assigned character, and neither can tell. The rule turns that silent divergence into a visible rejection: an implementation on an older database may refuse an input that a newer one accepts, but no two conforming implementations derive different anchors for an input both accept.
* **Presented images.** A verifier that hands the file to a platform image decoder judges whatever that decoder chooses to draw. An animated GIF named `.png` can carry a genuine seal in the first frame, which is what a canvas reads, while the frames a viewer mostly sees show another mark; a PNG with a colour-space or orientation chunk can look different from the pixels compared. The verifiable-PNG profile (§5) closes this by requiring the verifier to decide on its own decode of a narrow, fixed set of PNG files.
* **Erosion of the forgery figures.** Every visual-forgery cost above is stated in 2026 classical compute and decays. Ordinary hardware growth lowers the classical cost over a decade, and Grover search takes a birthday bound of `2^n` renders toward `2^(n/2)` for an adversary holding a cryptographically relevant quantum computer, which would reduce the `t1` figure of `2^40` toward the order of `2^20`. These figures bound how hard a picture is to counterfeit; they are not the security of the standard. The 32-byte anchor is the trust root, a verifier re-derives it, and none of the guarantees in this section rest on the seal being hard to draw.

## References

* FIPS 180-4, Secure Hash Standard (SHA-256).
* RFC 2119, Key words for requirement levels.
* RFC 3629, UTF-8.
* Unicode Standard Annex #15, Unicode Normalization Forms (NFC).
* Unicode Standard Annex #44, Unicode Character Database (General\_Category, including `Cn`).
* Unicode Character Encoding Stability Policies (normalization stability).
* RFC 6979, Deterministic ECDSA.
* SEC 2 / secp256k1, the ECDSA curve used for on-chain signed records.
* Portable Network Graphics (PNG) Specification, W3C Recommendation (the verifiable-PNG profile, §5).
* RFC 1950, ZLIB Compressed Data Format Specification.
* BRC-42, BSV Key Derivation Scheme (protocol/key-ID namespacing contrast).
* BRC-43, Security Levels, Protocol IDs, Key IDs, and Counterparties.
* BRC-100, Wallet-to-Application interface (`createAction` broadcast path used by the optional on-chain anchoring).
* BRC-220, NotaryHash, privacy-preserving signed-hash notarization; the signed-hash counterpart this typed commitment composes with.
* 1Sat Ordinals, 1-of-1 ordinal inscription convention (used, not redefined, by §7.4).
* Golay, M. J. E., Notes on digital coding (extended Golay \[24,12,8], the seal's distance floor).
* The Inkan Standard, version 1.0 (frozen; text revision 1.0.1, 2026-10-10, SHA-256 `5ef0baafc83b1c6e5d80664c889df06a4b22d4e4d6450e81e24bd36983d5e53c`), by RexStarBSV, published at <https://github.com/RexStarBSV/Inkan>: its Seal v3 construction (§3 of that document), its Seal v3 tables file (SHA-256 in §5 here) and its conformance vectors, from which this BRC is drawn.


---

# 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://bsv.brc.dev/apps/0145.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.
