qub-protokolspesifikasie

qub is 'n protokol vir kriptografiese temporale verbintenisse: 'n stelsel om woorde aan 'n toekomstige datum te verseël en, wanneer daardie datum aanbreek, presies te bewys wat gesê is en wanneer.

Drie primitiewe laat dit werk. drand is 'n gedesentraliseerde willekeurigheidsbaken — die onthulling-datum is afdwingbaar deur fisika, nie deur enige party se welwillendheid nie. Permanente openbare berging is 'n peuter-bestande openbare stoor — geen party kan 'n qub redigeer of uitvee sodra dit verseël is nie. ML-DSA-65 is 'n post-kwantum digitale handtekening — elke qub is gekoppel aan 'n sleutelpaar waarvan die geheim nooit die outeur se toestel verlaat nie.

Saam maak hierdie primitiewe 'n stelling wat tyd-geslote, peuter-aandui, en toeskryfbaar is — 'n kwitansie waarvan die waarde toeneem soos die wêreld se vermoë om die verlede te vervals verbeter.

Die res van hierdie dokument is die normatiewe spesifikasie wat vir interoperabele implementasies vereis word.


qub-protokolspesifikasie

Veld Waarde
Weergawe 1.0 (protokol-weergawe 0x01, buitenste omhulling-weergawe 0x01)
Datum 2026-05-01
Status Konsep
Hersien deur 2026-05-01

Hierdie dokument is die normatiewe protokolspesifikasie vir die qub tydverbintenis-stelsel. Dit definieer datastrukture, serialisering-reëls, afleidingsformules, en verifikasieprosedures wat vir interoperabele implementasies vereis word.

Omvang: die protokol-laag is opsetlik taal-neutraal — die qub-liggaam is ondeurskynende plaintext / markdown / verbond-grepe, en lokale-bewuste lewering is die kyker se verantwoordelikheid (qub.social-webtoepassing, <qub-embed> iframe, MCP-kliënte, ens.).


1. Notasie en konvensies

