URMA. GitHub

SPECIFICATION / REVISION 0.8

The normative URMA V0 specification, rendered from the exact published Markdown. The Markdown file is the document of record; its SHA-256 identifies this revision.

Document
URMA V0 — normative protocol specification, revision 0.8, 7 October 2026; wire version byte 00; CC0-1.0
Exact file
PROTOCOL.md (Markdown, UTF-8)
SHA-256
d74fd9287cd48d6896eb8b7193a698426103cb2f357270d27bc9ca2ac4d90745
Whitepaper
urma-whitepaper-2026-10-07.pdf (PDF, non-normative, CC0-1.0; Cristian Zmole, ROSINT) · SHA-256 088d6b6e7ca5e2c5da1d3e0b5580432d14898e87f5752aa286dc7a5ed0ad01df
Source
github.com/urma-protocol/urma — implementation, readers, test code and vector-generation scripts under 0BSD; serialized vector data declared CC0-1.0 stays CC0-1.0; third-party dependencies keep their own licenses

URMA V0 — normative protocol specification

Document revision: 0.8 · 2026-10-07 · SPDX-License-Identifier: CC0-1.0

Contents

1. Authority and status

This is the single normative URMA specification. MUST, MUST NOT and REQUIRED express conformance requirements. Application documents cannot override this document.

V0 is a new wire format. No compatibility, implicit migration, marker alias, fallback decoder or cryptographic equivalence with previous experiments is specified.

Application profiles, products and services are outside this protocol. Profile interoperability is separate from URMA conformance, and the protocol never depends on a product or profile interpreter. A separate non-normative whitepaper presents the protocol scientifically; it adds no rules. The revision history of this document is in §15.

2. Scope and semantic families

URMA defines writing, verification and recovery of:

  1. Encrypted private objects containing arbitrary original bytes. Private text, images, documents, audio and video use the same private-object format. Content type is encrypted metadata.
  2. Public signed text. Its author is the key authorizing the Taproot reveal; a detached application signature MUST NOT be added as a second author proof.
  3. Generic public signed multipart objects containing arbitrary bytes. Their bounded root → leaves → data graph is defined in §7.2–7.8. Payload meaning and application profile validation remain outside the protocol.
  4. Atomic public profile records carrying an eight-byte profile identifier and opaque bytes, defined in §7.1.2. Payload meaning and profile validation remain outside the protocol.

Public text kinds 02/04/05/0A/0B, avatar kind 06 and profile record kind 0C are atomic. They use the single Taproot profile in §8, without fragmentation, reassembly or size-based transport selection. Atomic public records are at most 32,768 bytes including headers. Multipart records 07/08/09 are at most 262,144 bytes (256 KiB) including headers. A multipart object has the separate bound in §7.2. Clients MUST count bytes, not displayed characters.

The protocol accepts arbitrary public bytes and assigns them no meaning; what an application accepts or displays is outside it. A reference to external media preserves the reference, not the media.

Private objects may span their full representable canonical chunk count. Resource policies may refuse work but MUST NOT redefine protocol validity.

3. Roles, secrets and dependencies

An archive root is exactly 32 uniformly random bytes. Every object under that root is recoverable with that root. Compartments use independent roots. V0 defines no recipients, key wrapping, invitations, revocation or root rotation.

The public author is a BIP 340 x-only secp256k1 public key. It identifies a pseudonymous identity in the selected chain context, not a globally registered name. Funding, private recovery and public authorship are different roles. The author key and the funding key MAY be the same key; separate keys are not required. The archive root remains a separate, independently generated secret; nothing in this section permits deriving it from, or using it as, any other key. V0 does not purport to detect or prohibit a third party reusing underlying key material.

A mnemonic, QR or encrypted client backup MUST recover the raw root exactly. None is required for interoperable recovery. A wallet seed, publisher journal, original filename, implementation database or founder service MUST NOT be a private recovery prerequisite.

Data sources, chain consensus and retained history remain dependencies. A central index, username registry, canonical feed or mandatory operator relay MUST NOT be required. Replaceable indexes may supply candidates, not authority.

4. Common byte conventions

Offsets are zero-based. Lengths are bytes. Integers are unsigned little-endian unless explicitly stated otherwise. Addition, multiplication, ceiling division and offsets MUST be checked for overflow before allocation or I/O.

The four marker bytes are ASCII URMA, hex 55 52 4d 41. Version is a separate one-byte unsigned field; V0 encodes 00. No terminator follows a marker or string.

