qub protokollspecifikáció

A qub egy protokoll kriptográfiai időbeli kötelezettségvállalásokhoz: olyan rendszer, amely szavakat zár le egy jövőbeli dátumhoz, és amikor ez a dátum elérkezik, bizonyítja, hogy pontosan mit mondtak és mikor.

Három alapelem teszi mindezt lehetővé. A drand egy decentralizált véletlenszerűségi jeladó — a felfedési dátumot a fizika érvényesíti, nem pedig bármely fél jóindulata. Az állandó nyilvános tárhely egy hamisíthatatlan nyilvános tár — egyetlen fél sem tudja szerkeszteni vagy törölni a qubot, miután lezárták. Az ML-DSA-65 egy posztkvantum digitális aláírás — minden qub egy kulcspárhoz kötődik, amelynek titkos kulcsa soha nem hagyja el a szerző eszközét.

Együttesen ezek az alapelemek olyan kijelentést hoznak létre, amely időzárolt, hamisítás-érzékeny és tulajdonítható — egy nyugta, amelynek értéke azzal nő, ahogy a világ képessége a múlt meghamisítására fejlődik.

A dokumentum hátralévő része az interoperábilis megvalósításokhoz szükséges normatív specifikáció.


qub protokoll-specifikáció

Mező Érték
Verzió 1.0 (protokollverzió 0x01, külső burkoló verziója 0x01)
Dátum 2026-05-01
Állapot Tervezet
Átnézve eddig 2026-05-01

Ez a dokumentum a qub időzített kötelezettségvállalási rendszer normatív protokoll-specifikációja. Meghatározza az interoperábilis megvalósításokhoz szükséges adatstruktúrákat, szerializációs szabályokat, levezetési képleteket és ellenőrzési eljárásokat.

Hatókör: a protokollréteg szándékosan nyelvfüggetlen — a qub törzse átlátszatlan egyszerű szöveg / markdown / paktum-bájtok, és a területi beállításnak megfelelő megjelenítés a megjelenítő felelőssége (qub.social webalkalmazás, <qub-embed> iframe, MCP-kliensek stb.).


1. Jelölés és konvenciók

