Untilum
← Back to Untilum Protocol

UDDP Container v1 — Autonomous Capsule File Format (.uddp)

Status: Draft Date: 2026-07-14 Reference implementation: packages/decryptor (generateUddp), tests in packages/decryptor/src/uddp.test.ts Depends on: capsule-v1.md, tsp-v1.md (capsule wire formats — the container packages a capsule, it does not redefine one)

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in BCP 14 (RFC 2119, RFC 8174) when, and only when, they appear in all capitals.


1. Purpose and model

A .uddp file is the distribution format of a sealed capsule: a single file the recipient can keep for decades and open with no software from this project — no web app, no CLI, no installed application. Everything needed travels inside:

The design answer to “what if the program we made no longer exists?” is: a copy of the opener is inside every capsule file. The project dying cannot take the opener away from a recipient who holds the file.

1.1 Why a ZIP container

A proprietary extension and platform-independent opening pull in opposite directions: an OS with no registered handler opens .uddp with nothing. The resolution is the .docx/.epub/.jar pattern — an own extension over plain ZIP. ZIP has a 30+ year ubiquity record, a public specification, built-in readers in every OS file manager, and unmistakable magic bytes (PK): any technician confronted with an unknown file will identify and unpack it. The container’s job is to make sure what they find inside explains itself.

1.2 The authority rule

The container is packaging, not protocol. The embedded capsule.json is the sole normative artifact: the creator signature, the payload hash, and every verification claim are defined over the capsule JSON exactly as in the wire-format specs — never over the container or the opener. Consequences:

  1. Extracting capsule.json and opening it with any conforming TSP opener MUST produce the identical plaintext.
  2. Re-wrapping the same capsule.json in a newer container changes nothing normative, and MUST NOT modify its bytes.

1.3 The opening ladder (design requirement)

Rung Requires Mechanism
A — one click our application installed app registers .uddp; double-click opens directly
B — two steps any OS + any ES2022 browser, offline open/rename as ZIP → README.txt says: open OPEN-ME.html in a browser
C — extract & open elsewhere any conforming TSP opener unzip, take capsule.json
D — re-implement a competent engineer, a text editor README.txt + spec/ + test vector: rebuild an opener from documents alone

Rung A is the product; rungs B–D are the century insurance. A container that delivers A but not B–D is a demo, not a UDDP file.

2. Container format

2.1 Type detection (the mimetype entry)

Following the EPUB convention, the first entry in the archive MUST be named mimetype, stored uncompressed (method 0), no extra field, containing exactly the ASCII bytes:

application/x-uddp

This places the string at a fixed offset near the start of the file (local header 30 bytes + 8-byte name → content at offset 38), so file-style detectors identify a UDDP container from the first ~60 bytes without unpacking: PK\x03\x04 at offset 0, mimetype at offset 30, application/x-uddp at offset 38.

2.2 Layout

mimetype                # REQUIRED, first, stored — §2.1
capsule.json            # REQUIRED — the normative capsule document, byte-exact (§3)
OPEN-ME.html            # REQUIRED — self-contained offline opener (§4)
README.txt              # REQUIRED — plain-text instructions (§6)
spec/                   # REQUIRED — the protocol documents (§6)
  capsule-v1.md         #   (and/or tsp-v1.md — whichever governs this capsule)
  vectors/…             #   at least one known-answer test vector
payload.bin             # OPTIONAL — raw ciphertext bytes for large payloads (§7)
beacon.json             # OPTIONAL — pre-fetched round-R beacon, if available (§5)
manifest.json           # OPTIONAL — SHA-256 of every other entry (§8)

Unknown additional entries: readers MUST ignore entries they do not recognize (forward compatibility of packaging) — but the opener and extractors MUST take the capsule only from capsule.json, never from an unknown entry.

3. capsule.json (rung C — machine extraction)

4. OPEN-ME.html (rung B — the embedded opener)

A single self-contained HTML5/UTF-8 document that opens from file://:

4.1 The opener template and cross-implementation identity

OPEN-ME.html embeds a compiled bundle that cannot be byte-identically rebuilt across toolchains, so it is produced once as a versioned template artifact (opener-template-v<N>.html, published with its SHA-256 — see RFC-0010 §3), and every generator produces the opener by single-pass placeholder substitution: the template is scanned for {{UNTILUM_[A-Z_]+}}; each match is replaced; substituted values are never re-scanned. Generators MUST NOT modify template bytes outside the placeholders.

Placeholder Replacement
{{UNTILUM_TITLE}} human title, HTML-escaped
{{UNTILUM_OPEN_DATE}} unlock.at (ISO-8601 UTC), HTML-escaped
{{UNTILUM_ROUND}} decimal round number
{{UNTILUM_CAPSULE_JSON}} capsule JSON, byte-exact, then </script><\/script> (case-insensitive)
{{UNTILUM_CONFIG_JSON}} the runtime config JSON (below), same </script> escaping

HTML-escaping: exactly &&amp;, <&lt;, >&gt;, "&quot;, in that order, nothing else.

The runtime config is serialized with JSON.stringify semantics (no added whitespace, UTF-8 verbatim, standard escapes) and this exact key order:

{"capsuleJson": "<capsule JSON as a string>",
 "openDateIso": "<unlock.at>",
 "round": <round>,
 "beaconId": "<unlock.beacons[0].id>",
 "embeddedBeacon": null | {"round": , "signature": "…", "randomness": "…"}}

beacon.json (§5) uses the same three-field key order. With the same inputs and the same template version, every conforming generator MUST produce a byte-identical container (RFC-0010 §F2); README.txt rendering follows the reference implementation byte-for-byte (packages/decryptor/src/uddp.ts).

5. Beacon acquisition

The opener MUST offer, in order of preference:

  1. beacon.json / embedded — when the container was generated (or re-issued) after the unlock date, the round-R beacon MAY ride along; it is still BLS-verified at open time (embedding bypasses the fetch, never the verification).
  2. Fetchhttps://api.drand.sh/<chainHash>/public/<round> and the Cloudflare relay, as pinned constants.
  3. Manual paste — a field accepting the beacon JSON or bare signature hex from any source (mirror, archive, another person), verified like all others.

README.txt MUST explain how to obtain a historical round if all listed URLs are dead (drand mirrors; the Arweave beacon archive when available).

6. README.txt and spec/ (rung D — re-implementation)

README.txt is plain ASCII/UTF-8 text, no markup dependency, and MUST contain:

  1. What this file is — one paragraph; the unlock date (UTC) and round number.
  2. How to open it — the two-step instruction (“this file is a ZIP archive; extract it and open OPEN-ME.html in any browser”), stated simply enough for a non-technical recipient.
  3. The password rule — the password is held by the sender only; it was never stored and cannot be recovered by anyone, including the project.
  4. The opening algorithm in brief — the envelope scheme of the spec governing the packaged capsule’s version (tlock-decrypt of tlsecret, Argon2id parameters, the version’s HKDF info strings — "untilum-v2" / "untilum-v2-meta" for v2, "capsule-v1" / "capsule-v1-meta" for v1 — AES-256-GCM layout) with all pinned quicknet constants, self-contained: correct even if every project URL is dead.
  5. Pointers — canonical spec/RFC URLs and their Arweave txids.

spec/ MUST contain the wire-format specification governing this capsule’s version and at least one known-answer test vector, so a re-implementation can prove itself before touching the real capsule. This is RFC-0009 §6 made literal: the documents that guarantee resurrection ride inside every capsule file.

7. Large payloads (payload.bin)

8. Integrity and the tampered-wrapper problem

The creator signature covers the capsule JSON only (§1.2). The wrapper — opener, README, specs — is not covered: a party who can modify the file could replace OPEN-ME.html with one that exfiltrates the password. This is inherent to any self-executing container. Mitigations, in force order:

  1. Storage pinning (primary). When the capsule is stored on Arweave, the stored object SHOULD be the entire .uddp file, so one transaction id immutably pins capsule and wrapper together. A recipient who fetches by txid holds a container nobody could alter after sealing.
  2. Reproducible generation. Container generation SHOULD be deterministic — fixed entry order, zeroed timestamps, no extra fields, stable deflate settings — so the same capsule + the same generator version yields a byte-identical file that any third party can regenerate and diff.
  3. Out-of-band hash. The receipt (RFC-0007 §4) SHOULD carry the SHA-256 of the full container alongside the capsule hash. The optional manifest.json (SHA-256 of every other entry) lets a reader localize which entry was altered — noting that a wrapper-tamperer can rewrite the manifest too; only the external pin (1) or hash (3) is load-bearing.
  4. CSP + offline habit. The opener’s CSP and the documented advice to open offline bound what a tampered opener can do quietly.

None of these makes tampering impossible for an attacker who controls both the file copy and the recipient’s trust; they make it detectable by anyone who checks.

9. Versioning

10. Conformance checklist

A file is a conforming UDDP v1 container iff:

  1. Valid ZIP; first entry mimetype, stored, exactly application/x-uddp (§2.1).
  2. Exactly one capsule.json, byte-exact as signed (§3).
  3. OPEN-ME.html: self-contained, offline-capable, embeds a byte-identical capsule copy, BLS-verifies every beacon, runs the KAT self-test, leaks no secrets (§4).
  4. All three beacon acquisition paths, all verified identically (§5).
  5. README.txt with the two-step instruction, algorithm summary, and constants; spec/ with the governing spec and a test vector (§6).
  6. Payload packaged per §7 with hash verification on every non-inline path.
  7. Unlock date, round, and capsule hash displayed before password entry (§4).

10.1 Implementation status (informative)

Implemented in packages/decryptor (generateUddp): the deterministic store-only ZIP container with the mimetype convention (§2), byte-exact capsule.json (§3), OPEN-ME.html with the extractable untilum-capsule block, CSP declaration, and emergency appendix (§4), beacon.json embedding (§5), generated README.txt + spec/ packaging (§6), inline-ceiling enforcement (§7), and manifest.json (§8.3).

Not yet implemented: the payload.bin + file-picker flow for large payloads (§7 — the generator rejects ciphertext above the inline ceiling instead), the by-reference variant (§7), and .uddp registration in the desktop application (rung A of §1.3).

Revision History

Date Change
2026-07-14 Initial version (as TDDP; renamed to UDDP 2026-07-16 with the project rename, same wire layout).
2026-07-17 Publication pass for the series Last Call: BCP 14 boilerplate.