Offset Length Field
0 4 URMA marker
4 1 Version, 00
5 1 Record/container kind
6 2 Flags, zero unless a defined kind explicitly assigns bits

Kind values below are hexadecimal; all V0 flags are zero.

Kind Encoding Carrier
01 Private canonical record Taproot reveal or offline container
02 Public plain post Taproot reveal
03 Private-object container Offline only
04 Public reply Taproot reveal
05 Public profile Taproot reveal
06 Public avatar Taproot reveal
07 Public multipart data part Taproot reveal
08 Public multipart leaf manifest Taproot reveal
09 Public multipart root manifest Taproot reveal
0A Public structured Wire post Taproot reveal
0B Public structured Wire reply Taproot reveal
0C Public atomic profile record Taproot reveal

No other kind or flag is allocated.

Unknown versions, unallocated kinds and nonzero reserved flags MUST be rejected as unsupported encodings; they MUST NOT be parsed under a guessed layout. Malformed instances of an allocated encoding are invalid. Neither result is a complete authenticated object.

5. Private canonical record

5.1 Fragmentation and representational bound

Let C = 32,768 original bytes per chunk and L be the original byte length. Nonempty objects have N = ceil(L / C), with 1 <= N <= 2^32 - 1. An index i satisfies 0 <= i < N. Chunk i contains n = min(C, L - i*C) original bytes. All but the last contain C bytes.

The count and index are u32. Length is u64, additionally bounded by N*C and the count relation. The maximum representable original length is

140,737,488,322,560 bytes = (2^32 - 1) * 32,768 = 2^47 - 32,768.

There is no smaller normative private-object policy ceiling. Empty private objects are not encoded by this layout. A client limit, insufficient funds or local capacity refusal MUST be reported separately from invalid object data.

Every new encryption generates an independent OS-random 32-byte object ID. Each index is encrypted once under that ID. Resume MUST retain the original encrypted bytes. Reencrypting, including after snapshot restoration, requires a fresh object ID. RNG failure is outside the construction's security guarantee.

The public count is the real count. Dummy chunks, size classes, cover modes and power-of-two object padding are absent. Only the last record's unused plaintext capacity is randomly padded.

5.2 Layout

Every canonical private record is exactly 32,928 bytes.

Offset Length Field
0 8 Common header; kind 01, flags 0000
8 32 Random object ID
40 16 Keyed discovery tag
56 4 Index i, u32
60 4 Real total count N, u32
64 16 Canonical AES counter IV
80 32,816 Ciphertext
32,896 32 HMAC-SHA256 tag

The first 64 bytes are the public authenticated header. The decrypted body is:

Body offset Length Field
0 32 SHA-256 of the complete original bytes
32 8 Original length L, u64
40 4 Private content-type hint, u32
44 4 Reserved metadata flags, zero
48 n Original chunk bytes
48 + n 32,768 - n OS-random padding

Content-type hints: 0 opaque bytes, 1 text, 2 image, 3 audio, 4 video. They describe the original bytes; they neither transform them nor authorize execution. Text encoding or codec inference by a client MUST NOT alter recovery. Other hint values are unsupported in V0. All chunks of an object MUST agree on the authenticated digest, length, count, content type and metadata flags.

Original embedded metadata remains within the original encrypted bytes. Filename, identity, GPS and capture time are not additional public fields. The count leaks (N-1)*C < L <= N*C.

5.3 Cryptographic construction

Use SHA-256, HMAC-SHA256, HKDF-SHA256 and AES-256 in CTR mode. The HKDF labels below are exact ASCII bytes, without NUL or newline. Keys are raw bytes, not passwords, hexadecimal text or mnemonic strings.

PRK = HKDF-Extract-SHA256(salt = object_id, IKM = archive_root)
Kenc = HKDF-Expand-SHA256(PRK, "URMA/V0/private/content", 32)
Kmac = HKDF-Expand-SHA256(PRK, "URMA/V0/private/authentication", 32)
tag  = HKDF-Expand-SHA256(PRK, "URMA/V0/private/discovery", 16)
IV   = big_endian_u64(i) || zero[8]
ciphertext = AES-256-CTR(Kenc, IV, body)
MAC = HMAC-SHA256(Kmac, public_header || IV || ciphertext)

HKDF-Extract is HMAC with salt as HMAC key and the root as message. CTR encrypts the initial 128-bit big-endian counter, XORs the resulting keystream with the body, and increments that entire counter by one per block. No cipher padding is used. Internal random record padding is plaintext data covered by encryption and authentication. The 32,816-byte body consumes 2,051 AES blocks; adjacent index ranges start 2^64 blocks apart.