Jelölés Jelentés
u8, u64, i64 Megadott bitszélességű előjel nélküli/előjeles egészek
[u8; N] N bájt fix hosszúságú bájttömb
Vec<u8> Változó hosszúságú bájttömb
Option<T> T típusú érték, vagy hiányzó
String UTF-8 szöveges karakterlánc, NFC-normalizált
`
SHA3-256(x) Az x bájtsorozat NIST SHA3-256 kivonata (FIPS 202)
ceil(x) Felső egészrész függvény: a legkisebb egész ≥ x
CBOR Concise Binary Object Representation (RFC 8949)
big-endian A legjelentősebb bájt elöl

A bemeneti képek (preimage) konstrukcióiban minden egész szám big-endian fix szélességű bájttömbként van kódolva (i64 → 8 bájt, u8 → 1 bájt), hacsak másként nincs megadva.

Minden időbélyeg Unix másodperc UTC-ben.


2. Adatstruktúrák

2.1 ComposeQub (alkotó memóriában lévő állapota)

Nem szerializálódik CBOR-ba. Nem íródik állandó tárhelyre. Az alkotó alkalmazásához helyi.

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,              // 0x01 = public (only value in MVP)
    content_type:   u8,              // 0x01 = text (only value in MVP)
    plaintext:      Vec<u8>,         // UTF-8 qub body
    sender_label:   Option<String>,  // Decorative display name; not authenticated
    status:         DraftStatus,     // Composing | Sealed | Uploaded | Failed
}

2.2 QubEnvelope (visszafejtett hasznos adat)

Kanonikus CBOR-ral szerializálva (§3). A SealedQub belsejében titkosítva. Ez a struktúra bizonyítja a tartalom integritását a visszafejtés után.

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>,     // V1.1 — when reality renders judgment (verdict-uplift-plan §3.1)
    sender_label:        Option<String>,  // Decorative; not authenticated in MVP
    reply_to:            Option<[u8; 32]>,// Parent qub_id for reply chains; not in qub_id preimage; not signed (see §9.3)
    body:                Vec<u8>,         // Content payload (UTF-8 for text, CBOR for pact)
    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
}

Alapeset (aláíratlan szöveges qub): version = 0x01, content_type = 0x01, sig_alg = 0x00, minden Option mező hiányzik.

Egyéb v1 konfigurációk: content_type = 0x03 (paktum-törzs, lásd §6.1); sig_alg = 0x01 (ML-DSA-65) jelen lévő author_signature és author_pubkey mezőkkel (lásd §9.3); cosigner_pubkey és cosigner_signature együttesen jelen van a társaláírt paktumoknál (lásd §9.7); reply_to a szülő qub qub_id értékére állítva a válaszlánc-qubokhoz (az aláírás-hatókör következményeiért lásd §9.3).

2.3 SealedQub (kanonikus wire-formátum)

Kanonikus CBOR-ral szerializálva (§3). Állandó tárhelyre írva. Ez a láncon lévő műtermék.

SealedQub {
    version:           u8,              // Protocol major version (0x01 for v1)
    qub_id:            [u8; 32],        // Same as QubEnvelope.qub_id
    visibility:        u8,              // 0x01 = public; v1 viewers reject other values
    unlock_at:         i64,             // Unix seconds UTC
    outcome_at:        Option<i64>,     // V1.1 — 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
    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 control characters.
}

2.4 RevealedQub (megjelenítő alkalmazás állapota)

Nem szerializálódik CBOR-ba. A megjelenítő alkalmazáshoz helyi. Sikeres visszafejtés és ellenőrzés után épül fel.

RevealedQub {
    qub_id:              [u8; 32],
    arweave_tx_id:       String,
    visibility:          u8,
    content_type:        u8,
    created_at:          i64,
    unlock_at:           i64,
    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. Kanonikus CBOR-profil

Minden SealedQub és QubEnvelope szerializációnak meg KELL felelnie ennek a profilnak. Két megvalósításnak ugyanazon logikai struktúra esetén azonos bájtokat KELL előállítania.

3.1 Kódolási szabályok

Szabály Specifikáció
Szabvány RFC 8949 §4.2.1 (Core Deterministic Encoding Requirements)
Kulcsrendezés a térképben Először a kódolt bájthossz szerint rendezve (rövidebb a hosszabb előtt), majd lexikografikusan (bájtonként az azonos hosszúságú kódolásoknál)
Egész szám kódolása Legrövidebb forma: 0–23 a kezdeti bájtban; 24–255 2 bájtban; 256–65535 3 bájtban; stb.
Hosszkódolás Csak határozott hosszúságok. Nincsenek határozatlan hosszúságú tömbök, térképek, bájtsorozatok vagy szöveges karakterláncok (a kiegészítő info = 31 tilos).
Címkék Nincsenek CBOR-címkék (a 6-os főtípus tilos).
Lebegőpontos Nincsenek lebegőpontos számok (a 7-es főtípus 0xF9–0xFB értékei tiltottak).
Szöveges karakterláncok UTF-8 kódolt, NFC-normalizált (Unicode Normalization Form C).
Bájtsorozatok Nyers bájtok. Nincs base64 kódolás a CBOR-rétegen.
Duplikált kulcsok Hibával elutasítva. Az elemzők NEM fogadhatják el csendben a duplikált térképkulcsokat.
Egyszerű értékek Csak a true (0xF5), false (0xF4) és null (0xF6) megengedett.
Opcionális mezők A hiányzó opcionális mezők teljesen kihagyásra kerülnek a CBOR-térképből (nem null-ként kódolva). A jelen lévő opcionális mezők rendezett kulcssorrendben szerepelnek.

3.2 Ellenőrzött kanonikus kulcssorrendek

Ezek a kulcssorrendek normatívak. A megvalósításoknak pontosan ebben a sorrendben KELL kibocsátaniuk a kulcsokat. A hibakeresési ellenőrzéseknek (debug assertions) ELLENŐRIZNIÜK KELLENE a sorrendet a nem kiadási buildekben.

QubEnvelope (0x01-es verzió, aláíratlan, minden opcionális mező hiányzik):

"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 (V1.1 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)

QubEnvelope kulcssorrend levezetése: minden kulcs egy CBOR szöveges karakterlánc. Kódolt hossz = 1 bájt fejléc + a karakterlánc hossza (24 bájtnál rövidebb karakterláncoknál). Először a teljes kódolt hossz szerint rendezve, majd lexikografikusan az azonos hosszúságú kulcsoknál.

SealedQub (0x01-es verzió, nyilvános, címzett nélkül):

"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 (V1.1 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)

PactTerms (paktum-törzs, 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 (a terms tömb egy sora):

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

PartyIdentifier (party_a / party_b térkép):

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

3.3 Bájtkódolási referencia

Típus CBOR-kódolás Példa
SHA3-256 kivonat (32 bájt) 0x58 0x20 + 32 bájt body_hash, qub_id
Időbélyegek (i64) 0-s főtípus (pozitív) vagy 1-es (negatív), legrövidebb kódolás Unix másodperc
Verzió (u8, 1-es érték) 0x01 (egyetlen bájt)
Tartalomtípus (u8, 1-es érték) 0x01 (egyetlen bájt)
sig_alg (u8, 0-s érték) 0x00 (egyetlen bájt)
ML-DSA-65 aláírás (3 309 bájt) 0x59 0x0C 0xED + 3 309 bájt author_signature, cosigner_signature
ML-DSA-65 nyilvános kulcs (1 952 bájt) 0x59 0x07 0xA0 + 1 952 bájt author_pubkey, cosigner_pubkey

4. Normatív levezetések

4.1 qub_id

A qub_id egyedileg azonosít egy qubot, és összeköti a QubEnvelope-ot a SealedQub-bal. Determinisztikusan vezetődik le a boríték tartalmából.

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

Tartománymegkülönböztető kódolása: a "QUB_ID_V2" karakterlánc 9 ASCII-bájt. Egyetlen 0x00 kitöltőbájt fűződik hozzá, hogy elérje a 10 bájtot az igazításhoz. A megvalósításoknak pontosan ezt a 10 bájtot KELL használniuk: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].

outcome_at kódolása: a V1.1 92 bájtról 100 bájtra bővítette a bemeneti képet, hogy az opcionális outcome_at mezőt a kötésbe fonja. A hiányzó outcome_at 8 nulla bájtként kódolódik; a protokoll-validátorok mindenhol elutasítják az outcome_at <= 0 értékeket, így ez az őrérték nem ütközhet egy érvényes értékkel. Lásd a §3.2-t (wire-formátum) és a fán belüli tasks/verdict-uplift-plan.md fájlt a mezőt motiváló ítélet-mechanikáért.

drand_round kódolása: A V1.2 a preimage-et 100 bájtról 108 bájtra bővítette, hogy a drand_round mezőt (a cél drand-kör, §4.3) is bekösse a kötésbe, és a tartományelválasztót QUB_ID_V2-re emelte. Ez beköti az időzáras kört a qub identitásába: egy átjáró nem kötheti át a titkosított szöveget egy másik (pl. már elmúlt) körhöz, mint amit a megjelenített unlock_at sugall. A feloldási eljárás (§8) ezen felül igazolja, hogy a tlock titkosított szöveg szakaszába (stanza) sütött kör megegyezik az unlock_round(unlock_at) értékével, így a megjelenített feloldási idő bizonyíthatóan az a kör, amely a visszafejtést kapuzza.

Tulajdonságok:

4.2 body_hash

body_hash = SHA3-256(body)

Ahol a body a nyers Vec<u8> tartalom-hasznosadat. Szöveges qubok esetén ez az UTF-8 kódolt qub-törzs.

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

Ahol a title az opcionális egyszerű szöveges cím, amely a megjelenítő visszaszámlálásán jelenik meg a felfedés előtt (lásd §3.2). Az NFC-normalizálás a kivonatképzés idején fut, így a kivonat stabil a vizuálisan egyenértékű kódpont-sorozatok között. A csupa nulla őrérték a hiányzó esetre van fenntartva; az üres karakterlánc a kanonikus CBOR-határon a „hiányzó” nem kanonikus kódolásaként kerül elutasításra (a kanonikus kódolás teljesen kihagyja a mezőt).

4.3 Feloldási kör leképezése

drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds)
Paraméter Forrás Példa
unlock_at Felhasználó által választott Unix másodperc UTC 1735689600 (2025-01-01 00:00:00 UTC)
chain_genesis_time drand-lánc info (genesis_time) 1595431050
chain_period_seconds drand-lánc info (period) 30

A ceil() művelet az első olyan drand-kört választja ki, amelynek felfedési ideje ≥ unlock_at. Ez biztosítja, hogy a qub ne váljon visszafejthetővé a választott feloldási idő előtt.

Szélső eset: ha az (unlock_at - chain_genesis_time) pontosan osztható a chain_period_seconds értékkel, az eredmény pontosan az a kör — a qub pontosan az adott kör felfedési idejében oldódik fel.

Validáció: az unlock_at értéknek a lezárás idején a jövőben KELL lennie. Az unlock_at NEM lehet több mint 10 évvel a created_at után (a hosszú távú drand-függőség kockázatának korlátozása érdekében; a felhasználói felületnek FIGYELMEZTETNIE KELLENE a 2 éven túli feloldási dátumoknál).


5. Wire-formátum newtype-ok

A wire-formátum newtype-ok fordítási idejű biztonságot nyújtanak a CBOR-bájtok JSON-nal, nyers egyszerű szöveggel vagy más bájtkódolásokkal való összetévesztése ellen.

Típus Tartalmazza Előállítja Felhasználja
SealedQubCbor A SealedQub kanonikus CBOR-ja serialize_sealed_qub() Állandó tárhelyre feltöltés, megjelenítő lekérés
QubEnvelopeCbor A QubEnvelope kanonikus CBOR-ja serialize_qub_envelope() tlock-titkosítás bemenete, tlock-visszafejtés kimenete

5.1 Konstrukciós szabályok

// 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 Validáció a konstrukciónál

A from_encoded()-nak ELLENŐRIZNIE KELLENE, hogy a bemenet érvényes CBOR-térképfejléccel kezdődik. A teljes szerkezeti validáció az elemzés idején történik, nem a konstrukció idején, hogy elkerülje a dupla elemzést.


6. Tartalomtípus-nyilvántartás

Érték Típus Max. törzsméret Megjegyzések
0x00 Fenntartott (érvénytelen) NEM HASZNÁLHATÓ
0x01 Egyszerű szöveg (UTF-8, korlátozott Markdown) 50 KB fizetős / 10 KB ingyenes A megjelenítési szabályokért lásd §10. Az ingyenes / fizetős felosztást a feltöltési szolgáltatás érvényesíti; a protokollréteg kemény felső határa 50 KB.
0x02 Fenntartott (jövőbeli) Jövőbeli tartalomtípus számára lefoglalva; v1-ben nem érvényes. A megjelenítőknek el KELL utasítaniuk az alábbi szabály szerint.
0x03 Paktum (kétoldalú megállapodás, CBOR-törzs) 100 KB A törzs kanonikus CBOR PactTerms (§6.1). Társaláírás a §9.7 szerint.

A megjelenítőknek el KELL utasítaniuk az ismeretlen tartalomtípusokat egyértelmű, felhasználó számára látható hibával. A megjelenítők NEM kísérelhetik meg az ismeretlen típusok szövegként való megjelenítését.

6.1 Paktum-törzs (content_type = 0x03)

A paktum-törzs egy PactTerms érték kanonikus CBOR-kódolása:

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), value: String (≤ 2,000) }   // NFC on both sides
PartyIdentifier{ label: String (≤ 100), contact: Option<String (≤ 320)> }

A három térkép kanonikus CBOR-kulcssorrendjeit a §3.2 adja meg. A teljes szerializált paktum-CBOR NEM haladhatja meg a 100 KB-ot (megegyezik a §6-tal).

Sémamegkülönböztető. A structured/v1 paktum terms tömbjének első sorának { key: "pact_schema", value: "structured/v1" }-nek KELL lennie. Az ezen jelölés nélküli sorok „egyedi” paktumok, és nem kapnak strukturált validációt vagy sématudatos megjelenítést.

Fagyasztott visszaigazolási helyek. A structured/v1 paktumok pontosan négy visszaigazolási sort hordoznak ezeken a kulcsokon:

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

Mindegyik value mezője nyolc fagyasztott angol karakterlánc egyike, amelyet a (role, kind) pár választ ki, ahol role ∈ { seller, buyer, provider, client } és kind ∈ { standard, capacity }. Maguk a karakterláncok normatív protokolladatok — mindkét fél ML-DSA-65 aláírása a body_hash-en keresztül a pontos bájtokra kötelez. NEM lokalizáltak; az aláírt törzs nyelvfüggetlen. Bármely szövegezési változtatás új sémaverziót igényel (structured/v2).

A nyolc karakterláncot, keresésüket (acknowledgement_for(role, kind)) és mindegyik indoklását a referencia-megvalósítás rögzíti. A megfelelő megvalósításoknak bájtazonos visszaigazolási értékeket KELL kibocsátaniuk; mind a négy szerepkombinációt lefedő arany-fixture SHA3-256 body-hash tesztek elkapnak bármely eltérést.

Megjelenítő-megjelenítési sorrend. A visszaigazolási karakterláncok olyan kifejezéseket tartalmaznak, mint a „described above” (fent leírt), amely feltételezi, hogy a leírás / hatókör sorok a visszaigazolások előtt jelennek meg. A megjelenítőknek a terms tömböt CBOR-sorrendben KELL megjeleníteniük; az átrendezés megtöri a prózai szemantikát.

Ellenérdekelt fél kapcsolata. Amikor a B fél contact mezője érvényes e-mail-cím, a qub feltöltési szolgáltatás automatikusan elküld egy áttekintési / társaláírási meghívó e-mailt a színrevitel idején, és az esetleges társaláírást ugyanazon cím ellenőrzéséhez köti (§9.7). Azok a paktumok, amelyek B felének kapcsolata hiányzik, továbbra is társaláírhatók, de csak sávon kívüli csatornán keresztül — a szolgáltatás visszautasítja azokat a társaláírási kéréseket, amelyek nem tudnak megfelelő 15 perces e-mail-ellenőrzési jelölőt előállítani.

6.2 Ítélet-törzs (content_type = 0x04)

Az ítélet-törzs egy VerdictBody érték kanonikus CBOR-kódolása:

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
}

Kanonikus CBOR-kulcssorrend:

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

A teljes szerializált ítélet-CBOR NEM haladhatja meg a 8 KB-ot (megegyezik a fenti regisztrációs sorral).

Eredmény-enumeráció. A wire-bájt szándéktól független; a négy kategória — Right / Partial / Wrong / Unfalsifiable — lefedi minden ítéletet hordozó szándék eredményterét. A szándékfüggő címkék („Megmondtam” / „Megtartottam” / „Terv szerint szállítottuk” / „Az események igazolták” a Right esetében stb.) a megjelenítő oldali renderelés kérdése, a szülő qub szándéka alapján — a wire nyelv- és szándék-független marad. Az 1..=4 tartományon kívüli értékeket dekódoláskor el KELL utasítani.

Szülő-kapcsolat. Egy ítélet-qub NEM hordozza a szülő-hivatkozást a törzsében. A szülő qub Arweave-tranzakcióazonosítója a Parent-Tx-Id tárolási címkeként kerül kibocsátásra feltöltéskor (§7 tárolási címkeréteg). Ez a törzset önálló, aláírt önértékelési nyilatkozatként tartja meg; az audit-lánc („mire vonatkozóan volt igaza?”) az Arweave-címke keresése révén jön létre.

Bizonyíték-URL biztonsága (normatív). Amikor az evidence_url jelen van, a validátoroknak (összeállítás-oldal, wire-oldal, Worker él) érvényesíteniük KELL:

  1. Csak HTTPS. A karakterláncnak a https:// bájtsorozattal KELL kezdődnie. Bármely más séma — http, ftp, javascript, data, file stb. — elutasításra kerül.
  2. Hosszkorlát. ≤ 2 048 bájt (gyakorlati böngésző-URL-korlát).
  3. NFC + ellenséges-kódpont-ellenőrzés. Ugyanaz a szabály, mint a title és reflection esetén — bidi-felülírás / nulla szélességű / tag-blokk / BOM / C0 / C1 kódpontok elutasításra kerülnek. A definíció megegyezik a Rust crate::handle::contains_hostile_text_codepoint és a TS workers/api/src/utils/unicode.ts::isHostileCodepoint függvényekkel (tartsd őket szinkronban).
  4. Nincs szóköz, nincsenek ASCII-vezérlők. A szóközök / DEL / 0x20 alatti bájtok az URL bármely pontján elutasításra kerülnek — ez zárja le a \n/\t injekciós vektort, amelyet a bidi-szabály nem fed le.
  5. Nem üres gazdagép-szegmens. A https:// és az első /, ? vagy # közötti tartománynak nem üresnek KELL lennie.

Nincs szerveroldali lekérés. A Worker NEM proxyzhatja, kérheti le vagy nézheti meg előnézetben az URL-t. A protokoll egy karakterláncot tárol; a megjelenítés megjelenítő-oldalon történik rel="nofollow noopener noreferrer" target="_blank" attribútumokkal és a linkszöveg mellett láthatóan megjelenített gazdagéppel.

Reflexió. Opcionális alkotó által írt reflexiós szöveg („mi változott, mit tanultál”). Ugyanaz az NFC + ellenséges-kódpont-validáció, mint a title esetében. Az üres / csak szóközből álló bemenet az építéskor hiányzóra esik vissza.

Sémaverzió. A v1 csak verdict_version = 0x01 értéket támogat. A jövőbeli sémaverziók megemelik ezt a bájtot, és új protokollverzióval együtt érkeznek a §12 szerint.


7. Lezárási protokoll

A teljes lezárási sorozat. Minden lépés normatív.

 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.
 3. Compute body_hash = SHA3-256(body).
 4. Set created_at = current Unix seconds UTC.
 5. Válaszd ki a drand láncot. Töltsd be a chain_genesis_time és chain_period_seconds
    értékeket, és számítsd ki: drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds).
    (Itt számoljuk ki, a qub_id előtt, mert a drand_round be van kötve a qub_id
    preimage-be — §4.1, V1.2.)
 6. Számítsd ki a qub_id-t (lásd §4.1), bekötve az 5. lépés drand_round értékét.
 7. Hozd létre a QubEnvelope-ot minden mezővel.
 8. Szerializáld a QubEnvelope-ot kanonikus CBOR-ral → B bájtok.
    Ellenőrzés: a szerializált kimenet megfelel a kanonikus profilnak (§3).
 9. Számítsd ki: C = tlock_encrypt(B, drand_round, drand_chain_public_key).
10. Hozd létre a SealedQub-ot tlock_ciphertext = C értékkel, és egyező qub_id, version,
    unlock_at, drand_chain_id, drand_round mezőkkel.
12. Serialise SealedQub using canonical CBOR → SealedQubCbor.
12a. Generate K = 32 random bytes (CSPRNG) and N = 12 random bytes (CSPRNG).
     Compute W = wrap_sealed_qub(SealedQubCbor, qub_id=qub_id, key=K, nonce=N)
     per §13. The bytes uploaded to permanent storage are the OuterWrapper CBOR W,
     never the bare SealedQubCbor. K leaves the device only as the URL
     fragment in step 16.
13. Display seal-time disclosure. User confirms.
14. Validate upload eligibility via the qub upload service (bot-detection, entitlement, rate limits).
15. Submit W (the OuterWrapper bytes) to the qub upload service; the service
    signs and uploads to permanent storage. The service is byte-blind to the inner
    SealedQubCbor and never receives K.
16. Receive arweave_tx_id from the service. Construct delivery URL as
    `<origin>/c/<arweave_tx_id>#<base64url(K)>` (or `<origin>/s/<short_code>#<base64url(K)>`
    when a short code is allocated). Browsers do not transmit URL fragments
    to servers, so K is never observed by qub.social or any storage gateway.

