Specifikace protokolu qub

qub je protokol pro kryptografické časové závazky: systém pro zapečetění slov k budoucímu datu a pozdější ověření přesně toho, co bylo zapečetěno, které kolo drand podmínilo jeho uvolnění a — je-li k dispozici transakce úložiště nebo důkaz z logu transparentnosti — nezávisle časově označené horní meze okamžiku, kdy byl šifrovaný text potvrzen.

Fungování zajišťují tři primitiva. drand je decentralizovaný maják náhodnosti — datum odhalení je vynuceno kryptograficky, nikoli dobrou vůlí qub. Trvanlivé úložiště spolu s logem transparentnosti pouze pro připojování uchovává zapečetěné bajty a kotví dávkové závazky do trvalého veřejného úložiště; placená cesta T3 zapisuje také samostatnou transakci trvalého úložiště. ML-DSA-65 je postkvantový digitální podpis — je-li autorství zapnuto, qub je svázán s párem klíčů, jehož tajná část nikdy neopustí zařízení autora.

Společně tato primitiva vytvářejí výrok, který je časově uzamčený a prokazatelně odolný proti manipulaci, volitelně přiřaditelný a nezávisle časově označitelný — potvrzení, jehož hodnota roste s tím, jak se zlepšuje schopnost světa falšovat minulost.

Zbytek tohoto dokumentu je normativní specifikace nutná pro interoperabilní implementace.


Specifikace protokolu qub

Pole Hodnota
Vydání dokumentu 1.0.0 (protocol-v1.0.0)
Přenosový protokol 0x01
Vnější obal 0x01
Datum účinnosti 2026-09-23
Stav Aktuální
Zkontrolováno do 2026-09-23

Tento dokument je normativní specifikací protokolu pro systém časových závazků qub. Definuje datové struktury, pravidla serializace, odvozovací vzorce a ověřovací postupy nutné pro interoperabilní implementace.

Rozsah: vrstva protokolu je záměrně jazykově neutrální — tělo qubu je neprůhledný prostý text / markdown / bajty paktu a vykreslení s ohledem na lokalizaci je odpovědností prohlížeče (webová aplikace qub.social, iframe <qub-embed>, klienti MCP atd.).


1. Notace a konvence