Every implementation uses this construction; portable recovery and negative vectors are mandatory conformance evidence (§12).

5.4 Validation and reconstruction

A reader MUST:

  1. Check exact record length, common header, nonzero count and index bounds.
  2. Derive the object's keys and tag locally and compare the derived tag with the stored discovery tag. A record whose stored tag differs is unrelated to this root: it MUST NOT be accepted for the archive even if its MAC would verify. A matching tag selects a candidate and authenticates nothing by itself; the MAC in step 3 decides.
  3. Verify the full HMAC over exactly bytes 0 through 32,895, and require the canonical IV, before decrypting or trusting encrypted metadata.
  4. Check decrypted length bounds, N = ceil(L/C), type, reserved flags and consistency with the object's other authenticated records.
  5. Accept identical authenticated duplicates idempotently. A different authenticated record for the same object/index is a conflict; arbitrary first-wins or last-wins resolution is forbidden.
  6. Gather every distinct index, remove only padding beyond the derived n, concatenate original chunks by index and verify both L and SHA-256.

A truncated, malformed or authentication-failing record is invalid. A set of valid records missing a required index is an incomplete object. Neither may be represented as a successfully recovered canonical object. A conflict invalidates that object's reconstruction; other objects remain independent.

Discovery skips unrelated records and SHOULD reject/count forged candidates; in both cases it continues, and neither outcome blocks other objects. A strict offline open (§6) rejects a supplied set that contains an unrelated or invalid record. Zero authentic chunks reveal no discoverable object. A wrong root and absence of matching objects are not distinguishable from this data alone. Omission of a whole object by a provider cannot be disproved without additional inventory.

Erasure coding is not a normative recovery step. Optional application artifacts can supply candidate reconstructed bytes, which MUST pass ordinary validation.

6. Offline private-object container and independent recovery

The optional offline container contains exactly one private object:

common_header(kind = 03, flags = 0)
record_count_u32
(record_length_u32 || canonical_record_bytes) repeated record_count times

Record count is 1 through 2^32-1, and every record length is exactly 32,928. Total container length is 12 + record_count * 32,932. Readers MUST detect truncation, overflow and trailing bytes. The wrapper has no separate MAC; the records authenticate their identity, ordering, metadata and content.

Mixed objects are rejected. Identical duplicates do not satisfy missing positions. A strict offline open rejects any invalid or unrelated supplied record and outputs only the complete validated original. Implementations may stream records to bounded storage rather than allocating from an untrusted count.

Independent recovery requires the raw root, canonical records or accessible source bytes, and the source coordinates necessary to retrieve them. For a chain scan: select the exact chain/network, a start height no later than the first relevant reveal, and an accessible history source. No final manifest is necessary for canonical private recovery.

A system-tool implementation must be possible from §§4–6 and the referenced standard primitives alone; no particular tool defines correctness. Scripts MUST authenticate before decrypting and avoid exporting partial plaintext. Derived-key exposure in process arguments, temporary plaintext handling and constant-time limitations must be disclosed for each actual script; they are not protocol security guarantees.

An offline public-text export cannot discard the transaction proof and still claim authenticated authorship; §8 applies.

7. Public record layouts

7.1 Atomic text, reply, identity profile and avatar

All public kinds use the common 8-byte prefix, flags zero, and the single Taproot publication profile in §8. Each record occupies one separate reveal transaction. The complete serialized record MUST be at most 32,768 bytes. The prefix determines one fixed body layout; no JSON, CBOR, TLV, extension map, lengthless intermediate field, separator or terminator is permitted.

Kind Body starting at offset 8 Complete length
02 plain post Exact UTF-8 text consuming all remaining bytes 8–32,768
04 reply Raw target TXID[32], then exact UTF-8 text consuming the remainder 40–32,768
05 profile UTF-8 pseudonym consuming all remaining bytes 8–32,768
06 avatar Exactly 512 raw pixel bytes Exactly 520
0C profile record Profile identifier[8], then opaque payload consuming all remaining bytes; §7.1.2 16–32,768

The reply target occupies offsets 8–39. Its bytes use transaction-hash wire order, as in a serialized transaction input's previous-outpoint hash. Convert a usual displayed TXID hex string by decoding hex and reversing the 32 bytes; reverse again for display. It refers to the selected chain/network, which is not duplicated in the record. Reply text starts at offset 40. There is exactly one target and no separate quote kind.