Tárolási címkeréteg (sávon kívül). A qub feltöltési szolgáltatás szándékosan kis halmaz tárolási tranzakciós címkét csatol a becsomagolt hasznos adat mellé. A Content-Type=application/octet-stream normatívan kötelező. A referencia-szolgáltatás emellett három opcionális címkét csatol, amikor az alkotó úgy dönt, hogy megjeleníti őket: Intent (engedélylistával validált összeállítási szándék — pl. quote, reply, commitment), Author (az alkotó §9.3 nyilvános kulcsának ujjlenyomata 64 karakteres kisbetűs hexként) és Parent-Tx-Id (a szülő qub tárolási tranzakciós azonosítója válaszláncokhoz, 43 karakteres base64url).

Az Author címke qubonként választható: a referencia-alkotóalkalmazás csak akkor csatolja, ha a felhasználó kifejezetten engedélyezi a nyilvános tulajdonítást a lezárás idején. Amikor a kapcsoló ki van kapcsolva — ez az alapértelmezett —, nem íródik Author címke, és a qub a láncon tulajdonítatlan: semmi az állandó tárhelyen nem köti össze a feltöltést egy alkotó kezelőjével, e-mail-címével vagy más qubjaival. Amikor a kapcsoló be van kapcsolva, az Author ujjlenyomat az alkotó által választott @kezelő-re oldódik fel a §9.5 attesztációs láncon keresztül. A válaszlánc-kapcsolatok és az Intent nem azonosítóak. A külső burkoló (§13) védi a belső törzset a rejtjelezett szöveg korrelációjától — megakadályozva, hogy egy gyűjtő felismerje és tömegesen visszafejtse a qub formájú feltöltéseket, miután a drand-körük közzétételre kerül.