Notasie Betekenis
u8, u64, i64 Onderteken/getekende heelgetalle van gespesifiseerde bisbreedte
[u8; N] Vaste-lengte grepe-skikking van N grepe
Vec<u8> Veranderlike-lengte grepe-skikking
Option<T> Waarde van tipe T, of afwesig
String UTF-8 teksstring, NFC genormaliseer
`
SHA3-256(x) NIST SHA3-256 hash van greep-string x (FIPS 202)
ceil(x) Plafonfunksie: kleinste heelgetal ≥ x
CBOR Concise Binary Object Representation (RFC 8949)
big-endian Mees beduidende greep eerste

Alle heelgetalle in preimage-konstruksies word as big-endian vaste-breedte grepe-skikkings gekodeer (i64 → 8 grepe, u8 → 1 greep) tensy anders gespesifiseer.

Alle tydstempels is Unix-sekondes in UTC.


2. Datastrukture

2.1 ComposeQub (skepper-geheue-toestand)

Nie na CBOR geserialiseer nie. Nie in permanente berging gestoor nie. Plaaslik tot die skepper se toepassing.

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 (gedekripteerde data-vrag)

Geserialiseer met kanonieke CBOR (§3). Geënkripteer binne die SealedQub. Dit is die struktuur wat inhouds-integriteit ná dekripsie bewys.

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 — wanneer die werklikheid 'n oordeel vel (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
}

Basislyn (ongeteken-teks qub): version = 0x01, content_type = 0x01, sig_alg = 0x00, alle Option-velde afwesig.

Ander v1-konfigurasies: content_type = 0x03 (verbond-liggaam, sien §6.1); sig_alg = 0x01 (ML-DSA-65) met author_signature en author_pubkey teenwoordig (sien §9.3); cosigner_pubkey en cosigner_signature saam teenwoordig vir mede-onderteken verbonde (sien §9.7); reply_to gestel op die ouer-qub se qub_id vir antwoord-ketting qubs (sien §9.3 vir die handtekening-omvang implikasies).

2.3 SealedQub (kanonieke draadformaat)

Geserialiseer met kanonieke CBOR (§3). In permanente berging geskryf. Dit is die op-ketting artefak.

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 — vertoon op die oordeel-wag-CTA
                                        //   voor onthulling; weerspieël QubEnvelope.outcome_at;
                                        //   gebind aan qub_id via die §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 (kyker-toepassingstoestand)

Nie na CBOR geserialiseer nie. Plaaslik tot die kyker se toepassing. Gekonstrueer ná suksesvolle dekripsie en verifikasie.

RevealedQub {
    qub_id:              [u8; 32],
    arweave_tx_id:       String,
    visibility:          u8,
    content_type:        u8,
    created_at:          i64,
    unlock_at:           i64,
    outcome_at:          Option<i64>,       // V1.1 — oorgedra vanaf QubEnvelope.outcome_at / SealedQub.outcome_at; dryf die onthul-bladsy se uitspraak-wag-blok (verdict-uplift-plan §5.1)
    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. Kanonieke CBOR-profiel

Alle SealedQub- en QubEnvelope-serialisering MOET aan hierdie profiel voldoen. Twee implementasies wat dieselfde logiese struktuur ontvang MOET identiese grepe lewer.

3.1 Enkoderingsreëls

Reël Spesifikasie
Standaard RFC 8949 §4.2.1 (Core Deterministic Encoding Requirements)
Kaart-sleutel orde Gesorteer volgens gekodeerde greep-lengte eers (korter voor langer), dan leksikografies (greep-vir-greep vir gelyke-lengte enkoderings)
Heelgetal-enkodering Kortste vorm: 0–23 in aanvanklike greep; 24–255 in 2 grepe; 256–65535 in 3 grepe; ens.
Lengte-enkodering Slegs definitiewe lengtes. Geen onbepaalde-lengte skikkings, kaarte, greep-stringe, of teksstringe nie (additional info = 31 is verbode).
Etikette Geen CBOR-etikette nie (hoof-tipe 6 is verbode).
Drywende-punt Geen drywendes nie (hoof-tipes 7 waardes 0xF9–0xFB is verbode).
Teksstringe UTF-8 gekodeer, NFC genormaliseer (Unicode Normalization Form C).
Greep-stringe Rou grepe. Geen base64-enkodering by die CBOR-laag nie.
Duplikaat-sleutels Verwerp met fout. Parsers MOET NIE stilweg duplikaat kaart-sleutels aanvaar nie.
Eenvoudige waardes Slegs true (0xF5), false (0xF4), en null (0xF6) word toegelaat.
Opsionele velde Afwesige opsionele velde word uitgelaat van die CBOR-kaart geheel (nie as null gekodeer nie). Teenwoordige opsionele velde word in gesorteerde sleutel-orde ingesluit.

3.2 Geverifieerde kanonieke sleutel-ordes

Hierdie sleutel-ordes is normatief. Implementasies MOET sleutels presies in hierdie orde uitstuur. Foutsoek-aansprake BEHOORT ordening in nie-vrystellingsbouë te verifieer.

QubEnvelope (weergawe 0x01, ongeteken, alle opsionele velde afwesig):

"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 sleutel-orde afleiding: elke sleutel is 'n CBOR-teksstring. Gekodeerde lengte = 1 greep kop + string-lengte (vir stringe onder 24 grepe). Sorteer eers volgens totale gekodeerde lengte, dan leksikografies vir sleutels van gelyke lengte.

SealedQub (weergawe 0x01, publiek, geen ontvanger):

"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 (verbond-liggaam, 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 (ry van die terms-skikking):

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

PartyIdentifier (party_a / party_b kaart):

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

3.3 Greep-enkodering verwysing

Tipe CBOR-enkodering Voorbeeld
SHA3-256 hash (32 grepe) 0x58 0x20 + 32 grepe body_hash, qub_id
Tydstempels (i64) Hoof-tipe 0 (positief) of 1 (negatief), kortste enkodering Unix-sekondes
Weergawe (u8, waarde 1) 0x01 (enkele greep)
Inhoudstipe (u8, waarde 1) 0x01 (enkele greep)
sig_alg (u8, waarde 0) 0x00 (enkele greep)
ML-DSA-65 handtekening (3 309 grepe) 0x59 0x0C 0xED + 3 309 grepe author_signature, cosigner_signature
ML-DSA-65 publieke sleutel (1 952 grepe) 0x59 0x07 0xA0 + 1 952 grepe author_pubkey, cosigner_pubkey

4. Normatiewe afleidings

4.1 qub_id

Die qub_id identifiseer 'n qub uniek en bind die QubEnvelope aan die SealedQub. Dit word deterministies afgelei van die koevert-inhoud.

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

Domein-skeier enkodering: Die string "QUB_ID_V2" is 9 ASCII-grepe. 'n Enkele 0x00-vullinggreep word bygevoeg om 10 grepe vir belyning te bereik. Implementasies MOET presies hierdie 10 grepe gebruik: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].

outcome_at-enkodering: V1.1 het die preimage van 92 na 100 grepe uitgebrei om die opsionele outcome_at-veld in die binding in te vou. 'n Afwesige outcome_at word as 8 nul-grepe gekodeer; die protokol se valideerders verwerp outcome_at <= 0 oral, sodat hierdie sentinel-waarde nie met 'n wettige waarde kan bots nie. Sien §3.2 (draadformaat) en die in-boom tasks/verdict-uplift-plan.md vir die oordeel-meganiek wat hierdie veld motiveer.

drand_round-kodering: V1.2 het die voorbeeld van 100 na 108 grepe uitgebrei om drand_round (die teiken-drand-rondte, §4.3) in die binding te vou, en die domeinskeier na QUB_ID_V2 opgegradeer. Dit bind die tydslotrondte in die qub-identiteit in: 'n poort kan nie die syferteks aan 'n ander rondte (bv. 'n reeds-verlede rondte) herbind as wat die vertoonde unlock_at impliseer nie. Die ontsluitprosedure (§8) verifieer boonop dat die rondte wat in die tlock-syferteksstrofe ingebak is, ooreenstem met unlock_round(unlock_at), sodat die vertoonde ontsluittyd bewysbaar die rondte is wat ontsleuteling beheer.

Eienskappe:

4.2 body_hash

body_hash = SHA3-256(body)

Waar body die rou Vec<u8> inhoud-vrag is. Vir teks-qubs is dit die UTF-8 gekodeerde qub-liggaam.

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

Waar title die opsionele plaintext-titel is wat op die kyker-aftelling voor onthulling verskyn (sien §3.2). NFC-normalisering loop by hash-tyd sodat die spysverteringskode stabiel is oor visueel-ekwivalente kodepunt-rye. Die alles-nulle sentinel is gereserveer vir die afwesige geval; 'n leë string word by die kanonieke CBOR-grens verwerp as 'n nie-kanonieke enkodering van "afwesig" (die kanonieke enkodering laat die veld heeltemal uit).

4.3 Ontsluitings-rondte kartering

drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds)
Parameter Bron Voorbeeld
unlock_at Gebruiker-gekose Unix-sekondes UTC 1735689600 (2025-01-01 00:00:00 UTC)
chain_genesis_time drand ketting-info (genesis_time) 1595431050
chain_period_seconds drand ketting-info (period) 30

Die ceil()-bewerking kies die eerste drand-rondte waarvan die onthulling-tyd ≥ unlock_at is. Dit verseker die qub kan nie dekripteerbaar word voor die gekose ontsluitings-tyd nie.

Rand-geval: as (unlock_at - chain_genesis_time) presies deelbaar is deur chain_period_seconds, is die resultaat daardie presiese rondte — die qub ontsluit presies by daardie rondte se onthulling-tyd.

Validasie: unlock_at MOET in die toekoms wees by seël-tyd. unlock_at MOET NIE meer as 10 jaar van created_at af wees nie (om langtermyn drand-afhanklikheidsrisiko te beperk; die UI BEHOORT te waarsku vir ontsluitings-datums verby 2 jaar).


5. Draadformaat newtypes

Draadformaat-newtypes verskaf kompilasie-tyd veiligheid teen die verwarring van CBOR-grepe met JSON, rou plaintext, of ander greep-enkoderings.

Tipe Bevat Geproduseer deur Verbruik deur
SealedQubCbor Kanonieke CBOR van SealedQub serialize_sealed_qub() Permanente-berging-oplaai, kyker-aflewering
QubEnvelopeCbor Kanonieke CBOR van QubEnvelope serialize_qub_envelope() tlock-enkripsie-inset, tlock-dekripsie-uitset

5.1 Konstruksie-reëls

// 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 Validasie by konstruksie

from_encoded() BEHOORT te valideer dat die inset met 'n geldige CBOR-kaart kop begin. Volledige strukturele validasie gebeur by ontleed-tyd, nie konstruksie-tyd nie, om dubbele-ontleding te vermy.


6. Inhoudstipe-register

Waarde Tipe Maks. liggaam-grootte Notas
0x00 Gereserveer (ongeldig) MOET NIE gebruik word nie
0x01 Plat teks (UTF-8, beperkte Markdown) 50 KB betaald / 10 KB gratis Sien §10 vir lewering-reëls. Die gratis / betaalde verdeling word deur die oplaai-diens afgedwing; die protokol-laag harde plafon is 50 KB.
0x02 Gereserveer (toekoms) Toegeken vir 'n toekomstige inhoudstipe; nie geldig in v1 nie. Kykers MOET verwerp volgens die reël hieronder.
0x03 Verbond (bilaterale ooreenkoms, CBOR-liggaam) 100 KB Liggaam is kanonieke CBOR PactTerms (§6.1). Mede-ondertekenaar ondertekening per §9.7.
0x04 Uitspraak (skepper se self-beoordeling, CBOR-liggaam) 8 KB Liggaam is kanonieke CBOR VerdictBody (§6.2). Word slegs deur die stelsel-kant verdict-intensie uitgestuur. Ouer-verhouding is op die Parent-Tx-Id Arweave-merker, nie op die liggaam nie. Sien verdict-uplift-plan §3.4.

Kykers MOET onbekende inhoudstipes met 'n duidelike gebruiker-sigbare fout verwerp. Kykers MOET NIE poog om onbekende tipes as teks te lewer nie.

6.1 Verbond-liggaam (content_type = 0x03)

'n Verbond-liggaam is die kanonieke CBOR-enkodering van 'n PactTerms-waarde:

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)> }

Kanonieke CBOR-sleutel-ordes vir al drie kaarte word in §3.2 gegee. Totale geserialiseerde verbond-CBOR MOET NIE 100 KB oorskry nie (stem ooreen met §6).

Skema-onderskeider. Die eerste ry in terms vir 'n structured/v1-verbond MOET wees { key: "pact_schema", value: "structured/v1" }. Rye sonder hierdie merker is "pasgemaakte" verbonde en ontvang geen gestruktureerde validasie of skema-bewuste lewering nie.

Bevrore erkenning-gleuwe. structured/v1-verbonde dra presies vier erkenning-rye onder hierdie sleutels:

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

Die value vir elk is een van agt bevrore Engelse stringe gekies deur die (role, kind)-paar, waar role ∈ { seller, buyer, provider, client } en kind ∈ { standard, capacity }. Die stringe self is normatiewe protokol-data — beide partye se ML-DSA-65 handtekeninge verbind tot die presiese grepe via body_hash. Hulle word NIE gelokaliseer nie; die ondertekende liggaam is taal-neutraal. Enige bewoording-verandering vereis 'n nuwe skema-weergawe (structured/v2).

Die agt stringe, hul opsoek (acknowledgement_for(role, kind)), en die rasionaal vir elkeen is vasgepen deur die verwysing-implementasie. Konforme implementasies MOET greep-identiese erkenning-waardes uitstuur; goue-vasstellings SHA3-256 liggaam-hash toetse wat al vier rol-kombinasies dek vang enige drywing.

Kyker-vertoonsorde. Die erkenning-stringe bevat frases soos "described above", wat veronderstel dat die beskrywing- / omvang-rye voor die erkennings lewer. Kykers MOET die terms-skikking in CBOR-orde lewer; herrangskikking breek die prosa-semantiek.

Teen-party kontak. Wanneer Party B se contact 'n geldige e-posadres is, stuur die qub-oplaaidiens outomaties 'n hersiening- / mede-ondertekening uitnodig-e-pos op verhoog-tyd uit en bind die uiteindelike mede-ondertekening aan verifikasie van dieselfde adres (§9.7). Verbonde waarvan die Party B-kontak afwesig is kan steeds mede-onderteken word, maar slegs deur 'n buite-band kanaal — die diens weier mede-ondertekening-versoeke wat nie 'n ooreenstemmende 15-minute e-pos-verifikasie merker kan lewer nie.

6.2 Uitspraak-liggaam (content_type = 0x04)

'n Uitspraak-liggaam is die kanonieke CBOR-enkodering van 'n VerdictBody-waarde:

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
}

Kanonieke CBOR-sleutel-orde:

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

Totale geserialiseerde uitspraak-CBOR MOET NIE 8 KB oorskry nie (stem ooreen met die register-ry hierbo).

Uitkoms-opsomming. Die draadgreep is intensie-neutraal; die vier emmers Right / Partial / Wrong / Unfalsifiable dek elke uitspraak-draende intensie se uitkoms-ruimte. Per-intensie etikette ("Reg geraai" / "Nagekom" / "Gelewer" / "Bevestig" vir Right, ens.) is 'n kyker-kant lewering-aangeleentheid wat teen die ouer-qub se intensie opgelos word — die draad bly taal- en intensie-neutraal. Waardes buite 1..=4 MOET by dekodering verwerp word.

Ouer-koppeling. 'n Uitspraak-qub dra NIE die ouer-verwysing in sy liggaam nie. Die ouer-qub se Arweave-transaksie-id word as die Parent-Tx-Id berging-merker by oplaai-tyd uitgestuur (§7 berging-merker-laag). Dit hou die liggaam 'n self-bevattende ondertekende stelling van self-beoordeling; die oudit-ketting ("reg waaroor?") word via die Arweave-merker-opsoek gevestig.

Bewys-URL-veiligheid (normatief). Wanneer evidence_url teenwoordig is, MOET valideerders (komponeer-kant, draad-kant, Worker-rand) afdwing:

  1. Slegs HTTPS. Die string MOET met die greep-volgorde https:// begin. Enige ander skema — http, ftp, javascript, data, file, ens. — word verwerp.
  2. Lengte-plafon. ≤ 2,048 grepe (praktiese URL-grens van blaaier).
  3. NFC + vyandige-kodepunt-toets. Dieselfde reël as title en reflection — bidi-oorheers- / nul-wydte- / merker-blok- / BOM- / C0- / C1-kodepunte word verwerp. Definisie stem ooreen met die Rust crate::handle::contains_hostile_text_codepoint en die TS workers/api/src/utils/unicode.ts::isHostileCodepoint (hou in pas).
  4. Geen wit-spasie, geen ASCII-beheerkarakters nie. Wit-spasie / DEL / sub-0x20 grepe enige plek in die URL word verwerp — sluit die \n/\t-inspuiting-vektor af wat die bidi-reël nie dek nie.
  5. Nie-leë gasheer-segment. Alles tussen https:// en die eerste /, ?, of # MOET nie-leeg wees.

Geen bediener-kant gaan haal nie. Die Worker MOET NIE die URL volmag, gaan haal, of voorskou nie. Die protokol stoor 'n string; lewering gebeur kyker-kant met rel="nofollow noopener noreferrer" target="_blank" en 'n sigbare gasheer vertoon langs die skakel-teks.

Refleksie. Opsionele skepper-geskrewe refleksie-teks ("wat het verander, wat het jy geleer"). Dieselfde NFC + vyandige-kodepunt-validasie as title. Leeg / slegs wit-spasie-inset val saam tot afwesig by konstruksie-tyd.

Skema-weergawe. v1 ondersteun slegs verdict_version = 0x01. Toekomstige skema-hersienings stoot hierdie greep op en land saam met 'n nuwe protokol-weergawe per §12.


7. Verseël-protokol

Die volledige verseël-volgorde. Elke stap is normatief.

 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. Select drand chain. Load chain_genesis_time and chain_period_seconds, and
    compute drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds).
    (Computed here, before qub_id, because drand_round is bound into the qub_id
    preimage — §4.1, V1.2.)
 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,
    unlock_at, drand_chain_id, drand_round.
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.

Bergingsetiket laag (buite-band). Die qub-oplaaidiens heg 'n opsetlik klein stel bergingstransaksie-etikette saam met die omhulde data-vrag. Content-Type=application/octet-stream word normatief vereis. Die verwysing-diens heg addisioneel drie opsionele etikette aan wanneer die skepper kies om hulle te wys: Intent (toelaatlys-gevalideerde komposisie-intensie — bv., quote, reply, commitment), Author (skepper se §9.3 publieke-sleutel vingerafdruk as 64-karakter kleinletter hex), en Parent-Tx-Id (ouer-qub se bergingstransaksie-ID vir antwoord-kettings, 43-karakter base64url).

Die Author-etiket is per-qub kies-in: die verwysing-skepper toepassing heg dit slegs aan wanneer die gebruiker openbare-toeskrywing eksplisiet by seël-tyd aktiveer. Wanneer die skakelaar af is — die verstek — word geen Author-etiket geskryf nie en die qub is nie toegeskryf op die ketting nie: niks in permanente berging koppel die oplaai aan 'n skepper se handvatsel, e-pos, of ander qubs nie. Wanneer die skakelaar aan is, los die Author-vingerafdruk op na die skepper se gekose @handvatsel via die §9.5 bevestigings-ketting. Antwoord-ketting verhoudings en Intent is nie-identifiserend. Die buitenste omhulling (§13) beskerm die binne liggaam teen ciphertext-korrelasie — wat verhinder dat 'n oeser qub-vormige oplaaie herken en grootmaat-dekripteer nadat hul drand-rondte gepubliseer is.

Die verwysing-diens heg opsetlik NIE App-Name-, App-Version-, of Type-etikette aan nie: enige sodanige enkel-waarde filter sou die hele qub-korpus na 'n GraphQL-navraag terugstuur, wat onversoenbaar is met die omhulling se liggaam-alleen vertroulikheid-omvang.

'n Konformerende verifieerder MOET NIE op enige bergingsetiket vir §11 derde-party verifikasie staatmaak nie; die liggaam-hash / qub_id / handtekening verbind slegs tot die binnenste CBOR, nooit tot die etiket-stel nie.


8. Ontsluit-protokol

Die volledige ontsluit-volgorde. Elke stap is normatief.

 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. Round-binding check (V1.2). Recompute expected_round =
    ceil((SealedQub.unlock_at - chain_genesis_time) / chain_period_seconds).
    Reject unless SealedQub.drand_round == expected_round AND the round baked
    into the tlock ciphertext stanza (read via the age/tlock header, no signature
    required) == expected_round. 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.
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. Outeurskap-ondertekening

9.1 Rasionaal

Elke qub word in permanente berging gestoor. Outeurskap-handtekeninge moet onbedrieglik bly vir onbepaalde tyd, en daarom gebruik v1.0 die post-kwantum ML-DSA-65 skema (FIPS 204) eerder as 'n klassieke skema waarvan die sekuriteit mag verswak binne die qub se permanente leeftyd.

9.2 Algoritme-register

sig_alg Skema Sleutel-grootte Handtekening-grootte
0x00 Geen handtekening (ongeteken)
0x01 ML-DSA-65 (FIPS 204) 1 952 grepe 3 309 grepe

Kykers MOET onbekende sig_alg-waardes verwerp.

9.3 Ondertekende preimage-konstruksie

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)

Domein-skeier: "QUB_AUTHOR_SIG_V1" is 17 ASCII-grepe: [0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31]. Geen vulling.

Sleep-greep: die 91ste preimage-greep MOET 0x00 wees. Die verwysing-implementasie ontbloot dit as die konstante ORG_ID_PRESENT_INDIVIDUAL = 0x00 in crates/qub-core/src/signing.rs; kykers wat sig_input vir verifikasie herbou MOET dieselfde greep uitstuur.

Handtekening-omvang — wat gedek word en wat nie. sig_input verbind tot vier koevert-velde: version, qub_id, body_hash, unlock_at (plus die vaste domein-skeier en org_id_present-greep). Drie van daardie vier is strukturele invariante: qub_id word self afgelei van version, content_type, created_at, unlock_at, outcome_at, drand_round, en body_hash via die §4.1 preimage, dus enige verandering aan daardie velde lewer 'n verskillende qub_id en maak die handtekening transitief ongeldig. Die direk-geverifieerde oppervlak is dus:

Veld Geverifieer deur handtekening Hoe
version Direkte inset na sig_input
qub_id Direkte inset
body_hash Direkte inset
unlock_at Direkte inset
content_type Transitief, via qub_id-preimage
created_at Transitief, via qub_id-preimage
outcome_at Transitief, via qub_id-preimage
drand_round Transitief, via qub_id-preimage (V1.2)
body Transitief, via body_hash = SHA3-256(body)
author_pubkey — (implisiet) Sleutel wat die handtekening geverifieer het is per definisie die outeur
sender_label Slegs-vertoon teks; veranderbaar sonder handtekening-breuk
reply_to Draad-wyser; veranderbaar sonder handtekening-breuk
cosigner_pubkey / cosigner_signature Onafhanklik onderteken oor dieselfde sig_input (sien §9.7)
drand_chain_id, tlock_ciphertext, visibility Buitenste SealedQub-velde, nie binne die koevert nie — gedek deur hul eie strukturele invariante (rondte / ketting konsekwentheid) maar nie deur die outeur-handtekening nie. (drand_round word nou transitief gebind via die qub_id-preimage — sien hierbo.)

Sekuriteits-implikasies van nie-geverifieerde velde.

Implementasies wat sender_label of reply_to aan eind-gebruikers vertoon MOET die geverifieerde identiteit (publieke-sleutel vingerafdruk, bevestiging) as die primêre identiteit-sein vertoon, nie die etiket nie.

9.4 Verifikasie-prosedure

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."

Handtekening-verifikasie is die mees duur bewerking (veral ML-DSA-65). Dit BEHOORT uitgevoer te word nadat alle goedkoper kontroles (hash, qub_id, unlock_at) geslaag het.

9.5 Identiteit-bevestigings

Identiteit-bevestigings — die kartering van author_pubkey na mens-herkenbare identiteit-eise soos 'n qub-handvatsel, e-posadres, sosiale handvatsel, of passleutel-geloofsbrief — is 'n kyker-kant progressiewe verbetering en word nie vereis vir handtekening-verifikasie nie. Kykers wat bevestigings na 'n vertoon-identiteit oplos MOET die voorrang toepas:

handle > email > social > fingerprint

Die vingerafdruk-terugval is die kleinletter-hex van SHA3-256(author_pubkey); dit is altyd beskikbaar vir enige onderteken-qub. Kykers MAG dit afkort vir vertoning — die verwysing-kyker lewer qub: gevolg deur die eerste en laaste vier grepe (qub:<8 hex>…<8 hex>).

'n Konformerende verifieerder kan elke kontrole in §9.4 voltooi sonder om die qub-API te kontak, sonder enige netwerk buite permanente berging en drand, en sonder enige bediener-kant opsoek. Bevestiging-oplossing is 'n aparte beste-poging stap wat slegs uitgevoer word nadat handtekening-verifikasie geslaag het.

9.6 Grootte-impak

Ed25519 ML-DSA-65
Handtekening 64 grepe 3 309 grepe
Publieke sleutel 32 grepe 1 952 grepe
Totaal per qub 96 grepe 5 261 grepe
Bergingskoste delta (teen ~$5/MB) ~$0.0005 ~$0.026

Vir 'n teks-qub van 500–2 000 grepe, verdriedubbel ML-DSA-65 ongeveer die gestoorde grootte. Die absolute koste is weglaatbaar.

9.7 Mede-ondertekenaar-verifikasie (verbond bilaterale ooreenkomste)

Vir bilaterale ooreenkomste (content_type = 0x03) bewys 'n tweede handtekening-laag dat albei partye tot dieselfde voorwaardes ingestem het.

Koevert-velde:

Beide velde MOET saam teenwoordig wees of albei afwesig. As presies een teenwoordig is, MOET kykers 'n integriteitsfout rapporteer.

Verifikasie-prosedure:

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."

Eienskappe:

E-pos-bindings hek (operasioneel). Wanneer 'n verhoogde verbond 'n Party B e-pos-kontak dra (§6.1), MOET die qub-oplaaidiens die mede-ondertekening-versoek weier tensy 'n kort-leef e-pos-verifikasie merker bestaan wat ooreenstem met beide die verhoog-id en die genormaliseerde-e-pos hash van daardie kontak. Die merker word deur /api/v1/auth/verify geskryf wanneer die magiese-skakel teken 'n staging_id dra en die geverifieerde adres ooreenstem met SHA-256(normalise_email(party_b.contact)) — waar normalise_email(addr) die plaaslike-deel kas behoue hou en slegs die domein-deel na kleinletter omskakel (per RFC 5321 §2.3.11), en SHA-256 hier is die NIST FIPS 180-4 hash (onderskeie van die SHA3-256 wat in §4-afleidings gebruik word) — en verloop 900 sekondes (15 minute) ná uitgifte. Dit is 'n operasionele anti-personifikasie hek, NIE deel van die op-ketting qub-bewys nie — 'n derde-party verifieerder wat §11 herspeel benodig slegs permanente berging en drand, sonder enige bediener-kant opsoek. Die merker bestaan slegs bediener-kant en is nooit deel van die onderteken-liggaam nie.

Grootte-impak (ML-DSA-65 outeur + mede-ondertekenaar):

Komponent Grootte
Outeur-handtekening 3 309 grepe
Outeur publieke sleutel 1 952 grepe
Mede-ondertekenaar-handtekening 3 309 grepe
Mede-ondertekenaar publieke sleutel 1 952 grepe
Totale kripto-bo-koste 10 522 grepe
Bergingskoste delta ~$0.05

10. Markdown-lewering en saniteer

Hierdie afdeling is sekuriteit-krities. Die kyker lewer teks-qubs (content_type = 0x01) met 'n beperkte Markdown-subversameling.

10.1 Toegelate elemente

10.2 Verbode elemente

Element Hantering
Rou HTML (<div>, <script>, ens.) Heeltemal verwyder. Geen HTML gaan deur.
Beelde (![alt](url)) Verwyder. Beeld-sintaks word uit uitset verwyder.
Skakels ([text](url)) URL gelewer as sigbare plat teks. Nie outomaties geskakel nie. Nie klikbaar sonder eksplisiete gebruikersaksie nie.
Gevaarlike URL-skemas javascript:, data:, vbscript:, file: — verwyder.
Iframes, ingebed, objekte Verwyder.
HTML-entiteite Gedekodeer na vertoon-karakters slegs as veilig.

10.3 Implementasie

Implementasies MOET 'n streng toelaatlys-parser gebruik, nie 'n bloklys nie. Die aanbevole benadering:

  1. Ontleed Markdown met pulldown-cmark (of ekwivalent).
  2. Loop deur die AST en laat val enige nood wat nie in die toelaatlys is nie (§10.1).
  3. Vir skakel-nodes: stuur die URL as sigbare teks uit, nie as 'n klikbare <a>-element nie.
  4. Skakel die gefiltreerde AST om na 'n getipte tussen-verteenwoordiging (bv., 'n MarkdownNode-enum met slegs veilige variante). Rou HTML is struktureel onverteenwoordigbaar in hierdie IR.
  5. Lewer van die getipte IR na die teiken-aansig laag (bv., reaktiewe aansig-komponente, DOM-nodes). Geen HTML-string aaneenskakeling of innerHTML op enige punt nie.

Bloklys-benaderings is brokkelig omdat nuwe Markdown-uitbreidings of parser-eienaardighede ongefiltreerde elemente kan invoer. Die getipte-AST benadering maak XSS struktureel onmoontlik — daar is geen variant wat arbitrêre HTML kan dra nie.

10.4 Grootte- en struktuur-limiete


11. Derde-party verifikasie

Enige derde party kan 'n publieke qub verifieer sonder qub-samewerking. Die verifikasie-prosedure:

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.

Wat verifikasie bewys:

Bewys Wat dit vasstel
Verbintenis Die ciphertext het bestaan teen die bergingsblok tydstempel.
Integriteit Die plaintext-liggaam stem ooreen met die verbinde hash en is nie verander nie.
Tydsberekening Die inhoud was onleesbaar tot die drand-rondte, wat ooreenstem met die gekose ontsluitings-tyd (onderhewig aan tlock- en drand-sekuriteits-aannames).

Wat verifikasie NIE bewys nie:

Nie-bewys Hoekom
Outeurskap Die sender_label is dekoratief. Sonder sig_alg0x01 kon enigeen hierdie inhoud verseël het.
Intensie Die qub bewys inhoud en tydsberekening, nie wat die skepper subjektief bedoel het nie.
Voor-gebeurtenis tydsberekening Bergingsblok insluiting mag werklike oplaai met minute vertraag. Die verbintenis-tydstempel is die blok-tyd, nie die oomblik waarop die gebruiker "verseël" gedruk het nie.

12. Weergawe-bestuur

12.1 Protokol-weergawe

Die version-veld (u8) in beide SealedQub en QubEnvelope identifiseer die hoof protokol-weergawe.

12.2 Weergawe-geskiedenis

Weergawe Waarde Beskrywing
v1 0x01 Publieke teks-qubs (content_type 0x01), verbond bilaterale ooreenkomste (0x03, structured/v1-skema, ML-DSA-65 outeur + mede-ondertekenaar), tlock, SHA3-256

12.3 Voorwaartse versoenbaarheid

'n v1-kyker wat 'n QubEnvelope met onbekende opsionele CBOR-kaart sleutels teëkom (sleutels nie in die §3.2 kanonieke orde nie) BEHOORT daardie sleutels te ignoreer en voort te gaan met verifikasie met bekende velde. Dit laat toekomstige klein toevoegings toe (bv., nuwe metadata) sonder om 'n hoof-weergawe stamp te vereis.

'n v1-kyker wat sig_alg = 0x01 (ML-DSA-65) teëkom maar ML-DSA-65 verifikasie-ondersteuning kortkom BEHOORT die qub-inhoud met 'n "handtekening teenwoordig maar nie verifieerbaar nie" kennisgewing te vertoon, nie die qub geheel te verwerp nie. Die verwysing-implementasie verwerp vandag elke sig_alg-waarde anders as 0x00 en 0x01 omdat die v1-register geen ander geldige algoritme bevat nie — streng verwerping en sag-misluk is waarnemingsgewys identies tot 'n derde algoritme geregistreer word. Die sag-misluk gedrag hierbo word dra-belastend sodra §9.2 'n nuwe inskrywing toelaat, en die verwysing-kyker sal op daardie punt opgedateer word om sag te misluk.

12.4 Buitenste omhulling-weergawe

Die OuterWrapper wat in §13 beskryf word dra sy eie version-greep, onafhanklik van SealedQub.version en QubEnvelope.version. Die twee weergawe-ruimtes ontwikkel afsonderlik: 'n toekomstige post-kwantum-veilige simmetriese vervanging stamp die omhulling-greep sonder om die binne-protokol-weergawe aan te raak, en 'n toekomstige protokol-laag toevoeging (bv., 'n nuwe koevert-veld) stamp die binne-weergawe sonder om die omhulling-greep aan te raak.

OUTER_WRAPPER_VERSION_* Waarde Algoritme Status
OUTER_WRAPPER_VERSION_1 0x01 AES-256-GCM met 12-greep nonce, 16-greep verifikasie-etiket, AAD gebind aan qub_id v1 verstek
0x020xFF Gereserveer Toekoms

Kykers MOET onbekende omhulling-weergawes met 'n duidelike fout verwerp. Die protokol hou die omhulling-weergawe-ruimte opsetlik nou tot 'n konkrete migrasie-dryver verskyn (bv., NIST-leiding wat 'n ander AEAD bevoordeel); 'n 0x02-gleuf sal in dieselfde hersiening toegeken word wat die algoritme inlei.


13. Buitenste enkripsie-omhulling

13.1 Rasionaal

Die protokol-lae (QubEnvelope → tlock → SealedQub) maak 'n verseëlde qub tyd-geslote: die liggaam is onleesbaar totdat unlock_at en die drand-rondte handtekening gepubliseer is. Ná ontsluiting is die rondte-handtekening egter openbaar en die kanonieke CBOR-vorm van SealedQub is herkenbaar, sodat 'n oeser wat permanente-berging-transaksies geïndekseer het die hele qub-korpus grootmaat sou kon dekripteer.

Die buitenste enkripsie-omhulling sluit daardie kanaal deur 'n addisionele simmetriese AEAD-laag tussen die kanonieke SealedQubCbor en die grepe wat na permanente berging geskryf word te interponeer. Die 256-bis sleutel K leef slegs in die URL-fragment van die afleweringskakel en op gebruikers-toestelle; blaaiers stuur nie URL-fragmente na bedieners nie, dus is qub.social, elke bergingspoort, en elke CDN voor elkeen waarnemingsgewys blind vir K. Elke qub in permanente berging is dus 'n ondeurskynende ciphertext waarvan die plaintext nie herstelbaar is sonder die URL wat die skepper gekies het om te deel nie.

Netto effek:

13.2 Lae

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)

Verseël en ontsluit by die protokol-laag (§7, §8) is onveranderd onder die omhulling-grens; die omhulling heg aan by die oproep-plek van seal() en ontkoppel by die oproep-plek van unlock().

13.3 OuterWrapper-datastruktuur

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
}

Veld-invariante.

CBOR-enkodering. Kanonieke CBOR per §3, met dieselfde sleutel-orde reël (gesorteer volgens gekodeerde greep-lengte stygend, dan leksikografies). Die vier sleutels is:

Sleutel Gekodeerde grepe Orde
nonce 6 1
qub_id 7 2
version 8 3
ciphertext 11 4

Die eerste greep van die OuterWrapper CBOR is dus die definitiewe-lengte kaart-kop vir 'n 4-inskrywing kaart (0xA4).

13.4 AAD-binding aan qub_id

Die omhulling bind qub_id as AEAD bykomende geverifieerde data. Dit is die dra-belastende strukturele verdediging teen drie klasse aanvalle:

Aanval Verdediging
Skuif ciphertext onder 'n verskillende qub_id-veld in die omhulling AAD-wanverhouding → AEAD-verifikasie misluk
Meng die URL-fragment van qub A met die permanente-berging-grepe van qub B AAD-wanverhouding → AEAD-verifikasie misluk
Peuter met die qub_id-veld van die omhulling ná oplaai AAD-wanverhouding → AEAD-verifikasie misluk

Om qub_id in die omhulling-plaintext te dra verswak nie enumerasie-immuniteit op betekenisvolle wyse nie — qub_id is self 'n SHA3-256 hash van die §4.1 preimage met geen herwinbare preimage van die spysvertering nie, en 'n enumereerder wat reeds die omhulling-grepe geoes het leer niks van die sigbare qub_id wat hulle nie reeds van die bestaan van die oplaai self kon aflei nie.

13.5 Omhulling- en uitpak-algoritmes

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

Mislukking-modus inval. Verkeerde K, verkeerde nonce, AAD-wanverhouding, en gepeuterde ciphertext lewer almal dieselfde DECRYPT_FAILED fout. Dit is 'n opsetlike AEAD-eienskap: om die mislukking-modus te onderskei sou 'n sykanaal skep wat 'n afstand-aanvaller kon ondersoek deur misvormde omhullings te stuur en die antwoord te tydsbereken. Verwysing-implementasies MOET alle AEAD-mislukkings tot 'n enkele fout-vorm laat inval.

13.6 Sleutelmateriaal en verspreiding

Die omhullingssleutel K is 'n 256-bis eenvormige willekeurige waarde wat per-qub deur 'n CSPRNG gegenereer word. Die verwysing-implementasies bron dit uit:

Verspreiding: K MOET URL-veilig base64 (RFC 4648 §5, geen vulling) gekodeer word en aan die afleweringskakel as die fragment-komponent aangeheg word:

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

Die fragment word nooit deur 'n konformerende blaaier na enige bediener gestuur nie. Herwinning-kanale (bediener-kant geskiedenis-indeks, kies-in e-pos outo-stuur) wat die volle afleweringskakel — insluitend die fragment — buite die gebruiker se toestel volhou is 'n eksplisiete handelsverkeer teen die verstek kripto-versnipper postuur en MOET op eksplisiete gebruiker-toestemming gehek wees.

Fragment-verlies. As 'n gebruiker die URL-fragment verloor en geen herwinning-kanaal het nie, is die qub onleesbaar. Dit is die dra-belastende handelsverkeer van die ontwerp en MOET aan die gebruiker by seël-tyd geopenbaar word. Die MVP versterk die seël-tyd openbaring met eksplisiete "stoor hierdie URL" kopie en 'n geverifieerde-e-pos herwinning-kanaal vir gebruikers wat kies om in te skryf.

13.7 Buite omvang vir hierdie afdeling

13.8 Openbare qubs (omhulling-weglating)

Die buitenste omhulling is opsioneel by die aflewering-laag. 'n Skepper kan 'n qub as openbaar verseël, in welke geval die kanonieke SealedQubCbor direk na permanente berging geskryf word, sonder 'n OuterWrapper-laag en sonder sleutel K:

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

'n Openbare qub is tyd-geslote maar nie skakel-gehek nie: dit bly onleesbaar totdat sy drand-rondte publiseer (die tlock-laag is onveranderd), maar ná ontsluiting kan enigeen wat die arweave_tx_id het dit dekripteer — geen URL-fragment word vereis nie, omdat daar geen K is nie. Dit is die opsetlike handelsverkeer vir oppervlakke wat die bediener moet dryf: onthul-kennisgewing-e-posse, derdeparty-inbeddings, en ryker ná-onthul SEO benodig almal 'n skakel wat werk sonder 'n geheim wat die bediener nooit hou nie (§13.6).

Gevolge waarvoor 'n produsent rekening MOET hou:

Privaat (omhul) bly die verstek; openbaar is 'n eksplisiete per-qub skepper-keuse.


14. Toets-vektore

14.1 qub_id-afleiding

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

Implementasies MOET identiese body_hash- en qub_id-waardes vir hierdie inset lewer. Hierdie toets-vektor BEHOORT die eerste eenheidstoets te wees wat geskryf word. Die kanonieke waardes hierbo is deur die verwysing-implementasie bereken en MOET bit-vir-bit ooreenstem. Historiese preimage-uitlegte (voor-bekendstelling — geen lewende qubs het op hierdie waardes gesteun nie): die 92-greep V1.0 qub_id was 3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0; die 100-greep V1.1 qub_id (ná die invou van outcome_at_or_zero) was b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed. V1.2 vou drand_round in en gradeer die domeinskeier op na QUB_ID_V2.

14.2 Ontsluitings-rondte kartering

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 Kanonieke CBOR rondreis

Implementasies MOET verifieer dat serialize(parse(serialize(qub))) == serialize(qub) vir alle geldige insette. Dit is 'n eienskap-toets, nie 'n enkele vektor nie.

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)

Die kanonieke CBOR-grepe en SHA3-256 body_hash word deur die verwysing-implementasie bereken. Implementasies MOET greep-identiese CBOR vir hierdie inset lewer.

Implementasies MOET ook verifieer dat serialize(parse(serialize(pact))) == serialize(pact) vir alle geldige PactTerms-insette (eienskap-toets).

14.5 Buitenste omhulling kruis-taal vektore

Die buitenste omhulling (§13) het 'n aparte kanonieke vasstelling by crates/qub-core/tests/vectors/wrapper_v1.json. Elke geval pen 'n (key, nonce, qub_id, sealed_cbor)-tupel vas as ondeurskynende hex-insette en beweer 'n spesifieke expected_wrapper_hex-uitset. Albei verwysing-implementasies verbruik dieselfde JSON-lêer:

Die vasstelling pen tans drie gevalle vas:

Geval Dekking
basic-text-public Kleinste realistiese SealedQub-vorm; geen opsionele velde nie. Vestig die kanonieke omhulling-vorm vir 'n v1.0-tipiese qub.
with-recipient-pubkey SealedQub met recipient_pubkey gestel (Fase 2-pad). Verskillende binne CBOR-sleutel stel, verskillende qub_id.
longer-body ~4 KiB liggaam — oefen multi-greep CBOR lengte-voorvoegsels binne beide die binne-koevert en die buitenste ciphertext.

Implementasies MOET greep-identiese expected_wrapper_hex vir die opgeneemde insette lewer. Om die vasstelling te herskep vereis QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors en is gereserveer vir opsetlike formaat-veranderings.


15. Kripto-profiel-bestuur (toekoms)

Hierdie afdeling is informatief vir v1 en word normatief die eerste keer dat 'n tweede algoritme enige van qub se kriptografiese primitiewe binnegaan.

15.1 Huidige postuur

Protokol v1 bind presies een algoritme per primitief:

Verifieerders hard-kodeer tans sleutel- en handtekening-lengtes per primitief. Geen ratheid-oppervlak word deur die draadformaat blootgestel nie.

15.2 Beoogde vorm

Wanneer 'n tweede algoritme die protokol binnegaan, sal die verifieerder gekonfigureer word vir 'n benoemde CryptoProfile (bv., ExqubV1) wat die presiese stel toegelate waardes per primitief lys — sig_algs, drand-kettings, omhulling-weergawes, inhoudstipes. Die profiel is by verifikasie-tyd vasgestel, nooit in-baand onderhandel nie. Enige waarde buite die aktiewe profiel word verwerp.

Dit waarborg dat die toevoeging van ML-DSA-87 of die aktivering van Ed25519 nie retrospektief bestaande verifieerder-konfigurasies kan verswak nie: 'n v1-verifieerder bly 'n v1-verifieerder selfs nadat 'n v2-profiel gepubliseer is.

15.3 Snellervoorwaardes

Bevorder §15 na normatiewe status wanneer enige van die volgende voorgestel word:

Tot dan is §15 'n plekhouer wat die migrasie-vorm vasstel sodat toekomstige PR's teen 'n bekende teiken land eerder as om die onderhandelings-oppervlak van vooraf te herbedink.