Notace Význam
u8, u64, i64 Celá čísla bez znaménka / se znaménkem o specifikované bitové šířce
[u8; N] Pole bajtů s pevnou délkou N bajtů
Vec<u8> Pole bajtů s proměnnou délkou
Option<T> Hodnota typu T, nebo nepřítomná
String Textový řetězec UTF-8, normalizovaný do NFC
`
SHA3-256(x) Hash NIST SHA3-256 řetězce bajtů x (FIPS 202)
ceil(x) Funkce horní celá část: nejmenší celé číslo ≥ x
CBOR Concise Binary Object Representation (RFC 8949)
big-endian Nejvýznamnější bajt první

Všechna celá čísla v konstrukcích předobrazů jsou kódována jako big-endian pole bajtů s pevnou šířkou (i64 → 8 bajtů, u8 → 1 bajt), pokud není uvedeno jinak.

Všechny časové značky jsou sekundy Unixu v UTC.


2. Datové struktury

2.1 ComposeQub (stav tvůrce v paměti)

Neserializuje se do CBOR. Nezapisuje se do trvalého úložiště. Lokální pro aplikaci tvůrce.

ComposeQub {
    draft_id:       [u8; 16],        // Random, generated locally
    created_at:     i64,             // Unix seconds UTC
    unlock_at:      Option<i64>,     // Unix seconds UTC; None while composing
    visibility:     u8,              // 0x00 = private; 0x01 = public
    content_type:   u8,              // 0x01 text; 0x03 pact; 0x04 verdict
    plaintext:      Vec<u8>,         // Raw body bytes (UTF-8 for text)
    sender_label:   Option<String>,  // Display name; V2-signed when authorship is enabled
    title:          Option<String>,  // Plaintext countdown title; bound via title_hash
    reply_to:       Option<[u8; 32]>,// Parent qub_id; V2-signed when authorship is enabled
    outcome_at:     Option<i64>,     // Optional future judgment time; bound to qub_id
    status:         DraftStatus,     // Composing | Sealed | Uploaded | Failed
}

2.2 QubEnvelope (dešifrovaná datová část)

Serializuje se pomocí kanonického CBOR (§3). Šifrováno uvnitř SealedQub. Toto je struktura, která po dešifrování prokazuje integritu obsahu.

QubEnvelope {
    version:             u8,              // Protocol major version (0x01 for v1)
    qub_id:              [u8; 32],        // Derived (see §4.1)
    content_type:        u8,              // Content type registry (see §6)
    created_at:          i64,             // Unix seconds UTC
    unlock_at:           i64,             // Unix seconds UTC
    outcome_at:          Option<i64>,     // When reality renders judgment; bound to qub_id
    sender_label:        Option<String>,  // Not in qub_id; V2-signed when authorship is enabled
    reply_to:            Option<[u8; 32]>,// Parent qub_id; not in qub_id; V2-signed when present
    body:                Vec<u8>,         // UTF-8 text or canonical CBOR pact/verdict body
    body_hash:           [u8; 32],        // SHA3-256(body) (see §4.2)
    sig_alg:             u8,              // Signature algorithm (see §9.2)
    author_signature:    Option<Vec<u8>>, // Set when sig_alg != 0x00
    author_pubkey:       Option<Vec<u8>>, // Set when sig_alg != 0x00
    cosigner_pubkey:     Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
    cosigner_signature:  Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
}

Základní stav (nepodepsaný textový qub): version = 0x01, content_type = 0x01, sig_alg = 0x00; pole podpisu a spolupodpisu jsou nepřítomna. Ostatní volitelná metadata mohou být přítomna.

Další konfigurace v1: content_type = 0x03 (tělo paktu, viz §6.1); sig_alg = 0x01 (ML-DSA-65) s přítomnými author_signature a author_pubkey (viz §9.3); cosigner_pubkey a cosigner_signature přítomné společně pro spolupodepsané pakty (viz §9.7); reply_to nastavené na qub_id nadřazeného qubu pro quby v řetězcích odpovědí (důsledky pro rozsah podpisu viz §9.3).

2.3 SealedQub (kanonický formát na drátě)

Serializuje se pomocí kanonického CBOR (§3). Jde o vnitřní artefakt na drátě: veřejné doručení ukládá tyto bajty bez obalu, zatímco soukromé doručení je před uložením obaluje do OuterWrapper (§13).

SealedQub {
    version:           u8,              // Protocol major version (0x01 for v1)
    qub_id:            [u8; 32],        // Same as QubEnvelope.qub_id
    visibility:        u8,              // 0x00 = private/wrapped; 0x01 = public/bare
    unlock_at:         i64,             // Unix seconds UTC
    outcome_at:        Option<i64>,     // Surfaced on the verdict-watch CTA
                                        //   before reveal; mirrors QubEnvelope.outcome_at;
                                        //   bound to qub_id via the §4.1 preimage.
    drand_chain_id:    String,          // drand chain hash (hex string)
    drand_round:       u64,             // Target drand round number
    drand_chain_version: Option<u8>,    // W3 — chain-migration version. Absent / 0 = quicknet
                                        //   (the only chain today). Lets a future chain swap
                                        //   be expressed on the wire without a breaking format
                                        //   change. NOT part of the §4.1 qub_id preimage, so its
                                        //   addition never alters an existing qub's identity.
    tlock_ciphertext:  Vec<u8>,         // tlock-encrypted QubEnvelope CBOR bytes
    recipient_pubkey:  Option<[u8; 32]>,// Reserved field; accepted by canonical CBOR
                                        //   but not interpreted by the v1 reference viewer
    title:             Option<String>,  // Plaintext title surfaced on the viewer
                                        //   countdown before reveal. Bound to qub_id
                                        //   via title_hash (§4.1). 1..=100 NFC code
                                        //   points, no hostile/control code points.
}

2.4 RevealedQub (stav aplikace prohlížeče)

Neserializuje se do CBOR. Lokální pro aplikaci prohlížeče. Konstruováno po úspěšném dešifrování a ověření.

RevealedQub {
    qub_id:              [u8; 32],
    arweave_tx_id:       String,
    visibility:          u8,
    content_type:        u8,
    created_at:          i64,
    unlock_at:           i64,
    outcome_at:          Option<i64>,       // Carried from both wire layers; drives the verdict-watch block
    drand_chain_id:      String,
    drand_round:         u64,
    sender_label:        Option<String>,
    title:               Option<String>,    // Carried forward from SealedQub.title
    reply_to:            Option<[u8; 32]>,
    body:                Vec<u8>,
    body_hash:           [u8; 32],
    body_hash_verified:  bool,
    author_signature:    Option<Vec<u8>>,
    author_pubkey:       Option<Vec<u8>>,
    signature_verified:  Option<bool>,
    cosigner_pubkey:     Option<Vec<u8>>,
    cosigner_signature:  Option<Vec<u8>>,
    cosigner_verified:   Option<bool>,
}

3. Kanonický profil CBOR

Veškerá serializace SealedQub a QubEnvelope MUSÍ odpovídat tomuto profilu. Dvě implementace dostávající stejnou logickou strukturu MUSÍ produkovat identické bajty.

3.1 Pravidla kódování

Pravidlo Specifikace
Standard RFC 8949 §4.2.1 (Core Deterministic Encoding Requirements)
Řazení klíčů mapy Seřazeno nejprve podle délky zakódovaných bajtů (kratší před delším), poté lexikograficky (bajt po bajtu pro kódování stejné délky)
Kódování celých čísel Nejkratší forma: 0–23 v úvodním bajtu; 24–255 ve 2 bajtech; 256–65535 ve 3 bajtech; atd.
Kódování délky Pouze definitivní délky. Žádná pole, mapy, řetězce bajtů ani textové řetězce s neurčitou délkou (doplňková informace = 31 je zakázána).
Značky Žádné značky CBOR (hlavní typ 6 je zakázán).
Plovoucí desetinná čárka Žádné plovoucí hodnoty (hodnoty hlavního typu 7 0xF9–0xFB jsou zakázány).
Textové řetězce Kódováno v UTF-8, normalizováno do NFC (Unicode Normalization Form C).
Řetězce bajtů Surové bajty. Žádné kódování base64 na vrstvě CBOR.
Duplicitní klíče Odmítnout s chybou. Parsery NESMÍ tiše akceptovat duplicitní klíče mapy.
Neznámé klíče Odmítnout s chybou. Parsery NESMÍ tolerovat klíče mapy mimo kanonickou sadu klíčů daného typu — dva odlišné kanonické bajtové řetězce se nikdy nesmí dekódovat na tutéž hodnotu (encode(decode(x)) == x) a u podepsaných dat by klíč navíc byl skrytým obsahem, ke kterému se zavazují oba podpisy. Vývoj schématu probíhá přes version, nikdy přes klíče navíc.
Jednoduché hodnoty Povoleny jsou pouze true (0xF5), false (0xF4) a null (0xF6).
Volitelná pole Nepřítomná volitelná pole jsou z mapy CBOR zcela vynechána (nekódují se jako null). Přítomná volitelná pole jsou zahrnuta v seřazeném pořadí klíčů.

3.2 Ověřená kanonická pořadí klíčů

Tato pořadí klíčů jsou normativní. Implementace MUSÍ vydávat klíče přesně v tomto pořadí. Ladicí kontrolní příkazy BY MĚLY ověřovat řazení v neuvolňovacích sestaveních.

QubEnvelope (verze 0x01, nepodepsaný, všechna volitelná pole nepřítomna):

"body"                (5 encoded bytes)
"qub_id"              (7 encoded bytes)
"sig_alg"             (8 encoded bytes)
"version"             (8 encoded bytes)
"reply_to"            (9 encoded bytes)   ← only if present (reply chains)
"body_hash"           (10 encoded bytes)
"unlock_at"           (10 encoded bytes)
"created_at"          (11 encoded bytes)
"outcome_at"          (11 encoded bytes)  ← only if present (verdict mechanic)
"content_type"        (13 encoded bytes)
"sender_label"        (13 encoded bytes)  ← only if present
"author_pubkey"       (14 encoded bytes)  ← only if present
"cosigner_pubkey"     (16 encoded bytes)  ← only if present (pact cosign)
"author_signature"    (17 encoded bytes)  ← only if present
"cosigner_signature"  (19 encoded bytes)  ← only if present (pact cosign)

Odvození pořadí klíčů QubEnvelope: každý klíč je textový řetězec CBOR. Zakódovaná délka = 1 bajt hlavičky + délka řetězce (pro řetězce kratší než 24 bajtů). Řadí se nejprve podle celkové zakódované délky, poté lexikograficky pro klíče stejné délky.

SealedQub (verze 0x01, veřejný, bez příjemce):

"title"             (6 encoded bytes)   ← only if present
"qub_id"            (7 encoded bytes)
"version"           (8 encoded bytes)
"unlock_at"         (10 encoded bytes)
"outcome_at"        (11 encoded bytes)  ← only if present (verdict mechanic)
"visibility"        (11 encoded bytes)
"drand_round"       (12 encoded bytes)
"drand_chain_id"    (15 encoded bytes)
"recipient_pubkey"  (17 encoded bytes)  ← only if present
"tlock_ciphertext"  (17 encoded bytes)
"drand_chain_version" (20 encoded bytes) ← only if present (W3; absent = quicknet)

PactTerms (tělo paktu, content_type 0x03):

"notes"         (6 encoded bytes)  ← only if present
"terms"         (6 encoded bytes)
"title"         (6 encoded bytes)
"party_a"       (8 encoded bytes)
"party_b"       (8 encoded bytes)
"pact_version"  (13 encoded bytes)

PactTerm (řádek pole terms):

"key"    (4 encoded bytes)
"value"  (6 encoded bytes)

PartyIdentifier (mapa party_a / party_b):

"label"    (6 encoded bytes)
"contact"  (8 encoded bytes)  ← only if present

3.3 Referenční tabulka kódování bajtů

Typ Kódování CBOR Příklad
Hash SHA3-256 (32 bajtů) 0x58 0x20 + 32 bajtů body_hash, qub_id
Časové značky (i64) Hlavní typ 0 (kladný) nebo 1 (záporný), nejkratší kódování sekundy Unixu
Verze (u8, hodnota 1) 0x01 (jeden bajt)
Typ obsahu (u8, hodnota 1) 0x01 (jeden bajt)
sig_alg (u8, hodnota 0) 0x00 (jeden bajt)
Podpis ML-DSA-65 (3 309 bajtů) 0x59 0x0C 0xED + 3 309 bajtů author_signature, cosigner_signature
Veřejný klíč ML-DSA-65 (1 952 bajtů) 0x59 0x07 0xA0 + 1 952 bajtů author_pubkey, cosigner_pubkey

4. Normativní odvození

4.1 qub_id

qub_id jednoznačně identifikuje qub a váže QubEnvelope na SealedQub. Je deterministicky odvozen z obsahu obálky.

qub_id = SHA3-256(
    "QUB_ID_V2"          ||  // domain separator: ASCII bytes [0x51 0x55 0x42 0x5F 0x49 0x44 0x5F 0x56 0x32] (9 bytes) + 0x00 padding (1 byte) = 10 bytes
    version              ||  // u8 (1 byte)
    content_type         ||  // u8 (1 byte)
    created_at           ||  // i64 big-endian (8 bytes)
    unlock_at            ||  // i64 big-endian (8 bytes)
    outcome_at_or_zero   ||  // i64 big-endian (8 bytes; 0 when outcome_at is absent)
    drand_round          ||  // u64 big-endian (8 bytes)
    body_hash            ||  // [u8; 32] (32 bytes)
    title_hash               // [u8; 32] (32 bytes; absent-sentinel = [0u8; 32])
)
// Total preimage: 108 bytes → 32-byte output

Kódování oddělovače domény: Řetězec "QUB_ID_V2" má 9 bajtů ASCII. Připojuje se jeden výplňový bajt 0x00, aby se dosáhlo 10 bajtů pro zarovnání. Implementace MUSÍ použít přesně těchto 10 bajtů: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].

Kódování outcome_at: Předběžná implementační revize rozšířila předobraz z 92 na 100 bajtů, aby do vazby zahrnula volitelné pole outcome_at. Nepřítomné outcome_at se kóduje jako 8 nulových bajtů; validátory protokolu všude odmítají outcome_at <= 0, takže tato zarážka nemůže kolidovat s legitimní hodnotou. Viz §3.2 (formát na drátě) a stromový dokument tasks/verdict-uplift-plan.md pro mechaniku verdiktu, která toto pole motivuje.

Kódování drand_round: Pozdější předběžná implementační revize rozšířila předobraz ze 100 na 108 bajtů, aby do vazby zahrnula drand_round (cílové kolo drand, §4.3), a změnila oddělovač domény na QUB_ID_V2. Tím se kolo časového zámku váže do identity qubu: brána nemůže přepojit šifrovaný text na jiné (např. již uplynulé) kolo, než jaké implikuje zobrazené unlock_at. Postup odemčení (§8) navíc ověřuje, že kolo zapečené do stanzy šifrovaného textu tlock se shoduje s unlock_round(unlock_at), takže zobrazený čas odemčení je prokazatelně kolem, které řídí dešifrování.

Vlastnosti:

4.2 body_hash

body_hash = SHA3-256(body)

Kde body je surová datová část obsahu typu Vec<u8>. U textových qubů je to tělo qubu kódované v UTF-8.

4.2.1 title_hash

title_hash = SHA3-256(NFC(title).utf8_bytes)   if title is present
title_hash = [0u8; 32]                         if title is absent

Kde title je volitelný prostý titulek vyvedený na odpočtu prohlížeče před odhalením (viz §3.2). Normalizace NFC běží v okamžiku výpočtu hashe, takže digest je stabilní napříč vizuálně rovnocennými sekvencemi kódových bodů. Návěstí samých nul je vyhrazeno pro nepřítomný případ; prázdný řetězec je na hranici kanonického CBOR odmítnut jako nekanonické kódování „nepřítomné“ (kanonické kódování pole zcela vynechává).

4.3 Mapování na kolo odemčení

drand_round = floor((unlock_at - chain_genesis_time) / chain_period_seconds) + 1
Parametr Zdroj Příklad
unlock_at Uživatelem zvolené sekundy Unixu UTC 1735689600 (2025-01-01 00:00:00 UTC)
chain_genesis_time Informace o řetězci drand (genesis_time) 1595431050
chain_period_seconds Informace o řetězci drand (period) 30

Toto je referenční mapování tlock (CurrentRound v drand). drand zveřejňuje kolo N v čase chain_genesis_time + (N - 1) * chain_period_seconds, takže vzorec vybere kolo aktuální v čase unlock_at — kolo, jehož podpis může jako první použít divák přicházející v čase unlock_at.

Vlastnost zarovnání (prakticky rozhodující případ): když je (unlock_at - chain_genesis_time) přesně dělitelné chain_period_seconds, podpis vybraného kola je zveřejněn přesně v čase unlock_at, nikdy dříve. U referenčního nasazení to platí vždy: čas geneze quicknet (1692803367) je dělitelný jeho třísekundovou periodou a referenční aplikace připínají časy odemčení na celé minuty. U nezarovnaného unlock_at vyjde podpis vybraného kola méně než jednu periodu před unlock_at — časová přesnost závazku je jedna perioda majáku.

Starší předběžné mapování a tolerance při odemčení: původní mapování bylo ceil((unlock_at - chain_genesis_time) / chain_period_seconds), které v případě zarovnaném na periodu vybralo kolo zveřejněné o jednu celou periodu před unlock_at, takže šifrovaný text byl dešifrovatelný přesně o jednu periodu dříve. Obě mapování se liší přesně o +1, když je rozdíl dělitelný periodou, a jinak se shodují. Protože je drand_round zahrnuto do neměnného předobrazu qub_id (§4.1), artefakty zapečetěné pod starším mapováním nelze znovu odvodit; ověřovatelé provádějící křížovou kontrolu kola v §8 kroku 6a proto MUSÍ přijmout uložené drand_round rovné buď odvozenému kolu, nebo odvozenému kolu minus jedna (a MUSÍ vyžadovat, aby se kolo stanzy tlock přesně rovnalo uloženému kolu). Tolerance posouvá nejdřívější uvolňující podpis nejvýše o jednu periodu. Služba přípravy paktu používá stejnou toleranci při opětovném odvození qub_id připraveného paktu: pokud kolo aktuálního mapování nezreprodukuje potvrzené qub_id a rozdíl je dělitelný periodou, zkusí kolo minus jedna a dokončený pakt zapečetí ke kolu, které qub_id skutečně váže — nikdy slepě k nově vypočtenému kolu.

Validace: unlock_at MUSÍ být v okamžiku zapečetění v budoucnosti. unlock_at NESMÍ být více než 10 let od created_at (kvůli omezení rizika dlouhodobé závislosti na drand; uživatelské rozhraní BY MĚLO varovat u dat odemčení přesahujících 2 roky).


5. Newtypy formátu na drátě

Newtypy formátu na drátě poskytují bezpečnost v době kompilace proti záměně bajtů CBOR s JSON, surovým prostým textem nebo jiným kódováním bajtů.

Typ Obsahuje Vyrábí Spotřebovává
SealedQubCbor Kanonický CBOR SealedQub serialize_sealed_qub() Vnitřní artefakt na drátě; uložen bez obalu pro veřejné doručení nebo obalen pro soukromé doručení, poté obnoven prohlížečem
QubEnvelopeCbor Kanonický CBOR QubEnvelope serialize_qub_envelope() Vstup šifrování tlock, výstup dešifrování tlock

5.1 Pravidla konstrukce

// Production code — only through CBOR serialisers:
let sealed = SealedQubCbor::from_encoded(cbor_bytes);

// There is deliberately NO From<Vec<u8>> implementation.
// You cannot accidentally wrap arbitrary bytes in a wire format type.

// Accessing raw bytes:
let bytes: &[u8] = sealed.as_bytes();
let bytes: Vec<u8> = sealed.into_bytes();

5.2 Validace při konstrukci

from_encoded() BY MĚLO ověřit, že vstup začíná platnou hlavičkou mapy CBOR. Plná strukturální validace probíhá v době parsování, nikoli v době konstrukce, aby se předešlo dvojímu parsování.


6. Registr typů obsahu

Hodnota Typ Max. velikost těla Poznámky
0x00 Vyhrazeno (neplatné) — NESMÍ se používat
0x01 Prostý text (UTF-8, omezený Markdown) 50 KB placené / 10 KB zdarma Pravidla vykreslování viz §10. Rozdělení zdarma / placené vynucuje služba nahrávání; pevný strop na vrstvě protokolu je 50 KB.
0x02 Vyhrazeno (budoucí) — Alokováno pro budoucí typ obsahu; v v1 neplatné. Prohlížeče MUSÍ odmítnout dle pravidla níže.
0x03 Pakt (dvoustranná dohoda, tělo CBOR) 100 KB Tělo je kanonický CBOR PactTerms (§6.1). Podepisování spolupodepisujícím dle §9.7.
0x04 Verdikt (sebehodnocení tvůrce, tělo CBOR) 8 KB Tělo je kanonický CBOR VerdictBody (§6.2). Vydáváno pouze záměrem verdict na straně systému. Vztah k rodiči je na štítku trvalého úložiště Parent-Tx-Id, nikoli v těle. Viz verdict-uplift-plan §3.4.

Prohlížeče MUSÍ odmítnout neznámé typy obsahu se srozumitelnou chybou viditelnou pro uživatele. Prohlížeče NESMÍ pokoušet vykreslovat neznámé typy jako text.

6.1 Tělo paktu (content_type = 0x03)

Tělo paktu je kanonické kódování CBOR hodnoty PactTerms:

PactTerms {
    pact_version:  u8,                    // 0x01 for structured/v1
    title:         String,                // ≤ 200 bytes, NFC
    terms:         Vec<PactTerm>,         // ≤ 20 rows
    party_a:       PartyIdentifier,       // initiator
    party_b:       PartyIdentifier,       // counter-signer
    notes:         Option<String>,        // ≤ 5,000 bytes, NFC; absent key if none
}

PactTerm       { key: String (≤ 100 bytes), value: String (≤ 2,000 bytes) } // NFC
PartyIdentifier{ label: String (≤ 100 bytes), contact: Option<String (≤ 320 bytes)> }

Kanonická pořadí klíčů CBOR pro všechny tři mapy jsou uvedena v §3.2. Celkový serializovaný CBOR paktu NESMÍ překročit 100 KB (odpovídá §6).

Diskriminátor schématu. První řádek v terms u paktu structured/v1 MUSÍ být { key: "pact_schema", value: "structured/v1" }. Řádky bez tohoto označení jsou „vlastní“ pakty a nedostávají žádnou strukturovanou validaci ani vykreslování s ohledem na schéma.

Zmrazené sloty potvrzení. Pakty structured/v1 nesou přesně čtyři řádky potvrzení pod těmito klíči:

"initiator_standard_terms"
"initiator_capacity_terms"
"counterparty_standard_terms"
"counterparty_capacity_terms"

value pro každý z nich je jeden z osmi zmrazených anglických řetězců zvolených podle dvojice (role, kind), kde role ∈ { seller, buyer, provider, client } a kind ∈ { standard, capacity }. Samotné řetězce jsou normativní data protokolu — podpisy ML-DSA-65 obou stran se zavazují k přesným bajtům prostřednictvím body_hash. NEJSOU lokalizovány; podepsané tělo je jazykově neutrální. Jakákoli změna formulace vyžaduje novou verzi schématu (structured/v2).

Osm řetězců, jejich vyhledání (acknowledgement_for(role, kind)) a zdůvodnění každého z nich jsou ukotveny referenční implementací. Vyhovující implementace MUSÍ vydávat bajtově identické hodnoty potvrzení; testy hashe těla SHA3-256 z referenčních (golden) fixtur pokrývající všechny čtyři kombinace rolí zachytí jakýkoli posun.

Pořadí zobrazení v prohlížeči. Řetězce potvrzení obsahují fráze jako „popsáno výše“, které předpokládají, že řádky popisu / rozsahu se vykreslí před potvrzeními. Prohlížeče MUSÍ vykreslit pole terms v pořadí CBOR; přeskupení naruší prozaickou sémantiku.

Kontakt protistrany. Když je contact strany B platná e-mailová adresa, služba nahrávání qubů automaticky odešle v okamžiku přípravy e-mailovou pozvánku k posouzení / spolupodpisu a váže případný spolupodpis na ověření téže adresy (§9.7). Pakty, jejichž kontakt strany B chybí, lze stále spolupodepsat, ale pouze kanálem mimo pásmo — služba odmítá požadavky na spolupodpis, které nemohou vytvořit odpovídající 15minutové návěstí ověření e-mailu.

6.2 Tělo verdiktu (content_type = 0x04)

Tělo verdiktu je kanonické kódování CBOR hodnoty VerdictBody:

VerdictBody {
    verdict_version: u8,                  // 0x01 for structured/v1
    outcome:         u8,                  // 1=Right · 2=Partial · 3=Wrong · 4=Unfalsifiable
    reflection:      Option<String>,      // ≤ 2,000 bytes NFC; "what changed, what did you learn"
    evidence_url:    Option<String>,      // ≤ 2,048 bytes; HTTPS only; absent key when omitted
}

Kanonické pořadí klíčů CBOR:

"outcome"          (8 encoded bytes)
"reflection"       (11 encoded bytes)  ← only if present
"evidence_url"     (13 encoded bytes)  ← only if present
"verdict_version"  (16 encoded bytes)

Celkový serializovaný CBOR verdiktu NESMÍ překročit 8 KB (odpovídá řádku registru výše).

Výčet outcome. Bajt na drátě je nezávislý na záměru; čtyři kategorie Right / Partial / Wrong / Unfalsifiable pokrývají prostor výsledků každého záměru nesoucího verdikt. Štítky specifické pro daný záměr („Trefa“ / „Dodrženo“ / „Vydáno“ / „Potvrzeno“ pro Right atd.) jsou věcí vykreslování na straně prohlížeče, řešenou podle záměru nadřazeného qubu — drát zůstává jazykově i záměrově neutrální. Hodnoty mimo 1..=4 MUSÍ být při dekódování odmítnuty.

Vazba na rodiče. Tělo qubu s verdiktem NENESE odkaz na rodiče. Identifikátor transakce trvalého úložiště nadřazeného qubu se vydává jako štítek úložiště Parent-Tx-Id v okamžiku nahrání (§7, vrstva štítků úložiště). Díky tomu zůstává tělo samostatným podepsaným prohlášením o sebehodnocení; auditní řetězec („pravdu o čem?“) se ustavuje vyhledáním štítku v trvalém úložišti.

Bezpečnost URL důkazu (normativní). Když je evidence_url přítomna, validátory (na straně skládání, na drátě, na hraně Workeru) MUSÍ vynutit:

  1. Pouze HTTPS. Řetězec MUSÍ začínat bajtovou sekvencí https://. Jakékoli jiné schéma — http, ftp, javascript, data, file apod. — je odmítnuto.
  2. Omezení délky. ≤ 2 048 bajtů (praktický limit URL v prohlížečích).
  3. Kontrola NFC a nepřátelských kódových bodů. Stejné pravidlo jako u title a reflection — kódové body bidi-override / nulové šířky / tag-block / BOM / C0 / C1 jsou odmítnuty. Definice odpovídá Rust crate::handle::contains_hostile_text_codepoint a TS workers/api/src/utils/unicode.ts::isHostileCodepoint (udržujte v synchronizaci).
  4. Žádné bílé znaky, žádné řídicí znaky ASCII. Bílé znaky / DEL / bajty pod 0x20 kdekoli v URL jsou odmítnuty — uzavírá to vektor injekce \n / \t, který pravidlo bidi nepokrývá.
  5. Neprázdný úsek hostitele. Vše mezi https:// a prvním /, ? nebo # MUSÍ být neprázdné.

Žádné stahování na straně serveru. Worker NESMÍ URL proxovat, stahovat ani předběžně zobrazovat. Protokol ukládá řetězec; vykreslení probíhá na straně prohlížeče s rel="nofollow noopener noreferrer" target="_blank" a viditelným hostitelem zobrazeným vedle textu odkazu.

Reflexe. Volitelný text reflexe psaný tvůrcem („co se změnilo, co jste se naučili“). Stejná validace NFC a nepřátelských kódových bodů jako u title. Prázdný vstup nebo vstup složený jen z bílých znaků se při konstrukci redukuje na nepřítomný.

Verze schématu. v1 podporuje pouze verdict_version = 0x01. Budoucí revize schématu zvýší tento bajt a budou nasazeny společně s novou verzí protokolu dle §12.


7. Protokol zapečetění

Kompletní sekvence zapečetění. Každý krok je normativní.

 1. User composes plaintext and metadata in ComposeQub.
 2. Validate:
    a. body is non-empty.
    b. body size ≤ max for content_type and user tier (see §6).
    c. unlock_at is in the future.
    d. unlock_at ≤ created_at + 10 years.
    e. content_type is a known, supported value.
    f. visibility is 0x00 (private) or 0x01 (public).
 3. Compute body_hash = SHA3-256(body).
 4. Set created_at = current Unix seconds UTC.
 5. Select drand chain. Load chain_genesis_time and chain_period_seconds, and
    compute drand_round = floor((unlock_at - chain_genesis_time) / chain_period_seconds) + 1
    (§4.3). (Computed here, before qub_id, because drand_round is bound into the
    qub_id preimage—§4.1.)
 6. Compute qub_id (see §4.1), folding in drand_round from step 5.
 7. Construct QubEnvelope with all fields.
 8. Serialise QubEnvelope using canonical CBOR → bytes B.
    Assert: serialised output matches canonical profile (§3).
 9. Compute C = tlock_encrypt(B, drand_round, drand_chain_public_key).
10. Construct SealedQub with tlock_ciphertext = C and matching qub_id, version,
    visibility, unlock_at, drand_chain_id, and drand_round.
11. Serialise SealedQub using canonical CBOR → SealedQubCbor.
12. Select the delivery shape from visibility:
    a. Private (0x00): generate K = 32 random bytes and N = 12 random bytes
       using a CSPRNG. Compute W = wrap_sealed_qub(SealedQubCbor,
       qub_id=qub_id, key=K, nonce=N) per §13. Upload payload = W.
    b. Public (0x01): upload payload = bare SealedQubCbor; do not generate K.
13. Display seal-time disclosure. User confirms.
14. Validate upload eligibility via the qub upload service (bot-detection, entitlement, rate limits).
15. Submit the selected upload payload to the qub upload service. For a private
    browser seal, the service is byte-blind to the inner SealedQubCbor and never
    receives K. The Builder `/api/v1/seal` route is an explicit exception: it
    receives plaintext and caller-supplied K in memory, then persists neither.
16. Receive arweave_tx_id from the service. For private delivery, construct
    `<origin>/c/<arweave_tx_id>#<base64url(K)>` (or the equivalent short-code
    path). For public delivery, omit the fragment. Browsers do not transmit URL
    fragments to servers, so K from the browser-seal path is not observed by
    qub.social or any storage gateway.