Text and pseudonyms MUST be valid UTF-8. Their bytes, including embedded U+0000 if present, are data, never terminators. Readers MUST NOT normalize Unicode, trim whitespace, change line endings, case-fold or reserialize before verification or recovery. Empty UTF-8 is structurally valid; client composition/display policy is separate. The text budget is 32,760 bytes for a plain post and 32,728 for a reply. The avatar body is binary and MUST NOT undergo UTF-8 decoding.

A plain post contains only text after its prefix. A reply contains only its fixed target and text. Profiles and avatars are independent author-authenticated transactions; neither is embedded in a post or reply. Identity-state resolution, caching and presentation are outside this protocol. These atomic kinds do not carry multipart content; kinds 07–09 have separate layouts and reuse the same transport.

Each public record is identified by its reveal TXID within the configured chain/network. Identical text in different transactions creates different posts. An unresolved target does not invalidate a reply's own bytes or author proof.

A different layout requires a newly assigned kind or protocol version. A future kind containing multiple variable-width fields MUST use explicit lengths; parsers MUST NOT infer boundaries from contents or append fields to a V0 kind.

7.1.1 Structured wires and hashtags (kinds 0A and 0B)

Kinds 0A and 0B are structured forms of kinds 02 and 04, which remain valid. The same 8-byte header, zero flags, one reveal, author proof and 32,768-byte complete record cap apply.

A kind 0A body contains the following fields in order. A kind 0B body first contains one raw 32-byte target TXID in the same wire order and selected-network context as kind 04, followed by these same fields:

Field Encoding
Wire length u8, 0–32
Wire Exactly wire-length ASCII bytes; zero length means no wire
Hashtag count u8, 0–8
Each hashtag u8 length (1–32), followed by exactly that many ASCII bytes
Text length u16 little-endian, number of UTF-8 bytes
Text Exactly text-length bytes, valid UTF-8, preserved without normalization

After text, the parser MUST be at the exact end of the record. Truncation, trailing bytes, excess lengths/counts and complete records above the cap are invalid. Empty text is structurally valid. Minimum complete lengths are 12 bytes for 0A and 44 for 0B. Metadata consumes part of the total byte budget; the u16 text field does not authorize text exceeding the record cap.

Each nonempty label MUST match [a-z0-9][a-z0-9_-]{0,31}. Wire names are single flat labels; there are no subchannels. / and # are display prefixes, never encoded label bytes. Hashtags MUST be strictly ascending in ASCII byte order, which also excludes duplicates. Decoders MUST reject noncanonical bytes, not normalize them.

Each record has zero or one wire and independently zero to eight hashtags. A hashtag denotes the same subject across wires within the selected network. Wire and hashtag labels are entirely open: no registration, owner, privilege, authorization or protocol moderation is implied. A label proves no affiliation.

A reply may target a post or reply (02, 04, 0A or 0B) on the selected network. Its wire and hashtags are its own declared metadata, not implicitly inherited from the target. An unresolved target does not invalidate the reply's own bytes or proof. Existing 02/04 text containing /labels or #words remains literal text, with no structured metadata inferred. Records without metadata may use 02/04; empty metadata in 0A/0B is also structurally valid. A reader that does not support 0A/0B rejects them as unsupported, rather than misreading their text.

Structured fields support deterministic grouping; they do not remove the need to discover, verify and index chain records.

7.1.2 Atomic profile record (kind 0C)

Kind 0C carries an application profile's bytes in one atomic record. The same 8-byte header, zero flags, one reveal, author proof and 32,768-byte complete record cap apply. Its body is:

Offset Length Field
8 8 Profile identifier
16 0–32,752 Payload consuming all remaining bytes

Complete lengths are 16 through 32,768 bytes. The profile identifier is authenticated but uninterpreted by the core; eight zero bytes mean unspecified profile, and an unknown identifier does not make an otherwise valid record invalid. The payload is opaque to the core: it undergoes no UTF-8 decoding, and bytes resembling another URMA record, manifest or archive trigger no parsing. Payload layout, meaning and validation belong to the profile's own specification; conformance to a profile is separate from URMA conformance, and the core never depends on a profile interpreter. A reader that does not support kind 0C rejects it as unsupported.

7.2 Constants, byte conventions and geometry

All offsets below are absolute within a canonical record. The common eight-byte URMA header is unchanged. Flags and explicitly reserved fields are zero. Integers in these records are unsigned little-endian. SHA-256 fields contain the 32 raw digest bytes, without display-order reversal. TXIDs contain the existing transaction-hash wire order from §7.1; displayed hexadecimal is converted by reversing the decoded 32-byte value.

