BRC-145 Applications

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

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 boundar

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

How it connects

Relationships

Found in the source

Implementation links

These links are extracted from the upstream implementation section. Inclusion is not an OpenStandards endorsement.

Authoritative content mirror

Full upstream text

View source ↗
Rendered from apps/0145.md at the indexed upstream commit

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 proposal 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 proposal 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
  1. 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+nFFFE and U+nFFFF), 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.