Vrstva značek úložiště (mimo pásmo). Služba nahrávání qubů připojuje vedle vybraného nahrávaného payloadu záměrně malou sadu transakčních značek úložiště. Content-Type=application/octet-stream je normativně vyžadováno. Referenční služba navíc připojuje tři volitelné značky, když se je tvůrce rozhodne zveřejnit: Intent (záměr sestavení validovaný proti přesnému povolovacímu seznamu: announcement, thesis, prediction, letter, secret, commitment, proof nebo systémem vydaný verdict), Author (otisk veřejného klíče tvůrce dle §9.3 jako 64znakový hexadecimální řetězec malými písmeny) a Parent-Tx-Id (id transakce úložiště nadřazeného qubu pro řetězce odpovědí, 43znakový base64url).

Značka Author je přihlašovaná pro každý qub zvlášť: referenční aplikace tvůrce ji připojí pouze tehdy, když uživatel v okamžiku zapečetění výslovně povolí veřejné přiřazení autorství. Když je přepínač vypnutý — což je výchozí stav — žádná značka Author se nezapisuje a qub je na řetězci bez přiřazeného autora: nic v trvalém úložišti nespojuje nahrání s identifikátorem (handle), e-mailem ani jinými quby tvůrce. Když je přepínač zapnutý, otisk Author se rozřeší na zvolený @handle tvůrce prostřednictvím atestačního řetězce dle §9.5. Vztahy řetězců odpovědí a Intent neidentifikují. U soukromého doručení vnější obal (§13) šifruje rozpoznatelný vnitřní artefakt SealedQub, takže sklizeň uložených obalů spolu se získáním veřejných podpisů drand stále nestačí k obnovení těla bez K; úložištní značky zůstávají záměrně veřejnými metadaty.

Referenční služba záměrně NEPŘIPOJUJE značky App-Name, App-Version ani Type: jakýkoli takový filtr o jediné hodnotě by na dotaz GraphQL vrátil celý korpus qubů, což je v rozporu s rozsahem důvěrnosti obalu omezeným pouze na tělo.

Vyhovující ověřovatel NESMÍ při ověřování třetí stranou dle §11 záviset na žádné značce úložiště; hash těla / qub_id / podpis se zavazují pouze k vnitřnímu CBOR, nikdy k sadě značek.


8. Protokol odemčení

Kompletní sekvence odemčení. Každý krok je normativní.

 1. Viewer opens delivery URL. Extract arweave_tx_id from the path and retain
    the optional URL fragment. Do not assume a missing fragment is an error:
    public/bare delivery intentionally has no K.
 2. Check denylist. If tx_id is denylisted → display block message. Stop.
 3. Fetch the stored bytes (with multi-gateway fallback).
 3a. Resolve the delivery shape structurally:
    a. If the bytes parse as OuterWrapper, require a well-formed 32-byte K in
       the URL fragment, require wrapper version 0x01, and unwrap per §13.
       Any missing/malformed K or AEAD failure is a terminal error.
    b. Otherwise require the bytes to parse as bare SealedQubCbor; no K is
       required. If neither shape parses, report an integrity error.
 4. Parse SealedQubCbor → SealedQub.
 5. Validate: SealedQub.version is known (0x01), visibility is known, and the
    delivery shape matches it (wrapped = 0x00 private; bare = 0x01 public).
    Reject any mismatch or unknown value.
 6. If current time < SealedQub.unlock_at → display countdown. Poll or wait.
 6a. Round-binding check. Recompute expected_round from
    SealedQub.unlock_at per §4.3. Reject unless SealedQub.drand_round ==
    expected_round OR SealedQub.drand_round == expected_round - 1 (the
    legacy pre-release mapping—see §4.3), AND the round baked into the tlock
    ciphertext stanza (read via the age/tlock header, no signature required)
    == SealedQub.drand_round exactly. The stanza round is the one that
    actually gates decryption; without this check a malicious creator could
    bind the ciphertext to an already-past round while displaying a future
    countdown, so anyone reading the stored bytes could decrypt before
    unlock_at. Implementations with no chain identity (test mocks) skip this
    check.
 7. Once current time ≥ SealedQub.unlock_at:
    a. Fetch drand round signature for SealedQub.drand_round from drand network.
    b. Compute B = tlock_decrypt(SealedQub.tlock_ciphertext, round_signature).
 8. Parse B → QubEnvelope.
 9. Validate QubEnvelope.version is known.
10. Verify: SHA3-256(QubEnvelope.body) == QubEnvelope.body_hash.
    Fail → integrity error.
11. Verify: QubEnvelope.qub_id == SealedQub.qub_id.
    Fail → integrity error.
12. Verify: QubEnvelope.unlock_at == SealedQub.unlock_at.
    Fail → integrity error.
12a. Verify: QubEnvelope.outcome_at == SealedQub.outcome_at (both absent, or
    both present and equal). Fail → integrity error.
12b. Content re-derivation. Recompute qub_id per §4.1 from the decrypted
    fields — (QubEnvelope.version, content_type, created_at, unlock_at,
    outcome_at, SealedQub.drand_round, QubEnvelope.body_hash,
    title_hash(SealedQub.title)) — and verify it equals SealedQub.qub_id.
    Fail → integrity error. The pairwise checks in steps 10-12a only prove
    the two layers agree with EACH OTHER; a forger who rewrites a bound
    field consistently on both surfaces (a pre-reveal title swap, or a
    post-round body swap with a recomputed body_hash re-encrypted to the
    same round under the same qub_id) passes them all. Only re-deriving
    the identity from content closes this.
13. Verify: QubEnvelope.content_type is known and renderable.
    Known values: 0x01 (text), 0x03 (pact), 0x04 (verdict).
    Unknown → display error.
14. If QubEnvelope.sig_alg != 0x00 → verify author signature (see §9.4).
15. If cosigner_pubkey or cosigner_signature present → verify cosigner (see §9.7).
16. Render content using the appropriate renderer (see §10 for text and §6 for pact/verdict).
17. Construct RevealedQub for display.

9. Podepisování autorství

9.1 Zdůvodnění

Každý qub se ukládá do trvalého úložiště. Podpisy autorství musí zůstat neomezeně nepadělatelné, a proto verze 1.0 používá postkvantové schéma ML-DSA-65 (FIPS 204) namísto klasického schématu, jehož bezpečnost se může v rámci trvalé životnosti qubu zhoršit.

9.2 Registr algoritmů

sig_alg Schéma Velikost klíče Velikost podpisu Stav
0x00 Bez podpisu (nepodepsaný) — — Aktivní
0x01 ML-DSA-65 (FIPS 204) 1 952 bajtů 3 309 bajtů Aktivní
0x02 Ed25519 32 bajtů 64 bajtů Vyhrazená konstanta; protokol v1 ji nepodporuje

Prohlížeče protokolu v1 MUSÍ odmítnout každou hodnotu mimo {0x00, 0x01}, včetně vyhrazené hodnoty 0x02. Rezervace brání náhodnému opětovnému použití; neznamená aktivaci. Aktivace vyžaduje řízenou změnu popsanou v §15.

9.3 Konstrukce podepsaného předobrazu

Existovaly dvě verze předobrazu. Všechny podpisy MUSÍ používat V2 a ověřovatelé MUSÍ přijímat pouze V2. Starší předobraz V1 (níže zdokumentovaný pro historickou referenci) byl během migrace na V2 přijímán jako záložní varianta pouze pro ověření; tato záložní varianta byla vyřazena a podpis pouze podle V1 je nyní odmítán.

V2 (aktuální — vytvářený veškerým novým podepisováním autora i oběma podpisy toku přípravy / spolupodpisu paktu):

sig_input = SHA3-256(
    "QUB_AUTHOR_SIG_V2"  ||    // domain separator (17 bytes)
    version              ||    // u8 (1 byte)
    qub_id               ||    // [u8; 32] (32 bytes)
    body_hash            ||    // [u8; 32] (32 bytes)
    unlock_at            ||    // i64 big-endian (8 bytes)
    0x00                 ||    // u8 (1 byte): MUST be 0x00 in v1.x
    sender_label_hash    ||    // [u8; 32]: SHA3-256(NFC(sender_label)),
                               //   or 32 zero bytes when absent
    reply_to_or_zero           // [u8; 32]: parent qub_id, or 32 zero
                               //   bytes when absent
)

// Total preimage: 155 bytes → 32-byte hash

signature = Sign(author_secret_key, sig_input)

sender_label_hash se řídí stejnou konvencí sentinelu nepřítomnosti jako title_hash (§4.2.1): 32 nulových bajtů není platný výstup SHA3-256, takže „nepřítomný“ nikdy nemůže kolidovat s přítomným štítkem. Všechna pole mají pevnou šířku, takže předobraz je jednoznačný i bez délkových prefixů.

V1 (starší — VYŘAZENÝ; již se nevytváří a při ověření se již nepřijímá):

sig_input = SHA3-256(
    "QUB_AUTHOR_SIG_V1"  ||    // domain separator (17 bytes)
    version              ||    // u8 (1 byte)
    qub_id               ||    // [u8; 32] (32 bytes)
    body_hash            ||    // [u8; 32] (32 bytes)
    unlock_at            ||    // i64 big-endian (8 bytes)
    0x00                       // u8 (1 byte): MUST be 0x00 in v1.0
)

// Total preimage: 91 bytes → 32-byte hash

Předobraz V1 vynechával sender_label a reply_to. Během migrace na V2 byl přijímán jako záložní varianta pouze pro ověření; tato záložní varianta byla od té doby vyřazena — ověřovatelé MUSÍ přijímat pouze předobraz V2. Definice je zde ponechána pro historickou referenci a k vysvětlení níže uvedeného oddělovače domény. Podpis, který se ověří pouze proti V1, MUSÍ být považován za selhání ověření.

Oddělovače domény: "QUB_AUTHOR_SIG_V1" / "QUB_AUTHOR_SIG_V2" mají každý 17 bajtů ASCII ([0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31/0x32]). Bez výplně. Odlišný oddělovač zajišťuje doménové oddělení obou konstrukcí, takže podpis nad jedním předobrazem se nikdy nemůže ověřit jako druhý.

Bajt org_id_present: bajt následující po unlock_at MUSÍ být 0x00. Referenční implementace jej vystavuje jako konstantu ORG_ID_PRESENT_INDIVIDUAL = 0x00 v crates/qub-core/src/signing.rs; prohlížeče rekonstruující sig_input pro ověření MUSÍ vydat stejný bajt.

Rozsah podpisu — co je a není pokryto. sig_input verze V2 se přímo zavazuje k polím version, qub_id, body_hash, unlock_at, sender_label a reply_to (plus pevný oddělovač domény a bajt org_id_present). qub_id je sám odvozen z version, content_type, created_at, unlock_at, outcome_at, drand_round a body_hash prostřednictvím předobrazu dle §4.1, takže jakákoli změna těchto polí vytvoří jiný qub_id a tranzitivně zneplatní podpis. Přímo ověřená plocha je tedy:

Pole Ověřeno podpisem Jak
version ✓ Přímý vstup do sig_input
qub_id ✓ Přímý vstup
body_hash ✓ Přímý vstup
unlock_at ✓ Přímý vstup
sender_label ✓ Přímý vstup přes sender_label_hash (předobraz V2 — jediná přijímaná forma)
reply_to ✓ Přímý vstup přes reply_to_or_zero (předobraz V2 — jediná přijímaná forma)
content_type ✓ Tranzitivně, přes předobraz qub_id
created_at ✓ Tranzitivně, přes předobraz qub_id
outcome_at ✓ Tranzitivně, přes předobraz qub_id
drand_round ✓ Tranzitivně, přes předobraz qub_id
body ✓ Tranzitivně, přes body_hash = SHA3-256(body)
author_pubkey — (implicitně) Klíč, který ověřil podpis, je z definice autor
cosigner_pubkey / cosigner_signature — Nezávisle podepsáno nad stejným sig_input (viz §9.7)
drand_chain_id, tlock_ciphertext, visibility — Vnější pole SealedQub, nikoli uvnitř obálky — pokryta vlastními strukturálními invarianty (konzistence kola / řetězce), nikoli však podpisem autora. (drand_round je nyní vázán tranzitivně prostřednictvím předobrazu qub_id — viz výše.)

Proč je V2 jediný přijímaný předobraz.

Implementace, které koncovým uživatelům zobrazují sender_label nebo reply_to, MUSÍ vyvést na povrch ověřenou identitu (otisk veřejného klíče, atestaci) jako primární signál identity, nikoli štítek.

9.4 Ověřovací postup

1. Read sig_alg from QubEnvelope.
2. If sig_alg == 0x00 → unsigned. No verification. Display "unsigned qub."
3. If sig_alg is unknown → reject. Display "unrecognised signature scheme."
4. Extract author_signature and author_pubkey. If either is absent → integrity error.
5. Reconstruct sig_input using fields from QubEnvelope (V2 formula, §9.3).
6. Verify(author_pubkey, sig_input, author_signature). The V2 preimage is the
   only accepted form — the legacy V1 fallback is retired (§9.3), so a
   signature that does not verify against V2 fails, full stop.
7. If verification succeeds → display "signed by [key fingerprint]."
8. If verification fails → display "signature verification failed."

Ověření podpisu je nejnákladnější operace (zejména ML-DSA-65). BY MĚLO být provedeno až poté, co projdou všechny levnější kontroly (hash, qub_id, unlock_at).

9.5 Atestace identity

Atestace identity — mapování author_pubkey na člověkem rozpoznatelná tvrzení o identitě, jako je identifikátor (handle) qubu, e-mailová adresa, identifikátor na sociální síti nebo přihlašovací údaj passkey — jsou progresivní vylepšení na straně prohlížeče a nejsou vyžadovány pro ověření podpisu. Prohlížeče, které rozřeší atestace na zobrazovanou identitu, MUSÍ uplatnit přednost:

handle > email > social > fingerprint