A referencia-szolgáltatás szándékosan NEM csatol App-Name, App-Version vagy Type címkéket: bármely ilyen egyértékű szűrő egy GraphQL-lekérdezésnek a teljes qub-korpuszt visszaadná, ami nincs összhangban a burkoló csak-törzs bizalmassági hatókörével.

A megfelelő ellenőrzőnek NEM szabad semmilyen tárolási címkétől függenie a §11 harmadik fél általi ellenőrzéshez; a body hash / qub_id / aláírás csak a belső CBOR-ra kötelez, soha nem a címkehalmazra.


8. Feloldási protokoll

A teljes feloldási sorozat. Minden lépés normatív.

 1. Viewer opens delivery URL. Extract arweave_tx_id from path AND
    K = base64url_decode(fragment) from the URL fragment. If the fragment
    is absent or malformed → display "this URL is missing its decryption
    key" and stop; the viewer MUST NOT contact the storage gateway
    without K, since fetching wrapped bytes the viewer cannot decrypt
    serves no purpose and only leaks the access attempt.
 2. Check denylist. If tx_id is denylisted → display block message. Stop.
 3. Fetch OuterWrapper bytes from permanent storage (with multi-gateway fallback).
 3a. Unwrap: parse the bytes as OuterWrapper (§13), verify the wrapper
    `version` byte is `0x01`, and compute SealedQubCbor =
    unwrap_sealed_qub(OuterWrapper, key=K). Any AEAD authentication
    failure (wrong K, tampered ciphertext, swapped qub_id-as-AAD,
    swapped nonce) → display "this URL's decryption key does not match
    the stored qub" and stop. Authentication failures are
    indistinguishable to the viewer per §13.5.
 4. Parse SealedQubCbor → SealedQub.
 5. Validate: SealedQub.version is known (0x01). Reject unknown versions.
 6. If current time < SealedQub.unlock_at → display countdown. Poll or wait.
 6a. Körkötés-ellenőrzés (V1.2). Számold újra az expected_round =
    ceil((SealedQub.unlock_at - chain_genesis_time) / chain_period_seconds) értéket.
    Utasítsd el, kivéve ha SealedQub.drand_round == expected_round ÉS a tlock titkosított
    szöveg szakaszába (stanza) sütött kör (az age/tlock fejlécből beolvasva, aláírás nélkül)
    == expected_round. A szakasz köre az, amely ténylegesen kapuzza a visszafejtést; ezen
    ellenőrzés nélkül egy rosszindulatú szerző egy már elmúlt körhöz köthetné a titkosított
    szöveget, miközben jövőbeli visszaszámlálást jelenít meg, így bárki, aki beolvassa a
    tárolt bájtokat, az unlock_at előtt visszafejthetné. A láncidentitás nélküli
    implementációk (tesztmockok) kihagyják ezt az ellenőrzést.
 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.