Symbol Value / definition
R 262,144 maximum bytes per complete multipart record
C R - 12 = 262,132 original object bytes per data part
F 511 data references per leaf
G 511 leaf references per root
L Original public object payload length
N max(1, ceil(L / C)) data parts
M ceil(N / F) leaves

The explicit object ceiling remains 8,553,279,476 bytes, including profile framing. It is not enlarged by the larger record capacity. Readers MUST require 0 <= L <= 8,553,279,476, derive N and M as above, and require 1 <= N <= 32,630 and 1 <= M <= 64. Fanout remains 511; the largest permitted object has 32,630 parts, 64 leaves and one root (32,695 records). The final part at that ceiling has 174,448 payload bytes. The structural 511-by-511 tree capacity does not authorize larger objects.

Readers MUST NOT fall back to 32,756-byte slices or accept a root whose counts encode that rejected geometry; there is no legacy interpretation.

For L > 0, part i has exactly min(C, L - i*C) payload bytes. For L = 0, there is exactly part 0 with zero payload. There are no dummy parts, variable chunk sizes, padding records or direct root-to-data shortcuts.

Leaf j covers indices j*F through j*F + min(F, N-j*F)-1. Every nonfinal leaf therefore has exactly 511 entries. The root has exactly M entries in increasing leaf-index order. A one-part object still uses all three levels. Use checked arithmetic for every count, offset and multiplication before I/O or allocation.

7.3 Kind 07: public data part

Offset Bytes Field
0 8 Common header: version 00, kind 07, flags zero
8 4 Global part index i, u32
12 0–262,132 Exact consecutive object payload bytes

Record length is 12 through 262,144 bytes. The zero-payload case is legal only when the root declares the empty object above. Index and exact payload length are validated against the root/leaf context; reject i >= 32630 even before that context is available. A structurally valid detached part does not by itself establish a complete object.

The payload is opaque. Bytes resembling another URMA record, archive, executable or manifest do not trigger parsing or recursive traversal.

7.4 Shared child-reference entry

An entry is exactly 64 bytes:

child_reveal_txid[32] || SHA256(exact_complete_child_record)[32]

Hash the complete child record including its common header, not a decoded object, application payload, script or transaction. The TXID locates the reveal; the record hash binds expected content independently of witness-excluding TXID semantics. No URLs, block heights, filenames, inferred ordering or index-service IDs occur in these references.

7.5 Kind 08: public leaf manifest

Offset Bytes Field
0 8 Common header: version 00, kind 08, flags zero
8 2 Leaf index j, u16
10 2 Data entry count k, u16
12 4 First global part index, u32
16 64*k Ordered references to kind 07 reveals

Readers MUST require j < 511, 1 <= k <= 511, first_index = j*511, exact length 16 + 64*k, and no trailing bytes. The maximum leaf length remains 32,720 bytes; unused capacity under the 262,144-byte multipart record ceiling MUST NOT be padded. In object context also require j < M and k = min(511, N-first_index). Entry r requires part index first_index+r. Leaves cannot reference leaves or roots.

7.6 Kind 09: public root manifest

Offset Bytes Field
0 8 Common header: version 00, kind 09, flags zero
8 8 Original object length L, u64
16 32 SHA-256 of the complete original object payload
48 4 Total data part count N, u32
52 2 Total leaf count M, u16
54 2 Reserved, zero
56 8 Opaque application profile identifier
64 64*M Ordered references to kind 08 reveals

Readers MUST require the geometry of §7.2 and exact length 64 + 64*M, from 128 through 4,160 bytes under the explicit object ceiling. Unused capacity under the 262,144-byte record ceiling MUST NOT be padded. Root entry j requires leaf index j. The profile identifier is authenticated but uninterpreted by the core. Eight zero bytes mean unspecified application; unknown identifiers do not make an otherwise valid URMA object invalid. Profile-specific interpretation is outside core validity.

The root reveal TXID is the locator. A root does not contain its own TXID. There is no content-derived substitute for this locator and no global object-ID registry. Re-publication in another root transaction produces another locator.

7.7 Author proof, membership and rejection

Every root, leaf and data part uses the existing canonical Taproot commit/reveal profile in §8. All three kinds are allowed on-chain. A commit may fund multiple public reveals as a transport-client choice; each reveal still carries exactly one complete record and retains the current transaction shape.

Let A be the root's verified BIP 340 x-only author. Readers MUST require every referenced leaf and data reveal to verify under that same A. No detached signature is added. Funding keys do not establish authorship. Signatures inside an application payload are preserved content, not a second URMA author proof.