Záložní otisk je hexadecimální zápis malými písmeny SHA3-256(author_pubkey); je vždy k dispozici pro jakýkoli podepsaný qub. Prohlížeče MOHOU jej pro zobrazení zkrátit — referenční prohlížeč vykresluje qub: následované prvními a posledními čtyřmi bajty (qub:<8 hex>…<8 hex>).

Vyhovující ověřovatel může dokončit každou kontrolu v §9.4 bez kontaktování API qubu, bez jakékoli sítě kromě trvalého úložiště a drand a bez jakéhokoli vyhledávání na straně serveru. Rozřešení atestace je samostatný krok podle nejlepší snahy prováděný pouze poté, co ověření podpisu uspělo.

9.6 Dopad na velikost

Ed25519 ML-DSA-65
Podpis 64 bajtů 3 309 bajtů
Veřejný klíč 32 bajtů 1 952 bajtů
Celkem na qub 96 bajtů 5 261 bajtů
Rozdíl nákladů na úložiště (při ~5 $/MB) ~0,0005 $ ~0,026 $

U textového qubu o 500–2 000 bajtech zhruba ztrojnásobí ML-DSA-65 uloženou velikost. Absolutní náklady jsou zanedbatelné.

9.7 Ověření spolupodepisujícího (dvoustranné dohody paktů)

U dvoustranných dohod (content_type = 0x03) druhá vrstva podpisu prokazuje, že obě strany souhlasily se stejnými podmínkami.

Pole obálky:

Obě pole MUSÍ být přítomna společně, nebo obě nepřítomna. Je-li přítomno přesně jedno, prohlížeče MUSÍ ohlásit chybu integrity.

Ověřovací postup:

1. If cosigner_pubkey absent and cosigner_signature absent → no cosigner. Done.
2. If exactly one is present → integrity error.
3. Verify cosigner_pubkey != author_pubkey (prevent self-cosigning).
   Fail → display "cosigner pubkey must differ from author."
4. Reconstruct sig_input using the same formula as §9.3 (V2 only — the
   legacy V1 fallback is retired; all pact clients produce V2 signatures).
5. Verify(cosigner_pubkey, sig_input, cosigner_signature).
6. Success → display "co-signed by [cosigner fingerprint]."
7. Failure → display "co-signature verification failed."

Vlastnosti:

Brána vázání na e-mail (provozní). Když připravený pakt nese e-mailový kontakt strany B (§6.1), služba nahrávání qubů MUSÍ odmítnout požadavek na spolupodpis, pokud neexistuje krátkodobé návěstí ověření e-mailu odpovídající jak id přípravy, tak hashi normalizovaného e-mailu tohoto kontaktu. Návěstí zapisuje /api/v1/auth/verify, když token přihlašovacího odkazu nese staging_id a ověřená adresa odpovídá SHA-256(normalise_email(party_b.contact)) — kde normalise_email(addr) zachovává velikost písmen lokální části a převádí na malá písmena pouze část domény (dle RFC 5321 §2.3.11) a SHA-256 je zde hash NIST FIPS 180-4 (odlišný od SHA3-256 použitého v odvozeních dle §4) — a vyprší 900 sekund (15 minut) po vydání. Toto je provozní brána proti zosobnění, NIKOLI součást důkazu qubu na řetězci — ověřovatel třetí strany přehrávající §11 potřebuje pouze trvalé úložiště a drand, bez jakéhokoli vyhledávání na straně serveru. Návěstí existuje pouze na straně serveru a nikdy není součástí podepsaného těla.

Dopad na velikost (autor ML-DSA-65 + spolupodepisující):

Komponenta Velikost
Podpis autora 3 309 bajtů
Veřejný klíč autora 1 952 bajtů
Podpis spolupodepisujícího 3 309 bajtů
Veřejný klíč spolupodepisujícího 1 952 bajtů
Celková kryptografická režie 10 522 bajtů
Rozdíl nákladů na úložiště ~0,05 $

10. Vykreslování a sanitizace Markdownu

Tato část je kriticky důležitá pro bezpečnost. Prohlížeč vykresluje textové quby (content_type = 0x01) pomocí omezené podmnožiny Markdownu.

10.1 Povolené prvky

10.2 Zakázané prvky

Prvek Zpracování
Surové HTML (<div>, <script> atd.) Zcela odstraněno. Žádné HTML neprojde.
Obrázky (![alt](url)) Odstraněno. Syntaxe obrázku je z výstupu odstraněna.
Odkazy ([text](url)) URL vykreslena jako viditelný prostý text. Bez automatického propojení. Bez možnosti kliknutí bez výslovné akce uživatele.
Nebezpečná schémata URL javascript:, data:, vbscript:, file: — odstraněna.
Iframy, vložené prvky, objekty Odstraněno.
Entity HTML Dekódovány na zobrazované znaky pouze, je-li to bezpečné.

10.3 Implementace

Implementace MUSÍ použít přísný parser s povolovacím seznamem, nikoli zakazovací seznam. Doporučený přístup:

  1. Parsovat Markdown pomocí pulldown-cmark (nebo ekvivalentu).
  2. Projít AST a zahodit jakýkoli uzel, který není v povolovacím seznamu (§10.1).
  3. U uzlů odkazů: vydat URL jako viditelný text, nikoli jako klikatelný prvek <a>.
  4. Převést filtrovaný AST na typovanou mezireprezentaci (např. výčet MarkdownNode pouze s bezpečnými variantami). Surové HTML je v této IR strukturálně nereprezentovatelné.
  5. Vykreslit z typované IR do cílové vrstvy zobrazení (např. reaktivní komponenty zobrazení, uzly DOM). V žádném okamžiku žádné zřetězení řetězců HTML ani innerHTML.

Přístupy se zakazovacím seznamem jsou křehké, protože nová rozšíření Markdownu nebo zvláštnosti parseru mohou zavést nefiltrované prvky. Přístup s typovaným AST činí XSS strukturálně nemožným — neexistuje varianta, která by mohla nést libovolné HTML.

10.4 Limity velikosti a struktury


11. Ověření třetí stranou

Kterákoli třetí strana, která drží uložené bajty (a u soukromého/zabaleného qubu také K), může ověřit kryptografický artefakt bez spolupráce qub. Nezávisle časově označené tvrzení o existenci navíc vyžaduje buď ověřené zařazení samostatného qubu do trvalého úložiště, nebo ověřený důkaz z logu transparentnosti dle §16.

1. Obtain the stored bytes. For a private delivery, also obtain K from the
   delivery-link fragment.
2. Resolve the delivery shape (§8 step 3a): unwrap OuterWrapper with K, or
   accept bare SealedQubCbor. Require shape/visibility agreement.
3. Parse SealedQubCbor → SealedQub; validate protocol version, visibility,
   content type, chain identity, and structural bounds.
4. Recompute expected_round from unlock_at (§4.3); require the stored round
   (allowing the documented legacy minus-one case) and ciphertext-stanza round
   to agree exactly as §8 step 6a specifies.
5. Obtain the drand signature for SealedQub.drand_round and verify its BLS
   signature against the pinned chain public key.
6. tlock_decrypt(tlock_ciphertext, round_signature) → QubEnvelope CBOR bytes.
7. Parse → QubEnvelope.
8. Verify SHA3-256(body) == body_hash.
9. Verify envelope/sealed equality for qub_id, unlock_at, and outcome_at.
10. Recompute qub_id from the decrypted fields, sealed drand_round, and title;
    require it to equal the carried qub_id (§4.1).
11. If sig_alg != 0x00, verify the V2 author signature (§9.4). If cosigner
    fields are present, verify their pairing, key separation, and signature
    (§9.7).
12. For an existence-time claim, independently verify either:
    a. the permanent-storage transaction's data-to-id binding, owner, block
       inclusion, and block timestamp; or
    b. a §16 inclusion proof through its pinned anchor and anchor block time.
13. Report each verified claim separately; do not collapse an absent storage
    or anchor proof into a successful timing verdict.

Co ověření prokazuje:

Vstup důkazu Co stanovuje
Platný balíček / zapečetěný artefakt + podpis drand Obnovené tělo odpovídá body_hash; metadata vázaná do qub_id jsou neporušená; šifrovaný text je svázán s deklarovaným kolem drand a toto kolo uplynulo. Nestanovuje, kdy byl šifrovaný text vytvořen.
Platný podpis autora/spolupodepisujícího V2 Držitel či držitelé odpovídajících tajných klíčů autentizovali podepsanou plochu z §9.3.
Nezávisle ověřená transakce samostatného qubu v úložišti Přesný uložený šifrovaný text existoval nejpozději v čase příslušného bloku.
Platný ukotvený důkaz z logu transparentnosti Tvrzení specifické pro druh listu dle §16.11, včetně horní meze času závazku z bloku kotvy.

Co ověření NEPROKAZUJE:

Nedůkaz Proč
Autorství sender_label je dekorativní. Bez sig_alg ≥ 0x01 mohl tento obsah zapečetit kdokoli.
Záměr Artefakt prokazuje bajty a kryptografické vztahy, nikoli to, co tvůrce subjektivně mínil.
Dřívější závazek ze samotného .qub Tvůrce může sestavit platný balíček až poté, co svázané kolo uplynulo. Vložený podpis drand prokazuje uplynutí kola, nikoli existenci šifrovaného textu před tímto kolem.
Přesný čas stisknutí tlačítka pečetění Časová značka bloku úložiště nebo kotvy je nezávisle ověřitelnou horní mezí a může za místní akcí uživatele zaostávat. Tvrzení sealed_at / received_at nemají důkazní hodnotu.

Implementovaný log transparentnosti (§16) rozšiřuje ověření napříč quby o prokazatelně nezměnitelné pořadí a na důvěře nezávislou horní mez času závazku (čas bloku kotvy), vymezenou druhem listu (§16.11). Nepřidává autorství ani záměr; u výchozí bajtově slepé cesty nahrání sám neprokazuje body_hash ani drand_round, které nadále vycházejí z kontrol artefaktu.


12. Verzování a řízení vydání

Vydání dokumentu, vnitřní přenosový protokol a vnější obal jsou samostatné prostory verzí. Pouhé upřesnění dokumentace tak tiše nemění bajty a budoucí migrace formátu na drátě se nemůže vydávat za redakční revizi.

12.1 Verze vydání dokumentu

Tato specifikace používá sémantické verze dokumentu (MAJOR.MINOR.PATCH) a neměnný Git tag pojmenovaný protocol-v<release>.

Stav vydání je Návrh (zatím není normativní), Aktuální (jediný doporučený cíl implementace) nebo Nahrazené (uchované pro historické ověřování). Nečíslovaná trasa /protokol zobrazuje Aktuální vydání; tag vydání uchovává jeho přesný zdroj a každou lokalizaci publikovanou spolu s ním. Změna stavu nebo čísla vydání vyžaduje v téže zkontrolované změně aktualizaci této tabulky i historie vydání.

Vydání dokumentu Datum účinnosti Stav Přenosový protokol Obal Zdroj
1.0.0 2026-09-23 Aktuální 0x01 0x01 protocol-v1.0.0

12.2 Verze protokolu

Pole version (u8) jak v SealedQub, tak v QubEnvelope identifikuje hlavní verzi protokolu.

12.3 Historie verzí protokolu

Verze Hodnota Popis
v1 0x01 Soukromé/zabalené a veřejné/nezabalené doručení; těla typu text (0x01), pakt (0x03) a verdikt (0x04); podepisování autora/spolupodepisujícího V2 pomocí ML-DSA-65; tlock na drand quicknet; SHA3-256.

12.4 Dopředná kompatibilita

Prohlížeč v1 narazivší na QubEnvelope s neznámými klíči mapy CBOR (klíče nezahrnuté v kanonickém pořadí dle §3.2) jej MUSÍ odmítnout s chybou dekódování (§3.1). Dopředná kompatibilita stojí na poli version, nikoli na toleranci klíčů: budoucí přídavky — i drobná metadata — se dodávají pod novou hodnotou version, kterou prohlížeč v1 odmítne se srozumitelnou chybou „novější protokol“, místo aby tiše zahazoval obsah, ke kterému se zavazují podpisy.

Prohlížeč v1 narazivší na sig_alg = 0x01 (ML-DSA-65), avšak postrádající podporu ověřování ML-DSA-65, BY MĚL zobrazit obsah qubu s upozorněním „podpis přítomen, ale neověřitelný“, nikoli qub zcela odmítnout. Referenční implementace dnes odmítá každou hodnotu sig_alg jinou než 0x00 a 0x01, protože registr v1 neobsahuje žádný jiný platný algoritmus — přísné odmítnutí a měkké selhání jsou pozorovatelně totožné, dokud není registrován třetí algoritmus. Výše popsané chování měkkého selhání se stane nosným, jakmile §9.2 přijme novou položku, a referenční prohlížeč bude v tom okamžiku aktualizován tak, aby selhával měkce.

12.5 Verze vnějšího obalu

OuterWrapper popsaný v §13 nese vlastní bajt version, nezávislý na SealedQub.version a QubEnvelope.version. Oba prostory verzí se vyvíjejí samostatně: budoucí postkvantově bezpečná symetrická náhrada zvýší bajt obalu, aniž by se dotkla verze vnitřního protokolu, a budoucí přídavek na vrstvě protokolu (např. nové pole obálky) zvýší vnitřní verzi, aniž by se dotkl bajtu obalu.

OUTER_WRAPPER_VERSION_* Hodnota Algoritmus Stav
OUTER_WRAPPER_VERSION_1 0x01 AES-256-GCM s 12bajtovým nonce, 16bajtovou autentizační značkou, AAD vázaným na qub_id Aktivní pro soukromé doručení
— 0x02–0xFF Vyhrazeno Budoucí

Prohlížeče MUSÍ odmítnout neznámé verze obalu se srozumitelnou chybou. Protokol záměrně udržuje prostor verzí obalu úzký, dokud se neobjeví konkrétní hnací důvod migrace (např. pokyny NIST upřednostňující jiný AEAD); slot 0x02 bude přidělen ve stejné revizi, která zavede algoritmus.


13. Vnější šifrovací obal

13.1 Zdůvodnění

Vrstvy protokolu (QubEnvelope → tlock → SealedQub) činí zapečetěný qub časově uzamčeným: tělo je nečitelné až do unlock_at a do zveřejnění podpisu kola drand. Po odemčení je však podpis kola veřejný a kanonický tvar CBOR SealedQub je rozpoznatelný, takže sklízeč, který zaindexoval transakce trvalého úložiště, by mohl hromadně dešifrovat celý korpus qubů.

U soukromého doručení tento kanál uzavírá vnější šifrovací obal vložením dodatečné symetrické vrstvy AEAD mezi kanonický SealedQubCbor a uložené bajty. V prohlížečové cestě pečetění žije 256bitový klíč K pouze ve fragmentu doručovací adresy URL a na zařízeních uživatelů; prohlížeče nepřenášejí fragmenty URL na servery, takže qub.social, každá brána úložiště a každá CDN před nimi jsou vůči K slepé. Uložená reprezentace soukromého qubu je proto neprůhledný šifrovaný text, jehož prostý text nelze obnovit bez adresy URL, kterou se tvůrce rozhodl sdílet. Veřejné doručení tuto vrstvu záměrně vynechává (§13.8).

Čistý efekt:

13.2 Vrstvení

plaintext body                       ← QubEnvelope.body (§2.2)
  ↓ canonical CBOR (§3)
envelope CBOR
  ↓ tlock encrypt to drand round (§7 step 10)
tlock_ciphertext (inside SealedQub) (§2.3)
  ↓ canonical CBOR (§3)
SealedQubCbor bytes                  ← inner wire artifact
  ├─ public (visibility=0x01) ───────────────▶ stored directly (§13.8)
  └─ private (visibility=0x00)
       ↓ AES-256-GCM(K, nonce, AAD=qub_id) (§7 step 12, this section)
     OuterWrapper CBOR bytes         ← stored private payload