13. Verify: QubEnvelope.content_type is known and renderable.
    Known values: 0x01 (text), 0x03 (pact). 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 appropriate renderer (see §10 for text, §6 for pact).
17. Construct RevealedQub for display.

9. Szerzőség aláírása

9.1 Indoklás

A qubok állandó tárhelyen tárolódnak. A szerzőségi aláírásoknak határozatlan ideig hamisíthatatlannak kell maradniuk, ezért a v1.0 a posztkvantum ML-DSA-65 sémát (FIPS 204) használja, nem pedig egy klasszikus sémát, amelynek biztonsága a qub állandó élettartamán belül romolhat.

9.2 Algoritmus-nyilvántartás

sig_alg Séma Kulcsméret Aláírásméret
0x00 Nincs aláírás (aláíratlan)
0x01 ML-DSA-65 (FIPS 204) 1 952 bájt 3 309 bájt

A megjelenítőknek el KELL utasítaniuk az ismeretlen sig_alg értékeket.

9.3 Aláírt bemeneti kép konstrukciója

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

signature = Sign(author_secret_key, sig_input)

Tartománymegkülönböztető: a "QUB_AUTHOR_SIG_V1" 17 ASCII-bájt: [0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31]. Nincs kitöltés.

Záró bájt: a bemeneti kép 91. bájtjának 0x00-nak KELL lennie. A referencia-megvalósítás ezt ORG_ID_PRESENT_INDIVIDUAL = 0x00 konstansként teszi elérhetővé a crates/qub-core/src/signing.rs fájlban; az ellenőrzéshez sig_input-ot újraépítő megjelenítőknek ugyanazt a bájtot KELL kibocsátaniuk.

Aláírás-hatókör — mi van és mi nincs lefedve. A sig_input négy borítékmezőre kötelez: version, qub_id, body_hash, unlock_at (plusz a rögzített tartománymegkülönböztető és az org_id_present bájt). E négyből három szerkezeti invariáns: a qub_id maga is a version, content_type, created_at, unlock_at és body_hash mezőkből vezetődik le a §4.1 bemeneti képen keresztül, így a content_type vagy created_at bármely változtatása eltérő qub_id-t eredményez, és tranzitíven érvényteleníti az aláírást. A közvetlenül hitelesített felület tehát:

Mező Aláírás által hitelesített Hogyan
version Közvetlen bemenet a sig_input-hoz
qub_id Közvetlen bemenet
body_hash Közvetlen bemenet
unlock_at Közvetlen bemenet
content_type Tranzitíven, a qub_id bemeneti képen keresztül
created_at Tranzitíven, a qub_id bemeneti képen keresztül
body Tranzitíven, a body_hash = SHA3-256(body)-on keresztül
author_pubkey — (implicit) Az aláírást ellenőrző kulcs definíció szerint a szerző
sender_label Csak megjelenítési szöveg; az aláírás megtörése nélkül módosítható
reply_to Szálazási mutató; az aláírás megtörése nélkül módosítható
cosigner_pubkey / cosigner_signature Függetlenül aláírva ugyanazon sig_input-ra (lásd §9.7)
drand_chain_id, tlock_ciphertext, visibility Külső SealedQub mezők, nem a borítékon belül — saját szerkezeti invariánsaik (kör- / lánckonzisztencia) fedik le őket, de nem a szerzői aláírás (A drand_round mostantól tranzitívan kötve van a qub_id preimage-en keresztül — lásd fent.)

A nem hitelesített mezők biztonsági következményei.

Azoknak a megvalósításoknak, amelyek sender_label-t vagy reply_to-t jelenítenek meg a végfelhasználóknak, a hitelesített identitást (nyilvános kulcs ujjlenyomata, attesztáció) KELL elsődleges identitásjelzésként megjeleníteniük, nem a címkét.

9.4 Ellenőrzési eljárás

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 (same formula as §9.3).
6. Verify(author_pubkey, sig_input, author_signature).
7. If verification succeeds → display "signed by [key fingerprint]."
8. If verification fails → display "signature verification failed."

Az aláírás-ellenőrzés a legdrágább művelet (különösen az ML-DSA-65). Az összes olcsóbb ellenőrzés (kivonat, qub_id, unlock_at) sikeres áthaladása UTÁN KELLENE elvégezni.

9.5 Identitás-attesztációk

Az identitás-attesztációk — az author_pubkey leképezése ember számára felismerhető identitási állításokra, mint például egy qub-kezelő, e-mail-cím, közösségi kezelő vagy passkey hitelesítő adat — megjelenítő-oldali progresszív fejlesztés, és nem szükségesek az aláírás-ellenőrzéshez. Azoknak a megjelenítőknek, amelyek megjelenítési identitásra oldják fel az attesztációkat, az alábbi elsőbbséget KELL alkalmazniuk:

handle > email > social > fingerprint

Az ujjlenyomat-tartalékérték a SHA3-256(author_pubkey) kisbetűs hexa formája; mindig elérhető bármely aláírt qubhoz. A megjelenítők megjelenítés céljából RÖVIDÍTHETIK — a referencia-megjelenítő a qub: előtagot, majd az első és utolsó négy bájtot jeleníti meg (qub:<8 hex>…<8 hex>).

A megfelelő ellenőrző a §9.4 minden ellenőrzését el tudja végezni anélkül, hogy a qub API-val kapcsolatba lépne, az állandó tárhelyen és a drandon túli bármilyen hálózat nélkül, és bármilyen szerveroldali keresés nélkül. Az attesztáció feloldása külön, legjobb erőfeszítésű lépés, amelyet csak az aláírás-ellenőrzés sikeres befejezése után végeznek el.

9.6 Méretre gyakorolt hatás

Ed25519 ML-DSA-65
Aláírás 64 bájt 3 309 bájt
Nyilvános kulcs 32 bájt 1 952 bájt
Összesen qubonként 96 bájt 5 261 bájt
Tárolási költségkülönbség (~$5/MB-nál) ~$0,0005 ~$0,026

Egy 500–2 000 bájtos szöveges qub esetén az ML-DSA-65 nagyjából megháromszorozza a tárolt méretet. Az abszolút költség elhanyagolható.

9.7 Társaláíró-ellenőrzés (paktum kétoldalú megállapodások)

Kétoldalú megállapodásoknál (content_type = 0x03) egy második aláírási réteg bizonyítja, hogy mindkét fél hozzájárult ugyanazokhoz a feltételekhez.

Borítékmezők:

Mindkét mezőnek együtt KELL jelen lennie, vagy mindkettőnek hiányoznia. Ha pontosan az egyik van jelen, a megjelenítőknek integritáshibát KELL jelenteniük.

Ellenőrzési eljárás:

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.
5. Verify(cosigner_pubkey, sig_input, cosigner_signature).
6. Success → display "co-signed by [cosigner fingerprint]."
7. Failure → display "co-signature verification failed."

Tulajdonságok:

E-mail-kötési kapu (működési). Amikor egy színrevitt paktum B fél e-mail-kapcsolatot hordoz (§6.1), a qub feltöltési szolgáltatásnak el KELL utasítania a társaláírási kérést, hacsak nem létezik rövid élettartamú e-mail-ellenőrzési jelölő, amely megegyezik mind a színrevitel azonosítójával, mind az adott kapcsolat normalizált e-mail-kivonatával. A jelölőt a /api/v1/auth/verify írja, amikor a magic-link token staging_id-t hordoz, és az ellenőrzött cím megegyezik a SHA-256(normalise_email(party_b.contact))-tal — ahol a normalise_email(addr) megőrzi a helyi rész kis-/nagybetűjét, és csak a tartományrészt kisbetűsíti (az RFC 5321 §2.3.11 szerint), és a SHA-256 itt a NIST FIPS 180-4 kivonat (eltérő a §4 levezetésekben használt SHA3-256-tól) —, és 900 másodperccel (15 perccel) a kibocsátás után lejár. Ez működési megszemélyesítés-elleni kapu, NEM a láncon lévő qub-bizonyíték része — egy harmadik fél ellenőrzőnek, amely a §11-et lejátssza, csak állandó tárhelyre és drandra van szüksége, bármilyen szerveroldali keresés nélkül. A jelölő csak szerveroldalon létezik, és soha nem része az aláírt törzsnek.

Méretre gyakorolt hatás (ML-DSA-65 szerző + társaláíró):

Komponens Méret
Szerzői aláírás 3 309 bájt
Szerzői nyilvános kulcs 1 952 bájt
Társaláírói aláírás 3 309 bájt
Társaláírói nyilvános kulcs 1 952 bájt
Teljes kriptográfiai többletteher 10 522 bájt
Tárolási költségkülönbség ~$0,05

10. Markdown-megjelenítés és -tisztítás

Ez a szakasz biztonságkritikus. A megjelenítő a szöveges qubokat (content_type = 0x01) korlátozott Markdown-részhalmazzal jeleníti meg.

10.1 Engedélyezett elemek

10.2 Tiltott elemek

Elem Kezelés
Nyers HTML (<div>, <script> stb.) Teljesen eltávolítva. Semmilyen HTML nem jut át.
Képek (![alt](url)) Eltávolítva. A kép-szintaxis eltávolításra kerül a kimenetből.
Hivatkozások ([text](url)) Az URL látható egyszerű szövegként jelenik meg. Nincs automatikus hivatkozás. Kifejezett felhasználói művelet nélkül nem kattintható.
Veszélyes URL-sémák javascript:, data:, vbscript:, file: — eltávolítva.
Iframe-ek, beágyazások, objektumok Eltávolítva.
HTML-entitások Megjelenítési karakterekké dekódolva, csak ha biztonságosak.

10.3 Megvalósítás

A megvalósításoknak szigorú engedélylista-elemzőt KELL használniuk, nem tiltólistát. Az ajánlott megközelítés:

  1. A Markdown elemzése pulldown-cmark (vagy egyenértékű) használatával.
  2. Az AST bejárása, és bármely olyan csomópont elvetése, amely nincs az engedélylistán (§10.1).
  3. Hivatkozási csomópontoknál: az URL kibocsátása látható szövegként, nem kattintható <a> elemként.
  4. A szűrt AST átalakítása típusos köztes ábrázolássá (pl. egy MarkdownNode enum csak biztonságos variánsokkal). A nyers HTML szerkezetileg nem ábrázolható ebben a köztes ábrázolásban.
  5. Megjelenítés a típusos köztes ábrázolásból a cél megjelenítési rétegbe (pl. reaktív megjelenítési komponensek, DOM-csomópontok). Semmilyen ponton nincs HTML-karakterlánc-összefűzés vagy innerHTML.

A tiltólista-megközelítések törékenyek, mert az új Markdown-bővítmények vagy elemző-furcsaságok szűretlen elemeket vezethetnek be. A típusos-AST megközelítés szerkezetileg lehetetlenné teszi az XSS-t — nincs olyan variáns, amely tetszőleges HTML-t hordozhatna.

10.4 Méret- és szerkezeti korlátok


11. Harmadik fél általi ellenőrzés

Bármely harmadik fél ellenőrizhet egy nyilvános qubot a qub együttműködése nélkül. Az ellenőrzési eljárás:

1. Obtain arweave_tx_id (from delivery URL or direct knowledge).
2. Fetch SealedQubCbor from any storage gateway.
3. Confirm storage block inclusion (block height, block timestamp).
4. Parse SealedQubCbor → SealedQub.
5. Fetch drand round signature for SealedQub.drand_round.
6. tlock_decrypt(tlock_ciphertext, round_signature) → QubEnvelope CBOR bytes.
7. Parse → QubEnvelope.
8. Verify SHA3-256(body) == body_hash.
9. Verify QubEnvelope.qub_id == SealedQub.qub_id.
10. Verify QubEnvelope.unlock_at == SealedQub.unlock_at.
11. If sig_alg != 0x00: verify author_signature (see §9.4).
12. All checks pass → qub is verified.

Mit bizonyít az ellenőrzés:

Bizonyíték Mit állapít meg
Kötelezettségvállalás A rejtjelezett szöveg létezett a tárolási blokk időbélyege idején.
Integritás Az egyszerű szöveges törzs megegyezik a vállalt kivonattal, és nem módosították.
Időzítés A tartalom olvashatatlan volt a drand-körig, amely megfelel a választott feloldási időnek (a tlock és a drand biztonsági feltételezéseitől függően).

Mit NEM bizonyít az ellenőrzés:

Nem-bizonyíték Miért
Szerzőség A sender_label dekoratív. sig_alg0x01 nélkül bárki lezárhatta volna ezt a tartalmat.
Szándék A qub a tartalmat és az időzítést bizonyítja, nem azt, amit az alkotó szubjektíven gondolt.
Esemény előtti időzítés A tárolási blokkba kerülés perceket késhet a tényleges feltöltéshez képest. A kötelezettségvállalás időbélyege a blokk ideje, nem az a pillanat, amikor a felhasználó megnyomta a „lezárás”-t.

12. Verziókezelés

12.1 Protokollverzió

A version mező (u8) mind a SealedQub-ban, mind a QubEnvelope-ban a fő protokollverziót azonosítja.

12.2 Verziótörténet

Verzió Érték Leírás
v1 0x01 Nyilvános szöveges qubok (content_type 0x01), paktum kétoldalú megállapodások (0x03, structured/v1 séma, ML-DSA-65 szerző + társaláíró), tlock, SHA3-256

12.3 Előrekompatibilitás

Egy v1-megjelenítőnek, amely ismeretlen opcionális CBOR-térképkulcsokat (a §3.2 kanonikus sorrendjében nem szereplő kulcsokat) tartalmazó QubEnvelope-ba ütközik, FIGYELMEN KÍVÜL KELLENE HAGYNIA ezeket a kulcsokat, és folytatnia kellene az ellenőrzést az ismert mezők használatával. Ez lehetővé teszi a jövőbeli kisebb kiegészítéseket (pl. új metaadatok) a fő verzió emelése nélkül.