For each node, verify the full reveal, actual commit outpoint/amount/script, canonical script, single-leaf commitment, control block and SIGHASH_DEFAULT signature exactly as in §8. Parsing a key or comparing a TXID alone is insufficient. Preserve witnesses for offline verification.

Maintain a graph-wide set initialized with the root TXID. Reject any reference to an already referenced reveal TXID, including the root itself, repeated leaves, repeated data within/across leaves and ancestor references. Also enforce the kind required at each edge. Together these rules reject cycles and aliasing; no generic recursive graph walker is needed. Identical payload bytes at two positions are allowed, but their records have different indices and distinct reveal TXIDs. Different roots may independently reference the same otherwise valid records; duplicate prohibition is per root graph.

At a source boundary, a reveal with the wrong TXID, or one whose proof does not verify, is an invalid candidate: discard it and try another source. Such a candidate does not poison authentic data already retained. Verify the proof before judging the record hash. Once the reveal at the requested TXID has a fully verified proof, the record it carries is fixed by the commitment; if that record's SHA-256 differs from the reference in the signed manifest, the object structure is invalid, and no other source is tried for that object. A signed manifest containing duplicate slots, wrong geometry or an impossible reference type is likewise invalid object structure. Never resolve disagreements by first-wins or last-wins selection.

7.8 Completeness and independent recovery

Recovery from a known (chain, root_txid) is:

  1. Fetch and verify the root and its commit context; validate bounded geometry.
  2. Fetch every named leaf; verify its proof and author, then check its record hash against the reference and its position.
  3. Fetch every named part; verify its proof and author, then check its record hash against the reference, its index and its length.
  4. Concatenate payloads by global index. Check exact L and root SHA-256.
  5. Only then export the complete generic payload or claim full application-profile validation. A bounded diagnostic inspection of already authenticated segments MAY precede completeness, but MUST be labelled partial and MUST NOT establish whole-object integrity, profile acceptance or successful recovery.

The public manifest hierarchy is mandatory inventory for this family. The existing rule that private bytes can be recovered without a final manifest remains true for private objects; it must not be generalized to this family. Missing a leaf or part means incomplete recovery, even if an application could use some available bytes. Source failure is distinct from missing data. No verified partial payload is labelled a complete snapshot.

Cryptographic validity, generic completeness, application-profile validity, chain inclusion and confirmations are separately reported. A reorg does not corrupt retained bytes. URMA has no all-or-nothing chain transaction spanning this graph.

Implementations MUST bound I/O, memory and storage before allocation. They MAY refuse a valid graph exceeding declared local capacity; such a refusal is not invalidity. Reconstruction MUST use bounded staging and MUST NOT expose a partial payload as a completed object. Partial diagnostic inspection is not a complete payload handoff. Source adapters must bound raw responses and retries before decoding. No profile parser is a prerequisite to generic recovery.

8. Canonical Taproot publication profile

8.1 Transaction and key roles

For public records, A is the stable x-only public author key. For a private record, A is a transport signing key: it authorizes the transaction and is not private-content authorship. It MUST NOT be derived from the archive root. It MAY be reused across private records and MAY be the writer's author or funding key; V0 requires no per-record separation.

One reveal carries exactly one complete record of kind 01, 02, 04, 05, 06, 07, 08, 09, 0A, 0B or 0C. The offline container kind 03 MUST NOT be accepted as an on-chain record. A commit creates the native P2TR output that this reveal spends. A commit may fund several private or public reveals; it does not make their object complete until all required canonical records are recoverable.

The reveal has transaction version 2, exactly one input spending its commit outpoint, an empty scriptSig, and one native SegWit return output (P2WPKH or P2TR). Output value, fee, nLockTime and nSequence are chosen before signing and must satisfy the selected chain's rules; they are not content metadata. The commit's other funding/change details are transport-client concerns. A funding signature alone never establishes public author identity.

8.2 Script and commitment

There is one tapscript leaf, leaf version c0, and no additional Merkle path. The internal key is A. The leaf is precisely:

PUSH32(A) OP_CHECKSIG
OP_0 OP_IF
  PUSH4("URMA")
  PUSH(record segment 0) ... PUSH(record segment k)
OP_ENDIF