Zapečetění a odemčení na vrstvě protokolu (§7, §8) jsou pod hranicí obalu beze změny; obal se připojuje na místě volání seal() a odpojuje na místě volání unlock().

13.3 Datová struktura OuterWrapper

struct OuterWrapper {
    version:    u8,           // 0x01, see §12.5
    qub_id:     [u8; 32],     // copied from inner SealedQub; AEAD AAD
    nonce:      [u8; 12],     // 96-bit AEAD nonce
    ciphertext: Vec<u8>,      // AES-256-GCM(K, nonce, SealedQubCbor, AAD=qub_id) || 16-byte tag
}

Invarianty polí.

Kódování CBOR. Kanonický CBOR dle §3, se stejným pravidlem řazení klíčů (seřazeno vzestupně podle zakódované délky bajtů, poté lexikograficky). Čtyři klíče jsou:

Klíč Zakódované bajty Pořadí
nonce 6 1
qub_id 7 2
version 8 3
ciphertext 11 4

Prvním bajtem CBOR OuterWrapper je tedy hlavička mapy s definitivní délkou pro mapu se 4 položkami (0xA4).

13.4 Vázání AAD na qub_id

Obal váže qub_id jako dodatečná autentizovaná data AEAD. To je nosná strukturální obrana proti třem třídám útoků:

Útok Obrana
Přesunout šifrový text pod jiné pole qub_id v obalu Neshoda AAD → autentizace AEAD selže
Smíchat fragment URL qubu A s uloženými bajty qubu B Nesprávný klíč (a nezávisle vázané AAD) → autentizace AEAD selže
Manipulovat s polem qub_id obalu po nahrání Neshoda AAD → autentizace AEAD selže

Nesení qub_id v prostém textu obalu významně neoslabuje imunitu vůči výčtu — qub_id je sám hash SHA3-256 předobrazu dle §4.1 bez obnovitelného předobrazu z digestu a vyčíslovatel, který už sklidil bajty obalu, se z viditelného qub_id nedozví nic, co by nemohl odvodit ze samotné existence nahrání.

13.5 Algoritmy obalení a rozbalení

wrap_sealed_qub(SealedQubCbor S, qub_id Q, key K, nonce N):
    require K.len() == 32 and N.len() == 12 and Q.len() == 32
    I := canonical_cbor_decode(S) as SealedQub
    require I.qub_id == Q               // reject mismatched caller AAD
    C := AES_256_GCM_encrypt(key=K, nonce=N, msg=S, aad=Q)
    // C includes the 16-byte authentication tag at the end
    return canonical_cbor_encode(OuterWrapper{
        version:    0x01,
        qub_id:     Q,
        nonce:      N,
        ciphertext: C,
    })

unwrap_sealed_qub(OuterWrapper bytes W, key K):
    require K.len() == 32
    O := canonical_cbor_decode(W) as OuterWrapper
    require O.version == 0x01           // §12.5
    P := AES_256_GCM_decrypt(
            key=K, nonce=O.nonce, ciphertext=O.ciphertext, aad=O.qub_id
         )
    // any AEAD failure → DECRYPT_FAILED, indistinguishable to caller
    S := canonical_cbor_decode(P) as SealedQub
    require S.qub_id == O.qub_id       // explicit inner/outer cross-check
    return P                            // P is the validated SealedQubCbor

Sloučení režimů selhání. Špatný K, špatný nonce, neshoda AAD a pozměněný šifrový text — všechny produkují stejnou chybu DECRYPT_FAILED. Toto je záměrná vlastnost AEAD: rozlišování režimu selhání by vytvořilo postranní kanál, který by vzdálený útočník mohl zkoumat odesíláním poškozených obalů a měřením času odezvy. Referenční implementace MUSÍ sloučit všechna selhání AEAD do jediného tvaru chyby.

13.6 Klíčový materiál a distribuce

Obalovací klíč K je 256bitová rovnoměrně náhodná hodnota generovaná pro každý qub pomocí CSPRNG. Referenční implementace jej získávají z:

Distribuce: K MUSÍ být kódován jako base64 bezpečné pro URL (RFC 4648 §5, bez výplně) a připojen k doručovací URL jako komponenta fragmentu:

delivery_url = <origin>/c/<arweave_tx_id>#<base64url(K)>

Fragment není vyhovujícím prohlížečem nikdy přenášen na žádný server. Obnovovací kanály (index historie na straně serveru, přihlašované automatické zasílání e-mailem), které trvale uchovávají úplnou doručovací URL — včetně fragmentu — mimo zařízení uživatele, jsou výslovným kompromisem oproti výchozímu postoji kryptografické skartace a MUSÍ být podmíněny výslovným souhlasem uživatele.

Ztráta fragmentu. Pokud uživatel ztratí fragment URL a nemá obnovovací kanál, qub je nečitelný. Toto je nosný kompromis návrhu a MUSÍ být uživateli sdělen v okamžiku zapečetění. MVP posiluje sdělení v okamžiku zapečetění výslovným textem „uložte si tuto URL“ a obnovovacím kanálem s ověřeným e-mailem pro uživatele, kteří se přihlásí.

13.7 Mimo rozsah této části

13.8 Veřejné quby (vynechání obalu)

Vnější obal je na doručovací vrstvě volitelný. Tvůrce může zapečetit qub jako veřejný, v kterémžto případě kanonický SealedQubCbor vstupuje přímo do úložného procesu, bez vrstvy OuterWrapper a bez klíče K:

SealedQubCbor bytes  ──(public)──▶  stored as-is
SealedQubCbor bytes  ──(private)─▶  AES-256-GCM(K, …) ▶ OuterWrapper ▶ stored

Veřejný qub je časově uzamčený, ale není uzamčený odkazem: zůstává nečitelný, dokud se nezveřejní jeho kolo drand (vrstva tlock je beze změny), ale po odemčení jej může kdokoli, kdo má identifikátor transakce úložiště, dešifrovat — žádný fragment URL není potřeba, protože žádné K neexistuje. To je záměrný kompromis pro plochy, které musí řídit server: e-maily s oznámením o odhalení, odkazy oEmbed/automatického vložení bez fragmentu i bohatší SEO po odhalení potřebují odkaz, který funguje bez tajemství, jež server nikdy nedrží (§13.6). Soukromý qub může nadále používat explicitní podobu <qub-embed src="full_delivery_url">, pokud vydavatel poskytne úplnou schopnost včetně fragmentu.

Důsledky, s nimiž producent MUSÍ počítat:

Soukromý (obalený) zůstává výchozím; veřejný je výslovnou volbou tvůrce u jednotlivého qubu.


14. Testovací vektory

14.1 Odvození qub_id

Input:
  version      = 0x01
  content_type = 0x01
  created_at   = 1735689600 (2025-01-01 00:00:00 UTC)
  unlock_at    = 1736294400 (2025-01-08 00:00:00 UTC)
  outcome_at   = absent
  drand_round  = 4695446  (= floor((1736294400 - 1595431050) / 30) + 1, §4.3 mapping, drand mainnet params §14.2)
  body         = "Hello, future."  (UTF-8, 14 bytes)
  title        = absent

Intermediate:
  body_hash  = SHA3-256("Hello, future.")
             = 76ab8b3f843c6ed4f2d0fd75b9f457b4
               ad49dd4450f9c22723ae430e3af3211d
  title_hash = [0u8; 32]   (title absent — §4.2.1 sentinel)

Domain separator (10 bytes):
  [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00]

Preimage (108 bytes—current protocol v1):
  domain_separator    ||  // 10 bytes
  0x01                ||  // version
  0x01                ||  // content_type
  0x0000000067748580  ||  // created_at as i64 big-endian (1735689600)
  0x00000000677DC000  ||  // unlock_at as i64 big-endian (1736294400)
  0x0000000000000000  ||  // outcome_at_or_zero (outcome_at absent)
  0x000000000047A596  ||  // drand_round as u64 big-endian (4695446)
  body_hash           ||  // 32 bytes
  title_hash              // 32 bytes (all-zeros sentinel; title absent)

Expected output:
  qub_id = SHA3-256(preimage)
         = 4a84e3dfaec32954949c30073f8e6506
           fd3204c1bb97f9162b81c7587afe412e

Implementace MUSÍ pro tento vstup produkovat identické hodnoty body_hash a qub_id. Tento testovací vektor BY MĚL být prvním napsaným jednotkovým testem. Výše uvedené kanonické hodnoty byly vypočítány referenční implementací a MUSÍ se shodovat bit po bitu. Historická uspořádání prototypu před spuštěním (na prvních dvou nezávisely žádné živé quby) používala 92 bajtů před přidáním outcome_at (3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0) a 100 bajtů po přidání outcome_at_or_zero (b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed). Současné 108bajtové uspořádání poté přidalo drand_round a oddělovač domény QUB_ID_V2. Raný 108bajtový vektor použil starší mapování ceil (drand_round = 4695445) a vytvořil 3a9fcb31b750d985c262fada6d4f777fd6a28be831d941d85c131f5a4bbaf8a4 — pro tento vstup kola jde stále o platné qub_id, zatímco výše uvedený příklad používá aktuální mapování z §4.3.

14.2 Mapování na kolo odemčení

Input:
  unlock_at           = 1735689600
  chain_genesis_time  = 1595431050
  chain_period_seconds = 30

Calculation:
  (1735689600 - 1595431050) / 30 = 4675285.0
  floor(4675285.0) + 1 = 4675286

drand_round = 4675286

Kolo 4675286 je zveřejněno v čase 1595431050 + (4675286 - 1) * 30 = 1735689600 — přesně v unlock_at, nikdy dříve. (Starší předběžné mapování ceil dalo 4675285, zveřejněné v 1735689570 — o 30 sekund dříve; ověřovatelé toto starší kolo přijímají dle §4.3.)

14.3 Obousměrná konverze kanonického CBOR

Implementace MUSÍ ověřit, že serialize(parse(serialize(qub))) == serialize(qub) pro všechny platné vstupy. Toto je vlastnostní test, nikoli jediný vektor.

14.4 CBOR PactTerms (content_type 0x03)

Input:
  pact_version = 1
  title        = "Scooter deposit"
  terms        = [
    { key: "Item",    value: "Honda Metropolitan scooter" },
    { key: "Price",   value: "$100" },
    { key: "Deposit", value: "$10" }
  ]
  party_a      = { label: "Alice" }
  party_b      = { label: "Bob", contact: "bob@example.com" }
  notes        = absent

Canonical CBOR key order (PactTerms):
  "notes"(6) < "terms"(6) < "title"(6) < "party_a"(8) < "party_b"(8) < "pact_version"(13)

Canonical CBOR key order (PactTerm):
  "key"(4) < "value"(6)

Canonical CBOR key order (PartyIdentifier):
  "label"(6) < "contact"(8)

Kanonické bajty CBOR a body_hash SHA3-256 jsou vypočítány referenční implementací. Implementace MUSÍ pro tento vstup produkovat bajtově identický CBOR.

Implementace MUSÍ rovněž ověřit, že serialize(parse(serialize(pact))) == serialize(pact) pro všechny platné vstupy PactTerms (vlastnostní test).

14.5 Mezijazykové vektory vnějšího obalu

Vnější obal (§13) má samostatnou kanonickou fixturu v crates/qub-core/tests/vectors/wrapper_v1.json. Každý případ fixuje n-tici (key, nonce, qub_id, sealed_cbor) jako neprůhledné hexadecimální vstupy a vyžaduje konkrétní výstup expected_wrapper_hex. Obě referenční implementace spotřebovávají stejný soubor JSON:

Fixtura aktuálně ukotvuje tři nízkoúrovňové případy obalu. Testují deterministické kódování OuterWrapper a interoperabilitu AEAD nezávisle na invariantu tvaru doručení dle §13.8; zejména historický název basic-text-public a jeho vnitřní visibility = 0x01 nečiní výsledné obalené bajty vyhovujícím veřejným doručením. Producent stále MUSÍ ukládat veřejné vnitřní bajty bez obalu a obalovat pouze soukromé (0x00) vnitřní bajty.

Případ Pokrytí
basic-text-public Historický název nízkoúrovňové fixtury. Nejmenší realistický tvar SealedQub bez volitelných polí; testuje pouze bajty obalu a není vyhovujícím uloženým doručením dle §13.8.
with-recipient-pubkey SealedQub s nastaveným recipient_pubkey (vyhrazená budoucí cesta). Procvičuje odlišnou sadu klíčů vnitřního CBOR; odlišný obsah fixtury nezávisle vytváří odlišné qub_id (samotné recipient_pubkey není v předobrazu dle §4.1).
longer-body Tělo ~4 KiB — procvičuje vícebajtové délkové prefixy CBOR uvnitř jak vnitřní obálky, tak vnějšího šifrového textu.

Implementace MUSÍ pro zaznamenané vstupy produkovat bajtově identický expected_wrapper_hex. Opětovné generování fixtury vyžaduje QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors a je vyhrazeno pro záměrné změny formátu.


15. Správa kryptografického profilu (budoucí)

Tato část je pro v1 informativní a stává se normativní v okamžiku, kdy poprvé vstoupí druhý algoritmus do kteréhokoli z kryptografických primitiv qubu.

15.1 Současný postoj

Protokol v1 váže přesně jeden algoritmus na primitivum:

Ověřovatelé v současnosti pevně zakódovávají délky klíčů a podpisů pro každé aktivní primitivum. Bajty sig_alg a verze obalu jsou výslovné selektory, ale v1 neprovádí žádné vyjednávání v pásmu a připouští pouze výše uvedené aktivní hodnoty.

15.2 Zamýšlený tvar

Když do protokolu vstoupí druhý algoritmus, ověřovatel bude nakonfigurován pro pojmenovaný CryptoProfile (např. ExqubV1) vyjmenovávající přesnou sadu povolených hodnot pro každé primitivum — sig_algs, řetězce drand, verze obalu, typy obsahu. Profil je pevně daný v okamžiku ověření, nikdy se nesjednává v pásmu. Jakákoli hodnota mimo aktivní profil je odmítnuta.

To zaručuje, že přidání ML-DSA-87 nebo aktivace Ed25519 nemůže zpětně oslabit existující konfigurace ověřovatelů: ověřovatel v1 zůstává ověřovatelem v1 i poté, co je zveřejněn profil v2.

15.3 Spouštěcí podmínky

Povýšte §15 na normativní stav, když je navržena kterákoli z následujících možností:

Do té doby je §15 zástupný text, který fixuje tvar migrace, aby budoucí PR dopadaly proti známému cíli, místo aby od základu znovu řešily plochu sjednávání.


16. Záznamy transparentnosti a úrovně odolnosti (implementováno — kontrola dokončena)