Egy v1-megjelenítőnek, amely sig_alg = 0x01 (ML-DSA-65) értékbe ütközik, de hiányzik az ML-DSA-65 ellenőrzési támogatása, a qub-tartalmat „aláírás jelen van, de nem ellenőrizhető” megjegyzéssel KELLENE megjelenítenie, nem pedig a qubot teljesen elutasítania. A referencia-megvalósítás ma minden sig_alg értéket elutasít a 0x00 és 0x01 kivételével, mert a v1 nyilvántartás nem tartalmaz más érvényes algoritmust — a szigorú elutasítás és a lágy hiba megfigyelhetően azonosak, amíg egy harmadik algoritmus regisztrálásra nem kerül. A fenti lágy-hiba viselkedés akkor válik teherviselővé, amikor a §9.2 új bejegyzést fogad be, és a referencia-megjelenítő ezen a ponton frissül lágy-hibára.

12.4 Külső burkoló verziója

A §13-ban leírt OuterWrapper saját version bájtot hordoz, függetlenül a SealedQub.version és QubEnvelope.version mezőktől. A két verziótér külön fejlődik: egy jövőbeli posztkvantum-biztonságos szimmetrikus csere megemeli a burkoló bájtját anélkül, hogy a belső protokollverziót érintené, és egy jövőbeli protokollréteg-kiegészítés (pl. egy új borítékmező) megemeli a belső verziót anélkül, hogy a burkoló bájtját érintené.

OUTER_WRAPPER_VERSION_* Érték Algoritmus Állapot
OUTER_WRAPPER_VERSION_1 0x01 AES-256-GCM 12 bájtos nonce-szal, 16 bájtos hitelesítési címkével, a qub_id-hoz kötött AAD-vel v1 alapértelmezett
0x020xFF Fenntartott Jövőbeli

A megjelenítőknek el KELL utasítaniuk az ismeretlen burkoló-verziókat egyértelmű hibával. A protokoll szándékosan szűken tartja a burkoló-verzióteret, amíg egy konkrét migrációs hajtóerő meg nem jelenik (pl. egy másik AEAD-et előnyben részesítő NIST-irányelv); egy 0x02 hely ugyanabban a felülvizsgálatban kerül kiosztásra, amely bevezeti az algoritmust.


13. Külső titkosító burkoló

13.1 Indoklás

A protokollrétegek (QubEnvelope → tlock → SealedQub) időzárolttá teszik a lezárt qubot: a törzs olvashatatlan az unlock_at-ig, és amíg a drand-kör aláírása közzétételre nem kerül. A feloldás után azonban a kör aláírása nyilvános, és a SealedQub kanonikus CBOR-alakja felismerhető, így egy gyűjtő, aki indexelte az állandó tárhelyű tranzakciókat, tömegesen visszafejthetné az egész qub-korpuszt.

A külső titkosító burkoló bezárja ezt a csatornát azáltal, hogy egy további szimmetrikus AEAD-réteget iktat be a kanonikus SealedQubCbor és az állandó tárhelyre írt bájtok közé. A 256 bites K kulcs kizárólag a kézbesítési URL fragmentjében és a felhasználói eszközökön él; a böngészők nem továbbítják az URL-fragmenteket a szervereknek, így a qub.social, minden tárolási átjáró és minden CDN bármelyikük előtt megfigyelhetően vak a K-ra. Az állandó tárhelyen lévő minden qub tehát átlátszatlan rejtjelezett szöveg, amelynek egyszerű szövege visszanyerhetetlen az alkotó által megosztani választott URL nélkül.

Nettó hatás:

13.2 Rétegezés

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
  ↓ AES-256-GCM(K, nonce, AAD=qub_id) (§7 step 12a, this section)
OuterWrapper CBOR bytes              ← uploaded to permanent storage (§7 step 15)

A lezárás és feloldás a protokollrétegen (§7, §8) változatlan a burkoló-határ alatt; a burkoló a seal() hívási helyén csatlakozik, és az unlock() hívási helyén válik le.

13.3 OuterWrapper adatstruktúra

struct OuterWrapper {
    version:    u8,           // 0x01, see §12.4
    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
}

Mező-invariánsok.

CBOR-kódolás. Kanonikus CBOR a §3 szerint, ugyanazzal a kulcsrendezési szabállyal (kódolt bájthossz szerint növekvően rendezve, majd lexikografikusan). A négy kulcs:

Kulcs Kódolt bájtok Sorrend
nonce 6 1
qub_id 7 2
version 8 3
ciphertext 11 4

Az OuterWrapper CBOR első bájtja tehát egy 4 bejegyzéses térkép határozott hosszúságú térképfejléce (0xA4).

13.4 AAD-kötés a qub_id-hoz

A burkoló a qub_id-t AEAD kiegészítő hitelesített adatként köti. Ez a teherviselő szerkezeti védelem három támadásosztály ellen:

Támadás Védelem
A rejtjelezett szöveg áthelyezése egy másik qub_id mező alá a burkolóban AAD-eltérés → az AEAD-hitelesítés meghiúsul
Az A qub URL-fragmentjének keverése a B qub állandó tárhelyű bájtjaival AAD-eltérés → az AEAD-hitelesítés meghiúsul
A burkoló qub_id mezőjének meghamisítása a feltöltés után AAD-eltérés → az AEAD-hitelesítés meghiúsul

A qub_id burkoló egyszerű szövegében való hordozása nem gyengíti érdemben a felsorolás-immunitást — a qub_id maga is a §4.1 bemeneti kép SHA3-256 kivonata, a kivonatból nem visszanyerhető bemeneti képpel, és egy felsoroló, aki már begyűjtötte a burkoló bájtjait, semmi olyat nem tanul a látható qub_id-ból, amit ne tudna kikövetkeztetni magának a feltöltésnek a létezéséből.

13.5 Becsomagolási és kicsomagolási algoritmusok

wrap_sealed_qub(SealedQubCbor S, qub_id Q, key K, nonce N):
    require K.len() == 32 and N.len() == 12 and Q.len() == 32
    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.4
    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
    return P                            // P is the inner SealedQubCbor

Hibamód-összeomlás. A rossz K, a rossz nonce, az AAD-eltérés és a meghamisított rejtjelezett szöveg mind ugyanazt a DECRYPT_FAILED hibát eredményezi. Ez szándékos AEAD-tulajdonság: a hibamód megkülönböztetése oldalcsatornát hozna létre, amelyet egy távoli támadó hibás formátumú burkolók küldésével és a válasz időzítésével vizsgálhatna. A referencia-megvalósításoknak minden AEAD-hibát egyetlen hibaalakra KELL összeomlasztaniuk.

13.6 Kulcsanyag és terjesztés

A becsomagoló K kulcs egy 256 bites egyenletes véletlen érték, amelyet qubonként generál egy CSPRNG. A referencia-megvalósítások az alábbiakból nyerik:

Terjesztés: a K mezőt URL-biztonságos base64-ként KELL kódolni (RFC 4648 §5, kitöltés nélkül), és a kézbesítési URL-hez fragmentkomponensként hozzáfűzni:

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

A fragmentet egy megfelelő böngésző soha nem továbbítja egyetlen szervernek sem. Azok a helyreállítási csatornák (szerveroldali előzményindex, választható e-mailes automatikus küldés), amelyek a teljes kézbesítési URL-t — beleértve a fragmentet — a felhasználó eszközén túl megőrzik, kifejezett kompromisszum az alapértelmezett kripto-aprítási tartással szemben, és kifejezett felhasználói beleegyezéshez KELL kötni őket.