Split the canonical record into consecutive 520-byte segments plus one final nonempty segment if needed. No empty trailing segment is added. Use minimal push encodings: a one-byte segment 01..10 uses OP_1..OP_16 and byte 81 uses OP_1NEGATE; all other segments of length 1–75 use direct length (including a one-byte 00 segment, encoded as 01 00). Lengths 76–255 use PUSHDATA1; 256–520 use PUSHDATA2 with a little-endian length. The parser reconstructs number-opcode segments as their one-byte values, requires canonical segmentation and rejects extra opcodes/data. The separate OP_0 before OP_IF is structural and contributes no record bytes.

The native commit output is OP_1 PUSH32(Q.x), where the Taproot output key Q is the BIP 341 tweak of internal key A with this exact single-leaf script. The control block is 33 bytes: c0 OR Q parity, followed by A. Reject a different internal key, leaf version, path length or output commitment.

8.3 Author proof

The reveal witness is exactly:

[64-byte Schnorr signature, exact tapscript, 33-byte control block]

There is no annex, detached application signature or extra witness item. Only SIGHASH_DEFAULT is accepted: a 64-byte BIP 340 signature with no appended sighash byte. The signature message is the BIP 341 transaction message with the BIP 342 script-path extension: ext_flag=1, key_version=0, this tapleaf hash, and codesep_pos=0xffffffff because the script has no code separator.

A verifier needs the full reveal, the exact commit prevout amount/script, the tapscript and control block. It MUST validate the commitment and Schnorr signature against A and the actual transaction/prevout context. Merely parsing a public key from a script, validating a commit funding signature or looking up a TXID is insufficient.

This binds the public record through its script-path commitment and author authorization. The TXID identifies the post, but does not itself commit to witness bytes. An offline public proof therefore retains both transactions/witness and the required prevout context; chain inclusion evidence is a separate claim.

8.4 Networks and discovery

Bitcoin and Litecoin use distinct chain contexts; network identity MUST be checked before fetching or submitting. Mainnet support cannot be inferred from test-network software. Feed identities, TXID references and observations are scoped to the configured chain/network. Cross-chain replies and a merged canonical feed are absent.

A scanner searches input witnesses for the exact envelope and validates the record and its proof. It MUST NOT treat an arbitrary occurrence of the marker as a valid record. Chain sources must preserve inclusion-evidence semantics.

Full consensus/chain selection may be supplied by a validating node. Bitcoin raw-block verification includes the witness commitment, not only the transaction Merkle root. A reader that takes Litecoin's scrypt proof of work, MWEB or inclusion validation from a validating node has delegated them; that delegation is not independent consensus validation by a URMA parser.

Record structural/cryptographic validity, chain inclusion, confirmation count, provider provenance and client lifecycle remain distinct. The canonical record does not contain a broad publication-state machine.

9. Private-object invariants and implementation boundaries

Each object is an independent reconstruction boundary. V0 defines no atomic archive/scan/account transaction. Implementations may export complete authentic objects while reporting other incomplete objects separately.

Source failures and reorg observations are not cryptographic record failures. An implementation MUST NOT claim a scan completed or a historical placement remains confirmed when its source/evidence does not establish that claim. Retained authentic private bytes do not become corrupt merely because their observed placement was orphaned.

Resource caps MUST be declared as local implementation capacity. Parsers must bound their work and storage without trusting unauthenticated lengths. Normative integer bounds, exact byte layouts and application capacity refusals must not be conflated.

Publication persistence, scheduling, quotes, fee budgets, retry/timeout handling, partial-success exit status, parity artifacts, UI labels and caches are outside this protocol.

10. Security model

Encryption does not hide marker use, chunk grouping/count, publication timing or funding relationships. Root compromise exposes that compartment's objects. An authorized holder can retain or redistribute recovered bytes.

Public author proof establishes authorization by a pseudonymous key, not the person's civil identity, uniqueness of display name, truth of a statement or capture time. Private MAC authentication likewise does not prove a photographed scene was genuine.

Recovery requires bytes that left the lost device, retained accessible copies and surviving secrets for private objects. Chain publication supplies no unconditional eternity, connectivity or censorship-resistance guarantee. External media linked by public text is mutable and not canonically recovered.

The originating application and operating system can see the original plaintext; nothing in the protocol protects plaintext held on that device.

11. Versioning and licensing

A change to accepted bytes, key derivation domains, mandatory validation or meaning requires an explicit versioned specification update and new vectors. Unknown versions are never guessed. Compatibility follows the serialized marker, version and kind bytes; a project name confers none.

The specification, scientific whitepaper, public vectors and protocol data are CC0-1.0. Project-authored source and reference applications are 0BSD. Dependencies retain their own licenses.