Stav. Tato sekce je implementována (W5/UP-B1, fáze 1–8), přičemž zde je uveden rozsah producenta a trust-root. Formáty drátů, hashování a ověřovací cesty jsou živé: základní Merkle + canonical-CBOR typy (qub-core), TypeScript mirror + ANS-104 bundler (workers/api/src/crypto/), single-writer LogDO + souřadnicově klíčový R2 node store, pokus o přidání logu /upload, denní anchor + bundler-drain crons, GET /api/v1/qub/:tx_id/proof (inclusion) a GET /api/v1/log/consistency (RFC 9162) proof endpointy, typovaný inclusion proof v .qub bundle (§17.5), nativní ANS-104 ověřovač kotvy (tools/qub-verify) a dvojitý samo-publikovaný háček hlav (§16.6). Úspěšný /upload je vždy R2-trvalý, ale je kryt logem pouze tehdy, když je LOG_DO nakonfigurován a inline dodatek uspěje; teprve tehdy jeho odpověď přenáší log_seq, receipt a anchor_status. Pokud RECEIPT_SK chybí nebo je neplatný, sig_b64url tohoto potvrzení je prázdný a neposkytuje žádnou nepopiratelnou záruku. Aktuální cesty publikace /seal a paktu plánují jednotlivé Arweave transakce, ale nepřidávají log leaf. Žádný kód v současnosti neprovádí navrhované pozdější smíření /upload komentáře po selhání připojení. Externí přezkum W5 je dokončen: §16.15 zaznamenává rozhodnutí o návrhu a omezení při spuštění, ale tato omezení nerozšiřují právě uvedené pokrytí producenta. Tři položky důvěry/nasazení zůstávají omezené: (a) dedikovaná peněženka kotvy (ANCHOR_JWK; LogProfile.anchor_owner je stále zástupcem [0xAB; 32]); (b) klíč k podpisu účtenek a odpovídající PIN veřejného klíče (RECEIPT_SK je volitelný a LogProfile.receipt_pubkey je momentálně prázdný); a (c) samopublikované GitHub repozitář + token (§16.6). Dokud nejsou kotevní/profilové piny zřízeny, samostatný ověřovatel hlásí stav důkazu poctivě, místo aby tvrdil plně ukotvené, připnuté ověření. Design je striktně aditivní a není žádná změna SealedQub / QubEnvelope formátu vodiče.

16.1 Úrovně odůvodnění a odolnosti

Současné cesty publikace oddělují potvrzení od potvrzení Arweave: odvozují a podepisují jednotlivou transakci, uchovávají artefakt a přesný stav odeslání v R2 a poté zveřejňují asynchronně. Průhlednostní log přidává nezávisle ukotvenou vrstvu pořadí pro podmnožinu obecných /upload požadavků, jejichž LogDO připojení uspěje:

Tier Název záruka Když
T1 R2 – první synchronní ACK Odolnostní spodní hranice — zapečetěné bajty a přesný stav publikace jsou zapsány do trvalé paměti před návratem úspěchu. Implementováno napříč současnými cestami publikace.
T2 Dávkové zahrnutí průhlednostního logu pouze přílohy, závazek s důkazem manipulace + celkové objednávky po vložení a ukotvení. Současný producent: úspěšný LogDO přidává z /upload; odpověď nese n-tici přijetí. Není univerzální.
T3 Per-qub Arweave trvalost Individuální transakce Arweave pro qub. Aktuálně připraveno pro každou přijatou publikaci a zveřejněno asynchronně; přesná podepsaná transakce zůstává v drainovatelné odchozí schránce až do doručení.

Úrovně popisují odlišné vlastnosti důkazů a odolnosti, nikoli současný komerční plán. Současný kód stále plánuje jednotlivou transakci Arweave pro každou přijatou publikaci; neukazuje T3 pouze jako placený upsell. Limity kvót API klíčů/účtů zůstávají samostatnými kontrolami aplikací.

Odolnost poctivosti. Zápis T1 je synchronní, takže úspěšná odpověď stanovuje trvanlivost na úrovni aplikace bez čekání na bránu Arweave. Sama nevytváří nezávislé časové razítko. Potvrzená individuální transakce poskytuje horní hranici blokového času. Pro odpověď nesoucí kompletní T2 přijímací tici může další potvrzený kotva dodat logarimický důkaz popsaný níže. Pokud n-tice chybí, žádný povrch nemůže znamenat, že tento qub je již v logu transparentnosti. Latence kotvy a publikace nemají protokolovou numerickou SLA.

16.2 Struktura LogLeaf (dva popsané tvary)

Záznam v deníku je LogLeaf, zakódovaný jako ručně psaný kanonický CBOR podle profilu §3.1 (délka pevná, žádné tagy, žádné floaty, celá čísla v nejkratší formě, text NFC, volitelná pole vynechána, pokud chybí, klíče seřazeny podle délky zakódovaného bytu a pak bytově). Kanonická ochrana §3.1 parse → re-encode → compare se aplikuje na cestě kódování před hashováním (nejen při dekódování), takže dvě implementace nemohou nesouhlasit ve výstupech listů kvůli rozdílu v šířce celých čísel nebo pořadí klíčů. Všechna celá čísla jsou u8 / u64 / i64; všechny digesty jsou 32-bytové řetězce bytů (bstr[32]). Uložené ID transakce Arweave je surový 32-bytový SHA-256 digest nesený jako bstr[32], nikdy textový řetězec base64url (odpovídá §3.3).

List má dva tvary vybrané podle kind byte, protože obecná cesta nahrávání nevidí byty: POST /api/v1/upload úmyslně zachází s oběma přijatými tvary payloadu jako s neprůhlednými a přijímá qub_id a unlock_at pouze jako nedůvěryhodná tvrzení klienta. Na výchozí soukromé cestě, body_hash, drand_round, created_at a drand_chain_version jsou navíc skryty uvnitř §13 vnějšího obalu, jehož klíč Worker nikdy nemá. Typový systém také definuje potvrzený tvar pro producenta, který odvozuje body_hash / drand_round sám. Současná /seal cesta má tyto hodnoty, ale nevolá LogDO, takže produkce aktuálně vydává pouze potvrzené (0x02) listy z úspěšných obecných nahrání. Toto rozdělení udržuje každou potvrzenou hodnotu poctivou, aniž by se předstíralo, že potvrzený producent je provázán:

Klíč Enc. len Typ Přítomnost Význam
seq 4 u64 vyžadováno globálním indexu listů založeném na 0; pozici, ke které se zavazuje důkaz zařazení.
kind 5 u8 vyžadovalo 0x01 attested-capable (definované, aktuálně nevysílané) nebo 0x02 afirmované (klient-pečeť / nahrávání bez bajtů).
ref 4 bstr[32] vyžadoval referenční ID listu. Ověřeno → surové qub_id. Tvrdilo → blinded id SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1).
chash 6 bstr[32] vyžadováno Content address SHA3-256(stored_bytes) — jedinou obsahovou vazbu, kterou Worker vždy dokáže upřímně vypočítat, na obou cestách.
unlock_at 10 i64 vyžadovalo zkopírovat (potvrdit) nebo potvrdit (asserted); validovat > 0 před vstupem do listu.
received_at 12 i64 vyžadováno nástěnné hodiny pracovníka na R2-ack. Ne-důkazní (operátorem tvrděno; §16.6). Přítomno pro sebepopis, nikdy důkaz. Ověřeno > 0.
body_hash 10 bstr[32] kind=0x01 pouze vynecháno na 0x02 — Worker jej podle §13 postrádá.
drand_round 12 u64 kind=0x01 pouze Na 0x02 vynecháno.

List kind=0x02 záměrně necommituje ani body_hash, ani drand_round: potvrzuje závazek a uspořádání neprůhledného šifrovaného textu na adresě obsahu chash, nárokuje qub_id a unlock_at — nikoli jeho otevřený text nebo kolo. Otevřený text/kulaté nohy pro tvrdý qub pocházejí z existující verifikace .qub-bundle §11, nikoli z logu (§16.11). drand_chain_version není v listu (je uvnitř obalu na výchozí cestě); granularita řetězce je na kotvě (§16.7). Disciplína enkodéru: odmítnout ref nebo chash s nulou a odmítnout nepozitivní unlock_at / received_at, což zrcadlí outcome_at > 0 strážce strážce v cbor.rs.

16.2.1 Oslepování soukromého qubu

Log se nesmí stát enumeračním orákulem, které vnější obal §13 existuje zabránit (§13.1). Pro soukromý (zabalený) qub list asserted commituje SHA3-256(qub_id ‖ log_blind_secret) identifikátor blinded, kde log_blind_secret je tajemství držené serverem, a vynechá body_hash. Třetí strana nemůže takový list svázat s konkrétním qub_id; držitel qubu, který má doručovací URL a tedy qub_id, může slepého znovu vypočítat a potvrdit své vlastní zařazení. Veřejný qub (již spočetný, již nesoucí tag Visibility: public Arweave podle §13.8) commituje surový qub_id. Právě zde samostatná ověřitelnost záměrně ustupuje invariantu s nosným soukromím; samostatná vazba pro soukromé quby je chash (§16.9).

log_blind_secret úschově (vyřešeno — §16.15 Q4). Slepá clona chrání nespojitelnost listu, nikoli důvěrnost otevřeného textu (obal §13 to drží nezávisle). Při log_blind_secret kompromisu, pro jakýkoli qub_id už útočník drží nebo dokáže rekonstruovat (každý qub, jehož balíček nebo URL má, plus jakýkoli qub_id s nízkou entropií nebo veřejnou ), přepočítá listovou ref v jednom hashi a propojí ji — jde o přímé propojení známé populace, nikoli o hrubou sílu přes neznámý prostor. Klasifikujte log_blind_secret jako korelacní/Sybil-grade tajemství ve stejné úrovni úschůvy jako ostatní serverová tajemství a rotujte pouze dopředu (rotace znovu zaslepuje budoucí odchody; nemůže zpětně odpojit již ukotvené).

16.3 Hašování listů a uzlů

RFC 6962 §2.1 doménově oddělené hashování s SHA-256 nahrazeným SHA3-256:

leaf_hash      = SHA3-256(0x00 || canonical_cbor(LogLeaf))
node_hash(l,r) = SHA3-256(0x01 || l || r)
empty tree     = SHA3-256("")          // defined but never anchored

Doménové prefixové bajty 0x02 (vstupní řetězec, §16.4) a 0x03 (STH hash, §16.6) jsou rezervované a od nich nespojivé. Jsou to jednobajtové a nemohou kolidovat s existujícími 10bajtovými ASCII doménovými oddělovači (QUB_ID_V2 atd.). Strom je RFC 6962 levo-plný nevyvážený strom (každý vnitřní rozdělení je největší mocninou dvou přísně menší než počet listů podstromu), což umožňuje sdílení jednoho algoritmu pro inkluzi a konzistenci pro inkluzi a konzistenci sdílený jeden algoritmus auditní cesty. Referenční specifikace nese explicitní pseudokód pro levo/pravé odvození a připíná testovací vektor bez mocniny dvou (5-listů), takže je použit případ povýšení na pravém okraji — který čtyřlistý vektor skrývá.

16.4 Řetězení hashů (interní)

LogDO udržuje interní řetězec vstupů pouze pro konzistenci při pádu. Nikdy není publikován a nikdy není vystaven ověřovatelům**:

entry_chain[seq] = SHA3-256(0x02 || entry_chain[seq-1] || leaf_hash[seq])
entry_chain[-1]  = SHA3-256("QUB_TLOG_GENESIS_V1")

Publikovanou autoritou pouze pro přílohy je kumulativní Merkleův kořen + jeho kotva (§16.5–16.6), nikdy nejde o surové pořadí, v jakém operátor servíruje, listy: řetězec přepočítává pro každý podaný příkaz, takže pouze ukotvený kořenový půj má kanonickou pozici.

16.5 Kumulativní Merkle strom a dávkování

Existuje jeden stále rostoucí RFC 6962 strom nad všemi listy v seq pořadí — ne izolované stromy pro každou dávku. (Byla odmítnuta konstrukce s řetězcem přenosu listů pro každou dávku: není to pravá relace s prefixem, takže její "důkazy konzistence" jsou nespolehlivé.) Kumulativní strom poskytuje skutečné důkazy konzistence podle RFC 9162 a umožňuje jediný nedávný kotv dokázat zařazení pro jakýkoli starší qub.

LogDO Durable Object je jeden zapisovatel (blockConcurrencyWhile, zrcadlující QuotaDO / EntitlementDO) — připojování ke sdílenému logu je read-modify-write na sdíleném stavu, a proto MUSÍ projít DO, nikdy KV. Ukládá pravý okraj stromu (O(log n) hashe), takže uzavření batch je O(batch). batch je sada listů ukotvených dohromady; její implementované triggery jsou tree_size advance alespoň LOG_BATCH_MAX_LEAVES (výchozí 4096), age dosahující anchor cadence nebo explicitní administrativní/cron force-close. root_i je kumulativní Merkle Tree Hash nad listy 0 .. tree_size_i.

16.6 Podepsáno Tree Head přes Arweave Anchor

Transakce Arweave kotvy je podepsaná hlava stromu a nahrazuje operátorový podpis pro samotnou hlavu stromu: denní kotva nepotřebuje qub klíč, protože podpis je Arweave tx owner. Teze o příkopu platí — neměnný substrát, nikoli qub-tajemství, je nosný pro ukotvený kořen.

Design logu vyžaduje jeden teplý úspěšný připojovací přijímací klíč (§16.10), připnutý LogProfile a podepsán anchor_owner. Současná implementace tuto důvěru na kořen nedokončila: RECEIPT_SK je volitelné, chybějící/neplatný klíč přináší sig_b64url: "" a zkompilovaný LogProfile.receipt_pubkey je prázdný. Takový doklad může popsat připojený list, ale není nepopiratelným podepsaným účtenkem. Silnější návrhové tvrzení platí pouze poté, co ověřovací uvolnění připne odpovídající veřejný klíč a vlastník kotvy jej podepíše. Publikační odpověď bez n-tice úplného přijetí nečiní žádné přijetí logu; ta s prázdným podpisem vydává požadavek na připojovací pozici, ale žádný požadavek na ověření podpisu.

SignedTreeHead je kanonický CBOR (klíče podle zakódované délky): size:u64, root:bstr[32], batch:u64, prev:bstr[32] (před sth_hash; genesis = 32 nula bajtů), log_id:bstr[32], first_seq:u64, anchored_at:i64. Jeho hash je sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead)).

Připnutý kořen důvěry. log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address). Konformní ověřovatel MUSÍ vyžadovat anchor_tx.owner == LogProfile.anchor_owner, kde anchor_owner (a veřejný klíč účtu pro potvrzení) je zabudován do qub_core jako LogProfile — spolu s konstantami Quicknet, které jsou již v DrandTimelockProvider::quicknet() — a distribuován s binárním souborem ověřovatele. Ověřovatel MUSÍ rovněž ověřit vazbu Arweave tx data → tx_id lokálně místo důvěry v odpověď brány /raw/. Tím se uzavírá mezera způsobená podvodnými peněženkami: „zakotvena na Arweave“ je bezvýznamné, dokud ověřovatel nepřipne kterou peněženku.

Rotace je §15 vládní rozšíření, nikoli znovupoužití (vyřešeno — §16.15 Q3). Povrch profilu §15.2 v současnosti vyjmenovává pouze sig_algs / drand řetězce / verze wrapperu / typy obsahu a seznam spouštěčů §15.3 neobsahuje žádný z nich — LogProfile / anchor_owner ještě není na povrchu §15. Vládnutí rotací musí být proto vybudováno: §15.3 je rozšířeno (níže) o přidání spouštěče LogProfile a rotace je podepsaný zvýšení LogProfile doručené v aktualizaci ověřovatele. Plánovaná rotace nese křížový podpis odcházejícího → přicházejícího; rotace vyvolaná kompromisem jej nemůže nést (odcházející klíč je v té chvíli nedůvěryhodný / nedostupný) a spadá zpět na §15 řízené zvýšení, přičemž kontrola rozvětvení předchozího kotvy (níže) omezuje škody v mezidobí.