Fragmentvesztés. Ha egy felhasználó elveszíti az URL-fragmentet, és nincs helyreállítási csatornája, a qub olvashatatlan. Ez a tervezés teherviselő kompromisszuma, és a lezárás idején KÖZÖLNI KELL a felhasználóval. Az MVP megerősíti a lezárás-idejű közlést kifejezett „mentse el ezt az URL-t” szöveggel és egy ellenőrzött e-mailes helyreállítási csatornával azoknak a felhasználóknak, akik ezt választják.

13.7 A szakasz hatókörén kívül

13.8 Nyilvános qubok (a burkoló elhagyása)

A külső burkoló a kézbesítési rétegen választható. Egy alkotó nyilvánosként is lepecsételhet egy qubot, amely esetben a kanonikus SealedQubCbor közvetlenül kerül az állandó tárhelyre, OuterWrapper réteg és K kulcs nélkül:

SealedQubCbor bytes  ──(public)──▶  uploaded to permanent storage as-is
SealedQubCbor bytes  ──(private)─▶  AES-256-GCM(K, …) ▶ OuterWrapper ▶ uploaded

Egy nyilvános qub időzárolt, de nincs hivatkozással kapuzva: olvashatatlan marad, amíg a drand-köre közzétételre nem kerül (a tlock-réteg változatlan), de a feloldás után bárki, akinek megvan az arweave_tx_id, vissza tudja fejteni — nincs szükség URL-fragmentre, mert nincs K. Ez a szándékos kompromisszum azokhoz a felületekhez, amelyeket a szervernek kell mozgatnia: a felfedés-értesítő e-mailek, a harmadik féltől származó beágyazások és a gazdagabb felfedés-utáni SEO mind olyan hivatkozást igényel, amely olyan titok nélkül működik, amelyet a szerver soha nem tart a kezében (§13.6).

Következmények, amelyekkel egy előállítónak SZÁMOLNIA KELL:

A privát (becsomagolt) marad az alapértelmezett; a nyilvános kifejezett, qubonkénti alkotói választás.


14. Tesztvektorok

14.1 qub_id levezetése

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  = 4695445  (= (1736294400 - 1595431050) / 30, 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 — V1.2):
  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)
  0x000000000047A595 ||  // drand_round as u64 big-endian (4695445)
  body_hash          ||  // 32 bytes
  title_hash             // 32 bytes (all-zeros sentinel; title absent)

Expected output:
  qub_id = SHA3-256(preimage)
         = 3a9fcb31b750d985c262fada6d4f777f
           d6a28be831d941d85c131f5a4bbaf8a4

Az implementációknak ehhez a bemenethez azonos body_hash és qub_id értékeket KELL előállítaniuk. Ezt a tesztvektort érdemes első egységtesztként megírni. A fenti kanonikus értékeket a referencia-implementáció számította ki, és bitről bitre egyezniük KELL. Korábbi preimage-elrendezések (indulás előtt — egyetlen élő qub sem függött ezektől): a 92 bájtos V1.0 qub_id 3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0 volt; a 100 bájtos V1.1 qub_id (az outcome_at_or_zero bekötése után) b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed volt. A V1.2 bevonja a drand_round mezőt, és a tartományelválasztót QUB_ID_V2-re emeli.

14.2 Feloldási kör leképezése

Input:
  unlock_at           = 1735689600
  chain_genesis_time  = 1595431050
  chain_period_seconds = 30

Calculation:
  (1735689600 - 1595431050) / 30 = 4675285.0
  ceil(4675285.0) = 4675285

drand_round = 4675285

14.3 Kanonikus CBOR oda-vissza

A megvalósításoknak ellenőrizniük KELL, hogy a serialize(parse(serialize(qub))) == serialize(qub) minden érvényes bemenetre. Ez tulajdonságteszt, nem egyetlen vektor.

14.4 PactTerms CBOR (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)

A kanonikus CBOR-bájtokat és a SHA3-256 body_hash-t a referencia-megvalósítás számítja ki. A megvalósításoknak bájtazonos CBOR-t KELL előállítaniuk erre a bemenetre.

A megvalósításoknak emellett ellenőrizniük KELL, hogy a serialize(parse(serialize(pact))) == serialize(pact) minden érvényes PactTerms bemenetre (tulajdonságteszt).

14.5 Külső burkoló nyelvközi vektorok

A külső burkolónak (§13) külön kanonikus fixture-je van itt: crates/qub-core/tests/vectors/wrapper_v1.json. Minden eset egy (key, nonce, qub_id, sealed_cbor) rendezett ötöst rögzít átlátszatlan hexa-bemenetekként, és egy konkrét expected_wrapper_hex kimenetet állít. Mindkét referencia-megvalósítás ugyanazt a JSON-fájlt használja:

A fixture jelenleg három esetet rögzít:

Eset Lefedettség
basic-text-public A legkisebb realisztikus SealedQub alak; opcionális mezők nélkül. Megállapítja a kanonikus burkoló-alakot egy v1.0-tipikus qubhoz.
with-recipient-pubkey SealedQub beállított recipient_pubkey-vel (2. fázis útvonal). Eltérő belső CBOR-kulcskészlet, eltérő qub_id.
longer-body ~4 KiB-os törzs — gyakorolja a többbájtos CBOR-hosszelőtagokat mind a belső borítékon, mind a külső rejtjelezett szövegen belül.

A megvalósításoknak bájtazonos expected_wrapper_hex-et KELL előállítaniuk a rögzített bemenetekre. A fixture újragenerálása QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors parancsot igényel, és szándékos formátumváltoztatásokra van fenntartva.


15. Kriptográfiai profil kormányzása (jövőbeli)

Ez a szakasz informatív a v1-re, és normatívvá válik, amikor először lép be egy második algoritmus a qub kriptográfiai alapelemeinek bármelyikébe.

15.1 Jelenlegi tartás

A v1 protokoll alapelemenként pontosan egy algoritmust köt:

Az ellenőrzők jelenleg keményen kódolják a kulcs- és aláíráshosszakat alapelemenként. A wire-formátum nem tár fel agilitási felületet.

15.2 Tervezett alak

Amikor egy második algoritmus belép a protokollba, az ellenőrző egy elnevezett CryptoProfile-hoz (pl. ExqubV1) lesz konfigurálva, amely felsorolja az alapelemenként engedélyezett értékek pontos halmazát — sig_alg-ek, drand-láncok, burkoló-verziók, tartalomtípusok. A profil az ellenőrzés idején rögzül, soha nincs sávon belül egyeztetve. Az aktív profilon kívüli bármely érték elutasításra kerül.

Ez garantálja, hogy az ML-DSA-87 hozzáadása vagy az Ed25519 aktiválása nem gyengítheti visszamenőlegesen a meglévő ellenőrző-konfigurációkat: egy v1-ellenőrző v1-ellenőrző marad még egy v2-profil közzététele után is.

15.3 Kiváltó feltételek

Léptesse elő a §15-öt normatív státuszra, amikor az alábbiak bármelyike javaslatra kerül:

Addig a §15 helykitöltő, amely rögzíti a migrációs alakot, hogy a jövőbeli PR-ek ismert célpontra érkezzenek, ahelyett, hogy a tárgyalási felületet a nulláról újratárgyalnák.