12. Conformance and completion

URMA V0 conformance requires a public deterministic corpus derived from this specification, with exact input/output bytes and expected outcomes. Self round-trips alone are insufficient.

The corpus must cover private key derivation, encryption/MAC, padding and count, all integer and size boundaries, invalid records versus incomplete objects, unrelated records including a differing discovery tag with a valid MAC, duplicates/conflicts, unknown versions/flags/kinds, public UTF-8 preservation, atomic caps, author proof, script/commitment/sighash rejection, and every other normative rejection. Include every public kind from §7, exact reply TXID byte order, truncated targets, profile/UTF-8 handling, avatar lengths and forbidden appended fields.

For 07–09, the corpus MUST additionally cover empty objects, every record/count boundary, 511-entry leaves, multiple leaves, exact two-level geometry, order, duplicates/cycles/wrong edge kinds, missing nodes, candidate TXID and proof failures versus invalid signed structure, including a fully verified child whose record hash differs from its signed reference, author mismatch, unknown opaque profiles, reserved/trailing bytes, checked overflow and bounded exact recovery. For 0C, the corpus MUST cover the minimum and maximum complete lengths, an empty payload, zero and unknown profile identifiers, truncation below 16 bytes and the absence of any inferred payload structure.

An independent reader MUST verify public graph proofs and reconstruct payloads without the URMA binary or SDK. Existing 01–06 results remain regression gates. Maximum-count arithmetic vectors MUST be labelled sparse; claiming the full 8,553,279,476-byte capacity requires an actual streamed maximum reconstruction with measured memory, disk and time. Public-chain acceptance requires separate evidence.

At least one independent implementation must recover private original bytes and reject invalid authentication without an URMA binary, SDK, service or implementation database. Each supported chain transport needs published evidence under its declared validation model. Scripts, code and vectors must agree before the format revision is complete.

Conformance, stability and external audit are separate claims; an audit claim requires an attributable report. A public corpus and readers are published in the project repository (https://github.com/urma-protocol/urma); implementation status and evidence are recorded outside this document.

13. Primary specifications

14. Decision traceability

The following internal decision identifiers record provenance. The requirements are fully stated in the sections cited; no internal document is needed to implement them.

Owner decisions Location
D-001–D-003 §§1, 4, 11
D-004–D-005 §§2–3; Capture, Archive and Wire application contracts
D-006–D-007 §§5–6, 12
D-008–D-010 §§3, 7–8; Wire profile rules
D-011–D-012 §§5.4, 9; shared export services and Archive Redundancy Profile
D-013–D-014 §7; Wire reply/profile/avatar application rules
D-015 §§2, 5, 7
D-016–D-018 Wire rollback/visibility and shared publication persistence
D-019 §§3, 6
D-020–D-021 Wire external-image behavior
D-022–D-023 §§5.1, 9, 12
D-024–D-025 §§2, 8, 11–12
D-026 §§4, 7; Wire separate profile/avatar transactions and cache
D-027–D-029 §§1–3, 9; architecture, profiles, applications and funding documents
D-030 §§2, 4, 7.2–7.8, 8, 12; generic public multipart allocation
D-031–D-032 Git profile/application and shared-services contracts; no Git semantics in core
D-033 §3; one public identity may both sign author proofs and fund transactions
D-036 §§2, 4, 7.1.2, 8.1, 12; atomic profile record kind 0C; registry profiles outside the core

D-035 governs the multipart record capacity and the separately retained object ceiling in §7.2. D-036 governs kind 0C; the names registry profile that uses it is a separate interoperable specification, not a core rule. The owner decisions of 2026-10-07 govern key roles (§3, §8.1), discovery-tag acceptance (§5.4, §6) and the classification of a record-hash mismatch after a verified proof (§7.7, §7.8).

15. Document revisions

This list is history, not a rule; every rule is stated in the sections cited.

  • 0.4: public multipart kinds 07–09 under wire version 00; kinds 01–06, their byte layouts, private cryptographic domains and Taproot validation unchanged.
  • 0.5: the multipart geometry of §7.2 retained.
  • 0.6: atomic profile record kind 0C (§7.1.2); kinds 01–0B unchanged.
  • 0.7: no byte layout, key derivation or kind changed; discovery-tag acceptance (§5.4, §6), record-hash mismatch after a fully verified proof (§7.7, §7.8), shared author and funding key (§3), reusable private transport key (§8.1).
  • 0.8: no rule changed; implementation state, historical evidence and dependencies on auxiliary documents removed, so that this document stands alone.