Okno ekvivokace (parametr důvěry první třídy). List je odolný proti ekvivokaci pouze tehdy, když je jeho krycí kotva Arweave-potvrzena. Okno je received_at → anchor confirmation (kadence + konečnost Arweave, bez záruky latence protokolu). Před poskytováním kořene důvěry současná implementace dodává provozní integritu qub spolu s jakýmkoli nepodepsaným metadatem pro připojení; neposkytuje plánovanou záruku nepopiratelnosti. Tři artefakty odpovědnosti definují dokončený návrh (model svědka je řešení §16.15 Q2):

  1. Zapečetěte potvrzení (závislé na provisioningu) — analog SCT vrácený, když je záznam připojit k nahrávání úspěšný (§16.10). Stává se nevyvratitelným pouze tehdy, když sig_b64url není prázdný **a ** je v ověřovači připnut odpovídající vztah veřejného klíče/kotvy-vlastníka. Aktuálně prázdný pin produkčního profilu nemůže tento verdikt podporovat. Tato kontrola se nevztahuje na vynechanou n-tici účtenky ani na nepodepsanou účtenku.
  2. Publikovaná monitorovací metodologie + předchozí řetězová chůze — kotva prev řetězu je chůze hlava→geneze; rozcestí (dvě kotvy na jednom size s různými root nebo rozbitý prev) je publikovatelný důkaz nevhodného chování. Detekce nejasnosti je deklarovaný provozní závazek, nikoli tichý předpoklad.
  3. Dvojité samopublikované hlavy — každá nová {sth_hash, tree_size} hlavy je zveřejněna do dedikovaného qub-vlastněného veřejného, pouze připojeného GitHub repozitáře (zátěžová část samopublikace s evidentním zásahem), přičemž sociální příspěvek slouží pouze jako potvrzení nejlepšího úsilí. Stránka s neúspěšným zveřejněním MUSÍ být (ne neúspěšná tichá). Implementováno (Fáze 8) jako publishHead háček na anchor cron (workers/api/src/utils/heads-publish.ts): PUT do Content API bez sha je pouze pro připojení (422 znamená, že hlava je již publikována, nikdy není přepsána); opt-in / nasazení na PUBLISH_HEAD_GITHUB_{TOKEN,OWNER,REPO} a neaktivní, dokud není repozitář provisionován. Tvrdý GitHub selhání se zobrazí přes health_alert kanál a zobrazí trvalou metriku selhání (m:tlog_publish_head_fail); samotný Arweave anchor se při neúspěchu publikace nikdy nevrátí zpět. "Ne selhání tiché" je zaručeno touto trvalou metrikou — na kterou operátor MUSÍ dashboard-alert — i když e-mailová stránka s nejlepším úsilím nemůže být doručena. Z "anchor-on-advance" plynou dva upřímná omezení (cron publikuje pouze při posunu velikosti): přechodné selhání GitHubu zanechává mezru v sekvenci publikovaných hlav pro tuto velikost — omezenou, ne tichou (stránkuje), a protože každá hlava commituje nadmnožinový strom, důkaz konzistence podle §16.9 překlenuje mezeru; Klíčové je, že důkaz konzistence je vypočítán z autoritativního stromu ukotveného Arweave, nikoli z povrchu GitHubu, takže mezera v GitHubu nikdy neoslabuje ověřitelnost. Dohnané doplnění, které vyplňuje mezery v publikovaných hlavách, je odloženým vylepšením.

Vázaná poctivostí (závazné omezení). Protože qub kontroluje obě plánované zveřejňovací plochy, jedná se o vlastní publikaci, nikoli o nezávislé svědectví. Žádný produkt, marketingový ani právní povrch nesmí tvrdit, že záznam je "nezávisle svědčen". Po zajištění účtenky/profilu/hlavy je povoleným tvrzením, že neúměrnost je detekovatelná a úspěšně podepsaný dodatek zanechává nevyvratitelný doklad. Do té doby je tento nárok nedostupný. Skutečný nezávislý svědek třetí strany je odložen na budoucí zvýšení správy podle §15.

received_at je nárokováno operátorem a na něm nesmí být žádný nárok — nikdy není předložen jako důkaz nebo jako potvrzení sporu na žádném produktu / právním / API / důkazním povrchu. Časová T kotvy Arweave je jediným nedůvěryhodným časovým razítkem (horní hranice pro "logován"). Jakákoli kontrola sanity monitoru na received_at MUSÍ být porovnávána s T, nikoli s operátorem řízeným polem anchored_at STH; taková kontrola je pouze ochranou proti hodinové chybě poctivého operátora, ni kontrolou odpovědnosti proti škodlivému operátorovi (§16.15 Q5).

16.7 Formát a kadence kotvných transakcí

AnchorBundle je kanonické transakční tělo Arweave s CBOR CBOR, zapsané pomocí bundleru §16.8: ver:u8, sth:bstr (kanonické SignedTreeHead bajty), prev_anchor:bstr (předchozí anchor tx id raw bajty; vynecháno při vzniku), chain_hash:tstr (řetězec drand v platnosti — quicknet) a listový CBOR stream dávky v seq pořadí**, takže kotva je samostatná: monitor odvozuje root z těla s nulovou závislostí na qub. (Pokud se listový proud při vysokém objemu zvětší, budoucí revize může podle odkazu zaznamenat pouze rozsah listu; poznamenáno, ve verzi 1 není přijato.)

Štítky Arweave jsou záměrně spočetné — záznam je určen k nalezení, na rozdíl od soukromých qubů: App-Name: qub-tlog, Anchor-Format: 1, Log-Id: <hex>, Batch: <n>, Tree-Size: <n>, Root: <hex>, Prev-Anchor: <tx>, Content-Type: application/cbor. Tagy jsou nedůvěryhodné nápovědy; tělo CBOR je jedinou autoritou.

Cadence: denně ve výchozím nastavení, upravováno podle objemu (spouštěč velikosti automaticky zkracuje efektivní kadenci při zátěži). Současný producent neimplementuje háček pro nucenou platbu seal. Anchor peněženka je vyhrazená a s nízkou rychlostí transakcí, oddělená od upload peněženky — MUSÍ mít vlastní JWK (odlišný klíč, nikoli logická role v upload peněžence), aby kompromitace upload peněženky nemohla padělat anchory — s jasným denním limitem transakcí. Custody postoj je jasně uveden: úzce účelový hot key s přísným obvodovým jističem a nízkým zůstatkem, ne „cold“ — peněženka, která automaticky podepisuje denně, nemůže být studená, a specifikace neudává opak.

16.8 ANS-104 Bundler

Vlastní ANS-104 DataItem enkodér a deep-hash signer, přibližně 300 řádků, pouze Web Crypto, žádné npm závislosti (oba Turbo SDK selhávají při supply-chain kontrole npm ci --ignore-scripts). Rozložení bytů DataItem:

signatureType (2, LE) || raw_signature || owner || target(flag+0|32) || anchor(flag+0|32) || num_tags(8, LE) || tags_len(8, LE) || avro_tags || data

Podepisování je Arweave deepHash — rekurzivní SHA-384 digest (požadavek Arweave pro wire, crypto.subtle.digest("SHA-384")) přes ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] — poté RSA-PSS přes deep hash s JWK peněženky přes crypto.subtle; id = base64url(SHA-256(signature)). SHA-384 zde je karanténována pouze jako Arweave-wire primitivum, nikdy jako qub trust primitivum (§15 zaznamenává oddělení; qub trust hashing je celý SHA3-256).

ANS-104 kódová cesta slouží pro zpožděný fallback/drain mechanismus a zapisuje AnchorBundle DataItems. Běžná publikace nejprve vytvoří přesnou podepsanou Arweave transakci a uloží její JSON do trvalého outboxu; přímé zaslání je optimalizace latence, a drain cesta opakovaně odesílá stejnou transakci před aplikací bundler fallback. Schéma podpisu (vyřešeno — §16.15 Q8): v1 podepisuje s RSA-PSS (typ podpisu 1) opětovným použitím existujícího mechanizmu JWK Arweave peněženky (žádná nová dlouhodobá držba klíče, podporující tézi „o jeden tajný prvek méně“); Ed25519 je odloženo pro §15 PQ-migrační cestu.

Ručně vytvořený deep hash je nejrizikovější kód s nejnižším přirozeným pokrytím ve W5, takže jeho kontrola je nevyjednatelná (§16.15 Q8):

  1. Mezikulturní fixture tlog_v1.json (Rust + TS, vzor §14.5 wrapper_v1.json) zahrnuje deep-hash, DataItem bytes + id, leaf hashe, 5-listý root + audit path, STH hash, důkaz začlenění a důkaz konzistence — v obou směrech sign a verify (směr verify je důležitý, protože kontrola §16.6 local tx → tx_id přenáší deep hash do každého samostatného ověřovače, nejen do zapisovače).
  2. Jednorázový interoperabilní round-trip přes referenční ANS-104 bundler, konzumovaný pouze jako statická testovací data — nikdy jako runtime dependency npm (postoj Web-Crypto-only / no-install-scripts zůstává).
  3. Deep-hash + RSA-PSS cesta musí projít přes stejné primitivy crypto.subtle, jak je používáno v produkci, takže interní enkodér je byte-kompatibilní.
  4. Probíhající monitor akceptace po bundlu potvrzuje, že každý anchor / fallback DataItem skutečně dosahuje akceptace Arweave, s alarmem + vypínačem — protože deep hash slouží také jako fallback fronta při nedostupnosti Arweave, takže tichá regrese by naplnila tuto frontu položkami odmítnutými sítí během přesného výpadku, který má pokrýt.

16.9 Důkazy začlenění a konzistence

Oba jsou RFC 9162, SHA3-256, podávány jako kanonický CBOR.

InclusionProof — GET /api/v1/qub/:tx_id/proof: ver:u8, leaf:bstr (přesný leaf CBOR — ověřovatel si sám přepočítává leaf_hash a nikdy nespoléhá na dodaný hash), index:u64, size:u64, audit:[bstr[32]], root:bstr[32], anchor:{ txid:bstr[32], batch:u64, sth:bstr[32], log_id:bstr[32], block_height?:u64, anchored_at?:i64 }.

ConsistencyProof — GET /api/v1/log/consistency?first=<size_a>&second=<size_b>: ver:u8, first_size:u64, second_size:u64, first_root:bstr[32], second_root:bstr[32], nodes:[bstr[32]], first_anchor, second_anchor. Jediný jednoznačný seznam klíčů, připnutý testovacím vektorem.

Samostatná verifikace (bez qub serveru, rozšiřuje §11):

1.  Parse .qub bundle → SealedQub; recompute qub_id (§4.1).
2.  Read leaf.kind.
3a. kind=0x01 (attested):
      assert leaf.ref == qub_id
      assert leaf.body_hash   == SHA3-256(body)
      assert leaf.drand_round == unlock_round(unlock_at)
3b. kind=0x02 (asserted):
      assert leaf.ref == SHA3-256(qub_id || blind)   // holder supplies blind
      OR treat ref as opaque and bind via leaf.chash == SHA3-256(stored_bytes)
4.  Recompute leaf_hash = SHA3-256(0x00 || leaf); fold `audit` per RFC 6962
    using index/size; require derived root == proof.root.
5.  Fetch anchor.txid from any gateway; verify the tx data → tx_id binding
    (do not trust a gateway /raw/ response); REQUIRE anchor_tx.owner ==
    LogProfile.anchor_owner.
6.  Parse AnchorBundle; require committed root == proof.root and size ==
    proof.size; read the Arweave block time T.
7.  Emit the claim scoped by leaf.kind (§16.11).

Úložiště podávající důkazy MUSÍ být koordinovaně klíčované (vyřešeno — §16.15 Q7, blokovací předpoklad). Generování důkazů na studeném listu je neutrální vůči správnosti pouze pokud auditní materiál R2 je persistentní Merkle-node úložiště klíčované podle absolutních souřadnic stromu (level, index) — nikoli podle batch uzlových delta. U úložiště s koordinačními klíči je každá (leaf i, size N) auditní cesta sada O(log N) přímých R2 GET bez žádného přepočítání přes hranice dávky; u batch-keyed úložiště není, což je mezera mezi úložištěm a rozložením, kterou toto rozlišení uzavírá. Těla listů jsou rovněž obsahově adresovatelná seq. Testovací vektor W5 MUSÍ prokázat studený list z éry geneze proti mnohem pozdějšímu kořenu pouze s použitím R2 + Arweave s vymazaným LogDO úložiště, takže tvrzení o bezpečnosti rekultivace v §16.13 je podloženo, nikoli prosazováno. O(log N) sekvenční R2 GET patří pouze na asynchronní důkazní koncový bod — nikdy na těsnící horkou cestu (§16.10) ani na cronu na každý tick.

16.10 R2 – První objednávka ACK

Implementovaná POST /api/v1/upload sekvence je:

  1. Brány v přední polovině (autentifikace, validace, klíč idempotence shard) — beze změny.
  2. Vytvořit, označit a podepsat přesně jednotlivou transakci Arweave. To odvozuje tx_id lokálně, i když vytvoření transakce může získat metadata odměn/kotvení z brány. Selhání přípravy stále způsobí neúspěch požadavku před potvrzením.
  3. Synchronně zapíšte vybraný artefakt v qub-cache/<tx_id> a zachováte stabilní záznamy o tvorbě/výstupní schránce. Jedná se o úroveň odolnosti a opakovaného pokusu; selhání před osídlením se vrátí 503.
  4. Když je LOG_DO konfigurován, synchronně se pokusit LogDO.append(leaf). Jeden zapisovatel přiřadí seq, rozšíří řetězec vstupů a aktualizuje hranici. Připojený RPC dělá pouze toto; batch close běží mimo cestu alarmu. Selhání append transport/aplikace je aktuálně fail-soft: odpověď může uspět i bez log_seq, receipt nebo anchor_status. Navzdory komentáři k implementaci dnes není automatické pozdější sladění logů zapojeno.
  5. Vraťte potvrzení. Zahrňte { log_seq, anchor_status: "pending", receipt } pouze tehdy, když připojený řetězec vrátil kompletní úspěšnou n-tici. receipt.sig_b64url je prázdná, když podpisující potvrzení není dostupná; klienti NESMÍ tuto hodnotu označit za podepsanou nebo nevyvratitelnou. Absence n-tice znamená pouze trvalou publikaci, nikoli přijetí v logu transparentnosti.
  6. Použít jednu odloženou úlohu k zaznamenání přesné podepsané transakce. Úspěch odstraní odchozí schránku; neúspěch ji nechá pro omezený drain cron a nesmí měnit již potvrzený tx_id. Provizorní metadata a další nejlepší snahy jsou také odloženy.

Hranice latence. Cesta požadavku zahrnuje práci s autoritou/kvótou v první polovině, přípravu/podepisování transakcí, trvalé zápisy do R2 a (při konfiguraci) pokus o LogDO. < 300 ms se v přezkumu návrhu objevuje jako provozní cíl, nikoli jako záruka protokolu; aktuální krok přípravy transakce může provést požadavek na metadata brány. Alarmy latence a spouštěcí brány jsou operační kontroly, nikoli důkaz dostupný ověřovateli.

16.11 Model důvěry — přesný nárok, omezený podle typu listu

Pro kind=0x01 (ověřeno): *"Tento obsah — odpovídající tělesu body_hash, identifikovaný qub_id — byl zavěšen do logu qub, který je pouze připojen, na pozici seq a existoval nejpozději do bloku Arweave T; byl kryptograficky nečitelný až do drand kola R = unlock_round(unlock_at)." * Toto je celý {tlock round binding + Merkle inclusion + anchored root} trojitý záznam.

Pro kind=0x02 (afirmované, výchozí): * "Neprůhledný šifrovaný text s obsahovou adresou chash, který tvrdí qub_id a unlock_at, byl zavázán do logu pouze pro připojení na pozici seq a existoval nejpozději do bloku Arweave T." * Kulaté a tělové nohy jsou dodávány existujícím ověřováním .qub-bundle §11 (qub_core::unlock), ne logem; to, co log přidává k holé transakci na kub, je pořadí s evidentním zásahem, nedůvěryhodný horní limit závazku a odolnost vůči nejednoznačnosti.

Oba nároky podle §11 vylučují: autorství bez sig_alg ≥ 0x01, úmysl a načasování s pod-kotvou granularity. Ani jedno z nich nedovolí, aby se jakýkoli nárok opíral o received_at.

Strop nároku (závazné omezení spuštění — vyřešeno §16.15 Q1). Pro tvrdící (kind=0x02) list je výše scoped nárok strop toho, co může jakýkoli produkt, marketing, podmínky nebo povrch pro vykreslování důkazů tvrdit. Žádný povrch nesmí uvádět ani naznačovat, že log dokazuje obsah nebo odemkací kolo nahrávání bez bajtu — log dokazuje pořadí + nedůvěryhodnou horní hranici závazku neprůhledného šifrovaného textu. Důkaz obsahu a kola pocházejí výhradně z existujícího ověření .qub balíčku §11, které je nezávislé na logu. Publikace bez úspěšného připojování/přijetí nemá žádný log nárok.

16.12 Verze a koordinace W3

Neexistuje žádný SealedQub wire bump a tedy žádný bump verze protokolu (§12.2): log je sidecar, který se zavazuje k existujícím polím a bajtům, takže nevstupuje do historie verzí protokolu §12.3. Volitelný drand_chain_version W3 zůstává nedotčen a zůstává jediným volitelným SealedQub polem. Log místo toho zavádí své vlastní nezávislé prostory pro verze — LOG_VERSION_1, ANCHOR_FORMAT_1 InclusionProof.ver — které zrcadlí nezávislost na verzi obalu §12.5 (obal nese bajt nezávislý na verzi protokolu a logové verze dodržují stejné oddělení).

Doručení důkazů se načítá ve výchozím nastavení, s volitelným doprovodem na Log-Id. Důkaz nemůže existovat v čase pečeti (kotva ještě nebyla napsána), takže svazek .qub časem pečetě zůstává bez důkazů. Ověřovatel W7 načítá GET …/proof jednou, nebo v plně offline režimu rekonstruuje důkaz z veřejného AnchorBundle pomocí dotazu Arweave na . .qub svazek (W7) vyhrazuje volitelného člena inclusion_proof — chybějícího při pečeti, vyplněného reexportem po kotvě pro studenou archivaci — podle stejného vzoru "volitelné, výchozím nastavením vynecháno, aditivní" jako drand_chain_version ve W3.

16.13 Udržení

Retention okna pro otevřený ocas, substrát pro R2 pro důkazy, počítadla kotvicích jističů a záložní frontu bundleru jsou specifikována v docs/DATA-RETENTION.md. Princip: horké úložiště logu pro každý záznam (LogDO) je zpětně dostupné po ukotvení; jeho auditní materiály — souřadnicově klíčované (level, index) Merkle-node store + seq-adresované listové těla (§16.9) + Arweave kotvy — jsou trvalé. Získání studeného listu od DO nikdy nezneplatní vydaný důkaz, protože důkaz se vyřeší proti tomuto trvalému R2 uzlovému úložišti a Arweave kotvě, nikoli proti DO (a §16.9 vymazaný DO testovací vektor to dokazuje).

16.14 Testovací vektory

W5 přináší cross-language fixture tlog_v1.json (§16.8) plus upravené vektory: kind=0x01 a kind=0x02 listový → leaf_hash; 5-listový kumulativní kořen; jeden důkaz inkluze; jeden důkaz konzistence; jeden AnchorBundle; a jeden DataItem id. Tyto vektory existují vedle vektorů vnějšího obalu §14.5 a jsou prováděny jak implementacemi Rust (qub-core), tak TypeScript (Worker).

16.15 Rozhodnutí o přezkumu (W5 — rozhodnuto)

Externí přezkum W5 (adversariální návrh + schválení majitelem) je dokončen. Každé rozhodnutí níže je vyřešeno a odráženo v textu §16 výše; závazná omezení při spuštění jsou na konci znovu uvedena. Implementace může pokračovat podle nich.

  1. Výchozí cesta (kind=0x02) listová poctivost — VYŘEŠENO. Distribuujte dvoulistové rozdělení podle specifikace: kind=0x02 necommituje ani body_hash, ani drand_round. Na bajtově-slepé cestě není *_body_hash pole (bylo by to nejčitelnější falešné "ověřené" signály pro integrátory a je to výhoda, kterou §11 již poskytuje ze svazku). Nevyžadují ne server-seal pro logem attestované quby (které by donutilo otevřený text projít Workerem a zničilo krypto-shreddingový příkop). Jakýkoli samopopisující se zkrat patří do .qub svazku / důkazní obálky jako pole přepočítané ověřovatelem, nikdy jako listové pole. Strop potvrzení nároku vlastníkem: §16.11.

  2. Odpovědnost za nejasnost / opomenutí — NÁVRH VYŘEŠEN, PROVISIONING NEDOKONČENÝ. Návrh vyžaduje, aby klíč k přijetí pečetě byl připnut LogProfile a podepsán anchor_owner, plus metodiku monitorování, předchozí chain walk a dvojité samopublikované hlavy. Zkompilovaný profil a nasazení háčky jsou stále zástupné/volitelné, jak je popsáno v §16.6, takže silnější detekovatelné + přijaté tvrzení není aktuální, dokud se tato brána neuzavře. Nikdy nesmí být propagováno jako nezávisle ověřené. Skutečný svědek třetí strany je odložen na zvýšení správy podle §15.

  3. Připnutý kořen důvěry vlastníka kotvy + rotace — VYŘEŠENO. Přijměte pin LogProfile (§16.6); ověřovatel kontroluje anchor_tx.owner == anchor_owner a ověřuje data o výkazu → tx_id lokálně závazná. Správa rotací je §15 rozšíření pro build (přidán spouštěč §15.3), nikoli opětovné použití; plánované rotace křížově znaménkové, kompromitované rotace se vracejí k nárůstu §15 s omezením kontroly vidlice.

  4. Oslepování listů soukromých qubů — ROZHODNUTO. Pokračujte oslepování pro soukromé quby (ref = SHA3-256(qub_id ‖ log_blind_secret)), surové qub_id pro veřejné quby (již §16.2.1) chash jako samostatnou vazbu. log_blind_secret je korelace/Sybil-grade tajemství, pouze pro rotaci dopředu (§16.2.1).

  5. received_at — ROZHODNUTO. Ponechejte to v listu, zavázané, ale výslovně nedůkazní; nikdy nebylo předloženo jako důkaz nebo sporné potvrzení na žádném povrchu. Jakákoli kontrola sanitity monitoru se srovnává s časovou T Arweave bloku, nikoli s operátorem řízeným anchored_at (§16.6).

  6. Tiered prokazatelné časování — ŘEŠENÍ NÁVRHU, NIKOLI AKTUÁLNÍ SMĚROVÁNÍ. Recenzovaný návrh přiřazuje časové označení anchor-block pro batch tier a důkaz pro přesné hodiny pro placený T3, bez číselného SLA pro první variantu. Současné trasy tento obchodní rozdíl nedefinují: plánují individuální transakci pro každou přijatou publikaci a pokrytí logem zůstává podmíněné, jak je uvedeno v §16.1/§16.10. Produktová kopie musí popisovat implementaci, nikoli toto budoucí rozdělení na tier.

  7. Kumulativní strom na pracovnících — VYŘEŠENO. Jeden kumulativní strom RFC 9162 + hranicemi cachovaný LogDO s jedním zapisovatelem (komfortní rezerva vůči stropu ~1k zápisů/s; shardování Merkle-kořenů odložit až k jeho dosažení). Ukládání uzlů (level, index) s klíčem koordinát + testovací vektor wipe-DO cold-leaf je implementováno (§16.9). < 300 ms zůstává návrhovým/provozním cílem, ne protokolovým závazkem (§16.10).

  8. ANS-104 podpisový schéma + deep-hash — VYŘEŠENO. RSA-PSS (typ sig 1, znovupoužití dedikovaného JWK anchor-wallet); Ed25519 odloženo do cesty §15 PQ. Ručně vytvořený SHA-384 deep hash je podmíněn cross-impl fixture obousměrně, kontrolou interoperabilního referenčního bundleru pouze pro statické použití, sdíleným crypto.subtle round-trip a post-bundle monitorováním akceptace na Arweave (§16.8).

Vazební spouštěcí omezení (zohlednit při implementaci + přezkoumání produktem/právním oddělením):

17. Přenosný ověřovací balíček (.qub)

Stav. Tato část je implementována (W7 / UP-C2): qub_core::export balíček vytváří a parsuje a tools/qub-verify je veřejné samostatné CLI, které jej ověřuje offline. §11 a §16.9 již označují „balíček .qub“ za jednotku používanou samostatným ověřovatelem; tato část specifikuje jeho bajty a postup ověření. Je striktně aditivní — balíček sdružuje stávající vstupy dle §11 a nemění žádný formát na drátě uložený v řetězci.

17.1 Účel

§11 stanovuje, že kterákoli třetí strana může ověřit kryptografický artefakt qubu bez spolupráce qub. Balíček .qub činí toto ověření přenosným a offline: sdružuje zapečetěný CBOR a podpis kola drand, který jej odemyká, do jediného soběstačného artefaktu, takže příjemce může ověřit integritu obsahu, vazbu na kolo a případné podpisy autorství zcela bez síťového volání (bez načítání z úložiště, živého požadavku na drand nebo API qub). Samotný balíček neprokazuje, kdy byl jeho šifrovaný text vytvořen; samostatné tvrzení o čase existence dodává nezávisle ověřená transakce úložiště nebo ukotvený důkaz logu (§11, §17.5).

17.2 Formát balíčku

QubBundle je ručně psaný kanonický CBOR podle profilu §3.1 (definitivní délky, bez značek, bez plovoucích hodnot, celá čísla v nejkratším tvaru, text NFC, volitelná pole při nepřítomnosti vynechána, klíče řazené vzestupně podle délky zakódovaných bajtů a poté bajtově). Tři klíče o 15 znacích se řadí d < i < s. Surový soubor .qub tvoří přesně tyto bajty; pro přenos v URL nebo kopírováním se stejné bajty kódují jako base64url (bez výplně).

Klíč Délka kódování Typ Přítomnost Význam
version 8 u8 povinný Verze formátu balíčku (0x01).
sealed_at 10 i64 volitelný Tvůrcem tvrzený čas zapečetění (sekundy Unixu); sebepopisný, bez důkazní hodnoty.
drand_round 12 u64 povinný Kolo, ke kterému je qub uzamčen. Projekce vloženého zapečetěného qubu.
arweave_tx_id 14 tstr povinný ID transakce, pod kterým byly zapečetěné bajty uloženy (ukazatel provenience).
drand_chain_id 15 tstr povinný Řetězec drand (hex). Projekce vloženého zapečetěného qubu.
drand_signature 16 bstr povinný Podpis majáku drand pro drand_round — hodnota odemykající šifrovaný text.
inclusion_proof 16 bstr volitelný Merkleův důkaz o zařazení do logu transparentnosti dle §16 (§17.5).
sealed_qub_cbor 16 bstr povinný Vnitřní bajty SealedQubCbor (po rozbalení dle §13), tedy vstup ověření dle §11.

drand_round a drand_chain_id jsou pomocné projekce sealed_qub_cbor, které nástrojům umožňují číst je bez parsování vnitřního CBOR. Při konstrukci se odvozují a při dekódování se znovu kontrolují proti naparsovanému zapečetěnému qubu; balíček, jehož pole nejvyšší úrovně nesouhlasí s datovou úlohou, je odmítnut. Disciplína kodéru odpovídá zbytku formátu na drátě: prázdný drand_signature nebo arweave_tx_id se odmítá a každé pole proměnné délky je omezeno.

17.3 Co prokazuje vložený podpis drand

Balíček nese podpis drand, takže jej ověřovatel nemusí načítat. Dešifrování časovým zámkem (tlock nad řetězcem drand, §8) může uspět pouze s pravým podpisem majáku pro svázané kolo — hodnotou, kterou řetězec zveřejní teprve po uplynutí daného kola a která je platným podpisem BLS pod veřejným klíčem řetězce. Zfalšovaný nebo nesprávný podpis selže při ověření BLS nebo dešifrování IBE/AEAD. Balíček, který lze dešifrovat, proto prokazuje: šifrovaný text je svázán s kolem R a kolo R uplynulo. Ověřovatel připíná řetězec (DrandTimelockProvider::quicknet()) a uplatňuje kontrolu vazby na kolo dle §11, takže balíček nemůže tvrdit kolo, ke kterému jeho šifrovaný text není svázán.

Jde o důkaz podmínky uvolnění, nikoli časovou značku vytvoření. Po uplynutí kola R může kdokoli vytvořit nový šifrovaný text pro R a přibalit jeho již veřejný podpis. Samotný balíček proto NESMÍ být popisován jako důkaz, že šifrovaný text nebo obsah existoval před R, před unlock_at nebo před jakoukoli událostí.

17.4 Postup ověření offline

qub-verify <file.qub> provádí standardní postup dle §11 zcela z balíčku a řídí qub_core::unlock::unlock s připnutým DrandTimelockProvider:

1. Parse the .qub bytes → QubBundle (canonical-CBOR guard; bound every field;
   re-check drand_round / drand_chain_id against the embedded sealed qub).
2. BLS-verify bundle.drand_signature for the pinned chain and round, then
   tlock_decrypt(sealed.tlock_ciphertext, bundle.drand_signature) → QubEnvelope.
3. Verify SHA3-256(body) == body_hash               (§11 step 8).
4. Verify QubEnvelope.qub_id   == SealedQub.qub_id   (§11 step 9).
5. Verify QubEnvelope.unlock_at == SealedQub.unlock_at (§11 step 10).
6. Verify ciphertext round == unlock_round(unlock_at) and the chain binding.
7. If sig_alg != 0x00: verify author_signature (and any cosigner; §9.4).
8. Report integrity, round-elapsed/round-binding, authorship, and cosigner
   verdicts separately, plus the recovered body. Do not report a commitment
   timestamp unless step 9 succeeds.
9. Optional existence-time leg: verify an included §16 proof through its pinned
   anchor, or independently verify the referenced storage transaction. Report
   its block time as an upper bound on ciphertext existence.

CLI končí kódem 0 (ověřeno), 1 (ověření selhalo — stále uzamčeno, neshoda hashe těla, porušená vazba na kolo/řetězec nebo neplatný podpis) nebo 2 (chyba použití / poškozený balíček). Výstup --json nese stejné verdikty pro automatizaci. Protože je balíček soběstačný, potřebuje třetí strana pouze crate ověřovatele (qub-core) a CLI (qub-verify); obojí je veřejné a znovu používá stávající ověřovací cestu protokolu, nikoli zvláštní kryptografii.

17.5 Vztah k logu transparentnosti

inclusion_proof je volitelný slot pro Merkleův důkaz o zařazení dle §16. Ověření samotného balíčku (§17.4) je úplné pro integritu, vazbu na kolo / uplynutí kola a volitelné autorství, záměrně však nenese nezávisle časově označené tvrzení o existenci. Naplněný a plně proti kotvě ověřený inclusion_proof přidává závazek specifický pro druh listu a horní mez času z §16.11, aniž by se měnila verze formátu balíčku. Nepřítomný důkaz znamená pouze „důkaz není přiložen“ — nikoli „neplatné“ ani nutně „neukotvené“.

V referenční implementaci je slot nyní typovaný: qub_core::export::QubBundle::inclusion_proof_typed() vrací Option<InclusionProof> nesoucí úplnou strukturu dle §16.9 (list, auditní cestu, ukotvený kořen a AnchorRef) prostřednictvím téhož neprůhledného pole CBOR — bez zvýšení verze formátu balíčku. Samostatné CLI qub-verify jej spotřebovává svou větví --anchor a — dokud není zřízena peněženka kotvy (§16, Stav) — hlásí důkaz s naplněným, avšak zástupným vlastníkem pouze jako inclusion-only, nikoli plně anchored-verified.